Swap Promise for jQuery.Promise in loadingFinished() & doLoadingFinished()

This lets us add LitElement's updateComplete Promise into the list of things to wait for, and solves the problem of app init code being run before widgets are complete.

https://lit.dev/docs/components/lifecycle/#reactive-update-cycle-completing
This commit is contained in:
nathan 2022-03-08 15:11:32 -07:00
parent 1b74f1f1a7
commit f7f4053d1a
9 changed files with 145 additions and 120 deletions

View File

@ -394,6 +394,17 @@ const Et2WidgetMixin = (superClass) =>
return this.classList.value;
}
/**
* Set the widget class
*
* @deprecated Use this.class or this.classList instead
* @param {string} new_class
*/
set_class(new_class : string)
{
this.class = new_class;
}
/**
* Event handlers
*/
@ -771,8 +782,9 @@ const Et2WidgetMixin = (superClass) =>
child.loadingFinished(promises);
}
};
doLoadingFinished();
promises.push(this.getUpdateComplete());
}
getWidgetById(_id)

View File

@ -18,14 +18,8 @@ import {ClassWithAttributes} from './et2_core_inheritance';
import {et2_IDOMNode} from "./et2_core_interfaces";
import {et2_hasChild, et2_no_init} from "./et2_core_common";
import {et2_widget, WidgetConfig} from "./et2_core_widget";
import {
egw_getActionManager,
egwActionObject,
egwActionObjectInterface,
egw_getAppObjectManager,
egw_getObjectManager
} from '../egw_action/egw_action.js';
import {EGW_AI_DRAG_OVER, EGW_AI_DRAG_OUT} from '../egw_action/egw_action_constants.js';
import {egw_getActionManager, egw_getAppObjectManager, egw_getObjectManager} from '../egw_action/egw_action.js';
import {EGW_AI_DRAG_OUT, EGW_AI_DRAG_OVER} from '../egw_action/egw_action_constants.js';
import {egw} from "../jsapi/egw_global";
// fixing circular dependencies by only importing type
import type {et2_tabbox} from "./et2_widget_tabs";
@ -170,7 +164,7 @@ export abstract class et2_DOMWidget extends et2_widget implements et2_IDOMNode
/**
* Attaches the container node of this widget to the DOM-Tree
*/
doLoadingFinished() : boolean | JQueryPromise<unknown>
doLoadingFinished() : boolean | Promise<any>
{
// Check whether the parent implements the et2_IDOMNode interface. If
// yes, grab the DOM node and create our own.

View File

@ -18,7 +18,7 @@ import {et2_no_init} from "./et2_core_common";
import {ClassWithAttributes} from "./et2_core_inheritance";
import {et2_widget, WidgetConfig} from "./et2_core_widget";
import {et2_valueWidget} from './et2_core_valueWidget'
import {et2_IInput, et2_IInputNode, et2_ISubmitListener} from "./et2_core_interfaces";
import {et2_IInput, et2_ISubmitListener} from "./et2_core_interfaces";
import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions";
// fixing circular dependencies by only importing the type (not in compiled .js)
import type {et2_tabbox} from "./et2_widget_tabs";
@ -107,7 +107,7 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
/**
* Make sure dirty flag is properly set
*/
doLoadingFinished(): boolean | JQueryPromise<unknown>
doLoadingFinished() : boolean | Promise<unknown>
{
let result = super.doLoadingFinished();

View File

@ -900,12 +900,12 @@ export class et2_widget extends ClassWithAttributes
};
var result = this.doLoadingFinished();
if (typeof result == "boolean" && result)
if(typeof result == "boolean" && result)
{
// Simple widget finishes nicely
loadChildren.apply(this, arguments);
}
else if (typeof result == "object" && result.done)
else if(typeof result == "object" && result.then)
{
// Warn if list was not provided
if (warn_if_deferred)
@ -916,7 +916,7 @@ export class et2_widget extends ClassWithAttributes
// Widget is waiting. Add to the list
promises.push(result);
// Fihish loading when it's finished
result.done(jQuery.proxy(loadChildren, this));
result.then(loadChildren.bind(this));
}
}
@ -956,9 +956,9 @@ export class et2_widget extends ClassWithAttributes
* @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}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise Promise}
*/
doLoadingFinished(): JQueryPromise<any> | boolean
doLoadingFinished() : Promise<any> | boolean
{
return true;
}

