/**
* The base class of dialog boxes.
* <p>Part of the XUI module which, for now, has an undocumented API.
*/
export class Dialog {
constructor(options) {
this._testMode = false;
let opts = Object.assign({
type: "modal", movable: false, resizable: false,
classes: "xui-control xui-dialog xui-dlg",
title: null, closeable: false,
template: null,
buttons: null
}, options);
this._type = opts.type;
this._classes = opts.classes;
this._form = document.createElement("div");
this._form.classList.add("xui-dlg-container");
if (opts.resizable) {
this._form.classList.add("xui-dlg-resizable");
}
this._form.appendChild(Dialog.TEMPLATE.content.cloneNode(true));
this._titlePane = this._form.firstElementChild;
this._contentPane = this._titlePane.nextElementSibling;
this._buttonPane = this._form.lastElementChild;
this._titleText = this._titlePane.firstElementChild;
this._closeButton = this._titlePane.lastElementChild;
if (opts.title === null && !opts.closeable) {
this._titleText.style.display = "none";
this._closeButton.style.display = "none";
} else {
if (opts.title !== null) {
this._titleText.textContent = opts.title;
}
if (opts.closeable) {
this._closeButton.textContent = StockIcon["cancel"];
this._closeButton.setAttribute("title", "Close");
this._closeButton.addEventListener("click", (e) => {
Util.consumeEvent(e);
this.close(null);
});
}
}
if (opts.template !== null) {
this._contentPane.appendChild(opts.template.content.cloneNode(true));
}
this._buttons = [];
if (opts.buttons !== null) {
let buttonSpecs = opts.buttons;
if (!Array.isArray(buttonSpecs)) {
buttonSpecs = [ buttonSpecs ];
}
let containsSepar = false;
let defaultAction = null;
for (let buttonSpec of buttonSpecs) {
if (Object.keys(buttonSpec).length === 0) {
// Separator ---
let separ = document.createElement("span");
separ.setAttribute("class", "xui-dlg-button-separ");
this._buttonPane.appendChild(separ);
containsSepar = true;
} else {
// Button ---
let action = (typeof buttonSpec["action"] === "string")?
buttonSpec["action"] : null;
let label = (typeof buttonSpec["label"] === "string")?
buttonSpec["label"] : "???";
let isDefault = (buttonSpec["default"] === true);
let button = document.createElement("button");
button.setAttribute("type", "button");
button.classList.add("xui-control", "xui-dlg-button");
if (isDefault) {
button.classList.add("xui-dlg-default-button");
if (action !== null) {
defaultAction = this[action].bind(this);
}
}
button.textContent = label;
this._buttonPane.appendChild(button);
this._buttons.push(button);
if (action !== null) {
const buttonAction = this[action].bind(this);
button.addEventListener("click", (e) => {
Util.consumeEvent(e);
buttonAction();
});
}
}
}
if (defaultAction !== null) {
const onPressEnter = (e) => {
if (e.key === "Enter") {
Util.consumeEvent(e);
defaultAction();
}
};
const inputs = this._contentPane.querySelectorAll(
"input[type=text],input:not([type]),select,[tabindex]");
for (let input of inputs) {
input.addEventListener("keydown", onPressEnter);
}
}
if (!containsSepar) {
this._buttonPane.classList.add("xui-dlg-button-default-layout");
}
}
if (opts.movable && opts.title !== null) {
this._titleText.addEventListener("mousedown",
this.onDragStart.bind(this));
// When the mousemove handler is set on this._titleText, dragging
// stops when the mouse is moved quickly (because the mouse leaves
// this._titleText. That's why the mousemove handler must be set
// on the document.
this._onDrag = this.onDrag.bind(this);
this._titleText.addEventListener("mouseup",
this.onDragEnd.bind(this));
this._dragOffsetX = this._dragOffsetY = -1;
this._dragging = false;
}
}
get testMode() {
return this._testMode;
}
set testMode(testing) {
this._testMode = testing;
}
get form() {
return this._form;
}
get titlePane() {
return this._titlePane;
}
get contentPane() {
return this._contentPane;
}
get buttonPane() {
return this._buttonPane;
}
get titleText() {
return this._titleText;
}
get closeButton() {
return this._closeButton;
}
get buttons() {
return this._buttons;
}
open(position="center", reference=null) {
this._dialog = Dialogs.open({
type: this._type, classes: this._classes,
form: this._form,
position: position, reference: reference
});
this._dialog.addEventListener("dialogclosed", (e) => {
this.dialogClosed(e.xuiDialogResult);
});
return this._dialog;
}
close(result=null) {
Dialogs.close(this._dialog, result);
this._dialog = null;
}
dialogClosed(result) {
// Default implementation does nothing at all.
}
onDragStart(event) {
if (event.buttons === 1) { // Primary button pressed.
Util.consumeEvent(event);
this._dragging = true;
// Coordinates of the top/left of the dialog relative to this
// initial mouse down.
let dialogRect = this._dialog.getBoundingClientRect();
this._dragOffsetX = event.clientX - dialogRect.left;
this._dragOffsetY = event.clientY - dialogRect.top;
this._titleText.style.cursor = "move";
document.addEventListener("mousemove", this._onDrag);
}
}
onDrag(event) {
if (this._dragging) {
Util.consumeEvent(event);
this._dialog.style.left =
String(event.pageX - this._dragOffsetX) + "px";
this._dialog.style.top =
String(event.pageY - this._dragOffsetY) + "px";
}
}
onDragEnd(event) {
if (this._dragging) {
Util.consumeEvent(event);
document.removeEventListener("mousemove", this._onDrag);
this._titleText.style.cursor = null;
this._dragOffsetX = this._dragOffsetY = -1;
this._dragging = false;
}
}
}
Dialog.TEMPLATE = document.createElement("template");
Dialog.TEMPLATE.innerHTML = `
<div class="xui-dlg-title-pane">
<span draggable="false" class="xui-dlg-title"></span>
<span class="xui-small-icon xui-dlg-close-button"></span>
</div>
<div class="xui-dlg-content-pane"></div>
<div class="xui-dlg-button-pane"></div>
`;