2
0
mirror of https://github.com/esiur/iui.git synced 2025-06-27 09:23:12 +00:00
This commit is contained in:
2021-11-06 14:53:52 +03:00
parent 1bac10e60d
commit 589c4f3227
31 changed files with 12721 additions and 315 deletions

View File

@ -1,9 +1,11 @@
import IUIElement from "../Core/IUIElement.js";
import { IUI } from "../Core/IUI.js";
import RefsCollection from "./RefsCollection.js";
export default IUI.module(class App extends IUIElement {
constructor() {
super();
this.refs = new RefsCollection(this);
}
create() {
@ -11,8 +13,14 @@ export default IUI.module(class App extends IUIElement {
window.app = this;
}
created() {
IUI.bind(this, this, "/");
IUI.bind(this, this, "/", {app: this, refs: this.refs});
// update referencing
this.refs._build();
//IUIElement._make_bindings(this);
this.render();
this._emit("load", { app: this });

View File

@ -20,7 +20,7 @@ export const AttributeBindingDestination = {
const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
export class Binding {
static create(nodeOrAttributeOrIUIElement) {
static create(nodeOrAttributeOrIUIElement, scope) {
var code, isAsync, type, attrType, attrKey, func, script;
//if (nodeOrAttributeOrIUIElement.created)
@ -100,11 +100,17 @@ export class Binding {
// test the function
let scopeKeys = Object.keys(scope);
let scopeValues = Object.values(scope);
try {
let args = ["data", "d", "context", "_test",
...scopeKeys]
if (isAsync)
func = new AsyncFunction("data", "d", "context", "_test", code);
func = new AsyncFunction(...args, code);
else
func = new Function("data", "d", "context", "_test", code);
func = new Function(...args, code);
}
catch (ex) {
console.log("Test failed: " + ex, code);
@ -113,7 +119,7 @@ export class Binding {
let rt = new Binding();
Object.assign(rt, { isAsync, type, attrType, attrKey, func, target: nodeOrAttributeOrIUIElement, checked: false, script });
Object.assign(rt, { isAsync, type, attrType, attrKey, func, target: nodeOrAttributeOrIUIElement, checked: false, script, scopeKeys, scopeValues });
return rt;
}
@ -144,7 +150,9 @@ export class Binding {
let proxy = new Proxy(map, detector);
try {
let d = this.func.apply(thisArg, [proxy, proxy, {}, true]);
let d = this.func.apply(thisArg, [proxy, proxy, {}, true
, ...this.scopeKeys]);
this.map = map;
return d;
}
@ -158,9 +166,9 @@ export class Binding {
if (!this.checked)
this._findMap(thisArg);
let context = {};
var rt = this.func.apply(thisArg, [data, data, context, false]);
var rt = this.func.apply(thisArg, [data, data, context, false,
...this.scopeValues]);
//console.log(rt);
if (rt instanceof Promise)
@ -231,9 +239,12 @@ export class Binding {
try {
if (this.type === BindingType.IUIElement) {
let d = this.func.apply(this.target, [data, data]);
if (d instanceof Promise)
d = await d;
//let d = this.func.apply(this.target, [data, data]);
//if (d instanceof Promise)
// d = await d;
let d = await this._execute(this.target, data);
await this.target.setData(d);
}
else if (this.type === BindingType.TextNode) {
@ -277,8 +288,10 @@ export class Binding {
if (window?.app?.loaded)
{
await IUI.create(targetElement);
IUI.bind(targetElement, true, "content", targetElement.__i_bindings?.scope);
// update references
targetElement.__i_bindings?.scope?.refs?._build();
await IUI.created(targetElement);
IUI.bind(targetElement, targetElement, "content");
await IUI.render(targetElement, targetElement._data, true);
}
//await IUI.updateTree(targetElement);

21
src/Core/BindingList.js Normal file
View File

@ -0,0 +1,21 @@
export default class BindingList extends Array {
constructor(target, scope) {
super();
this.target = target;
this.scope = scope;
}
getArgumentsNames(){
if (this.scope == null)
return [];
let rt;
for (var i in this.scope.length)
rt.push(i);
return rt;
}
}

View File

@ -1,6 +1,7 @@
import IUIElement from "./IUIElement.js";
import { Binding, BindingType } from "./Binding.js";
//import Route from '../Router/Route.js';
import BindingList from "./BindingList.js";
export class IUI {
@ -124,13 +125,13 @@ export class IUI {
return objectClass;
}
static extend(properties, defaults, force)
static extend(properties, defaults, overwrite)
{
if (properties == null)
properties = defaults;
else
for(var i in defaults)
if (force)
if (overwrite)
properties[i] = defaults[i];
else if (properties[i] === undefined)
properties[i] = defaults[i];
@ -138,38 +139,55 @@ export class IUI {
}
static bind(element, rootElement, sourcePath){
static bind(element, skipAttributes, sourcePath, scope) {
// ::Attribute
// : Field
// : Field
// async:: Async Attribute
// async: Async Field
// @ Event
// skip element ?
if (element.hasAttribute("skip")
|| element.hasAttribute("i-skip")
|| element instanceof HTMLTemplateElement)
return;
// tags to skip
//if (element instanceof HTMLScriptElement )
//return;
if (rootElement == null)
rootElement = element;
let bindings;
if (element != rootElement)
{
element.view = rootElement.view;
element.route = rootElement.route;
if (scope == null)
scope = {};
// get refs before they get overwritten
//let refs = scope?.refs;
// some element extended or overwritten the binding arguments
if (element.scope != null)
IUI.extend(scope, element.scope, true);
bindings = new BindingList(element, scope);
if (skipAttributes)
{
// copy attributes bindings
if (element.__i_bindings != null)
for(var i = 0; i < element.__i_bindings.length; i++)
if (element.__i_bindings[i].type != BindingType.TextNode)
bindings.push(element.__i_bindings[i]);
}
else
{
bindings = [];
// compile attributes
for (var i = 0; i < element.attributes.length; i++) {
let b = Binding.create(element.attributes[i]);
let b = Binding.create(element.attributes[i],
bindings.scope);
if (b != null) {
if (b.type == BindingType.HTMLElementDataAttribute
@ -182,37 +200,50 @@ export class IUI {
}
}
// add reference
if (element.hasAttribute("ref")) {
rootElement.refs[el.getAttribute("ref")] = element;
}
// if (element.hasAttribute("ref")) {
// let ref = element.getAttribute("ref");
// if (refs[ref] == null)
// refs[ref] = element;
// else if (refs[ref] == element){
// // do nothing
// }
// else if (refs[ref] instanceof Array){
// refs[ref].push(element);
// } else {
// var firstRef = refs[ref];
// refs[ref] =[firstRef, element];
// }
// }
}
else
{
// remove previous text node bindings
bindings = element.bindings == null ? [] : element.bindings.filter(x=> x.type != BindingType.TextNode);
element.refs = {};
}
// get new refs (scope might been overwritten)
//refs = scope?.refs;
// compile nodes
for (var i = 0; i < element.childNodes.length; i++) {
let el = element.childNodes[i];
if (el instanceof IUIElement) {
// @TODO: check if the IUI element handles the binding
IUI.bind(el, rootElement, sourcePath);
IUI.bind(el, false, sourcePath, scope);
}
else if (el instanceof HTMLElement) {
IUI.bind(el, rootElement, sourcePath);
IUI.bind(el, false, sourcePath, scope);
}
else if (el instanceof Text) {
let b = Binding.create(el);
let b = Binding.create(el, bindings.scope);
if (b != null)
bindings.push(b);
}
else if (el instanceof HTMLScriptElement)
{
// this because HTML parser don't evaluate script tag
let func = new Function("//# sourceURL=iui://" + sourcePath + "-" + Math.round(Math.random() * 10000) + "\r\n return " + el.text.trim());
/// let func = new Function("//# sourceURL=iui://" + sourcePath + "-" + Math.round(Math.random() * 10000) + "\r\n return " + el.text.trim());
let func = new Function("//# sourceURL=iui://" + sourcePath + "-" + Math.round(Math.random() * 10000) + "\r\n" + el.text.trim());
let rt = func.call(el.parentElement);
if (typeof (rt) === "object") {
@ -222,24 +253,25 @@ export class IUI {
}
}
element.bindings = bindings;
element.__i_bindings = bindings;
}
static async render(element, data, textNodesOnly = false) {
if (!element.bindings) {
if (!element.__i_bindings) {
return;
}
let bindings = element.__i_bindings;
if (textNodesOnly) {
for (var i = 0; i < element.bindings.length; i++)
if (element.bindings[i].type == BindingType.TextNode)
await element.bindings[i].render(data);
for (var i = 0; i < bindings.length; i++)
if (bindings[i].type == BindingType.TextNode)
await bindings[i].render(data);
} else {
// render attributes & text nodes
for (var i = 0; i < element.bindings.length; i++)
await element.bindings[i].render(data);
for (var i = 0; i < bindings.length; i++)
await bindings[i].render(data);
}
// render children

View File

@ -41,7 +41,6 @@ export default class IUIElement extends HTMLElement {
async render() {
await IUI.render(this, this._data);
//await IUIElement._renderElement(this, this._data);
}
_getParentData() {
@ -57,22 +56,15 @@ export default class IUIElement extends HTMLElement {
async setData(value) {
this._data = value;
this._emit("data", {data: value});
await IUIElement._renderElement(this, value);
await IUI.render(this, value);
}
get data() {
return this._data;
}
async revert(){
//if (this.revertMap != null)
//{
//if (data == undefined)
// await this.revertMap.render(this._getParentData());
//else
// await this.revertMap.render(data);
// revert parents
let e = this;
do {
@ -81,7 +73,6 @@ export default class IUIElement extends HTMLElement {
if (e.revertMap != null)
await e.revertMap.render(p?.data);
} while (e = p);
//}
}
async update(data) {
@ -101,51 +92,17 @@ export default class IUIElement extends HTMLElement {
}
}
static async _renderElement(element, data) {
if (!element.bindings) {
return;
}
// render attributes & text nodes
for (var i = 0; i < element.bindings.length; i++)
await element.bindings[i].render(data);
// render children
for (var i = 0; i < element.children.length; i++) {
let e = element.children[i];
if (e instanceof IUIElement)
// @TODO should check if the element depends on parent or not
if (e.dataMap != null) {
// if map function failed to call setData, we will render without it
if (!(await e.dataMap.render(data)))
await e.render();
}
else
await e.setData(data);
else {
if (e.dataMap != null)
await e.dataMap.render(data);
else
e.data = data;
//let data = e.mapData(data);
await IUIElement._renderElement(e, e.data);
}
}
// bindings arguments
get scope(){
return null;
}
// this meant to be inherited
modified() {
}
get data() {
return this._data;
}
connectedCallback() {
if (this.hasAttribute("css-class"))
{
this.classList.add(this.getAttribute("css-class"));
@ -182,8 +139,6 @@ export default class IUIElement extends HTMLElement {
}
destroy() {
console.log("Destroy", this);
IUI.registry.splice(IUI.registry.indexOf(this), 1);
if (this.parentNode)
this.parentNode.removeChild(this);
@ -191,67 +146,6 @@ export default class IUIElement extends HTMLElement {
static _make_bindings(element, isRoot = false) {
// ::Attribute
// : Field
// async:: Async Attribute
// async: Async Field
// @ Event
// skip element ?
if (element.hasAttribute("skip"))
return;
// tags to skip
if (element instanceof HTMLScriptElement
|| element instanceof HTMLTemplateElement)
return;
let bindings = [];
if (!isRoot)
{
// 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)
element.dataMap = b;
else if (b.type == BindingType.RevertAttribute)
element.revertMap = b;
else
bindings.push(b);
}
}
}
// compile nodes
for (var i = 0; i < element.childNodes.length; i++) {
let e = element.childNodes[i];
if (e instanceof IUIElement) {
// @TODO: check if the IUI element handles the binding
IUIElement._make_bindings(e);
}
else if (e instanceof HTMLElement) {
IUIElement._make_bindings(e);
}
else if (e instanceof Text) {
let b = Binding.create(e);
if (b != null)
bindings.push(b);
}
}
element.bindings = bindings;
}
_emit(event, values) {
//var args = Array.prototype.slice.call(arguments, 1);
var e = new CustomEvent(event, values);
@ -304,13 +198,13 @@ export default class IUIElement extends HTMLElement {
}
}
off(event, fn) {
this.removeEventListener(event, fn);
off(event, func) {
this.removeEventListener(event, func);
return this;
}
on(event, fn) {
this.addEventListener(event, fn, false);
on(event, func) {
this.addEventListener(event, func, false);
return this;
}
}

View File

@ -0,0 +1,46 @@
export default class RefsCollection
{
constructor(rootElement){
this._rootElement = rootElement;
}
_build(element) {
if (element == undefined)
element = this._rootElement;
for(var i in this)
if (i != "_rootElement" && i != "_build")
delete this[i];
for(var i = 0; i < element.children.length; i++)
{
let child = element.children[i];
if (child.hasAttribute("ref"))
{
let ref = child.getAttribute("ref");
if (this[ref] == null)
this[ref] = child;
else if (this[ref] == child){
// do nothing
}
else if (this[ref] instanceof Array){
this[ref].push(child);
} else {
var firstRef = this[ref];
this[ref] =[firstRef, child];
}
}
if (child.refs != undefined)
// opt out if the element handles referencing
break;
else
this._build(child);
}
}
}

View File

@ -1,12 +1,13 @@
import IUIElement from "../Core/IUIElement.js";
import { IUI } from "../Core/IUI.js";
import RefsCollection from "../Core/RefsCollection.js";
export default IUI.module(class Include extends IUIElement
{
constructor()
{
super();
this.refs = {};
this.refs = new RefsCollection();
}
get src(){
@ -18,6 +19,11 @@ export default IUI.module(class Include extends IUIElement
this._load(value);
}
get scope() {
return {view: this, refs: this.refs};
}
async _load(url)
{
if (this._loading)
@ -42,8 +48,11 @@ export default IUI.module(class Include extends IUIElement
if (window?.app?.loaded)
{
await IUI.create(this);
IUI.bind(this, true, "include:" + src,
IUI.extend(this._i__bindings.scope, this.scope, true));
this.refs._build();
await IUI.created(this);
IUI.bind(this, this, "include:" + src);
await IUI.render(this, this._data, true);
}
@ -99,4 +108,8 @@ export default IUI.module(class Include extends IUIElement
await this._load(this.getAttribute("src"));
}
async created() {
this.refs._build();
}
});

View File

@ -22,7 +22,7 @@ export default IUI.module(class Repeat extends IUIElement
//////////////
/// Create ///
//////////////
if (this._created)
debugger;
@ -53,11 +53,11 @@ export default IUI.module(class Repeat extends IUIElement
}
var newElements = this.querySelectorAll("*");
for (var i = 0; i < newElements.length; i++)
newElements[i].repeat = this;
// var newElements = this.querySelectorAll("*");
// for (var i = 0; i < newElements.length; i++)
// newElements[i].repeat = this;
var self = this;
// var self = this;
/*
this._repeatModified = function(propertyName, value)
@ -104,31 +104,6 @@ export default IUI.module(class Repeat extends IUIElement
return this._data.length;
}
_assign(node, index) {
// update fields
// this so we won't mess with i-include view
if (node.view == undefined)
node.view = this.view;
node.rotue = this.route;
node.index = index;
// update references
if (node.hasAttribute("ref"))
{
let ref = node.getAttribute("ref");
// create new array
if (!(this.view.refs[ref] instanceof Array))
this.view.refs[ref] = [];
this.view.refs[ref][index] = node;
}
//Object.assign(node, customFields);
for (var i = 0; i < node.children.length; i++)
this._assign(node.children[i], index);
}
async setData(value)
{
@ -141,18 +116,14 @@ export default IUI.module(class Repeat extends IUIElement
return false;
}
//console.log("RPT: SetData", value);
this._busy = true;
// var id = Math.random();
//console.log("SetData " + this.getAttribute("ref") + " " + id, value);
//console.trace();
// clear
this.clear();
if (value instanceof Structure)
value = value.toPairs();
if (value?.toArray instanceof Function)
value = value.toArray();
else if (value == null || !(value instanceof Array || value instanceof Int32Array))
value = [];
@ -163,100 +134,24 @@ export default IUI.module(class Repeat extends IUIElement
for (let i = 0; i < value.length; i++) {
///console.log("ST1");
//let content = this.template.content.cloneNode(true);
//let nodes = content.childNodes;
let e = this._repeatNode.cloneNode(true);
this.list.push(e);
await IUI.create(e);
//console.log("ST2");
// Create node
if (e instanceof IUIElement)
await e.create();
IUI.bind(e, false, "repeat",
IUI.extend(this.__i_bindings?.scope,
{index: i, repeat: this}, true));
// console.log("ST3");
// Create children
//console.log("Create repeat " + i, this, e);
await IUI.create(e);
//console.log("Created repeat " + i, this, e);
//console.log("ST4");
//this._make_bindings(e)
IUI.bind(e, this, "repeat");
this._container.insertBefore(e, this._beforeNode);
// update referencing
this.__i_bindings?.scope?.refs?._build();
this._assign(e, i);// { view: this.view, route: this.route, index: i });
//console.log("ST5");
if (e instanceof IUIElement) {
// @TODO should check if the element depends on parent or not
if (e.dataMap != null) {
// if map function failed to call setData, we will render without it
if (!(await e.dataMap.render(value[i])))
await e.render();
}
else{
await e.setData(value[i]);
// console.log("ST6.1");
}
}
else {
if (e.dataMap != null)
await e.dataMap.render(value[i]);
else
e.data = value[i];
// console.log("ST6.2", e);
await this._renderElement(e, e.data);
// console.log("ST6.3");
}
// if (node.dataMap != null) {
// await node.dataMap.render(value[i]);
// this._renderElement(node, node.data);
// }
// else {
// node.data = value[i];
// this._renderElement(node, node.data);
// }
/*
var newElements = content.querySelectorAll("*");
while (nodes.length > 0) {
let n = nodes[0];
//n.index = i;
if (n instanceof HTMLElement)
n.setAttribute(":data", `d[${i}]`);
this._container.appendChild(n);
}
// this has to be called after appending the child otherwise node will be HTMLElement and not IUIElement , bug maybe in webkit ?
for (var j = 0; j < newElements.length; j++) {
let el = newElements[j];
// set route for all elements
el.index = i;
el.view = this;
el.route = this.route;
//newElements[j].route = this.route;
if (el instanceof IUIElement)
el.create();
}
*/
await IUI.created(e);
await IUI.render(e, value[i], false);
}

View File

@ -1,6 +1,7 @@
import IUIElement from "../Core/IUIElement.js";
import { IUI } from "../Core/IUI.js";
import Router from "./Router.js";
import RefsCollection from "../Core/RefsCollection.js";
export default IUI.module(class Route extends IUIElement {
@ -8,7 +9,7 @@ export default IUI.module(class Route extends IUIElement {
super();
this.routes = [];
this.refs = {};
this.refs = new RefsCollection(this);
this._register("show");
this._register("hide");
@ -21,6 +22,10 @@ export default IUI.module(class Route extends IUIElement {
return await super.setData(value);
}
get scope(){
return {route: this, view: this};
}
_updateLinks() {
for (var i = 0; i < this.children.length; i++) {
if (this.children[i] instanceof Route) {
@ -111,9 +116,9 @@ export default IUI.module(class Route extends IUIElement {
if (window?.app?.loaded)
{
await IUI.create(this);
IUI.bind(this, true, "route:" + src, this.scope);
this.refs._build();
await IUI.created(this);
IUI.bind(this, this, "route:" + src);
await IUI.render(this, this._data, true);
}
@ -152,7 +157,7 @@ export default IUI.module(class Route extends IUIElement {
created()
{
this.refs._build();
}
set(value) {

View File

@ -1,9 +1,11 @@
import IUIElement from "../Core/IUIElement.js";
import { IUI } from "../Core/IUI.js";
import RefsCollection from "../Core/RefsCollection.js";
export default IUI.module(class CodePreview extends IUIElement {
constructor(properties) {
constructor() {
super();
this.refs = new RefsCollection(this);
}
async create() {
@ -30,7 +32,7 @@ export default IUI.module(class CodePreview extends IUIElement {
let self = this;
this.editor.addEventListener("input", function() {
self._code = self.editor.innerText.trim();
self.update();
self.updatePreview();
}, false);
this.preview = document.createElement("div");
@ -44,11 +46,18 @@ export default IUI.module(class CodePreview extends IUIElement {
this.append(this.content);
this.field = this.getAttribute("field");
await this.update();
//await this.updatePreview();
}
async update() {
async created(){
await this.updatePreview();
}
get scope(){
return {view: this, refs: this.refs};
}
async updatePreview() {
if (this._updating)
return;
@ -56,10 +65,15 @@ export default IUI.module(class CodePreview extends IUIElement {
this._updating = true;
this.preview.innerHTML = this._code;
await IUI.create(this.preview);
await IUI.created(this.preview);
IUI.bind(this.preview, this.preview, "preview");
await IUI.render(this.preview, this._data, true);
if (window.app?.loaded)
{
await IUI.create(this.preview);
await IUI.created(this.preview);
IUI.bind(this.preview, true, "preview", this.scope);
this.refs._build();
await IUI.render(this.preview, this._data, true);
}
this._updating = false;
}

View File

@ -223,7 +223,7 @@ export default IUI.module(class Select extends IUIElement {
///console.log("Append", this.menu);
await this.menu.create();
IUI.bind(this.menu, this, "menu");
IUI.bind(this.menu, false, "menu");
await IUI.create(this.menu);
//this._make_bindings(this.menu);

View File

@ -909,7 +909,8 @@ export default IUI.module(class Table extends IUIElement {
//this._make_bindings(cl)
IUI.bind(cl, this, "table");
IUI.bind(cl, false, "table",
IUI.extend(this.__i_bindings, {index: i}, true));
tr.appendChild(cl);