ENHANCEMENT Using native jQuery.getScript() in customized jQuery.ondemand plugin. Moved processDemandHeaders() into jQuery namespace, and adjusted customized prototype.js library

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@92537 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2009-11-21 02:31:36 +00:00
parent b661b40865
commit f60e94b1ca
2 changed files with 92 additions and 163 deletions

View File

@ -1,34 +1,18 @@
/** /**
* On-demand JavaScript handler * On-demand JavaScript handler
* Based on http://plugins.jquery.com/files/issues/jquery.ondemand.js_.txt * Based on http://plugins.jquery.com/files/issues/jquery.ondemand.js_.txt
* and modified to integrate with SilverStripe and prototype.js. * and heavily modified to integrate with SilverStripe and prototype.js.
* Adds capabilities for custom X-Include-CSS and X-Include-JS HTTP headers * Adds capabilities for custom X-Include-CSS and X-Include-JS HTTP headers
* to request loading of externals alongside an ajax response. * to request loading of externals alongside an ajax response.
* *
* Modified 2009-08-03 by Ingo Schommer, SilverStripe Ltd. - Renamed property "queue" to "ondemand_queue" * IMPORTANT: This plugin monkeypatches the jQuery.ajax() method.
* to make compatible with jQuery 1.3
*/ */
(function($){ (function($){
function isExternalScript(url){
re = new RegExp('(http|https)://');
return re.test(url);
};
$.extend({ $.extend({
requireConfig : {
// empty default paths give more flexibility and user has
routeJs : '',
// choice of using this config or full path in scriptUrl argument
// previously were useless for users which don't use '_js/' and '_css/' folders. (by PGA)
routeCss : ''
},
ondemand_queue : [],
pending : null,
// loaded files list - to protect against loading existed file again (by PGA) // loaded files list - to protect against loading existed file again (by PGA)
loaded_list : null, _ondemand_loaded_list : null,
// Added by SRM: Initialise the loaded_list with the scripts included on first load // Added by SRM: Initialise the loaded_list with the scripts included on first load
initialiseItemLoadedList : function() { initialiseItemLoadedList : function() {
@ -38,7 +22,7 @@
$('script').each(function() { $('script').each(function() {
if($(this).attr('src')) $this.loaded_list[ $(this).attr('src') ] = 1; if($(this).attr('src')) $this.loaded_list[ $(this).attr('src') ] = 1;
}); });
$('link[@rel="stylesheet"]').each(function() { $('link[rel="stylesheet"]').each(function() {
if($(this).attr('href')) $this.loaded_list[ $(this).attr('href') ] = 1; if($(this).attr('href')) $this.loaded_list[ $(this).attr('href') ] = 1;
}); });
} }
@ -48,180 +32,72 @@
* Returns true if the given CSS or JS script has already been loaded * Returns true if the given CSS or JS script has already been loaded
*/ */
isItemLoaded : function(scriptUrl) { isItemLoaded : function(scriptUrl) {
this.initialiseItemLoadedList(); var self = this;
return this.loaded_list[scriptUrl] != undefined;
},
requireJs : function(scriptUrl, callback, opts, obj, scope) { if(this._ondemand_loaded_list == null) {
this._ondemand_loaded_list = {};
if(opts != undefined || opts == null){ $('script').each(function() {
$.extend($.requireConfig, opts); if($(this).attr('src')) self._ondemand_loaded_list[ $(this).attr('src') ] = 1;
} });
$('link[rel="stylesheet"]').each(function() {
var _request = { if($(this).attr('href')) self._ondemand_loaded_list[ $(this).attr('href') ] = 1;
url : scriptUrl,
callback : callback,
opts : opts,
obj : obj,
scope : scope
}
if(this.pending) {
this.ondemand_queue.push(_request);
return;
}
this.pending = _request;
this.initialiseItemLoadedList();
// if required file exists (by PGA)
if (this.loaded_list[this.pending.url] != undefined) {
// => request complete
this.requestComplete();
return;
}
var _this = this;
var _url = (isExternalScript(scriptUrl)) ? scriptUrl : $.requireConfig.routeJs + scriptUrl;
var _head = document.getElementsByTagName('head')[0];
var _scriptTag = document.createElement('script');
// Firefox, Opera
$(_scriptTag).bind('load', function(){
_this.requestComplete();
}); });
// IE
_scriptTag.onreadystatechange = function(){
if(this.readyState === 'loaded' || this.readyState === 'complete'){
_this.requestComplete();
}
} }
_scriptTag.type = "text/javascript"; return (this._ondemand_loaded_list[scriptUrl] != undefined);
_scriptTag.src = _url;
_head.appendChild(_scriptTag);
},
requestComplete : function() {
if(this.pending.callback){
if(this.pending.obj){
if(this.pending.scope){
this.pending.callback.call(this.pending.obj);
} else {
this.pending.callback.call(window, this.pending.obj);
}
} else {
this.pending.callback.call();
}
}
// adding loaded file to loaded list (by PGA)
this.loaded_list[this.pending.url] = 1;
this.pending = null;
if(this.ondemand_queue.length > 0) {
var request = this.ondemand_queue.shift();
this.requireJs(request.url, request.callback, request.opts, request.obj, request.scope);
}
}, },
requireCss : function(styleUrl, media){ requireCss : function(styleUrl, media){
if(media == null) media = 'all'; if(media == null) media = 'all';
// Don't double up on loading scripts // Don't double up on loading scripts
if(this.isItemLoaded(styleUrl)) return; if($.isItemLoaded(styleUrl)) return;
if(document.createStyleSheet){ if(document.createStyleSheet){
var ss = document.createStyleSheet($.requireConfig.routeCss + styleUrl); var ss = document.createStyleSheet(styleUrl);
ss.media = media; ss.media = media;
} else { } else {
var styleTag = document.createElement('link'); var styleTag = document.createElement('link');
$(styleTag).attr({ $(styleTag).attr({
href : $.requireConfig.routeCss + styleUrl, href : styleUrl,
type : 'text/css', type : 'text/css',
media : media, media : media,
rel : 'stylesheet' rel : 'stylesheet'
}).appendTo($('head').get(0)); }).appendTo($('head').get(0));
} }
this.loaded_list[styleUrl] = 1; this._ondemand_loaded_list[styleUrl] = 1;
} },
})
/** /**
* Sapphire extensions
* Ajax requests are amended to look for X-Include-JS and X-Include-CSS headers
*/
_originalAjax = $.ajax;
$.ajax = function(s) {
var _complete = s.complete;
var _success = s.success;
var _dataType = s.dataType;
// This replaces the usual ajax success & complete handlers. They are called after any on demand JS is loaded.
var _ondemandComplete = function(xml) {
var status = jQuery.httpSuccess(xml) ? 'success' : 'error';
if(status == 'success') {
data = jQuery.httpData(xml, _dataType);
if(_success) _success(data, status, xml);
}
if(_complete) _complete(xml, status);
}
// We remove the success handler and take care of calling it outselves within _ondemandComplete
s.success = null;
s.complete = function(xml, status) {
processOnDemandHeaders(xml, _ondemandComplete);
}
return _originalAjax(s);
}
})(jQuery);
/**
* This is the on-demand handler used by our patched version of prototype.
* once we get rid of all uses of prototype, we can remove this
*/
function prototypeOnDemandHandler(xml, callback) {
processOnDemandHeaders(xml, callback);
}
/**
* Process the X-Include-CSS and X-Include-JS headers provided by the Requirements class * Process the X-Include-CSS and X-Include-JS headers provided by the Requirements class
*/ */
function processOnDemandHeaders(xml, _ondemandComplete) { processOnDemandHeaders: function(xml, status, _ondemandComplete) {
var i; var self = this;
// CSS // CSS
if(xml.getResponseHeader('X-Include-CSS')) { if(xml.getResponseHeader('X-Include-CSS')) {
var cssIncludes = xml.getResponseHeader('X-Include-CSS').split(','); var cssIncludes = xml.getResponseHeader('X-Include-CSS').split(',');
for(i=0;i<cssIncludes.length;i++) { for(var i=0;i<cssIncludes.length;i++) {
// Syntax 1: "URL:##:media" // Syntax: "URL:##:media"
if(cssIncludes[i].match(/^(.*):##:(.*)$/)) { if(cssIncludes[i].match(/^(.*):##:(.*)$/)) {
jQuery.requireCss(RegExp.$1, RegExp.$2); $.requireCss(RegExp.$1, RegExp.$2);
// Syntax: "URL"
// Syntax 2: "URL"
} else { } else {
jQuery.requireCss(cssIncludes[i]); $.requireCss(cssIncludes[i]);
} }
} }
} }
// JavaScript // JavaScript
var newIncludes = []; var newJsIncludes = [];
if(xml.getResponseHeader('X-Include-JS')) { if(xml.getResponseHeader('X-Include-JS')) {
var jsIncludes = xml.getResponseHeader('X-Include-JS').split(','); var jsIncludes = xml.getResponseHeader('X-Include-JS').split(',');
for(i=0;i<jsIncludes.length;i++) { for(var i=0;i<jsIncludes.length;i++) {
if(!jQuery.isItemLoaded(jsIncludes[i])) { if(!$.isItemLoaded(jsIncludes[i])) {
newIncludes.push(jsIncludes[i]); newJsIncludes.push(jsIncludes[i]);
} }
} }
} }
@ -229,13 +105,66 @@ function processOnDemandHeaders(xml, _ondemandComplete) {
// We make an array of the includes that are actually new, and attach the callback to the last one // 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 // 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) // be able to execute script in the new includes (such as a livequery update)
if(newIncludes.length > 0) { var getScriptQueue = function() {
for(i=0;i<jsIncludes.length;i++) { if(newJsIncludes.length) {
jQuery.requireJs(jsIncludes[i], (i == jsIncludes.length-1) ? function() { _ondemandComplete(xml); } : null); var newJsInclude = newJsIncludes.shift();
} // emulates getScript() with addtl. setting
$.ajax({
// If there aren't any new includes, then we can just call the callbacks ourselves dataType: 'script',
url: newJsInclude,
success: function() {
self._ondemand_loaded_list[newJsInclude] = 1;
getScriptQueue();
},
cache: false,
// jQuery seems to override the XHR objects if used in async mode
async: false
});
} else { } else {
_ondemandComplete(xml, status); _ondemandComplete(xml, status);
} }
} }
if(newJsIncludes.length) {
getScriptQueue();
} else {
// If there aren't any new includes, then we can just call the callbacks ourselves
_ondemandComplete(xml, status);
}
}
});
/**
* Ajax requests are amended to look for X-Include-JS and X-Include-CSS headers
*/
_originalAjax = $.ajax;
$.ajax = function(s) {
// Avoid recursion in ajax callbacks caused by getScript(), by not parsing
// ondemand headers for 'script' datatypes
if(s.dataType == 'script') return _originalAjax(s);
var _complete = s.complete;
var _success = s.success;
var _dataType = s.dataType;
// This replaces the usual ajax success & complete handlers. They are called after any on demand JS is loaded.
var _ondemandComplete = function(xml, status) {
var status = $.httpSuccess(xml) ? 'success' : 'error';
if(status == 'success') {
var data = jQuery.httpData(xml, _dataType);
if(_success) _success(data, status);
}
if(_complete) _complete(xml, status);
}
// We remove the success handler and take care of calling it outselves within _ondemandComplete
s.success = null;
s.complete = function(xml, status) {
$.processOnDemandHeaders(xml, status, _ondemandComplete);
}
return _originalAjax(s);
}
})(jQuery);

View File

@ -852,8 +852,8 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
} }
// jquery ondemand integration patch // jquery ondemand integration patch
if(typeof prototypeOnDemandHandler != 'undefined') { if(typeof jQuery != 'undefined' && typeof jQuery.processOnDemandHeaders != 'undefined') {
prototypeOnDemandHandler(this.transport, completeHandler); jQuery.processOnDemandHeaders(this.transport, prototypeAjax.transport.status, completeHandler);
} else { } else {
completeHandler(); completeHandler();
} }