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
* recursively called for all child elements. Do not directly override this
* 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
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
for (var i = 0; i < this._children.length; i++)
{
try
{
this._children[i].loadingFinished();
this._children[i].loadingFinished(promises);
}
catch (e)
{
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() {
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
this.template = _value;
template.loadingFinished();
// Fetch the grid element and parse it
var definitionGrid = template.getChildren()[0];
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
$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() {
parse.call(this, template);
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)
{
var existing = this.headers[left_or_right == "left" ? 0 : 1];
if(existing)
if(existing && existing._type)
{
if(existing.id == template_name) return;
existing.free();
@ -1666,11 +1670,12 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader,
// Load the template
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");
this.headers.push(header);
this.headers[left_or_right == "left" ? 0 : 1] = header;
$j(header.getDOMNode()).on("load", jQuery.proxy(function() {
header.loadingFinished();
//header.loadingFinished();
this._bindHeaderInput(header);
},this));
header.loadingFinished();
},
/**
@ -1873,9 +1878,12 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader,
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;
},

View File

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

View File

@ -245,8 +245,11 @@ etemplate2.prototype.load = function(_name, _url, _data, _callback)
// Read the XML structure of the requested template
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.
this.widgetContainer.loadingFinished();
this.widgetContainer.loadingFinished(deferred);
// Insert the document fragment to the DOM Container
this.DOMContainer.appendChild(frag);
@ -258,19 +261,23 @@ etemplate2.prototype.load = function(_name, _url, _data, _callback)
}
etemplate2._byTemplate[_name].push(this);
// Trigger the "resize" event
this.resize();
// Wait for everything to be loaded, then finish it up
jQuery.when.apply(null, deferred).done(jQuery.proxy(function() {
// Trigger the "resize" event
this.resize();
if(typeof _callback == "function")
{
_callback.call(window,this);
}
if(_callback != app_callback)
{
app_callback.call(window,this);
}
// Tell others about it
if(typeof _callback == "function")
{
_callback.call(window,this);
}
if(_callback != app_callback)
{
app_callback.call(window,this);
}
$j(this.DOMContainer).trigger('load', this);
$j(this.DOMContainer).trigger('load', this);
},this));
};