From 1e8ae99d39b98eb473fbce935c6f347c68e7d33d Mon Sep 17 00:00:00 2001 From: Ahmed Zamil Date: Thu, 21 Oct 2021 14:15:22 +0300 Subject: [PATCH] 1.1.2 --- css/iui.css | 94 +++++++++++++++++++------- package.json | 31 +++++++++ src/Core/Binding.js | 7 +- src/Core/IUIElement.js | 30 +++++---- src/Data/Form.js | 25 ++----- src/Data/Include.js | 47 +++++++++---- src/Data/Modifiable.js | 139 +++++++++++++++++++++++++++++++++++++++ src/Data/Repeat.js | 5 +- src/Router/Route.js | 2 + src/Router/Router.js | 16 +++-- src/UI/DateTimePicker.js | 2 +- src/UI/Input.js | 15 +++++ src/UI/Tab.js | 4 +- src/UI/Table.js | 1 + src/UI/Tabs.js | 5 +- 15 files changed, 339 insertions(+), 84 deletions(-) create mode 100644 package.json create mode 100644 src/Data/Modifiable.js diff --git a/css/iui.css b/css/iui.css index 682fbe9..274a520 100644 --- a/css/iui.css +++ b/css/iui.css @@ -192,17 +192,17 @@ background: var(--textbox-background); } - .textbox-with-label > span { - padding: 10px; - pointer-events: none; - position: absolute; - top: 0; - transition: 0.4s; - transition-timing-function: ease; - transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1); - opacity: 0.5; - color: var(--default-link-color); - } +.textbox-with-label > span { + padding: 10px; + pointer-events: none; + position: absolute; + top: 0; + transition: 0.4s; + transition-timing-function: ease; + transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1); + opacity: 0.5; + color: var(--default-link-color); +} html[dir="rtl"] .textbox-with-label > span { right: 0; @@ -219,30 +219,75 @@ html[dir="ltr"] .textbox-with-label > span { background: var(--textbox-background-active); } -.textbox-with-label > input { +.textbox-with-label > input, +.textbox-with-label > i-input > input +{ padding: 5px; border: 0; background: var(--textbox-background); color: var(--textbox-color); } - .textbox-with-label > input:focus { - outline: none; - background: var(--textbox-background-active); - } +.textbox-with-label > input:focus, +.textbox-with-label > i-input:focus +{ + outline: none; + background: var(--textbox-background-active); +} - .textbox-with-label > input:focus + span, - .textbox-with-label > input:not(:placeholder-shown) + span { - opacity: 1; - transform: scale(0.75) translateY(-40%) translateX(-20px); - } +.textbox-with-label > input:focus + span, +.textbox-with-label > input:not(:placeholder-shown) + span +{ + opacity: 1; + transform: scale(0.75) translateY(-40%) translateX(-20px); +} html[dir="rtl"] .textbox-with-label > input:focus + span, -html[dir="rtl"] .textbox-with-label > input:not(:placeholder-shown) + span { +html[dir="rtl"] .textbox-with-label > input:not(:placeholder-shown) + span +{ opacity: 1; transform: scale(0.75) translateY(-40%) translateX(20%); } +.input > input:focus + span, +.input > input:not(:placeholder-shown) + span +{ + opacity: 1; + transform: scale(0.75) translateY(-40%) translateX(-20px); +} + +html[dir="rtl"] .input > input:focus + span, +html[dir="rtl"] .input > input:not(:placeholder-shown) + span +{ + opacity: 1; + transform: scale(0.75) translateY(-40%) translateX(20%); +} + +.input > span { + padding: 10px; + pointer-events: none; + position: absolute; + top: 0; + transition: 0.4s; + transition-timing-function: ease; + transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1); + opacity: 0.5; + color: var(--default-link-color); +} + +.input[caption] > input { + padding: 16px 10px 5px 10; +} + +html[dir="rtl"] .input > span { + right: 0; +} + +html[dir="ltr"] .input > span { + left: 0; +} + + .range2 { position: relative; } @@ -1932,6 +1977,9 @@ _:-moz-tree-row(hover), html[dir='rtl'] .autocomplete-menu, html[dir='rtl'] .sel .input-invalid > input { border-color: var(--textbox-border-color-invalid) !important; +} + +.input-invalid > input:focus { box-shadow: var(--textbox-box-shadow-invalid) !important; } @@ -2555,6 +2603,7 @@ html[dir='rtl'] .multiselect-list-remove { .router, i-router { + display: block; position: relative; } @@ -2897,6 +2946,7 @@ html[dir='rtl'] .multiselect-list-remove { min-width: 100%; /* top: calc(100% - 1px);*/ overflow: visible; + z-index: 10; } .sitebar-container > * diff --git a/package.json b/package.json new file mode 100644 index 0000000..8326988 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "@esiur/iui", + "version": "1.1.2", + "description": "Interactive User Interface", + "main": "iui.js", + "type": "module", + "directories": { + "test": "test" + }, + "dependencies": {}, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/esiur/iui.git" + }, + "keywords": [ + "Web", + "Interface", + "User", + "Esiur" + ], + "author": "Ahmed Kh. Zamil", + "license": "MIT", + "bugs": { + "url": "https://github.com/esiur/iui/issues" + }, + "homepage": "https://github.com/esiur/iui#readme" +} diff --git a/src/Core/Binding.js b/src/Core/Binding.js index 455dfc5..6ab76e5 100644 --- a/src/Core/Binding.js +++ b/src/Core/Binding.js @@ -274,11 +274,8 @@ export class Binding { { let d = await this._execute(this.target.ownerElement, data); - if (d === undefined) - return false; - - //if (d instanceof Promise) - // d = await d; + //if (d === undefined) + // return false; this.target.ownerElement.style.display = d ? "" : "none"; } diff --git a/src/Core/IUIElement.js b/src/Core/IUIElement.js index d617cff..ed1aadb 100644 --- a/src/Core/IUIElement.js +++ b/src/Core/IUIElement.js @@ -7,10 +7,15 @@ export default class IUIElement extends HTMLElement { this._events = []; this._data = null; + this._defaults = defaults; for (var i in defaults) if (this[i] == undefined) - this[i] = defaults[i]; + try { + this[i] = defaults[i]; + } catch { + // mostly because modifying dom attributes are not allowed in custom elements creation + } this._register("data"); } @@ -106,12 +111,6 @@ export default class IUIElement extends HTMLElement { return; } - - //console.log("_renderElement " + element.getAttribute("ref"), element); - - //if (element.hasAttribute("debug")) - // debugger; - // render attributes & text nodes for (var i = 0; i < element.bindings.length; i++) await element.bindings[i].render(data); @@ -153,11 +152,12 @@ export default class IUIElement extends HTMLElement { connectedCallback() { if (this.hasAttribute("css-class")) + { this.classList.add(this.getAttribute("css-class")); + } else { let className = this.constructor.moduleName; - this.setAttribute("css-class", className); this.classList.add(className); } @@ -204,19 +204,23 @@ export default class IUIElement extends HTMLElement { // async: Async Field // @ Event + + // tags to skip + if (element instanceof HTMLScriptElement + || element instanceof HTMLTemplateElement) + return; + let bindings = []; - //if (element.hasAttribute("debug")) - // debugger; - // compile attributes for (var i = 0; i < element.attributes.length; i++) { let b = Binding.create(element.attributes[i]); if (b != null) { - if (b.type == BindingType.HTMLElementDataAttribute || b.type == BindingType.IUIElementDataAttribute) + if (b.type == BindingType.HTMLElementDataAttribute + || b.type == BindingType.IUIElementDataAttribute) element.dataMap = b; else if (b.type == BindingType.RevertAttribute) element.revertMap = b; @@ -249,7 +253,7 @@ export default class IUIElement extends HTMLElement { _emit(event, values) { //var args = Array.prototype.slice.call(arguments, 1); var e = new CustomEvent(event, values); - for (var i in values) { + for (let i in values) { if (e[i] === undefined) e[i] = values[i]; } diff --git a/src/Data/Form.js b/src/Data/Form.js index 4e29e36..df1ffa1 100644 --- a/src/Data/Form.js +++ b/src/Data/Form.js @@ -1,10 +1,10 @@ import IUIElement from "../Core/IUIElement.js"; import { IUI } from "../Core/IUI.js"; +import Modifiable from "./Modifiable.js"; export default IUI.module(class Form extends IUIElement { constructor() { super(); - //this.form = {}; } static _copy(val){ @@ -34,34 +34,23 @@ export default IUI.module(class Form extends IUIElement { this.original = value; //var copy = {}; //Object.assign(copy, value); - super.setData(Form._copy(this.original)); + super.setData(new Modifiable(this.original));// Form._copy(this.original)); //super.setData({ ...this.original }); } async reset() { //super.setData({ ...this.original }); - super.setData(Form._copy(this.original)); + super.setData(new Modifiable(this.original));//Form._copy(this.original)); return this; } - // @TODO: Remove this when esiur adds suport to partially modified arrays with modified flag - static _areEqual(ar1, ar2) - { - if (!(ar1 instanceof Array) || !( ar2 instanceof Array)) - return false; - - if (ar1.length != ar2.length) - return false; - - for(var i = 0; i < ar1.length; i++) - if (ar1[i] != ar2[i]) - return false; - - return true; - } + get diff() { + + return this._data._diff; + if (this.original == null) return this._data; diff --git a/src/Data/Include.js b/src/Data/Include.js index 8bb40da..4299903 100644 --- a/src/Data/Include.js +++ b/src/Data/Include.js @@ -9,26 +9,35 @@ export default IUI.module(class Include extends IUIElement this.refs = {}; } - async create() + get src(){ + return this.getAttribute("src"); + } + + set src(value){ + this.setAttribute("src", value); + this._load(value); + } + + async _load(url) { - //console.log("Create ...", this.getAttribute("src")); + if (this._loading) + return; - if (this.getAttribute("src") == "views/studio/realestate.html") - console.log("Create include"); + this._loading = true; - if (this.hasAttribute("src")) { + let src = url.replace(/^\/+|\/+$/g, ''); - let src = this.getAttribute("src").replace(/^\/+|\/+$/g, ''); - let x = await fetch(src); + this.classList.add(this.cssClass + "-loading"); - if (x.status !== 200) - return; + let x = await fetch(src); + if (x.status == 200) + { let t = await x.text(); this.innerHTML = t; - let xeval = (code) => eval(code); + //let xeval = (code) => eval(code); // call create for the new elements var newElements = this.querySelectorAll("*"); @@ -58,11 +67,23 @@ export default IUI.module(class Include extends IUIElement } } - //this.updateBindings(); + this.classList.remove(this.cssClass + "-loading"); + + if (window?.app?.loaded) + { + await IUI.create(this); + await IUI.created(this); + this.updateBindings(); + await this.render(); + } + + this._loading = false; } - get src() + async create() { - return this._src; + if (this.hasAttribute("src")) + await this._load(this.getAttribute("src")); } + }); \ No newline at end of file diff --git a/src/Data/Modifiable.js b/src/Data/Modifiable.js new file mode 100644 index 0000000..4758f22 --- /dev/null +++ b/src/Data/Modifiable.js @@ -0,0 +1,139 @@ +export default class Modifiable +{ + static _copy(val){ + if (typeof val === 'object' && val !== null) + { + let rt = {}; + for(var i in val) + if (val[i] instanceof Array) + // copy array + rt[i] = [...val[i]]; + else + rt[i] = val[i]; + + return rt; + } + else + return val; + } + + // @TODO: Remove this when esiur adds suport to partially modified arrays with modified flag + static _areEqual(ar1, ar2) + { + if (!(ar1 instanceof Array) || !( ar2 instanceof Array)) + return false; + + if (ar1.length != ar2.length) + return false; + + for(var i = 0; i < ar1.length; i++) + if (ar1[i] != ar2[i]) + return false; + + return true; + } + + constructor(original){ + + this._events = {}; + this._data = Modifiable._copy(original); + this._original = original; + + for(let p in this._data) + { + if (p.startsWith("_")) + continue; + + this._register(":" + p); + + Object.defineProperty(this, p, { + get() { + return this._data[p]; + }, + set(value) { + this._data[p] = value; + this._emit(":" + p, value); + } + }); + } + + } + + + get _diff() { + if (this._original == null) + return this._data; + + var rt = {}; + for (var i in this._data) + if (this._data[i] != this._original[i]) + { + if (this._data[i] instanceof Array && Modifiable._areEqual(this._data[i], this._original[i])) + continue; + else + rt[i] = this._data[i]; + } + + return rt; + } + + _register(event) + { + this._events[event] = []; + } + + + _emit(event) + { + event = event.toLowerCase(); + var args = Array.prototype.slice.call(arguments, 1); + if (this._events[event]) + for(var i = 0; i < this._events[event].length; i++) + if (this._events[event][i].f.apply(this._events[event][i].i, args)) + return true; + + return false; + } + + _emitArgs(event, args) + { + event = event.toLowerCase(); + if (this._events[event]) + for(var i = 0; i < this._events[event].length; i++) + if (this._events[event][i].f.apply(this._events[event][i].i, args)) + return true; + return this; + } + + on(event, fn, issuer) + { + if (!(fn instanceof Function)) + return this; + + event = event.toLowerCase(); + // add + if (!this._events[event]) + this._events[event] = []; + this._events[event].push({f: fn, i: issuer == null ? this: issuer}); + return this; + } + + + off(event, fn) + { + event = event.toLowerCase(); + if (this._events[event]) + { + if (fn) + { + for(var i = 0; i < this._events[event].length; i++) + if (this._events[event][i].f == fn) + this._events[event].splice(i--, 1); + } + else + { + this._events[event] = []; + } + } + } +} \ No newline at end of file diff --git a/src/Data/Repeat.js b/src/Data/Repeat.js index 982b0e5..ae2112f 100644 --- a/src/Data/Repeat.js +++ b/src/Data/Repeat.js @@ -263,9 +263,10 @@ export default IUI.module(class Repeat extends IUIElement //super._uiBindings = null; //super.updateBindings(); - this._emit("modified", { data: value, property: "data" }); + // @TODO: check if this works for event names starting with ":" + this._emit(":data", { data: value }); + // this._emit("modified", { data: value, property: "data" }); - // console.log("SetDataEnd " + this.getAttribute("ref") + " " + id); this._busy = false; } diff --git a/src/Router/Route.js b/src/Router/Route.js index 97a6b11..0317db1 100644 --- a/src/Router/Route.js +++ b/src/Router/Route.js @@ -145,6 +145,8 @@ export default IUI.module(class Route extends IUIElement { this.setAttribute("selected", ""); this._emit("show"); + + } else { diff --git a/src/Router/Router.js b/src/Router/Router.js index 6b02bdf..42920f2 100644 --- a/src/Router/Router.js +++ b/src/Router/Router.js @@ -116,7 +116,7 @@ export default IUI.module(class Router extends Target return JSON.parse(JSON.stringify(rt)); } - async navigate(url, data, target, state) + async navigate(url, data, target, state, dataToQuery = true) { let q = url.match(/^\/*(.*?)\?(.*)$|^\/*(.*)$/); @@ -132,8 +132,8 @@ export default IUI.module(class Router extends Target } // do we have data ? else if (data !== undefined) { - path = q[3]; - url = path + "?" + this._toQuery(data); + path = q[3]; + url = dataToQuery ? path + "?" + this._toQuery(data) : path; } else { path = q[3]; @@ -144,13 +144,19 @@ export default IUI.module(class Router extends Target let [stateRoute, viewRoute] = this.getRoute(path, data); if (stateRoute == null) + { + console.warn("State not found ", path); return; + } let ok = this._emit("navigate", { url, stateRoute, viewRoute, base: path, data, cancelable: true }); if (!ok) + { + console.warn("Route not allowed", path); return; - + } + // destination view not found if (viewRoute == null) { console.log(`Destination route not found ${stateRoute.dst}`); @@ -173,11 +179,9 @@ export default IUI.module(class Router extends Target // } //} - if (!(target instanceof Target)) target = this; - if (state == null) { let id = Math.random().toString(36).substr(2, 10); state = { id, url, data, target, stateRoute, viewRoute }; diff --git a/src/UI/DateTimePicker.js b/src/UI/DateTimePicker.js index cbdc26b..8221ed1 100644 --- a/src/UI/DateTimePicker.js +++ b/src/UI/DateTimePicker.js @@ -115,7 +115,7 @@ export default IUI.module(class DateTimePicker extends IUIElement { self._value.setMonth(self._month); self.render(); self._emit("select", { value: self._value }); - self._emit("modified", { value, property: "value" }); + self._emit(":value", { value }); }); } } diff --git a/src/UI/Input.js b/src/UI/Input.js index a2d3e9d..aa69b14 100644 --- a/src/UI/Input.js +++ b/src/UI/Input.js @@ -32,6 +32,15 @@ export default IUI.module(class Input extends IUIElement { return true; } + get caption(){ + return this.getAttribute("caption");// this._span.innerHTML; + } + + set caption(value){ + this.setAttribute("caption", value); + this._span.innerHTML = value; + } + create() { this.isAuto = this.hasAttribute("auto"); @@ -44,7 +53,12 @@ export default IUI.module(class Input extends IUIElement { this.setAttribute("async:revert", `d['${this.field}'] = await this.getData()`); } + this._span = document.createElement("span"); + this._span.innerHTML = this.getAttribute("caption"); + this._input = document.createElement("input"); + this._input.placeholder = " "; + let self = this; this._input.addEventListener("input", () => { @@ -62,6 +76,7 @@ export default IUI.module(class Input extends IUIElement { this.accept = this.getAttribute("accept"); this.appendChild(this._input); + this.appendChild(this._span); if (this.type == "password") { diff --git a/src/UI/Tab.js b/src/UI/Tab.js index 08d750c..bef41b1 100644 --- a/src/UI/Tab.js +++ b/src/UI/Tab.js @@ -10,8 +10,8 @@ export default IUI.module(class Tab extends IUIElement { } - get title() { - return this.getAttribute("title"); + get caption() { + return this.getAttribute("caption"); } get selected() { diff --git a/src/UI/Table.js b/src/UI/Table.js index 3d7d1c8..0a4c5b5 100644 --- a/src/UI/Table.js +++ b/src/UI/Table.js @@ -855,6 +855,7 @@ export default IUI.module(class Table extends IUIElement { if (dynamicLoading) this._createTreeButton(newRow, true, item); + // @TODO: fix this since modified event is removed if (item.on) if (this.updateOnModification) item.on("modified", function(propertyName){ diff --git a/src/UI/Tabs.js b/src/UI/Tabs.js index 8df79d3..0ddc5e6 100644 --- a/src/UI/Tabs.js +++ b/src/UI/Tabs.js @@ -2,6 +2,7 @@ import IUIElement from "../Core/IUIElement.js"; import Tab from "./Tab.js"; import { IUI } from "../Core/IUI.js"; +import Check from "./Check.js"; export default IUI.module(class Tabs extends IUIElement { @@ -117,8 +118,8 @@ export default IUI.module(class Tabs extends IUIElement { add(item) { - var label = document.createElement("i-check"); - label.innerHTML = item.title; + var label = new Check();// document.createElement("i-check"); + label.innerHTML = item.caption; this._ext.insertAdjacentElement("beforebegin", label);