Bugfixes, dialog improvements

* It is now possible to pass an element as the dialog message to have it inserted as the body.
This commit is contained in:
2025-09-21 00:45:34 +02:00
parent 6e1351d9ab
commit bd7b7ac498
7 changed files with 95 additions and 20 deletions

View File

@@ -67,6 +67,13 @@ dialog.msgbox("Ready!").then(
dialog.select("Put in warehouse", "Select", { "none":"None", "1":"WH1", "2":"WH2" }).then(...)
```
* `dialog.TYPE(body, ?title, ?actions, ?options)``Promise`
Options:
- `showClose` - true to show close button in top left corner
- `width` - valid CSS width definition
### dom.js
This component offers `dom` and `el` that can be used to manipulate the DOM.
@@ -143,4 +150,16 @@ productForm.editable = true;
// when the user is done editing set editable = false and access the updated model
productForm.editable = false;
const newModel = productForm.model; // → { product:{ name:"Swedish Fish" }, price:"8.99" }
// You can even put a form inside a dialog!
dialog.dialog(productForm.dom(), "Title", {"ok":"Update"}, { showClose:true, width:'400px' }).then(
(result) => {
productForm.editable = false;
if (result == 'ok') {
// productForm.model is updated
}
}
)
productForm.editable = true;
```

View File

@@ -53,7 +53,7 @@
border-top-right-radius: 0.5rem;
/* background: rgba(0, 0, 0, 0.2); */
display: flex;
padding: 0.5rem;
padding: 0.75rem;
}
.-title {
@@ -68,18 +68,18 @@
}
.-content {
padding: 0.5rem;
padding: 0px 0.75rem;
}
.-buttons {
padding: 0.5rem;
padding-top: 0.2rem;
text-align: center;
padding: 0.75rem;
text-align: right;
.action {
font-size: 105%;
border: solid 1px #666;
border-radius: 0.25rem;
padding: 0.35rem 0.6rem;
padding: 0.45rem 0.8rem;
margin: 0px 0.25rem;
background: #eee;

View File

@@ -20,6 +20,7 @@ class UserDialog {
#title = "MessageBox";
#message;
#actions = { "ok":"Ok" };
#options = {};
/**
* Dialog constructor
@@ -29,11 +30,12 @@ class UserDialog {
* @param {string} message The dialog message
* @param {object} actions The dialog actions
*/
constructor(style, title, message, actions) {
constructor(style, title, message, actions, options) {
if (style) this.#style = style;
if (title) this.#title = title;
if (message) this.#message = message;
if (actions && typeof actions == 'object') this.#actions = actions;
if (options && typeof options == 'object') this.#options = options;
}
/**
@@ -56,6 +58,9 @@ class UserDialog {
throw new Error("#buildDialog should not be called with an active #dialog");
const dialog = el.dialog({ class: 'jsl-dialog' });
if (this.#options.width) {
dialog.style.width = this.#options.width;
}
dialog.addEventListener('close', event => {
if (this.#resolver) {
this.#resolver(false);
@@ -68,10 +73,16 @@ class UserDialog {
let cbtn;
dom.append(dialog, el.div({ class: '-header' }, [
el.div({ class: '-title' }, this.#title),
// cbtn = el.button({ class: '-close-btn' }, '✕')
cbtn = (this.#options.showClose ?? false) ? el.button({ class: '-close-btn' }, '✕') : null,
]));
if (this.#options.body ?? null) {
dom.append(dialog, el.div({ class: '-content' }, this.#options.body));
} else {
dom.append(dialog, el.div({ class: '-content' }, this.#message));
// cbtn.addEventListener('click', () => { this.#handleButton(false); });
}
if (this.#options.showClose ?? false) {
cbtn.addEventListener('click', () => { this.#handleButton(false); });
}
const buttons = el.div({ class: '-buttons' });
let tabindex = 1;
@@ -129,39 +140,39 @@ class UserDialogFactory {
* @param {object} actions The buttons to present at the bottom of the dialog
* @returns Promise
*/
showDialog(type, title, message, actions) {
showDialog(type, title, message, actions, options) {
let dialog;
switch (type) {
case 'msgbox':
title = title ?? "MessageBox";
actions = actions ?? { 'ok': 'Ok' };
dialog = new UserDialog('msgbox', title, message, actions);
dialog = new UserDialog('msgbox', title, message, actions, options);
break;
case 'information':
case 'info':
title = title ?? "Information";
actions = actions ?? { 'ok': 'Ok' };
dialog = new UserDialog('info', title, message, actions);
dialog = new UserDialog('info', title, message, actions, options);
break;
case 'error':
title = title ?? "Error";
actions = actions ?? { 'ok': 'Ok' };
dialog = new UserDialog('error', title, message, actions);
dialog = new UserDialog('error', title, message, actions, options);
break;
case 'warning':
title = title ?? "Warning";
actions = actions ?? { 'ok': 'Ok' };
dialog = new UserDialog('warning', title, message, actions);
dialog = new UserDialog('warning', title, message, actions, options);
break;
case 'confirm':
title = title ?? "Confirmation";
actions = actions ?? { 'ok': 'Ok', 'cancel': 'Cancel' };
dialog = new UserDialog('confirm', title, message, actions);
dialog = new UserDialog('confirm', title, message, actions, options);
break;
default:
title = title ?? "Dialog";
actions = actions ?? { 'ok': 'Ok' };
dialog = new UserDialog(type, title, message, actions);
dialog = new UserDialog(type, title, message, actions, options);
}
return dialog.show();
@@ -176,7 +187,7 @@ const dialog = new Proxy(factory, {
get(target, name) {
return name in target
? target[name]
: (message, title = null, actions = null) => target.showDialog(name, title, message, actions);
: (message, title = null, actions = null, options = {}) => target.showDialog(name, title, message, actions, options);
}
});

2
dom.js
View File

@@ -56,6 +56,8 @@ class DomHelper {
* @returns
*/
append(tag, children) {
// Handle null, to make it easy to skip items with conditionals
if (children === null) return;
//console.debug(`append: ${tag.nodeName}`, children);
if (typeof children == 'object' && children instanceof Element) {
//console.debug(`-> appended element`);

View File

@@ -15,7 +15,7 @@ import { jsonQuery, jsonPatch, tokenizePath } from './json.js';
import { date } from './date.js';
// build our dom
let btn, btn2, btn3;
let btn, btn2, btn3, btn4;
let root = el.div({}, [
el.div({ style:'color:#444;' }, "This is dynamically generated!"),
el.div({ style:'display:flex; gap:0.25rem; margin-top:0.25rem;' }, [
@@ -23,6 +23,7 @@ let root = el.div({}, [
btn = el.button({}, "Hello World"),
btn2 = el.button({}, "Error mode"),
btn3 = el.button({}, "Edit form"),
btn4 = el.button({}, "Form modal"),
])
]);
// click handler
@@ -49,6 +50,8 @@ userForm.layout.addRow()
userForm.layout.addRow()
.append(new TextField({ label:"Created", width:50, path:".meta.created", locked:true }))
.append(new TextField({ label:"Updated", width:50, path:".meta.updated", locked:true }));
userForm.layout.addRow()
.append(new NumericField({ label:"Salary", width:30, path:".meta.salary", fixed:2 }));
document.body.appendChild(userForm.dom());
// Set the model
@@ -67,6 +70,26 @@ btn3.addEventListener('click', () => {
}
});
const productForm = new JsonForm();
productForm.layout.addRow()
.append(new TextField({ label:"Product", width:60, path:".product.name" }))
.append(new NumericField({ label:"Price", path:".product.price", fixed:2 }));
productForm.model = {};
btn4.addEventListener('click', () => {
dialog.dialog(productForm.dom(), "Title", {"ok":"Update"}, { showClose:true, width:'400px' }).then(
(result) => {
productForm.editable = false;
if (result == 'ok') {
console.log(productForm.model);
}
}
)
productForm.editable = true;
});
// console.log(tokenizePath(".foo.bar[1].title"));
// console.log(jsonQuery({ "foo":{ "bar":"42" }}, ".foo.bar" ));
// console.log(jsonQuery([ { name:"cheese" }, { name:"bread" }, { name:"milk" } ], "[].name" ));

View File

@@ -90,7 +90,13 @@ function jsonPatch(json, path, value) {
if (search === 'key') {
// find key tok in ptr
if (typeof ptr[tok] === 'undefined') {
if (toks.length === 0) {
ptr[tok] = value;
return json;
} else {
ptr[tok] = {};
ptr = ptr[tok];
}
} else {
if (toks.length === 0) {
ptr[tok] = value;

View File

@@ -119,6 +119,7 @@ class FormRow {
updateModel(model) {
this.#fields.forEach(field => {
if (!field.options.path) return;
field.value = field.getValue();
model = jsonPatch(model, field.options.path, field.value);
});
return model;
@@ -206,9 +207,14 @@ class TextField extends FormField {
this.#input.innerText = this.value;
}
valueUpdated() {
if (!this.#input) return;
this.#input.innerText = this.value;
}
getValue() {
if (this.#input.innerText === '') {
if (this.options.nullable !== false)
return null;
}
return this.#input.innerText;
}
setEditable(state) {
@@ -231,9 +237,17 @@ class NumericField extends FormField {
this.#input.innerText = this.value;
}
valueUpdated() {
if (!this.#input) return;
this.#input.innerText = this.value;
}
getValue() {
if (this.#input.innerText === '') {
if (this.options.nullable !== false)
return null;
}
if (this.options.fixed && typeof this.options.fixed == 'number') {
return parseFloat(this.#input.innerText).toFixed(this.options.fixed);
}
if (this.options.float) {
return parseFloat(this.#input.innerText);
}