Et2Dialog changes

- Fix some event listeners
- Add getCompleted() promise that resolves when the dialog closes.
- Et2Dialog.alert() returns the promise directly, other static methods return the Et2Dialog to avoid breaking existing code

You can now wait for a dialog with:
Et2Dialog.show_prompt(null,"Name?").getComplete().then(([button_id,value]) => console.log(value));
or using async:
[button_id, value] = await Et2Dialog.show_prompt(null,"Name?").getComplete();
if(button_id === Et2Dialog.BUTTON_OK) {...}
This commit is contained in:
nathan 2022-03-17 11:19:47 -06:00
parent 7e01d1ae6a
commit 56571c6fc7
2 changed files with 105 additions and 61 deletions

View File

@ -55,8 +55,24 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
* @type {et2_template | null}
* @protected
*/
protected __template_widget : etemplate2 | null;
protected __template_promise : Promise<boolean>;
protected _template_widget : etemplate2 | null;
protected _template_promise : Promise<boolean>;
/**
* Treat the dialog as an atomic operation, and use this promise to notify when
* "done" instead of (or in addition to) using the callback function.
* It gives the button ID and the dialog value.
*/
protected _complete_promise : Promise<[number, Object]>;
/**
* The ID of the button that was clicked. Always one of the button constants,
* unless custom buttons were used
*
* @type {number|null}
* @protected
*/
protected _button_id : number | null;
/**
* Types
@ -172,8 +188,16 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
this.modal = false;
this.__value = {};
this._onOpen = this._onOpen.bind(this);
this._onClose = this._onClose.bind(this);
this._onClick = this._onClick.bind(this);
// Create this here so we have something, otherwise the creator might continue with undefined while we
// wait for the dialog to complete & open
this._complete_promise = new Promise<[number, Object]>((resolve) =>
{
this._completeResolver = value => resolve(value);
});
}
connectedCallback()
@ -183,10 +207,21 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
// Wait for everything to complete, then auto-open
this.getUpdateComplete().then(() =>
{
// _overlayCtrl is not available earlier
this._overlayCtrl?.addEventListener("show", this._onOpen);
this._overlayCtrl.addEventListener('hide', this._onClose);
window.setTimeout(this.open, 0);
});
}
disconnectedCallback()
{
super.disconnectedCallback();
this._overlayCtrl.removeEventListener("hide", this._onClose);
this._overlayCtrl.removeEventListener("show", this._onOpen);
}
// Need to wait for Overlay
async getUpdateComplete()
{
@ -194,21 +229,33 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
await this._overlayContentNode.getUpdateComplete();
// Wait for template to finish loading
if(this.__template_widget)
if(this._template_widget)
{
await this.__template_promise;
await this._template_promise;
}
// This calls _onClose() when the dialog is closed
this._overlayContentNode.addEventListener(
'close-overlay',
this._onClose,
);
this._overlayContentNode.querySelectorAll("et2-button").forEach((button) => button.addEventListener("click", this._onClick));
}
getComplete() : Promise<[number, Object]>
{
return this._complete_promise;
}
_onOpen()
{
this._button_id = null;
this._complete_promise = this._complete_promise || new Promise<[number, Object]>((resolve) => this._completeResolver);
}
_onClose(ev : PointerEvent)
{
this._completeResolver([this._button_id, this.value]);
this._button_id = null;
this._complete_promise = undefined;
// No real need to do this automatically, dialog could be reused without this
this._overlayCtrl.teardown();
this.remove();
}
@ -216,7 +263,7 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
_onClick(ev : MouseEvent)
{
// @ts-ignore
const button_id = parseInt(ev.target?.getAttribute("button_id")) || ev.target?.getAttribute("id") || null;
this._button_id = ev.target?.getAttribute("button_id") ? parseInt(ev.target?.getAttribute("button_id")) : (ev.target?.getAttribute("id") || null);
// Handle anything bound via et2 onclick property
try
@ -238,7 +285,7 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
// Callback expects (button_id, value)
try
{
let callback_result = this.callback ? this.callback(button_id, this.value) : true;
let callback_result = this.callback ? this.callback(this._button_id, this.value) : true;
if(callback_result === false)
{
ev.preventDefault();
@ -260,9 +307,9 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
get value() : Object
{
let value = this.__value;
if(this.__template_widget)
if(this._template_widget)
{
value = this.__template_widget.getValues(this.__template_widget.widgetContainer);
value = this._template_widget.getValues(this._template_widget.widgetContainer);
}
return value;
}
@ -315,33 +362,33 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
_loadTemplate()
{
if(this.__template_widget)
if(this._template_widget)
{
this.__template_widget.clear();
this._template_widget.clear();
}
this.__template_widget = new etemplate2(this._overlayContentNode._contentNode);
this._template_widget = new etemplate2(this._overlayContentNode._contentNode);
if(this.template.indexOf('.xet') > 0)
{
// File name provided, fetch from server
this.__template_promise = this.__template_widget.load("", this.template, this.__value || {content: {}},);
this._template_promise = this._template_widget.load("", this.template, this.__value || {content: {}},);
}
else
{
// Just template name, it better be loaded already
this.__template_promise = this.__template_widget.load(this.template, '', this.__value || {},
this._template_promise = this._template_widget.load(this.template, '', this.__value || {},
// true: do NOT call et2_ready, as it would overwrite this.et2 in app.js
undefined, undefined, true);
}
// Don't let dialog closing destroy the parent session
if(this.__template_widget.etemplate_exec_id && this.__template_widget.app)
if(this._template_widget.etemplate_exec_id && this._template_widget.app)
{
for(let et of etemplate2.getByApplication(this.__template_widget.app))
for(let et of etemplate2.getByApplication(this._template_widget.app))
{
if(et !== this.__template_widget && et.etemplate_exec_id === this.__template_widget.etemplate_exec_id)
if(et !== this._template_widget && et.etemplate_exec_id === this._template_widget.etemplate_exec_id)
{
// Found another template using that exec_id, don't destroy when dialog closes.
this.__template_widget.unbind_unload();
this._template_widget.unbind_unload();
break;
}
}
@ -498,6 +545,8 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
* @param {integer} _type One of the message constants. This defines the style of the message.
* @param {string} _icon URL of an icon to display. If not provided, a type-specific icon will be used.
* @param {string|egw} _egw_or_appname egw object with already laoded translations or application name to load translations for
*
* @return {Et2Dialog} You can use dialog.getComplete().then(...) to wait for the dialog to close.
*/
static show_dialog(_callback? : Function, _message? : string, _title? : string, _value? : object, _buttons?, _type? : number, _icon? : string, _egw_or_appname? : string | IegwAppLocal)
{
@ -519,10 +568,7 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
*/
modal: false
});
// Let other things run, then open
dialog.getUpdateComplete().then(() =>
{
});
document.body.appendChild(<LitElement><unknown>dialog);
return dialog;
};
@ -533,44 +579,45 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
* @param {string} _message Message to be place in the dialog.
* @param {string} _title Text in the top bar of the dialog.
* @param {integer} _type One of the message constants. This defines the style of the message.
*
* @return Promise<[ button_id : number, value : Object ]> will resolve when the dialog closes
*/
static alert(_message? : string, _title? : string, _type?)
{
let parent = et2_dialog._create_parent(et2_dialog._create_parent().egw());
et2_createWidget("dialog", {
callback: function()
{
},
let dialog = <Et2Dialog><unknown>document.createElement('et2-dialog');
dialog._setApiInstance();
dialog.transformAttributes({
callback: function() {},
message: _message,
title: _title,
buttons: et2_dialog.BUTTONS_OK,
dialog_type: _type || et2_dialog.INFORMATION_MESSAGE
}, parent);
});
document.body.appendChild(<LitElement><unknown>dialog);
return dialog.getComplete();
}
/**
* Show a prompt dialog
*
* @param {function} _callback Function called when the user clicks a button. The context will be the et2_dialog widget, and the button constant is passed in.
* @param {function} _callback Function called when the user clicks a button. The button constant is passed in along with the value.
* @param {string} _message Message to be place in the dialog.
* @param {string} _title Text in the top bar of the dialog.
* @param {string} _value for prompt, passed to callback as 2. parameter
* @param {integer|array} _buttons One of the BUTTONS_ constants defining the set of buttons at the bottom of the box
* @param {string|egw} _egw_or_appname egw object with already laoded translations or application name to load translations for
*
* @return {Et2Dialog} You can use dialog.getComplete().then(...) to wait for the dialog to close.
*/
static show_prompt(_callback, _message, _title?, _value?, _buttons?, _egw_or_appname?)
{
var callback = _callback;
// Just pass them along, widget handles defaults & missing
return et2_createWidget("dialog", {
callback: function(_button_id, _value)
{
if(typeof callback == "function")
{
callback.call(this, _button_id, _value.value);
}
},
title: _title || egw.lang('Input required'),
let dialog = <Et2Dialog><unknown>document.createElement('et2-dialog');
dialog._setApiInstance();
dialog.transformAttributes({
callback: _callback,
title: _title || 'Input required',
buttons: _buttons || et2_dialog.BUTTONS_OK_CANCEL,
value: {
content: {
@ -580,7 +627,11 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
},
template: egw.webserverUrl + '/api/templates/default/prompt.xet',
class: "et2_prompt"
}, et2_dialog._create_parent(_egw_or_appname));
});
document.body.appendChild(<LitElement><unknown>dialog);
return dialog
}
/**
@ -594,16 +645,16 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
*
* @description submit the form contents including the button that has been pressed
*/
static confirm(_senders, _dialogMsg, _titleMsg, _postSubmit)
static confirm(_senders, _dialogMsg, _titleMsg, _postSubmit?)
{
var senders = _senders;
var buttonId = _senders.id;
var dialogMsg = (typeof _dialogMsg != "undefined") ? _dialogMsg : '';
var titleMsg = (typeof _titleMsg != "undefined") ? _titleMsg : '';
var egw = _senders instanceof et2_widget ? _senders.egw() : et2_dialog._create_parent().egw();
var callbackDialog = function(button_id)
let senders = _senders;
let buttonId = _senders.id;
let dialogMsg = (typeof _dialogMsg != "undefined") ? _dialogMsg : '';
let titleMsg = (typeof _titleMsg != "undefined") ? _titleMsg : '';
let egw = _senders instanceof et2_widget ? _senders.egw() : undefined;
let callbackDialog = function(button_id)
{
if(button_id == et2_dialog.YES_BUTTON)
if(button_id == Et2Dialog.YES_BUTTON)
{
if(_postSubmit)
{
@ -621,7 +672,7 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
}
}
};
et2_dialog.show_dialog(callbackDialog, egw.lang(dialogMsg), egw.lang(titleMsg), {},
Et2Dialog.show_dialog(callbackDialog, dialogMsg, titleMsg, {},
et2_dialog.BUTTONS_YES_NO, et2_dialog.WARNING_MESSAGE, undefined, egw);
};
@ -831,7 +882,7 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
customElements.define("et2-dialog", Et2Dialog);
// make et2_dialog publicly available as we need to call it from templates
//if(typeof window.et2_dialog === 'undefined')
{
window['et2_dialog'] = Et2Dialog;
window['Et2Dialog'] = Et2Dialog;
}

View File

@ -105,13 +105,6 @@ export class Et2DialogOverlay extends SlotMixin(LitElement)
this.buttons = [];
}
firstUpdated(_changedProperties)
{
super.firstUpdated(_changedProperties);
// Tell content about its parent, but don't move it
//@ts-ignore
//(<Et2Widget><unknown>this.querySelector("[slot='content']"))._parent = this._dialog;
}
// Need to wait for Overlay