-
Notifications
You must be signed in to change notification settings - Fork 96
/
jquery.ondemand.js
180 lines (154 loc) · 5.39 KB
/
jquery.ondemand.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/**
* On-demand JavaScript handler
*
* Based on http://plugins.jquery.com/files/issues/jquery.ondemand.js_.txt
* and heavily modified to integrate with SilverStripe and prototype.js.
* Adds capabilities for custom X-Include-CSS and X-Include-JS HTTP headers
* to request loading of externals alongside an ajax response.
*
* Requires jQuery 1.5 ($.Deferred support)
*
* CAUTION: Relies on customization of the 'beforeSend' callback in jQuery.ajaxSetup()
*
* @author Ingo Schommer (ingo at silverstripe dot com)
* @author Sam Minnee (sam at silverstripe dot com)
*/
(function($){
var decodePath = function(str) {
return str.replace(/%2C/g,',').replace(/\&/g, '&').replace(/^\s+|\s+$/g, '');
};
var stripParams = function(str, whitelist) {
str = str.replace(new RegExp('('+whitelist.join('|')+')=([^&]*)&?', 'g'), '');
if (str.lastIndexOf("?") === str.length-1) {
str = str.substr(0, str.length-1);
}
return str;
};
const whitelist = ['m','_'];
$.extend({
// loaded files list - to protect against loading existed file again (by PGA)
_ondemand_loaded_list : null,
/**
* Returns true if the given CSS or JS script has already been loaded
*/
isItemLoaded : function(scriptUrl) {
var self = this, src;
if(this._ondemand_loaded_list === null) {
this._ondemand_loaded_list = {};
$('script').each(function() {
src = $(this).attr('src');
if(src) {
src = stripParams(decodePath(src), whitelist);
self._ondemand_loaded_list[src] = 1;
}
});
$('link[rel="stylesheet"]').each(function() {
src = $(this).attr('href');
if(src) self._ondemand_loaded_list[src] = 1;
});
}
return (this._ondemand_loaded_list[decodePath(scriptUrl)] !== undefined);
},
requireCss : function(styleUrl, media){
if(!media) media = 'all';
// Don't double up on loading scripts
if($.isItemLoaded(styleUrl)) return;
if(document.createStyleSheet){
var ss = document.createStyleSheet(styleUrl);
ss.media = media;
} else {
var styleTag = document.createElement('link');
$(styleTag).attr({
href : styleUrl,
type : 'text/css',
media : media,
rel : 'stylesheet'
}).appendTo($('head').get(0));
}
this._ondemand_loaded_list[styleUrl] = 1;
},
/**
* Process the X-Include-CSS and X-Include-JS headers provided by the Requirements class
*/
processOnDemandHeaders: function(xml, status, xhr) {
var self = this, processDfd = new $.Deferred();
// CSS
if(xhr.getResponseHeader && xhr.getResponseHeader('X-Include-CSS')) {
var cssIncludes = xhr.getResponseHeader('X-Include-CSS').split(',');
for(var i=0;i<cssIncludes.length;i++) {
// Syntax: "URL:##:media"
if(cssIncludes[i].match(/^(.*):##:(.*)$/)) {
$.requireCss(decodePath(RegExp.$1), RegExp.$2);
// Syntax: "URL"
} else {
$.requireCss(decodePath(cssIncludes[i]));
}
}
}
// JavaScript
var newJsIncludes = [];
if(xhr.getResponseHeader && xhr.getResponseHeader('X-Include-JS')) {
var jsIncludes = xhr.getResponseHeader('X-Include-JS').split(',');
for(var i=0;i<jsIncludes.length;i++) {
var jsIncludePath = stripParams(decodePath(jsIncludes[i]), whitelist);
if(!$.isItemLoaded(jsIncludePath)) {
newJsIncludes.push(jsIncludePath);
}
}
}
// We make an array of the includes that are actually new, and attach the callback to the last one
// They are placed in a queue and will be included in order. This means that the callback will
// be able to execute script in the new includes (such as a livequery update)
var getScriptQueue = function() {
if(newJsIncludes.length) {
var newJsInclude = newJsIncludes.shift();
// emulates getScript() with addtl. setting
$.ajax({
dataType: 'script',
url: newJsInclude,
complete: function() {
self._ondemand_loaded_list[newJsInclude] = 1;
getScriptQueue();
},
error: function(xhr, status, error) {
console.error(error);
},
cache: false,
// jQuery seems to override the XHR objects if used in async mode
async: false
});
} else {
processDfd.resolve(xml, status, xhr);
}
}
if(newJsIncludes.length) {
getScriptQueue();
} else {
// If there aren't any new includes, then we can just call the callbacks ourselves
processDfd.resolve(xml, status, xhr);
}
return processDfd.promise();
}
});
$.ajaxSetup({
// beforeSend is the only place to access the XHR object before success handlers are added
beforeSend: function(jqXHR, s) {
// Avoid recursion in ajax callbacks caused by getScript(), by not parsing
// ondemand headers for 'script' datatypes
if(s.dataType == 'script') return;
var dfd = new $.Deferred();
// Register our own success handler (assumes no handlers are already registered)
// 'success' is an alias for 'done', which is executed by the built-in deferred instance in $.ajax()
jqXHR.success(function(success, statusText, jXHR) {
$.processOnDemandHeaders(success, statusText, jXHR).done(function() {
dfd.resolveWith(s.context || this, [success, statusText, jXHR]);
});
});
// Reroute all external success hanlders through our own deferred.
// Not overloading fail() as no event can cause the original request to fail.
jqXHR.success = function(callback) {
dfd.done(callback);
}
}
});
})(jQuery);