View File

@ -41,7 +41,13 @@
*/
import {et2_csvSplit, et2_no_init} from "./et2_core_common";
import {et2_IInput, et2_IPrint, et2_IResizeable, implements_methods, et2_implements_registry} from "./et2_core_interfaces";
import {
et2_IInput,
et2_implements_registry,
et2_IPrint,
et2_IResizeable,
implements_methods
} from "./et2_core_interfaces";
import {ClassWithAttributes} from "./et2_core_inheritance";
import {et2_createWidget, et2_register_widget, et2_widget, WidgetConfig} from "./et2_core_widget";
import {et2_DOMWidget} from "./et2_core_DOMWidget";
@ -292,6 +298,12 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2
row_selector: '',
orientation_style: null
};
/**
* When loading the row template, we have to wait for the template before we try to process it.
* During the legacy load process, we need to return this from doLoadingFinished() so it can be waited on so we
* have to store it.
*/
private template_promise : Promise<void>;
/**
* Constructor
@ -497,8 +509,7 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2
this.controller._grid.doInvalidate = true;
}
}, this));
return true;
return this.template_promise ? this.template_promise : true;
}
/**
@ -2521,8 +2532,8 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2
template.loadingFinished(promise);
// Wait until template (& children) are done
jQuery.when.apply(null, promise).done(
jQuery.proxy(function()
// Keep promise so we can return it from doLoadingFinished
this.template_promise = Promise.all(promise).then(() =>
{
parse.call(this, template);
if(!this.dynheight)
@ -2531,9 +2542,10 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2
}
this.dynheight.initialized = false;
this.resize();
}, this)
);
return promise;
}
).finally(() => this.template_promise = null);
return this.template_promise;
}
// Some accessors to match conventions

View File

@ -112,7 +112,7 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
this.options.status_id = _new_id;
}
doLoadingFinished( ) : boolean | JQueryPromise<unknown>
doLoadingFinished() : boolean | Promise<unknown>
{
super.doLoadingFinished();
@ -121,7 +121,8 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
if(tab)
{
// Bind the action to when the tab is selected
const handler = function (e) {
const handler = function(e)
{
e.data.div.unbind("click.history");
// Bind on click tap, because we need to update history size
// after a rezise happend and history log was not the active tab

View File

@ -14,10 +14,9 @@
et2_core_valueWidget;
*/
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_widget, et2_createWidget, et2_register_widget, WidgetConfig } from "./et2_core_widget";
import { et2_DOMWidget } from './et2_core_DOMWidget'
import { et2_valueWidget } from './et2_core_valueWidget'
import {ClassWithAttributes} from "./et2_core_inheritance";
import {et2_createWidget, et2_register_widget, et2_widget, WidgetConfig} from "./et2_core_widget";
import {et2_valueWidget} from './et2_core_valueWidget'
import {et2_nextmatch} from "./et2_extension_nextmatch";
import {et2_no_init} from "./et2_core_common";
import {et2_IInput, et2_IPrint, et2_IResizeable} from "./et2_core_interfaces";
@ -26,7 +25,7 @@ import {et2_directChildrenByTagName, et2_filteredNodeIterator, et2_readAttrWithD
/**
* Class which implements the tabbox-tag
*/
export class et2_tabbox extends et2_valueWidget implements et2_IInput,et2_IResizeable,et2_IPrint
export class et2_tabbox extends et2_valueWidget implements et2_IInput, et2_IResizeable, et2_IPrint
{
static readonly _attributes : any = {
'tabs': {
@ -252,15 +251,17 @@ export class et2_tabbox extends et2_valueWidget implements et2_IInput,et2_IResiz
*/
doLoadingFinished()
{
var tab_deferred = jQuery.Deferred();
var promises = [];
var tabs = this;
// Specially process the selected index so it shows up right away
this._loadTab(this.selected_index,promises);
this._loadTab(this.selected_index, promises);
// Avoid reloading if tabs were modified by data
if(this.isInTree() && this.isAttached()) return;
if(this.isInTree() && this.isAttached())
{
return;
}
// Apply parent now, which actually puts into the DOM
// This has to be before loading the child, so the dom sub-tree is not
@ -271,20 +272,27 @@ export class et2_tabbox extends et2_valueWidget implements et2_IInput,et2_IResiz
// which has special handling
this._children[0].loadingFinished(promises);
// Defer parsing & loading of other tabs until later
window.setTimeout(function() {
for (var i = 0; i < tabs.tabData.length; i++)
let tab_deferred = new Promise<any>((resolve, reject) =>
{
// Defer parsing & loading of other tabs until later
window.setTimeout(function()
{
if (i == tabs.selected_index) continue;
tabs._loadTab(i,promises);
}
jQuery.when.apply(jQuery,promises).then(function() {
tab_deferred.resolve();
tabs.resetDirty();
});
},0);
return tab_deferred.promise();
for(var i = 0; i < tabs.tabData.length; i++)
{
if(i == tabs.selected_index)
{
continue;
}
tabs._loadTab(i, promises);
}
Promise.all(promises).then(function()
{
resolve();
tabs.resetDirty();
});
}, 0);
});
return tab_deferred;
}
/**

View File

@ -72,9 +72,9 @@ export class et2_template extends et2_DOMWidget
}
};
content: string;
div: HTMLDivElement;
loading: JQueryDeferred<unknown>;
content : string;
div : HTMLDivElement;
loading : Promise<any>;
/**
* Constructor
@ -94,66 +94,78 @@ export class et2_template extends et2_DOMWidget
this.div = document.createElement("div");
// Deferred object so we can load via AJAX
this.loading = jQuery.Deferred();
// run transformAttributes now, to get server-side modifications (url!)
if (_attrs.template)
this.loading = new Promise<any>((resolve, reject) =>
{
this.id = _attrs.template;
this.transformAttributes(_attrs);
this.options = et2_cloneObject(_attrs);
_attrs = {};
}
if (this.id != "" || this.options.template)
{
var parts = (this.options.template || this.id).split('?');
var cache_buster = parts.length > 1 ? parts.pop() : null;
var template_name = parts.pop();
// Check to see if XML is known
var xml = null;
var templates = etemplate2.templates; // use global eTemplate cache
if(!(xml = templates[template_name]))
// run transformAttributes now, to get server-side modifications (url!)
if(_attrs.template)
{
// Check to see if ID is short form --> prepend parent/top-level name
if(template_name.indexOf('.') < 0)
this.id = _attrs.template;
this.transformAttributes(_attrs);
this.options = et2_cloneObject(_attrs);
_attrs = {};
}
if(this.id != "" || this.options.template)
{
var parts = (this.options.template || this.id).split('?');
var cache_buster = parts.length > 1 ? parts.pop() : null;
var template_name = parts.pop();
// Check to see if XML is known
var xml = null;
var templates = etemplate2.templates; // use global eTemplate cache
if(!(xml = templates[template_name]))
{
var root = _parent ? _parent.getRoot() : null;
var top_name = root && root._inst ? root._inst.name : null;
if (top_name && template_name.indexOf('.') < 0) template_name = top_name+'.'+template_name;
}
xml = templates[template_name];
if(!xml)
{
// Ask server
var url = this.options.url;
if (!this.options.url)
// Check to see if ID is short form --> prepend parent/top-level name
if(template_name.indexOf('.') < 0)
{
var splitted = template_name.split('.');
var app = splitted.shift();
// use template base url from initial template, to continue using webdav, if that was loaded via webdav
url = this.getRoot()._inst.template_base_url + app + "/templates/default/" +
splitted.join('.')+ ".xet" + (cache_buster ? '?download='+cache_buster : '');
var root = _parent ? _parent.getRoot() : null;
var top_name = root && root._inst ? root._inst.name : null;
if(top_name && template_name.indexOf('.') < 0)
{
template_name = top_name + '.' + template_name;
}
}
// if server did not give a cache-buster, fall back to current time
xml = templates[template_name];
if(!xml)
{
// Ask server
var url = this.options.url;
if(!this.options.url)
{
var splitted = template_name.split('.');
var app = splitted.shift();
// use template base url from initial template, to continue using webdav, if that was loaded via webdav
url = this.getRoot()._inst.template_base_url + app + "/templates/default/" +
splitted.join('.') + ".xet" + (cache_buster ? '?download=' + cache_buster : '');
}
// if server did not give a cache-buster, fall back to current time
if (url.indexOf('?') == -1) url += '?download='+(new Date).valueOf();
if(this.options.url || splitted.length)
{
var fetch_url_callback = function(_xmldoc) {
var fetch_url_callback = function(_xmldoc)
{
// Scan for templates and store them
for(var i = 0; i < _xmldoc.childNodes.length; i++) {
for(var i = 0; i < _xmldoc.childNodes.length; i++)
{
var template = _xmldoc.childNodes[i];
if(template.nodeName.toLowerCase() != "template") continue;
if(template.nodeName.toLowerCase() != "template")
{
continue;
}
templates[template.getAttribute("id")] = template;
}
// Read the XML structure of the requested template
if (typeof templates[template_name] != 'undefined') this.loadFromXML(templates[template_name]);
if(typeof templates[template_name] != 'undefined')
{
this.loadFromXML(templates[template_name]);
}
// Update flag
this.loading.resolve();
};
resolve();
}.bind(this);
et2_loadXMLFromURL(url, fetch_url_callback, this, function( error) {
url = egw.link('/'+ app + "/templates/default/" +
@ -173,19 +185,20 @@ export class et2_template extends et2_DOMWidget
//this.loadingFinished();
// But resolve the promise
this.loading.resolve();
resolve();
}
else
{
this.egw().debug("warn", "Unable to find XML for ", template_name);
this.loading.reject();
reject()
}
}
else
{
// No actual template
this.loading.resolve();
}
}
else
{
// No actual template
reject();
}
});
}
/**
@ -252,10 +265,10 @@ export class et2_template extends et2_DOMWidget
super.doLoadingFinished();
// Fire load event when done loading
this.loading.done(jQuery.proxy(function() {jQuery(this).trigger("load");},this.div));
this.loading.then(function() {jQuery(this).trigger("load");}.bind(this.div));
// Not done yet, but widget will let you know
return this.loading.promise();
return this.loading;
}
}
et2_register_widget(et2_template, ["template"]);

View File

@ -638,24 +638,9 @@ export class etemplate2
{
egw.window.console.groupEnd();
}
if(deferred.length > 0)
{
let still_deferred = 0;
jQuery(deferred).each(function()
{
if(this.state() == "pending")
{
still_deferred++;
}
});
if(still_deferred > 0)
{
egw.debug("log", "Template loaded, waiting for %d/%d deferred to finish...", still_deferred, deferred.length);
}
}
// Wait for everything to be loaded, then finish it up
jQuery.when.apply(jQuery, deferred).done(jQuery.proxy(function()
Promise.all(deferred).then(() =>
{
egw.debug("log", "Finished loading %s, triggering load event", _name);
@ -736,7 +721,7 @@ export class etemplate2
gen_time_div.find('.et2RenderTime').remove();
gen_time_div.append('<span class="et2RenderTime">' + egw.lang('eT2 rendering took %1s', '' + ((end_time - start_time) / 1000)) + '</span>');
}
}, this));
});
};