Dialog work

- Switch from LionDialog to SlDialog as base
- First input should get focus
- First button gets set as primary (if no default set), Enter key will act as a click on it
- Escape key closes dialog
This commit is contained in:
nathan 2022-11-30 15:59:25 -07:00
parent 350dd31b2b
commit 390fbf3608
6 changed files with 325 additions and 207 deletions

View File

@ -620,8 +620,8 @@ class AdminApp extends EgwApp
{ {
dialog_options['width'] = 550; dialog_options['width'] = 550;
modifications.tabs = { modifications.tabs = {
add_tabs: true, addTabs: true,
tabs: [{ extraTabs: [{
label: egw.lang('Documentation'), label: egw.lang('Documentation'),
template: 'policy.admin_cmd', template: 'policy.admin_cmd',
prepend: false prepend: false

View File

@ -11,17 +11,17 @@
import {Et2Widget} from "../Et2Widget/Et2Widget"; import {Et2Widget} from "../Et2Widget/Et2Widget";
import {et2_button} from "../et2_widget_button"; import {et2_button} from "../et2_widget_button";
import {LionDialog} from "@lion/dialog";
import {et2_widget} from "../et2_core_widget"; import {et2_widget} from "../et2_core_widget";
import {html, LitElement, ScopedElementsMixin, SlotMixin} from "@lion/core"; import {classMap, css, html, ifDefined, LitElement, render, repeat, SlotMixin, styleMap} from "@lion/core";
import {Et2DialogOverlay} from "./Et2DialogOverlay";
import {Et2DialogContent} from "./Et2DialogContent";
import {et2_template} from "../et2_widget_template"; import {et2_template} from "../et2_widget_template";
import {etemplate2} from "../etemplate2"; import {etemplate2} from "../etemplate2";
import {IegwAppLocal} from "../../jsapi/egw_global"; import {egw, IegwAppLocal} from "../../jsapi/egw_global";
import interact from "@interactjs/interactjs"; import interact from "@interactjs/interactjs";
import type {InteractEvent} from "@interactjs/core/InteractEvent"; import type {InteractEvent} from "@interactjs/core/InteractEvent";
import {Et2Button} from "../Et2Button/Et2Button"; import {Et2Button} from "../Et2Button/Et2Button";
import shoelace from "../Styles/shoelace";
import {SlDialog} from "@shoelace-style/shoelace";
import {egwIsMobile} from "../../egw_action/egw_action_common";
export interface DialogButton export interface DialogButton
{ {
@ -122,11 +122,9 @@ export interface DialogButton
* let result = await dialog.getComplete(); * let result = await dialog.getComplete();
*``` *```
* *
* Because of how LionDialog does its layout and rendering, it's easiest to separate the dialog popup from * Customize initial focus by setting the "autofocus" attribute on a control, otherwise first input will have focus
* the dialog content. This allows us to easily preserve the WebComponent styling. You should interact with the
* Et2Dialog though, and ignore the Et2DialogOverlay & Et2DialogContent.
*/ */
export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialog))) export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
{ {
/** /**
* Dialogs don't always get added to an etemplate, so we keep our own egw * Dialogs don't always get added to an etemplate, so we keep our own egw
@ -184,6 +182,31 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
public static readonly YES_BUTTON : number = 2; public static readonly YES_BUTTON : number = 2;
public static readonly NO_BUTTON : number = 3; public static readonly NO_BUTTON : number = 3;
static get styles()
{
return [
...shoelace,
...(super.styles || []),
css`
:host {
--header-spacing: var(--sl-spacing-medium);
--body-spacing: var(--sl-spacing-medium)
}
.dialog__title {
user-select: none;
}
.dialog__footer {
display: flex;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: stretch;
gap: 5px;
}
`
];
}
static get properties() static get properties()
{ {
return { return {
@ -245,21 +268,14 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
get slots() get slots()
{ {
return { return {
...super.slots ...super.slots,
'': () =>
{
return this._contentTemplate();
}
} }
} }
// Still not sure what this does, but it's important.
// Seems to be related to the constructor, and what's available during the "creation"
static get scopedElements()
{
return {
...super.scopedElements,
'et2-dialog-overlay-frame': Et2DialogOverlay,
'et2-dialog-content': Et2DialogContent
};
}
/* /*
* List of properties that get translated * List of properties that get translated
* Done separately to not interfere with properties - if we re-define label property, * Done separately to not interfere with properties - if we re-define label property,
@ -323,14 +339,20 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
this.destroyOnClose = true; this.destroyOnClose = true;
this.hideOnEscape = this.hideOnEscape === false ? false : true; this.hideOnEscape = this.hideOnEscape === false ? false : true;
this.__value = {}; this.__value = {};
this.open = true;
this._onOpen = this._onOpen.bind(this); this.handleOpen = this.handleOpen.bind(this);
this._onClose = this._onClose.bind(this); this.handleClose = this.handleClose.bind(this);
this._onClick = this._onClick.bind(this); this._onClick = this._onClick.bind(this);
this._onButtonClick = this._onButtonClick.bind(this); this._onButtonClick = this._onButtonClick.bind(this);
this._onMoveResize = this._onMoveResize.bind(this); this._onMoveResize = this._onMoveResize.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this._adoptTemplateButtons = this._adoptTemplateButtons.bind(this); this._adoptTemplateButtons = this._adoptTemplateButtons.bind(this);
// Don't leave it undefined, it's easier to deal with if it's just already resolved.
// It will be re-set if a template is loaded
this._template_promise = Promise.resolve(false);
// Create this here so we have something, otherwise the creator might continue with undefined while we // Create this here so we have something, otherwise the creator might continue with undefined while we
// wait for the dialog to complete & open // wait for the dialog to complete & open
this._complete_promise = new Promise<[number, Object]>((resolve) => this._complete_promise = new Promise<[number, Object]>((resolve) =>
@ -343,32 +365,82 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
{ {
super.connectedCallback(); super.connectedCallback();
// Wait for everything to complete, then auto-open // Prevent close if they click the overlay when the dialog is modal
this.getUpdateComplete().then(() => this.addEventListener('sl-request-close', event =>
{ {
// _overlayCtrl is not available earlier if(this.modal && event.detail.source === 'overlay')
this._overlayCtrl?.addEventListener("show", this._onOpen); {
this._overlayCtrl.addEventListener('hide', this._onClose); event.preventDefault();
}
})
this.addEventListener("sl-after-show", this.handleOpen);
this.addEventListener('sl-hide', this.handleClose);
// Bind on the ancestor, not the buttons, so their click handler gets a chance to run
this._overlayContentNode.addEventListener("click", this._onButtonClick);
window.setTimeout(this.open, 0);
});
} }
disconnectedCallback() disconnectedCallback()
{ {
super.disconnectedCallback(); super.disconnectedCallback();
this._overlayCtrl.removeEventListener("hide", this._onClose); this.removeEventListener("sl-hide", this.handleClose);
this._overlayCtrl.removeEventListener("show", this._onOpen); this.removeEventListener("sl-after-show", this.handleOpen);
this._overlayContentNode.removeEventListener("click", this._onButtonClick); }
addOpenListeners()
{
//super.addOpenListeners();
// Bind on the ancestor, not the buttons, so their click handler gets a chance to run
this.addEventListener("click", this._onButtonClick);
this.addEventListener("keydown", this.handleKeyDown);
}
removeOpenListeners()
{
//super.removeOpenListeners();
this.removeEventListener("click", this._onButtonClick);
this.removeEventListener("keydown", this.handleKeyDown);
}
handleKeyDown(event : KeyboardEvent)
{
// Parent handles escape, but is already bound
super.handleKeyDown(event);
// Trigger the "primary" or first button
if(this.open && event.key === 'Enter')
{
let button = this.querySelectorAll("[varient='primary']");
if(button.length == 0)
{
// Nothing explicitly marked, check for buttons in the footer
button = this.querySelectorAll("et2-button[slot='footer']");
}
if(button && button[0])
{
event.stopPropagation();
button[0].dispatchEvent(new CustomEvent('click', {bubbles: true}));
}
}
}
firstUpdated()
{
super.firstUpdated();
// If we start open, fire handler to get setup done
if(this.open)
{
this.handleOpenChange();
}
this.updateComplete.then(() => this._setDefaultAutofocus());
} }
// Need to wait for Overlay // Need to wait for Overlay
async getUpdateComplete() async getUpdateComplete()
{ {
await super.getUpdateComplete(); await super.getUpdateComplete();
await this._overlayContentNode.getUpdateComplete();
// Wait for template to finish loading // Wait for template to finish loading
if(this._template_widget) if(this._template_widget)
@ -382,18 +454,28 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
return this._complete_promise; return this._complete_promise;
} }
_onOpen() handleOpen()
{ {
this.addOpenListeners();
this._button_id = null; this._button_id = null;
this._complete_promise = this._complete_promise || new Promise<[number, Object]>((resolve) => this._completeResolver); this._complete_promise = this._complete_promise || new Promise<[number, Object]>((resolve) => this._completeResolver);
this._setupMoveResize(this._overlayContentNode);
// Now consumers can listen for "open" event, though getUpdateComplete().then(...) also works // Now consumers can listen for "open" event, though getUpdateComplete().then(...) also works
this.dispatchEvent(new Event('open')); this.dispatchEvent(new Event('open'));
Promise.all([this._template_promise, this.updateComplete])
.then(() => this._setupMoveResize());
} }
_onClose(ev : PointerEvent) handleClose(ev : PointerEvent)
{ {
// Avoid closing if a selectbox is closed
if(ev.target !== this)
{
return;
}
this.removeOpenListeners();
this._completeResolver([this._button_id, this.value]); this._completeResolver([this._button_id, this.value]);
this._button_id = null; this._button_id = null;
this._complete_promise = undefined; this._complete_promise = undefined;
@ -406,7 +488,6 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
{ {
this._template_widget.clear(); this._template_widget.clear();
} }
this._overlayCtrl.teardown();
this.remove(); this.remove();
} }
} }
@ -468,7 +549,7 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
{ {
console.log(e); console.log(e);
} }
this.close(); this.hide();
} }
/** /**
@ -518,15 +599,6 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
return value; return value;
} }
/**
* Fire the close-overlay event to use all registered listeners
* @deprecated
*/
destroy()
{
this._overlayContentNode.dispatchEvent(new Event('close-overlay'));
}
/** /**
* @deprecated * @deprecated
* @returns {Object} * @returns {Object}
@ -555,6 +627,12 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
return this._template_widget || null; return this._template_widget || null;
} }
get title() : string { return this.label }
set title(new_title : string)
{
this.label = new_title;
}
updated(changedProperties) updated(changedProperties)
{ {
@ -563,6 +641,11 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
{ {
this._loadTemplate(); this._loadTemplate();
} }
if(changedProperties.has("buttons"))
{
render(this._buttonsTemplate(), this);
this.requestUpdate();
}
} }
_loadTemplate() _loadTemplate()
@ -571,13 +654,14 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
{ {
this._template_widget.clear(); this._template_widget.clear();
} }
this._contentNode.replaceChildren();
// Etemplate wants a content // Etemplate wants a content
if(typeof this.__value.content === "undefined") if(typeof this.__value.content === "undefined")
{ {
this.__value.content = {}; this.__value.content = {};
} }
this._template_widget = new etemplate2(this._overlayContentNode._contentNode); this._template_widget = new etemplate2(this._contentNode);
// Fire an event so consumers can do their thing - etemplate will fire its own load event when its done // Fire an event so consumers can do their thing - etemplate will fire its own load event when its done
if(!this.dispatchEvent(new CustomEvent("before-load", { if(!this.dispatchEvent(new CustomEvent("before-load", {
@ -631,83 +715,103 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
this._template_widget.DOMContainer.setAttribute('id', this.__template.replace(/^(.*\/)?([^/]+?)(\.xet)?(\?.*)?$/, '$2').replace(/\./g, '-')); this._template_widget.DOMContainer.setAttribute('id', this.__template.replace(/^(.*\/)?([^/]+?)(\.xet)?(\?.*)?$/, '$2').replace(/\./g, '-'));
// Look for buttons after load // Look for buttons after load
this._overlayContentNode._contentNode.addEventListener("load", this._adoptTemplateButtons); this._contentNode.addEventListener("load", this._adoptTemplateButtons);
}
render() // Default autofocus to first input if autofocus is not set
{ this._contentNode.addEventListener("load", this._setDefaultAutofocus);
return this._overlayTemplate();
}
/** // Need to update to pick up changes
* Don't allow any children here, pass them on to the content node this.requestUpdate();
*
* @param {HTMLElement} node
* @returns {any}
*/
appendChild(node : HTMLElement)
{
return this._overlayContentNode.appendChild(node);
}
/**
* Defining this overlay as a templates from OverlayMixin
* this is our source to give as .contentNode to OverlayController.
* @protected
*/
protected _overlayTemplate()
{
return html`
<div id="overlay-content-node-wrapper">
<et2-dialog-overlay-frame class="dialog__overlay-frame"
.width=${this.width}
.height=${this.height}
._dialog=${this}
.buttons=${this._getButtons()}>
<span slot="heading">${this.title}</span>
${this._contentTemplate()}
</et2-dialog-overlay-frame>
</div>
`;
}
/**
* @override Configures OverlayMixin
*/
get _overlayContentNode()
{
if(this._cachedOverlayContentNode)
{
return this._cachedOverlayContentNode;
}
this._cachedOverlayContentNode = /** @type {HTMLElement} */ (
/** @type {ShadowRoot} */ (this.shadowRoot).querySelector('.dialog__overlay-frame')
);
return this._cachedOverlayContentNode;
} }
_contentTemplate() _contentTemplate()
{ {
if(this.__template) /**
* Classes for dialog type options
*/
const _dialogTypes : any = [
//PLAIN_MESSAGE: 0
"",
//INFORMATION_MESSAGE: 1,
"dialog_info",
//QUESTION_MESSAGE: 2,
"dialog_help",
//WARNING_MESSAGE: 3,
"dialog_warning",
//ERROR_MESSAGE: 4,
"dialog_error"
];
let icon = this.icon || this.egw().image(_dialogTypes[this.dialogType] || "") || "";
let type = _dialogTypes[this.dialogType];
let classes = {
dialog_content: true,
"dialog--has_message": this.message,
"dialog--has_template": this.__template
};
if(type)
{ {
return html` classes[type] = true;
<div slot="content"></div>`;
} }
else
// Add in styles set via property
let styles = {};
if(this.width)
{ {
return html` styles["--width"] = this.width;
<et2-dialog-content slot="content" ?icon=${this.icon}
.dialog_type=${this.dialog_type}
.value=${this.value}
>
${this.message}
</et2-dialog-content>
`;
} }
if(this.height)
{
styles.height = "--height: " + this.height;
}
return html`
<div class=${classMap(classes)} style="${styleMap(styles)}">
${this.__template ? "" :
html` <img class="dialog_icon" src=${icon}/>
<slot>${this.message}</slot>`
}
</div>`;
} }
_getButtons() _buttonsTemplate()
{
if(!this.buttons)
{
return;
}
let buttons = this._getButtons();
let hasDefault = false;
buttons.forEach((button) =>
{
if(button.default)
{
hasDefault = true;
}
})
// Set button._parent here, otherwise button will have trouble finding our egw()
return html`${repeat(buttons, (button : DialogButton) => button.id, (button, index) =>
{
let isDefault = hasDefault && button.default || !hasDefault && index == 0;
return html`
<et2-button ._parent=${this} id=${button.id} button_id=${button.button_id}
label=${button.label}
slot="footer"
.image=${ifDefined(button.image)}
.noSubmit=${true}
?disabled=${button.disabled}
variant=${isDefault ? "primary" : "default"}
?outline=${isDefault}
align=${ifDefined(button.align)}>
</et2-button>
`
})}`;
}
_getButtons() : DialogButton[]
{ {
if(Number.isInteger(this.buttons)) if(Number.isInteger(this.buttons))
{ {
@ -733,7 +837,7 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
_adoptTemplateButtons() _adoptTemplateButtons()
{ {
// Check for something with buttons slot set // Check for something with buttons slot set
let search_in = this._template_widget?.DOMContainer || this._overlayContentNode._contentNode; let search_in = <HTMLElement>(this._template_widget?.DOMContainer || this._contentNode);
let template_buttons = [ let template_buttons = [
...search_in.querySelectorAll('[slot="buttons"]'), ...search_in.querySelectorAll('[slot="buttons"]'),
// Look for a dialog footer, which will contain several buttons and possible other widgets // Look for a dialog footer, which will contain several buttons and possible other widgets
@ -745,8 +849,8 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
{ {
template_buttons.forEach((button) => template_buttons.forEach((button) =>
{ {
button.setAttribute("slot", "buttons"); button.setAttribute("slot", "footer");
this._overlayContentNode.appendChild(button); this.appendChild(button);
}) })
} }
// do NOT submit dialog, if it has no etemplate_exec_id, it only gives and error on server-side // do NOT submit dialog, if it has no etemplate_exec_id, it only gives and error on server-side
@ -761,36 +865,56 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
} }
/** /**
* @override Configures OverlayMixin * Set autofocus on first input element if nothing has autofocus
* @desc overrides default configuration options for this component
* @returns {Object}
*/ */
_defineOverlayConfig() _setDefaultAutofocus()
{ {
let not_modal = { const autofocused = this.querySelector("[autofocus]");
hasBackdrop: false, if(autofocused)
preventsScroll: false, {
trapsKeyboardFocus: false, return;
} }
return { if(this.template)
...super._defineOverlayConfig(), {
hidesOnEscape: this.hideOnEscape, this.template.focusOnFirstInput();
...(this.modal ? {} : not_modal)
} }
else
{
// Not a template, but maybe something?
const $input = jQuery('input:visible,et2-textbox:visible,et2-select-email:visible', this)
// Date fields open the calendar popup on focus
.not('.et2_date')
.filter(function()
{
// Skip inputs that are out of tab ordering
const $this = jQuery(this);
return !$this.attr('tabindex') || parseInt($this.attr('tabIndex')) >= 0;
}).first();
// mobile device, focus only if the field is empty (usually means new entry)
// should focus always for non-mobile one
if(egwIsMobile() && $input.val() == "" || !egwIsMobile())
{
$input.focus();
}
}
} }
_setupMoveResize(element) get _contentNode() : HTMLElement
{
return this.querySelector('.dialog_content');
}
_setupMoveResize()
{ {
// Quick calculation of min size - dialog is made up of header, content & buttons // Quick calculation of min size - dialog is made up of header, content & buttons
let minHeight = 0; let minHeight = 0;
for(let e of element.shadowRoot.querySelector('.overlay').children) for(let e of this.panel.children)
{ {
minHeight += e.getBoundingClientRect().height + parseFloat(getComputedStyle(e).marginTop) + parseFloat(getComputedStyle(e).marginBottom) minHeight += e.getBoundingClientRect().height + parseFloat(getComputedStyle(e).marginTop) + parseFloat(getComputedStyle(e).marginBottom)
} }
interact(element) interact(this.panel)
.resizable({ .resizable({
edges: {bottom: true, right: true}, edges: {bottom: true, right: true},
listeners: { listeners: {
@ -810,7 +934,8 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
}) })
.draggable({ .draggable({
allowFrom: ".overlay__heading", allowFrom: ".dialog__header",
ignoreFrom: ".dialog__close",
listeners: { listeners: {
move: this._onMoveResize move: this._onMoveResize
}, },

View File

@ -445,47 +445,50 @@ export function nm_open_popup(_action, _selected)
var popup = document.body.querySelector("et2-dialog[id*='" + _action.id + "_popup']") || document.body.querySelector("#" + (uid || "") + "_" + _action.id + "_popup") || document.body.querySelector("[id*='" + _action.id + "_popup']"); var popup = document.body.querySelector("et2-dialog[id*='" + _action.id + "_popup']") || document.body.querySelector("#" + (uid || "") + "_" + _action.id + "_popup") || document.body.querySelector("[id*='" + _action.id + "_popup']");
if (popup && popup instanceof Et2Dialog) if (popup && popup instanceof Et2Dialog)
{ {
popup.open(); popup.show();
} }
else if (popup) else if (popup)
{ {
let dialog = new Et2Dialog(); let dialog = new Et2Dialog();
dialog.destroy_on_close = false; dialog.destroyOnClose = false;
dialog.id = popup.id; dialog.id = popup.id;
popup.setAttribute("id", "_" + popup.id); popup.removeAttribute("id");
// Set title
let title = popup.querySelector(".promptheader")
if (title)
{
title.slot = "label"
dialog.appendChild(title);
}
popup.slot = "";
dialog.addEventListener("close", () => dialog.addEventListener("close", () =>
{ {
window.nm_popup_action = null; window.nm_popup_action = null;
})
dialog.getUpdateComplete().then(() =>
{
let title = popup.querySelector(".promptheader")
if (title)
{
title.slot = "heading"
dialog.appendChild(title);
}
popup.slot = "content";
// Move buttons
popup.querySelectorAll('et2-button').forEach((button) =>
{
button.slot = "buttons";
let button_click = button.onclick;
button.onclick = (e) =>
{
window.nm_popup_action = button_click ? action : null;
window.nm_popup_ids = selected;
dialog.close();
return button_click?.apply(button, e.currentTarget);
};
dialog.appendChild(button);
})
dialog.appendChild(popup);
}); });
// Move buttons
popup.querySelectorAll('et2-button').forEach((button, index) =>
{
button.slot = "footer";
if (index == 0)
{
button.variant = "primary";
button.outline = true;
}
let button_click = button.onclick;
button.onclick = (e) =>
{
window.nm_popup_action = button_click ? action : null;
window.nm_popup_ids = selected;
dialog.hide();
return button_click?.apply(button, e.currentTarget);
};
dialog.appendChild(button);
})
dialog.appendChild(popup);
dialog.requestUpdate();
document.body.appendChild(dialog); document.body.appendChild(dialog);

View File

@ -88,25 +88,25 @@ export class et2_dialog extends Et2Dialog
return super._getButtons(); return super._getButtons();
} }
_onOpen() handleOpen()
{ {
super._onOpen(); super.handleOpen();
// move the overlay dialog into appendTo dom since we want it to be shown in that container // move the overlay dialog into appendTo dom since we want it to be shown in that container
if (this.appendTo) if(this.appendTo)
{ {
document.getElementsByClassName(this.appendTo.replace('.',''))[0].appendChild(this._cachedOverlayContentNode); document.getElementsByClassName(this.appendTo.replace('.', ''))[0].appendChild(this._cachedOverlayContentNode);
} }
} }
_onClose(ev: PointerEvent) handleClose(ev : PointerEvent)
{ {
// revert the moved container back to its original position in order to be able to teardown the overlay properly // revert the moved container back to its original position in order to be able to teardown the overlay properly
if (this.appendTo) if(this.appendTo)
{ {
document.getElementsByClassName('global-overlays__overlay-container')[0].appendChild(this._cachedOverlayContentNode); document.getElementsByClassName('global-overlays__overlay-container')[0].appendChild(this._cachedOverlayContentNode);
} }
super._onClose(ev); super.handleClose(ev);
} }
/** /**

View File

@ -186,7 +186,7 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
if (!dialog) return; if (!dialog) return;
// disable not matching / available menu-items // disable not matching / available menu-items
dialog._overlayContentNode.querySelectorAll('et2-button').forEach(button => dialog.querySelectorAll('et2-button').forEach(button =>
{ {
if (button.id.substring(0, 7) === 'overall') if (button.id.substring(0, 7) === 'overall')
{ {
@ -261,10 +261,16 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
// if dialog is open, it shows both timers // if dialog is open, it shows both timers
if (dialog) if (dialog)
{ {
const specific_timer = dialog._overlayContentNode.querySelector('div#_specific_timer'); const specific_timer = dialog.querySelector('div#_specific_timer');
const overall_timer = dialog._overlayContentNode.querySelector('div#_overall_timer'); const overall_timer = dialog.querySelector('div#_overall_timer');
if (specific_timer) updateTimer(specific_timer, specific); if (specific_timer)
if (overall_timer) updateTimer(overall_timer, overall); {
updateTimer(specific_timer, specific);
}
if (overall_timer)
{
updateTimer(overall_timer, overall);
}
} }
} }
@ -356,8 +362,8 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
// if dialog is shown, update its timer(s) too // if dialog is shown, update its timer(s) too
if (dialog) if (dialog)
{ {
const specific_timer = dialog._overlayContentNode.querySelector('div#_specific_timer'); const specific_timer = dialog.querySelector('div#_specific_timer');
const overall_timer = dialog?._overlayContentNode.querySelector('div#_overall_timer'); const overall_timer = dialog?.querySelector('div#_overall_timer');
if (specific_timer && _timer === specific) if (specific_timer && _timer === specific)
{ {
updateTimer(specific_timer, specific) updateTimer(specific_timer, specific)
@ -489,8 +495,9 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
}; };
// disable not matching / available menu-items // disable not matching / available menu-items
dialog._overlayContentNode.querySelectorAll('et2-date-time-today').forEach(_widget => { dialog.querySelectorAll('et2-date-time-today').forEach(_widget =>
const [,timer, action] = _widget.id.match(/times\[([^\]]+)\]\[([^\]]+)\]/); {
const [, timer, action] = _widget.id.match(/times\[([^\]]+)\]\[([^\]]+)\]/);
_widget.value = times[timer][action]; _widget.value = times[timer][action];
}); });
} }

View File

@ -776,35 +776,18 @@ button.et2_timestamper:hover {
/** /**
* Dialog widget * Dialog widget
* It uses jQueryUI, so this is just our little bits - icon on left
*/ */
.ui-dialog-content .dialog_icon { et2-dialog .dialog_icon {
vertical-align: middle; vertical-align: middle;
width: 2em; width: 2em;
margin: 0.5em;
} }
.ui-dialog-content { et2-dialog .dialog--has_message {
vertical-align: middle; vertical-align: middle;
display: flex;
} }
.ui-dialog-content > div {
white-space: pre-wrap;
display: inline-block;
vertical-align: middle;
}
/* These change button alignment, but it seems the standard is right-aligned for
action buttons, left aligned for "extra" controls
.ui-dialog .ui-dialog-buttonpane {
text-align: left;
}
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
float: none;
}
.ui-dialog .ui-dialog-buttonpane button {
float: none;
}
*/
/** /**
* Custom field list * Custom field list
*/ */