diff --git a/doc/ui-dialogs.md b/doc/ui-dialogs.md new file mode 100644 index 0000000..3fce5ab --- /dev/null +++ b/doc/ui-dialogs.md @@ -0,0 +1,102 @@ +# wolf.ui dialogs + +wolf.ui extension add capacity for pop-up dialogs in an easy way. + +the extensionadds the next tree dialogs functions to wolf.js object: +· wolf.dialog, to load a dialog out of an html file. +· wolf.messaeDialog, to show simple message dialgs. +· wolf.uiDialog, to create dialogs out of wolf.js UI templates (need knowledge about wolf.js UI templates). + +## using dialgos + +All dialogs on the library uses a very similar way of usage: + + wolf.dialog(,,,[buttons-definition],[controller],[creation-callback()],[close-callback()]); + +· app-element is the parent element of the dialog, the root node of the aplication is recommended, but can be any node. +· modal, boolean defining if the dialog is modal. +· dialog-data, dialog usage specific data, this type and maning depends on the dialog method used. See bottom sections. +· buttons-definition, optional but recommended, a definition of the buttons shown in the footer of the application. +· controller, controller object used to catch elements events, recommended, if not defined app-element controller will be used instead (except buttons-definitnion). +· creation-callback, callback to be executed once the dialog is ready and on screen, the dialog element is passed as parameter. +· close-callback, when buttons of buttons-definition does not have an asigned function, the default function is to close the dialog and call this callback passing +the id of the button pressed. + +This gives a lot of ways to define a dialog and the associated logic. + +### wolf.dialog + +The easyest way to call a dialog is creating a HTML with all the content of the dialog, and call the wolf.dialog pasing the url string in the dialog-data parameter. + + wolf.dialog(element.getApplication(),true,"dialogs/car-editor.html",{ + onClose:{text:"close",cancel:true}, + onSave:{text:"save"} + },{ + onSave:(element,event,dialog){ + saveData(getDataFrom(dialog)); //saveData and getDataFrom are not wolf nor wolf.ui framework methods. + dialog.close(); + } + }); + +this can be defined also without controller: + + wolf.dialog(element.getApplication(),true,"dialogs/car-editor.html",{ + onClose:{text:"close"}, + onSave:{ + text:"save", + click:(element,event,dialog) { + saveData(getDataFrom(dialog)); //saveData and getDataFrom are not wolf.js nor wolf.ui framework methods. + dialog.close(); + } + } + }); + +When defining a dialog controller, all events defined with event:... inside the HTML are handled by the dialog controller. + +### wolf.messgeDialog + +Intended to use simple text messages without the need of create an HTML page or advanced wolf.js UI template data. + + wolf.messageDialog(element.getApplication(),true,"Sure to delete car information?",{ + onNo:{text:"no",cancel:true}, + onYes:{ + text:"yes", + click:(element,event,dialog) { + deleteCar(id); //deleteCar and id parameter are not wolf.js nor wolf.ui framework methods. + dialog.close(); + } + }) + +Or using the result callback the dialog gets closed automatically. + + wolf.messageDialog(element.getApplication(),true,"Sure to delete car information?",{ + onNo:{text:"no",cancel:true}, + onYes:{text:"yes"},null,null,button=>{if (button=="onYes") deleteCar(id)}); + +If no buttons are defined a simple button with a cross icon named close will be defined by default. + + wolf.messageDialog(element.getApplication(),true,"Car information deleted"); + +### wolf.uiDialog + +UI dialog is similar to the HTML variant wolf.dialog but passing wolf.js UI template information to the dialog.data, meant to be used as advanced dialog content definition. + +### dialgos button definitions + +The dialog button definition is an object that define button id, content, function and stylish, can be defined in object notation as: + + { + :{ + text:, + icon:, + cancel:, + default:, + click: + } + } + +· button-id is the id of this button, if the close callback is defined, the button-id name will be passed as string or +if the controller have a function with the same id will be executed instead. +· function-definition if defined then this function is executed instead of controller's one. + +Multiple buttons can be defined in the definition object. diff --git a/ui/wolf.css b/ui/wolf.css deleted file mode 100644 index b8c574b..0000000 --- a/ui/wolf.css +++ /dev/null @@ -1,130 +0,0 @@ -/* === DEFAULT CONTROLS === */ - -button { - min-height: 1rem; - line-height: 1rem; - border: none; - background-color: #5a5a5a; - outline: none; - box-shadow: 0 0.2rem 0.5rem #0000005e; - color: white; - padding: 0.5rem 0.7rem; - border-radius: 0.2rem; - margin: 0.2rem; - transition: box-shadow 0.3s; - font-size: 0.8rem; -} - -button:hover { - background-color: #7c7c7c; -} - -button:active { - box-shadow: none; - border: 0.2rem #a1a8b6 solid; - margin: 0; -} - -button.default { - background-color: #0a3a94; -} - -button.default:hover { - background-color: #19469b; -} - -button.default:active { - border: 0.2rem #5c8eec solid; -} - -button.accept { - background-color: #0a9443; -} - -button.accept:hover { - background-color: #18a352; -} - -button.accept:active { - border: 0.2rem #2bb95a solid; -} - -button.cancel { - background-color: #cc2d2d; -} - -button.cancel:hover { - background-color: #da4141; -} - -button.cancel:active { - border: 0.2rem #eb8072 solid; -} - -input, select { - font-size: 0.8rem; - padding: 0.3rem 0.5rem; - border: 0.05rem solid #00000042; - border-radius: 0.2rem; - margin: 0.2rem; - height: 2rem; - background: white; -} - -input:disabled, select:disabled { - background: #f5f5f5; - color: #919191; -} - -/* === TOAST === */ - -.wolf-toast { - position: fixed; - border-radius: 0.4rem; - min-width: 20rem; - min-height: 2rem; - line-height: 2rem; - color: white; - text-align: center; - padding: 0.2rem; - bottom: 5rem; - z-index: 10000; - left: 50%; - transform: translateX(-50%); - font-size: 1rem; - background: #000000d7; - opacity: 1; - transition: opacity 1s, bottom 0.2s; -} - -.wolf-toast.wolf-toast-error { - background: #b93a1aec; -} - -/* === DIALOG === */ - -.wolf-dialog-modal-wall { - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; - background: #00000040; - z-index: 999; -} - -.wolf-dialog { - position: fixed; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - box-shadow: 0 0.1rem 0.25rem #000000A0; - border-radius: 0.2rem; - padding: 0.7rem; - background-color: white; - z-index: 1000; -} - -.wolf-dialog-message-buttons { - text-align: right; -} \ No newline at end of file diff --git a/ui/wolf.lib.css b/ui/wolf.lib.css new file mode 100644 index 0000000..1030855 --- /dev/null +++ b/ui/wolf.lib.css @@ -0,0 +1,77 @@ +/* === DEFAULT CONTROLS === */ + +button { + min-height: 1rem; + line-height: 1rem; + border: none; + background-color: #5a5a5a; + outline: none; + box-shadow: 0 0.2rem 0.5rem #0000005e; + color: white; + padding: 0.5rem 0.7rem; + border-radius: 0.2rem; + margin: 0.2rem; + transition: box-shadow 0.3s; + font-size: 0.8rem; +} + +button:hover { + background-color: #7c7c7c; +} + +button:active { + box-shadow: none; + border: 0.2rem #a1a8b6 solid; + margin: 0; +} + +button.default { + background-color: #0a3a94; +} + +button.default:hover { + background-color: #19469b; +} + +button.default:active { + border: 0.2rem #5c8eec solid; +} + +button.accept { + background-color: #0a9443; +} + +button.accept:hover { + background-color: #18a352; +} + +button.accept:active { + border: 0.2rem #2bb95a solid; +} + +button.cancel { + background-color: #cc2d2d; +} + +button.cancel:hover { + background-color: #da4141; +} + +button.cancel:active { + border: 0.2rem #eb8072 solid; +} + +input, select { + font-size: 0.8rem; + padding: 0.3rem 0.5rem; + border: 0.05rem solid #00000042; + border-radius: 0.2rem; + margin: 0.2rem; + height: 2rem; + background: white; +} + +input:disabled, select:disabled { + background: #f5f5f5; + color: #919191; +} diff --git a/ui/wolf.ui.css b/ui/wolf.ui.css new file mode 100644 index 0000000..1dbb64a --- /dev/null +++ b/ui/wolf.ui.css @@ -0,0 +1,103 @@ +/* === TOAST === */ + +.wolf-toast { + position: fixed; + border-radius: 0.4rem; + min-width: 20rem; + min-height: 2rem; + line-height: 2rem; + color: white; + text-align: center; + padding: 0.2rem; + bottom: 5rem; + z-index: 10000; + left: 50%; + transform: translateX(-50%); + font-size: 1rem; + background: #000000d7; + opacity: 1; + transition: opacity 1s, bottom 0.2s; +} + +.wolf-toast.wolf-toast-error { + background: #b93a1aec; +} + +/* === DIALOG === */ + +.wolf-dialog-modal-wall { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + background: #00000040; + z-index: 10000; +} + +.wolf-dialog { + position: fixed; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + box-shadow: 0 0.1rem 0.25rem #000000A0; + border-radius: 0.2rem; + padding: 1rem; + background-color: white; + /* z-index: 1000; */ + overflow: hidden; + max-height: 95%; + max-width: 95%; +} + +.wolf-dialog-message-text { + margin: 1rem 1rem 2rem 1rem; +} + +.wolf-dialog-body.with-footer { + margin-bottom: 4.5rem; +} + +.wolf-dialog-buttons { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 4.5rem; + text-align: right; + padding: 1rem; + background: #dadada; +} + +.wolf-dialog-buttons button { + text-decoration: none; + color: #fff; + background-color: #26a69a; + text-align: center; + letter-spacing: .5px; + transition: background-color .2s ease-out; + cursor: pointer; + border: none; + border-radius: 2px; + display: inline-block; + height: 2.5rem; + line-height: 36px; + padding: 0 16px; + vertical-align: middle; + -webkit-tap-highlight-color: transparent; + margin-left: 0.5rem; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2); +} + +.wolf-dialog-buttons button .material-icons { + line-height: unset; +} + +.wolf-dialog-buttons button.cancel { + background: #dd7777; +} + +.wolf-dialog-buttons button:hover { + box-shadow: 0 3px 3px 0 rgba(0, 0, 0, 0.14), 0 1px 7px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -1px rgba(0, 0, 0, 0.2); + background-color: #2bbbad; +} \ No newline at end of file diff --git a/ui/wolf.ui.js b/ui/wolf.ui.js index e599069..c3ed3df 100644 --- a/ui/wolf.ui.js +++ b/ui/wolf.ui.js @@ -22,9 +22,122 @@ (() => { 'use strict'; wolf.wolfExtension((K, D, UI, TOOLS) => { - var dlgctrl = { - cnt: 0, - modaling: false + + /** + * Common dialog controller + */ + + function dialogControl(modal, element, controller) { + var modalWall; //Modal wall DOM instance + var base; //Base dialog DOM + var dialog; //Dialog DOM element + var body; //Dialog body DOM element + var buttons;// Dialog buttons footer DOM element + var onClose; //Callback for on close + // Process modaling + if (modal) { + modalWall = UI.instanceTemplate({ type: "div", a: { "class": "wolf-dialog-modal-wall" }, w: {} }, { parent: element })[0]; + element.appendChild(modalWall); + base = modalWall; + } else + base = element; + + // Create dialog frame + var dialog = UI.instanceTemplate({ type: "div", a: { "class": "wolf-dialog" }, w: {}, controller: controller }, { parent: base })[0]; + base.appendChild(dialog); + + dialog.close = function () { + element.removeChild((modal ? modalWall : dialog)); + controller && controller.close && controller.close(); + onClose && onClose(dialog.dialogController.result); + }; + + // Controller logic + /** + * Adds a UI template to the dialog and instences it + * @param {*} ui UI template object or array + */ + function append(ui) { + if (!body) { + body = UI.instanceTemplate({ type: "div", a: { "class": "wolf-dialog-body" }, w: {} }, { parent: dialog })[0]; + if (buttons) + body.classList.add("with-footer"); + dialog.appendChild(body); + } + if (ui.insertTo) //Assuming fragment + ui.insertTo(body); + else { + if (!Array.isArray(ui)) + ui = [ui]; + ui.forEach(uielem => { + var uiDOM = UI.instanceTemplate(uielem, { parent: body }); + uiDOM.forEach(dom => body.appendChild(dom)); + }) + } + } + + /** + * Adds buttons definitions or UI template to the dialog foooter + * @param {*} buttonsDef Button definition or UI template object or array + */ + function appendButtons(buttonsDef) { + if (body) + body.classList.add("with-footer"); + if (!buttons) { + buttons = UI.instanceTemplate({ type: "div", a: { "class": "wolf-dialog-buttons" }, w: {} }, { parent: dialog })[0]; + dialog.appendChild(buttons); + } + if (!Array.isArray(buttonsDef)) + buttonsDef = [buttonsDef]; + buttonsDef.forEach(uidef => { + if (uidef.type || uidef.value) { + var uiDOM = UI.instanceTemplate(uidef); + uiDOM.forEach(dom => buttons.appendChild(dom)); + } else + for (var k in uidef) { + var def = uidef[k]; + var button = UI.instanceTemplate({ type: "button", a: {}, w: {}, c: [{ value: def.text }] }, { parent: dialog })[0]; + if (def.default) + button.classList.add("default"); + if (def.cancel) + button.classList.add("cancel"); + installEvent(button, k, def); + buttons.appendChild(button); + } + function installEvent(button, id, def) { + button.addEventListener("click", (evt) => { + var evtMethod; + if (controller) + evtMethod = controller[id]; + if (!evtMethod) + evtMethod = def.click; + if (!evtMethod) + evtMethod = () => { + dialog.dialogController.result = id; + dialog.close(); + } + evtMethod(element, evt, dialog); + }); + } + }); + } + + /** + * Raises the callback of the dialog + * @param {*} cb callback + * @param {*} close callback for on close + */ + function callback(cb, close) { + cb && cb(dialog, element, controller); + onClose = close; + } + + // Return controller + return dialog.dialogController = { + append: append, + appendButtons: appendButtons, + callback: callback, + } } // ==================== @@ -52,100 +165,57 @@ /** * Show a dialog embedding a fragment. - * @param {string} url Url of the fragment * @param {element} element Destination element, application element recommended * @param {boolean} modal True to show the dialog in modal way + * @param {string} url Url of the fragment + * @param {*} [buttons] Button definition * @param {*} [controller] Controller for the dialogs events, if none the parent element one will be used * @param {function} [callback] Callback when the dialog is shown + * @param {function} [onclose] Callback when dialog close, passing the dialog result */ - function dialog(url, element, modal, controller, callback) { - var modalWall, dialogFrame, modaling; - if (modal) { - if (!dlgctrl.modaling) - dlgctrl.modaling = modaling = true; - modalWall = UI.instanceTemplate({ type: "div", a: { "class": "wolf-dialog-modal-wall" }, w: {} }, { parent: element })[0]; - element.appendChild(modalWall); - } + function dialog(element, modal, url, buttons, controller, callback, onclose) { + var dc = dialogControl(modal, element, controller); + UI.loadFragment(url, (fragment) => { - dialogFrame = UI.instanceTemplate({ type: "div", a: { "class": "wolf-dialog" }, w: {}, controller: controller }, { parent: modal ? modalWall : element })[0]; - dialogFrame.close = function () { - element.removeChild((modal ? modalWall : dialogFrame)) - if (modaling) - dlgctrl.modaling = false; - }; - (modal ? modalWall : element).appendChild(dialogFrame); - fragment.insertTo(dialogFrame); - callback && callback(dialogFrame, element); + dc.append(fragment); + if (buttons) + dc.appendButtons(buttons); + dc.callback(callback, onclose); }); } /** * Show a dialog asking or showing information. - * @param {string} text Text to show - * @param {*} buttons Button definition * @param {element} element Destination element, application element recommended * @param {boolean} modal True to show the dialog in modal way + * @param {string} text Text to show + * @param {*} [buttons] Button definition * @param {*} [controller] Controller for the dialogs events, if none the parent element one will be used * @param {function} [callback] Callback when the dialog is shown + * @param {function} [onclose] Callback when dialog close, passing the dialog result */ - function messageDialog(text, buttons, element, modal, controller, callback) { - var modalWall, dialogFrame, modaling; - if (modal) { - modalWall = UI.instanceTemplate({ type: "div", a: { "class": "wolf-dialog-modal-wall" }, w: {} }, { parent: element })[0]; - if (!dlgctrl.modaling) { - dlgctrl.modaling = modaling = true; - modalWall.style.background = "transparent"; - } - element.appendChild(modalWall); - } - dialogFrame = UI.instanceTemplate({ type: "div", a: { "class": "wolf-dialog wolf-dialog-message" }, w: {}, controller: controller }, { parent: modal ? modalWall : element })[0]; - dialogFrame.close = function () { - element.removeChild((modal ? modalWall : dialogFrame)) - if (modaling) - dlgctrl.modaling = false; - }; - (modal ? modalWall : element).appendChild(dialogFrame); - - controller = controller || {}; - var bts = []; - buttons = buttons || { doOk: "Ok" };// Default ok button - - for (var bt in buttons) { - var btext = buttons[bt]; - var cancel = btext && btext[0] == '!'; - var deflt = btext && btext[0] == '*'; - if (cancel || deflt) - btext = btext.substr(1); - bts.push({ - type: "button", a: { class: deflt ? "default" : cancel ? "cancel" : "" }, w: {}, e: { click: bt }, c: [ - { type: "", value: btext } - ] - }); - controller[bt] = controller[bt] || dialogFrame.close; - } - - var dlg = UI.instanceTemplate({ - type: "div", controller: controller, a: { "class": "wolf-dialog-message-body" }, w: {}, c: [ - { type: "div", a: { "class": "wolf-dialog-message-text" }, w: {}, c: [{ type: "", value: text }] }, - { type: "div", a: { "class": "wolf-dialog-message-buttons" }, w: {}, c: bts } - ] - }, { parent: element })[0]; - dialogFrame.appendChild(dlg); - - callback && callback(dialogFrame, element); + function messageDialog(element, modal, text, buttons, controller, callback, onclose) { + if (!buttons) + buttons = { close: { icon: "close", cancel: true } }; + uiDialog({ value: text }, buttons, element, modal, controller, callback, onclose) } /** * Show a dialog asking or showing information. - * @param {*} ui UI definition - * @param {*} buttons Button definition * @param {element} element Destination element, application element recommended * @param {boolean} modal True to show the dialog in modal way + * @param {*} ui UI body definition + * @param {*} [buttons] Button definition * @param {*} [controller] Controller for the dialogs events, if none the parent element one will be used * @param {function} [callback] Callback when the dialog is shown + * @param {function} [onclose] Callback when dialog close, passing the dialog result */ - function uiDialog(ui, buttons, element, modal, controller, callback) { - + function uiDialog(ui, buttons, element, modal, controller, callback, onclose) { + var dc = dialogControl(modal, element, controller); + dc.append(ui); + if (buttons) + dc.appendButtons(buttons); + dc.callback(callback, onclose); } /** @@ -277,7 +347,7 @@ var script = c.c[0].value; if (script && (typeof script != "string" || script.indexOf("use strict") < 1)) throw new Error("Control definition wolf:" + id + " script 'use strict'; mandatory"); - scriptFactory = new Function('control', `${script};\n//# sourceURL=wolf:${id}`); + scriptFactory = new Function('control', 'K', 'D', 'UI', 'TOOLS', `${script};\n//# sourceURL=wolf:${id}`); break; } default: @@ -303,11 +373,20 @@ if (data instanceof D.Binding) data = data.getValue(ext.parent); return data; + }, + binding: name => { + var data = values[name]; + if (data instanceof D.Binding) + return data; + return null; + }, + childs: name => { + return ext.getChildNodes(name); } } - var script = scriptFactory ? scriptFactory(API) : {}; + var script = scriptFactory ? scriptFactory(API, K, D, UI, TOOLS) : {}; API.controller = script; - + API.instanceTemplate = (templ, ext2) => UI.instanceTemplate(templ, ext2 ? ext2 : { parent: ext.parent, customController: script }) if (!script.render) script.render = () => { var u = ui[""]; diff --git a/wolf.js b/wolf.js index bf5d469..1391cf6 100644 --- a/wolf.js +++ b/wolf.js @@ -906,7 +906,7 @@ */ function processElement(element, template, ext) { var contextPath = ext ? ext.contextPath : undefined; - var defaultParent = ext ? ext.parent : undefined; //TODO: UGLY WORKAROUND. default parent is used for parent tree calculation on early stages of control instancing. Avoid the need to have this default parent + var defaultParent = ext ? ext.parent : undefined; var customController = ext ? ext.customController : undefined; /** diff --git a/wolf.md b/wolf.md index 54de094..b05093b 100644 --- a/wolf.md +++ b/wolf.md @@ -15,9 +15,19 @@ #### i18n -### Predefined controls +### wolf.ui -toast -dialogs +wolf.ui library is an extension for the wolf.js used to add some predefined UI and UI design capabilities to wolf.js + +To load wolf.ui simply import it on the HTML header of your page after the wolf.js import (wolf.js must be imported before). + + + + + + + +Additionally the wolf.ui.css and wolf.lib.css are recommended. +For more information read the "ui-" documents inside doc folder ### Element API \ No newline at end of file