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:
19
README.md
19
README.md
@@ -67,6 +67,13 @@ dialog.msgbox("Ready!").then(
|
|||||||
dialog.select("Put in warehouse", "Select", { "none":"None", "1":"WH1", "2":"WH2" }).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
|
### dom.js
|
||||||
|
|
||||||
This component offers `dom` and `el` that can be used to manipulate the DOM.
|
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
|
// when the user is done editing set editable = false and access the updated model
|
||||||
productForm.editable = false;
|
productForm.editable = false;
|
||||||
const newModel = productForm.model; // → { product:{ name:"Swedish Fish" }, price:"8.99" }
|
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;
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
12
dialog.css
12
dialog.css
@@ -53,7 +53,7 @@
|
|||||||
border-top-right-radius: 0.5rem;
|
border-top-right-radius: 0.5rem;
|
||||||
/* background: rgba(0, 0, 0, 0.2); */
|
/* background: rgba(0, 0, 0, 0.2); */
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 0.5rem;
|
padding: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.-title {
|
.-title {
|
||||||
@@ -68,18 +68,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.-content {
|
.-content {
|
||||||
padding: 0.5rem;
|
padding: 0px 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.-buttons {
|
.-buttons {
|
||||||
padding: 0.5rem;
|
padding: 0.75rem;
|
||||||
padding-top: 0.2rem;
|
text-align: right;
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.action {
|
.action {
|
||||||
|
font-size: 105%;
|
||||||
border: solid 1px #666;
|
border: solid 1px #666;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
padding: 0.35rem 0.6rem;
|
padding: 0.45rem 0.8rem;
|
||||||
margin: 0px 0.25rem;
|
margin: 0px 0.25rem;
|
||||||
background: #eee;
|
background: #eee;
|
||||||
|
|
||||||
|
|||||||
35
dialog.js
35
dialog.js
@@ -20,6 +20,7 @@ class UserDialog {
|
|||||||
#title = "MessageBox";
|
#title = "MessageBox";
|
||||||
#message;
|
#message;
|
||||||
#actions = { "ok":"Ok" };
|
#actions = { "ok":"Ok" };
|
||||||
|
#options = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog constructor
|
* Dialog constructor
|
||||||
@@ -29,11 +30,12 @@ class UserDialog {
|
|||||||
* @param {string} message The dialog message
|
* @param {string} message The dialog message
|
||||||
* @param {object} actions The dialog actions
|
* @param {object} actions The dialog actions
|
||||||
*/
|
*/
|
||||||
constructor(style, title, message, actions) {
|
constructor(style, title, message, actions, options) {
|
||||||
if (style) this.#style = style;
|
if (style) this.#style = style;
|
||||||
if (title) this.#title = title;
|
if (title) this.#title = title;
|
||||||
if (message) this.#message = message;
|
if (message) this.#message = message;
|
||||||
if (actions && typeof actions == 'object') this.#actions = actions;
|
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");
|
throw new Error("#buildDialog should not be called with an active #dialog");
|
||||||
|
|
||||||
const dialog = el.dialog({ class: 'jsl-dialog' });
|
const dialog = el.dialog({ class: 'jsl-dialog' });
|
||||||
|
if (this.#options.width) {
|
||||||
|
dialog.style.width = this.#options.width;
|
||||||
|
}
|
||||||
dialog.addEventListener('close', event => {
|
dialog.addEventListener('close', event => {
|
||||||
if (this.#resolver) {
|
if (this.#resolver) {
|
||||||
this.#resolver(false);
|
this.#resolver(false);
|
||||||
@@ -68,10 +73,16 @@ class UserDialog {
|
|||||||
let cbtn;
|
let cbtn;
|
||||||
dom.append(dialog, el.div({ class: '-header' }, [
|
dom.append(dialog, el.div({ class: '-header' }, [
|
||||||
el.div({ class: '-title' }, this.#title),
|
el.div({ class: '-title' }, this.#title),
|
||||||
// cbtn = el.button({ class: '-close-btn' }, '✕')
|
cbtn = (this.#options.showClose ?? false) ? el.button({ class: '-close-btn' }, '✕') : null,
|
||||||
]));
|
]));
|
||||||
dom.append(dialog, el.div({ class: '-content' }, this.#message));
|
if (this.#options.body ?? null) {
|
||||||
// cbtn.addEventListener('click', () => { this.#handleButton(false); });
|
dom.append(dialog, el.div({ class: '-content' }, this.#options.body));
|
||||||
|
} else {
|
||||||
|
dom.append(dialog, el.div({ class: '-content' }, this.#message));
|
||||||
|
}
|
||||||
|
if (this.#options.showClose ?? false) {
|
||||||
|
cbtn.addEventListener('click', () => { this.#handleButton(false); });
|
||||||
|
}
|
||||||
|
|
||||||
const buttons = el.div({ class: '-buttons' });
|
const buttons = el.div({ class: '-buttons' });
|
||||||
let tabindex = 1;
|
let tabindex = 1;
|
||||||
@@ -129,39 +140,39 @@ class UserDialogFactory {
|
|||||||
* @param {object} actions The buttons to present at the bottom of the dialog
|
* @param {object} actions The buttons to present at the bottom of the dialog
|
||||||
* @returns Promise
|
* @returns Promise
|
||||||
*/
|
*/
|
||||||
showDialog(type, title, message, actions) {
|
showDialog(type, title, message, actions, options) {
|
||||||
let dialog;
|
let dialog;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'msgbox':
|
case 'msgbox':
|
||||||
title = title ?? "MessageBox";
|
title = title ?? "MessageBox";
|
||||||
actions = actions ?? { 'ok': 'Ok' };
|
actions = actions ?? { 'ok': 'Ok' };
|
||||||
dialog = new UserDialog('msgbox', title, message, actions);
|
dialog = new UserDialog('msgbox', title, message, actions, options);
|
||||||
break;
|
break;
|
||||||
case 'information':
|
case 'information':
|
||||||
case 'info':
|
case 'info':
|
||||||
title = title ?? "Information";
|
title = title ?? "Information";
|
||||||
actions = actions ?? { 'ok': 'Ok' };
|
actions = actions ?? { 'ok': 'Ok' };
|
||||||
dialog = new UserDialog('info', title, message, actions);
|
dialog = new UserDialog('info', title, message, actions, options);
|
||||||
break;
|
break;
|
||||||
case 'error':
|
case 'error':
|
||||||
title = title ?? "Error";
|
title = title ?? "Error";
|
||||||
actions = actions ?? { 'ok': 'Ok' };
|
actions = actions ?? { 'ok': 'Ok' };
|
||||||
dialog = new UserDialog('error', title, message, actions);
|
dialog = new UserDialog('error', title, message, actions, options);
|
||||||
break;
|
break;
|
||||||
case 'warning':
|
case 'warning':
|
||||||
title = title ?? "Warning";
|
title = title ?? "Warning";
|
||||||
actions = actions ?? { 'ok': 'Ok' };
|
actions = actions ?? { 'ok': 'Ok' };
|
||||||
dialog = new UserDialog('warning', title, message, actions);
|
dialog = new UserDialog('warning', title, message, actions, options);
|
||||||
break;
|
break;
|
||||||
case 'confirm':
|
case 'confirm':
|
||||||
title = title ?? "Confirmation";
|
title = title ?? "Confirmation";
|
||||||
actions = actions ?? { 'ok': 'Ok', 'cancel': 'Cancel' };
|
actions = actions ?? { 'ok': 'Ok', 'cancel': 'Cancel' };
|
||||||
dialog = new UserDialog('confirm', title, message, actions);
|
dialog = new UserDialog('confirm', title, message, actions, options);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
title = title ?? "Dialog";
|
title = title ?? "Dialog";
|
||||||
actions = actions ?? { 'ok': 'Ok' };
|
actions = actions ?? { 'ok': 'Ok' };
|
||||||
dialog = new UserDialog(type, title, message, actions);
|
dialog = new UserDialog(type, title, message, actions, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
return dialog.show();
|
return dialog.show();
|
||||||
@@ -176,7 +187,7 @@ const dialog = new Proxy(factory, {
|
|||||||
get(target, name) {
|
get(target, name) {
|
||||||
return name in target
|
return name in target
|
||||||
? target[name]
|
? 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
2
dom.js
@@ -56,6 +56,8 @@ class DomHelper {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
append(tag, children) {
|
append(tag, children) {
|
||||||
|
// Handle null, to make it easy to skip items with conditionals
|
||||||
|
if (children === null) return;
|
||||||
//console.debug(`append: ${tag.nodeName}`, children);
|
//console.debug(`append: ${tag.nodeName}`, children);
|
||||||
if (typeof children == 'object' && children instanceof Element) {
|
if (typeof children == 'object' && children instanceof Element) {
|
||||||
//console.debug(`-> appended element`);
|
//console.debug(`-> appended element`);
|
||||||
|
|||||||
25
index.html
25
index.html
@@ -15,7 +15,7 @@ import { jsonQuery, jsonPatch, tokenizePath } from './json.js';
|
|||||||
import { date } from './date.js';
|
import { date } from './date.js';
|
||||||
|
|
||||||
// build our dom
|
// build our dom
|
||||||
let btn, btn2, btn3;
|
let btn, btn2, btn3, btn4;
|
||||||
let root = el.div({}, [
|
let root = el.div({}, [
|
||||||
el.div({ style:'color:#444;' }, "This is dynamically generated!"),
|
el.div({ style:'color:#444;' }, "This is dynamically generated!"),
|
||||||
el.div({ style:'display:flex; gap:0.25rem; margin-top:0.25rem;' }, [
|
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"),
|
btn = el.button({}, "Hello World"),
|
||||||
btn2 = el.button({}, "Error mode"),
|
btn2 = el.button({}, "Error mode"),
|
||||||
btn3 = el.button({}, "Edit form"),
|
btn3 = el.button({}, "Edit form"),
|
||||||
|
btn4 = el.button({}, "Form modal"),
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
// click handler
|
// click handler
|
||||||
@@ -49,6 +50,8 @@ userForm.layout.addRow()
|
|||||||
userForm.layout.addRow()
|
userForm.layout.addRow()
|
||||||
.append(new TextField({ label:"Created", width:50, path:".meta.created", locked:true }))
|
.append(new TextField({ label:"Created", width:50, path:".meta.created", locked:true }))
|
||||||
.append(new TextField({ label:"Updated", width:50, path:".meta.updated", 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());
|
document.body.appendChild(userForm.dom());
|
||||||
|
|
||||||
// Set the model
|
// 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(tokenizePath(".foo.bar[1].title"));
|
||||||
// console.log(jsonQuery({ "foo":{ "bar":"42" }}, ".foo.bar" ));
|
// console.log(jsonQuery({ "foo":{ "bar":"42" }}, ".foo.bar" ));
|
||||||
// console.log(jsonQuery([ { name:"cheese" }, { name:"bread" }, { name:"milk" } ], "[].name" ));
|
// console.log(jsonQuery([ { name:"cheese" }, { name:"bread" }, { name:"milk" } ], "[].name" ));
|
||||||
|
|||||||
8
json.js
8
json.js
@@ -90,7 +90,13 @@ function jsonPatch(json, path, value) {
|
|||||||
if (search === 'key') {
|
if (search === 'key') {
|
||||||
// find key tok in ptr
|
// find key tok in ptr
|
||||||
if (typeof ptr[tok] === 'undefined') {
|
if (typeof ptr[tok] === 'undefined') {
|
||||||
return json;
|
if (toks.length === 0) {
|
||||||
|
ptr[tok] = value;
|
||||||
|
return json;
|
||||||
|
} else {
|
||||||
|
ptr[tok] = {};
|
||||||
|
ptr = ptr[tok];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (toks.length === 0) {
|
if (toks.length === 0) {
|
||||||
ptr[tok] = value;
|
ptr[tok] = value;
|
||||||
|
|||||||
14
jsonform.js
14
jsonform.js
@@ -119,6 +119,7 @@ class FormRow {
|
|||||||
updateModel(model) {
|
updateModel(model) {
|
||||||
this.#fields.forEach(field => {
|
this.#fields.forEach(field => {
|
||||||
if (!field.options.path) return;
|
if (!field.options.path) return;
|
||||||
|
field.value = field.getValue();
|
||||||
model = jsonPatch(model, field.options.path, field.value);
|
model = jsonPatch(model, field.options.path, field.value);
|
||||||
});
|
});
|
||||||
return model;
|
return model;
|
||||||
@@ -206,9 +207,14 @@ class TextField extends FormField {
|
|||||||
this.#input.innerText = this.value;
|
this.#input.innerText = this.value;
|
||||||
}
|
}
|
||||||
valueUpdated() {
|
valueUpdated() {
|
||||||
|
if (!this.#input) return;
|
||||||
this.#input.innerText = this.value;
|
this.#input.innerText = this.value;
|
||||||
}
|
}
|
||||||
getValue() {
|
getValue() {
|
||||||
|
if (this.#input.innerText === '') {
|
||||||
|
if (this.options.nullable !== false)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return this.#input.innerText;
|
return this.#input.innerText;
|
||||||
}
|
}
|
||||||
setEditable(state) {
|
setEditable(state) {
|
||||||
@@ -231,9 +237,17 @@ class NumericField extends FormField {
|
|||||||
this.#input.innerText = this.value;
|
this.#input.innerText = this.value;
|
||||||
}
|
}
|
||||||
valueUpdated() {
|
valueUpdated() {
|
||||||
|
if (!this.#input) return;
|
||||||
this.#input.innerText = this.value;
|
this.#input.innerText = this.value;
|
||||||
}
|
}
|
||||||
getValue() {
|
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) {
|
if (this.options.float) {
|
||||||
return parseFloat(this.#input.innerText);
|
return parseFloat(this.#input.innerText);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user