Use jQuery's Deferred to solve the deferred loading problem instead of load events.

This commit is contained in:
Nathan Gray 2013-10-16 20:48:05 +00:00
parent f10fc199a0
commit 4002907c05
4 changed files with 108 additions and 47 deletions

View File

@ -730,26 +730,68 @@ var et2_widget = Class.extend(
* function is called, the DOM-Tree is created. loadingFinished is * function is called, the DOM-Tree is created. loadingFinished is
* recursively called for all child elements. Do not directly override this * recursively called for all child elements. Do not directly override this
* function but the doLoadingFinished function which is executed before * function but the doLoadingFinished function which is executed before
* descending deeper into the DOM-Tree * descending deeper into the DOM-Tree.
*
* Some widgets (template) do not load immediately because they request
* additional resources via AJAX. They will return a Deferred Promise object.
* If you call loadingFinished(promises) after creating such a widget
* programmatically, you might need to wait for it to fully complete its
* loading before proceeding. In that case use:
* <code>
* var promises = [];
* widget.loadingFinished(promises);
* jQuery.when.apply(null, promises).done( doneCallback );
* </code>
* @see {@link http://api.jquery.com/category/deferred-object/|jQuery Deferred}
*
* @param {Promise[]} promises List of promises from widgets that are not done. Pass an empty array, it will be filled if needed.
*/ */
loadingFinished: function() { loadingFinished: function(promises) {
// Call all availble setters // Call all availble setters
this.initAttributes(this.options); this.initAttributes(this.options);
if (this.doLoadingFinished()) // Make sure promises is defined to avoid errors.
// We'll warn (below) if programmer should have passed it.
if(typeof promises == "undefined")
{
promises = [];
var warn_if_deferred = true;
}
var loadChildren = function()
{ {
// Descend recursively into the tree // Descend recursively into the tree
for (var i = 0; i < this._children.length; i++) for (var i = 0; i < this._children.length; i++)
{ {
try try
{ {
this._children[i].loadingFinished(); this._children[i].loadingFinished(promises);
} }
catch (e) catch (e)
{ {
egw.debug("error", "There was an error with a widget:\nError:%o\nProblem widget:%o",e.valueOf(),this._children[i],e.stack); egw.debug("error", "There was an error with a widget:\nError:%o\nProblem widget:%o",e.valueOf(),this._children[i],e.stack);
} }
} }
};
var result = this.doLoadingFinished();
if(typeof result == "boolean" && result)
{
// Simple widget finishes nicely
loadChildren.apply(this, arguments);
}
else if (typeof result == "object" && result.done)
{
// Warn if list was not provided
if(warn_if_deferred)
{
// Might not be a problem, but if you need the widget to be really loaded, it could be
egw.debug("warning", "Loading was deferred for widget %o, but creator is not checking. Pass a list to loadingFinished().");
}
// Widget is waiting. Add to the list
promises.push(result);
// Fihish loading when it's finished
result.done(jQuery.proxy(loadChildren, this));
} }
}, },
@ -779,6 +821,15 @@ var et2_widget = Class.extend(
} }
}, },
/**
* Does specific post-processing after the widget is loaded. Most widgets should not
* need to do anything here, it should all be done before.
*
* @return {boolean|Promise} True if the widget is fully loaded, false to avoid procesing children,
* or a Promise if loading is not actually finished (eg. waiting for AJAX)
*
* @see {@link http://api.jquery.com/deferred.promise/|jQuery Promise}
*/
doLoadingFinished: function() { doLoadingFinished: function() {
return true; return true;
}, },

View File

@ -1205,7 +1205,7 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput],
{ {
// Keep the name of the template, as we'll free up the widget after parsing // Keep the name of the template, as we'll free up the widget after parsing
this.template = _value; this.template = _value;
template.loadingFinished();
// Fetch the grid element and parse it // Fetch the grid element and parse it
var definitionGrid = template.getChildren()[0]; var definitionGrid = template.getChildren()[0];
if (definitionGrid && definitionGrid instanceof et2_grid) if (definitionGrid && definitionGrid instanceof et2_grid)
@ -1242,7 +1242,11 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput],
}; };
// Template might not be loaded yet, defer parsing // Template might not be loaded yet, defer parsing
$j(template.getDOMNode()).on("load", var promise = []
template.loadingFinished(promise);
// Wait until template (& children) are done
jQuery.when.apply(null, promise).done(
jQuery.proxy(function() { jQuery.proxy(function() {
parse.call(this, template); parse.call(this, template);
this.dynheight.initialized = false; this.dynheight.initialized = false;
@ -1656,7 +1660,7 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader,
_build_left_right: function(left_or_right, template_name) _build_left_right: function(left_or_right, template_name)
{ {
var existing = this.headers[left_or_right == "left" ? 0 : 1]; var existing = this.headers[left_or_right == "left" ? 0 : 1];
if(existing) if(existing && existing._type)
{ {
if(existing.id == template_name) return; if(existing.id == template_name) return;
existing.free(); existing.free();
@ -1666,11 +1670,12 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader,
// Load the template // Load the template
var header = et2_createWidget("template", {"id": template_name}, this); var header = et2_createWidget("template", {"id": template_name}, this);
jQuery(header.getDOMNode()).addClass(left_or_right == "left" ? "et2_hbox_left":"et2_hbox_right").addClass("nm_header"); jQuery(header.getDOMNode()).addClass(left_or_right == "left" ? "et2_hbox_left":"et2_hbox_right").addClass("nm_header");
this.headers.push(header); this.headers[left_or_right == "left" ? 0 : 1] = header;
$j(header.getDOMNode()).on("load", jQuery.proxy(function() { $j(header.getDOMNode()).on("load", jQuery.proxy(function() {
header.loadingFinished(); //header.loadingFinished();
this._bindHeaderInput(header); this._bindHeaderInput(header);
},this)); },this));
header.loadingFinished();
}, },
/** /**
@ -1873,9 +1878,12 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader,
return this.filters[0]; return this.filters[0];
} }
} }
for(var i = 0; i < this.headers.length; i++) if(_sender && _sender._type == "template")
{ {
if(_sender.id == this.headers[i].id && _sender._parent == this) return this.header_div[0]; for(var i = 0; i < this.headers.length; i++)
{
if(_sender.id == this.headers[i].id && _sender._parent == this) return this.header_div[0];
}
} }
return null; return null;
}, },

View File

@ -77,8 +77,8 @@ var et2_template = et2_DOMWidget.extend(
this.div = document.createElement("div"); this.div = document.createElement("div");
// Flag to indicate that loading is finished // Deferred object so we can load via AJAX
this.loading = false; this.loading = jQuery.Deferred();
if (this.id != "" || this.options.template) if (this.id != "" || this.options.template)
{ {
@ -108,8 +108,6 @@ var et2_template = et2_DOMWidget.extend(
if(splitted.length) if(splitted.length)
{ {
// Still loading, don't fire loading finished
this.loading = true;
et2_loadXMLFromURL(path, function(_xmldoc) { et2_loadXMLFromURL(path, function(_xmldoc) {
var templates = this.getInstanceManager().templates || {}; var templates = this.getInstanceManager().templates || {};
// Scan for templates and store them // Scan for templates and store them
@ -123,13 +121,7 @@ var et2_template = et2_DOMWidget.extend(
this.loadFromXML(templates[template_name]); this.loadFromXML(templates[template_name]);
// Update flag // Update flag
this.loading = false; this.loading.resolve();
// Fire the load event (after)
var self = this;
window.setTimeout(function() {
$j(self.getDOMNode()).trigger('load');
},0);
}, this); }, this);
} }
@ -140,17 +132,16 @@ var et2_template = et2_DOMWidget.extend(
{ {
this.egw().debug("log", "Loading template from XML: ", template_name); this.egw().debug("log", "Loading template from XML: ", template_name);
this.loadFromXML(xml); this.loadFromXML(xml);
// Don't call this here - premature // Don't call this here - done by caller, or on whole widget tree
//this.loadingFinished(); //this.loadingFinished();
// Fire the load event (after)
var self = this; // But resolve the promise
window.setTimeout(function() { this.loading.resolve();
$j(self.getDOMNode()).trigger('load');
},0);
} }
else else
{ {
this.egw().debug("warn", "Unable to find XML for ", template_name); this.egw().debug("warn", "Unable to find XML for ", template_name);
this.loading.reject();
} }
} }
}, },
@ -175,13 +166,17 @@ var et2_template = et2_DOMWidget.extend(
}, },
/** /**
* Override to trigger a load event, to facilitate processing when the xml file * Override to return the promise for deferred loading
* is loaded asyncronously
*/ */
doLoadingFinished: function() { doLoadingFinished: function() {
if(this.loading) return false; // Apply parent now, which actually puts into the DOM
this._super.apply(this, arguments); this._super.apply(this, arguments);
return true;
// Fire load event when done loading
this.loading.done(jQuery.proxy(function() {$j(this).trigger("load");},this.div));
// Not done yet, but widget will let you know
return this.loading.promise();
} }
}); });
et2_register_widget(et2_template, ["template"]); et2_register_widget(et2_template, ["template"]);

View File

@ -245,8 +245,11 @@ etemplate2.prototype.load = function(_name, _url, _data, _callback)
// Read the XML structure of the requested template // Read the XML structure of the requested template
this.widgetContainer.loadFromXML(this.templates[_name || missing_name]); this.widgetContainer.loadFromXML(this.templates[_name || missing_name]);
// List of Promises from widgets that are not quite fully loaded
var deferred = [];
// Inform the widget tree that it has been successfully loaded. // Inform the widget tree that it has been successfully loaded.
this.widgetContainer.loadingFinished(); this.widgetContainer.loadingFinished(deferred);
// Insert the document fragment to the DOM Container // Insert the document fragment to the DOM Container
this.DOMContainer.appendChild(frag); this.DOMContainer.appendChild(frag);
@ -258,19 +261,23 @@ etemplate2.prototype.load = function(_name, _url, _data, _callback)
} }
etemplate2._byTemplate[_name].push(this); etemplate2._byTemplate[_name].push(this);
// Trigger the "resize" event // Wait for everything to be loaded, then finish it up
this.resize(); jQuery.when.apply(null, deferred).done(jQuery.proxy(function() {
// Trigger the "resize" event
this.resize();
if(typeof _callback == "function") // Tell others about it
{ if(typeof _callback == "function")
_callback.call(window,this); {
} _callback.call(window,this);
if(_callback != app_callback) }
{ if(_callback != app_callback)
app_callback.call(window,this); {
} app_callback.call(window,this);
}
$j(this.DOMContainer).trigger('load', this); $j(this.DOMContainer).trigger('load', this);
},this));
}; };