mirror of
https://github.com/esiur/iui.git
synced 2025-05-06 06:42:58 +00:00
576 lines
14 KiB
JavaScript
576 lines
14 KiB
JavaScript
import IUIElement from "./IUIElement.js";
|
|
import { Binding, BindingType } from "./Binding.js";
|
|
//import Route from '../Router/Route.js';
|
|
import BindingList from "./BindingList.js";
|
|
|
|
const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
|
|
|
|
export class IUI {
|
|
|
|
static debugMode = true;
|
|
|
|
static _menus = [];
|
|
static views = [];
|
|
static modules = {};
|
|
static registry = [];
|
|
|
|
static format(input) {
|
|
if (typeof input == "string" || input instanceof String) {
|
|
let template = document.createElement("template");
|
|
template.innerHTML = input;
|
|
let nodes = template.content.cloneNode(true).childNodes;
|
|
return nodes;
|
|
}
|
|
else if (input instanceof HTMLCollection)
|
|
return input;
|
|
else if (input instanceof HTMLElement)
|
|
return [input];
|
|
else
|
|
return [];
|
|
}
|
|
|
|
static observer = new IntersectionObserver(function(entries) {
|
|
// isIntersecting is true when element and viewport are overlapping
|
|
// isIntersecting is false when element and viewport don't overlap
|
|
for(var i = 0; i < entries.length; i++)
|
|
{
|
|
if (entries[i].isIntersecting)
|
|
{
|
|
if (entries[i]._require_update)
|
|
entries[i].update();
|
|
}
|
|
}
|
|
|
|
}, { threshold: [0] });
|
|
|
|
|
|
static async created(element, includeThisElement = false) {
|
|
|
|
// @TODO: this should grow from root to leef
|
|
if (includeThisElement && element instanceof IUIElement) {
|
|
await element.created();
|
|
}
|
|
|
|
for (var i = 0; i < element.children.length; i++) {
|
|
let e = element.children[i];
|
|
if (e instanceof IUIElement)
|
|
await e.created();
|
|
await IUI.created(e);
|
|
}
|
|
|
|
}
|
|
|
|
static async create(element, includeThisElement = false)
|
|
{
|
|
|
|
if (includeThisElement && element instanceof IUIElement) {
|
|
await element.create();
|
|
}
|
|
|
|
for (let i = 0; i < element.children.length; i++) {
|
|
let e = element.children[i];
|
|
if (e instanceof IUIElement) {
|
|
await e.create();
|
|
}
|
|
|
|
await IUI.create(e);
|
|
}
|
|
/*
|
|
let router = document.getElementsByTagName("i-router")[0];
|
|
|
|
await router.create();
|
|
|
|
let elements = document.querySelectorAll("*");
|
|
for (var i = 0; i < elements.length; i++)
|
|
if (elements[i] instanceof IUIElement && elements[i].tagName != "I-ROUTER") {
|
|
console.log(elements[i]);
|
|
await elements[i].create();
|
|
}
|
|
*/
|
|
|
|
//for(var i = 0; i < IUI.registry.length; i++)
|
|
//{
|
|
// IUI.extend(IUI.registry[i], IUI.registry[i].properties);
|
|
// await IUI.registry[i].create();
|
|
// //await IUI.registry[i].updateAttributes();
|
|
//}
|
|
//return;
|
|
}
|
|
|
|
static get(o)
|
|
{
|
|
return document.getElementById(o);
|
|
|
|
//for(var i = 0; i < IUI.registry.length; i++)
|
|
// if (IUI.registry[i].id == o)
|
|
// return IUI.registry[i];
|
|
//return null;
|
|
}
|
|
|
|
static put(o)
|
|
{
|
|
IUI.registry.push(o);
|
|
}
|
|
|
|
static remove(id)
|
|
{
|
|
for(var i = 0; i < IUI.registry.length; i++)
|
|
if (IUI.registry[i].el.id == id)
|
|
{
|
|
IUI.registry.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static module(objectClass)
|
|
{
|
|
let moduleName = objectClass.moduleName;
|
|
|
|
if (IUI.modules[moduleName] === undefined) {
|
|
customElements.define("i-" + moduleName, objectClass);
|
|
IUI.modules[moduleName] = {
|
|
cls: objectClass, init: function (properties) {
|
|
return new objectClass(properties);
|
|
}
|
|
};
|
|
}
|
|
|
|
return objectClass;
|
|
}
|
|
|
|
static extend(properties, defaults, overwrite)
|
|
{
|
|
if (properties == null)
|
|
properties = defaults;
|
|
else
|
|
for(var i in defaults)
|
|
if (overwrite)
|
|
properties[i] = defaults[i];
|
|
else if (properties[i] === undefined)
|
|
properties[i] = defaults[i];
|
|
return properties;
|
|
}
|
|
|
|
|
|
static bind(element, skipAttributes, sourcePath, scope) {
|
|
|
|
// ::Attribute
|
|
// : 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;
|
|
|
|
let bindings;
|
|
|
|
|
|
if (scope == null)
|
|
scope = {};
|
|
else
|
|
scope = {...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);
|
|
|
|
if (element.hasAttribute(":scope"))
|
|
{
|
|
let script = element.getAttribute(":scope");
|
|
let code = `try {\r\n context.value = ${script}; \r\n}\r\n catch(ex) { context.error = ex; }`
|
|
let func = new Function("context", code);
|
|
let context = {};
|
|
|
|
func.call(element, context);
|
|
|
|
if (context.error != undefined)
|
|
console.log("Scope binding failed", context.error.name + ": " + context.error.message, this.script, this.target);
|
|
else if (context.value != undefined
|
|
&& context.value instanceof Object)
|
|
IUI.extend(scope, context.value, true);
|
|
}
|
|
|
|
let scopeArgs = Object.keys(scope);
|
|
let scopeValues = Object.values(scope);
|
|
|
|
|
|
bindings = new BindingList(element, scope);
|
|
|
|
if (skipAttributes)
|
|
{
|
|
// copy attributes bindings
|
|
if (element.__i_bindings != null)
|
|
for(let i = 0; i < element.__i_bindings.length; i++)
|
|
if (element.__i_bindings[i].type != BindingType.TextNode)
|
|
bindings.push(element.__i_bindings[i]);
|
|
}
|
|
else
|
|
{
|
|
element.__i_bindings?.destroy();
|
|
|
|
// compile attributes
|
|
for (let i = 0; i < element.attributes.length; i++) {
|
|
let attr = element.attributes[i];
|
|
|
|
// skip scope
|
|
if (attr.name == ":scope")
|
|
continue;
|
|
|
|
if (attr.name.startsWith("@")){
|
|
|
|
// make events
|
|
let code = attr.value;
|
|
//let code = `try {\r\n context.value = ${script}; \r\n}\r\n catch(ex) { context.error = ex; }`
|
|
let func = new Function("event", ...scopeArgs, code);
|
|
let handler = (event) => {
|
|
func.call(element, event, ...scopeValues);
|
|
}
|
|
|
|
bindings.addEvent(attr.name.substr(1), handler);
|
|
}
|
|
else if (attr.name.startsWith("event:"))
|
|
{
|
|
// make events
|
|
let code = attr.value;
|
|
//let code = `try {\r\n context.value = ${script}; \r\n}\r\n catch(ex) { context.error = ex; }`
|
|
let func = new Function("event", ...scopeArgs, code);
|
|
let handler = (event) => {
|
|
func.call(element, event, ...scopeValues);
|
|
}
|
|
|
|
bindings.addEvent(attr.name.substr(6), handler);
|
|
|
|
}
|
|
else if (attr.name.startsWith("async-event:")) {
|
|
// make events
|
|
let code = attr.value;
|
|
//let code = `try {\r\n context.value = ${script}; \r\n}\r\n catch(ex) { context.error = ex; }`
|
|
let func = new AsyncFunction("event", ...scopeArgs, code);
|
|
let handler = (event) => {
|
|
func.call(element, event, ...scopeValues);
|
|
}
|
|
|
|
bindings.addEvent(attr.name.substr(12), handler);
|
|
}
|
|
else
|
|
{
|
|
let b = Binding.create(attr, bindings.scope);
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// add reference
|
|
// 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];
|
|
// }
|
|
// }
|
|
}
|
|
|
|
// 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, false, sourcePath, scope);
|
|
}
|
|
else if (el instanceof HTMLScriptElement)
|
|
{
|
|
|
|
try
|
|
{
|
|
// 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(...scopeArgs,
|
|
"//# sourceURL=iui://" + sourcePath + "-"
|
|
+ Math.round(Math.random() * 10000)
|
|
+ "\r\n" + el.text.trim());
|
|
|
|
let rt = func.apply(el.parentElement, scopeValues);
|
|
|
|
// Apply the returned object to the parent element.
|
|
if (typeof (rt) === "object") {
|
|
for (var k in rt)
|
|
el.parentElement[k] = rt[k];
|
|
}
|
|
}
|
|
catch (ex) {
|
|
console.log(ex);
|
|
}
|
|
}
|
|
else if (el instanceof HTMLElement) {
|
|
IUI.bind(el, false, sourcePath, scope);
|
|
}
|
|
else if (el instanceof Text) {
|
|
let b = Binding.create(el, bindings.scope);
|
|
if (b != null)
|
|
bindings.push(b);
|
|
}
|
|
}
|
|
|
|
element.__i_bindings = bindings;
|
|
}
|
|
|
|
static async render(element, data, textNodesOnly = false, radix = null) {
|
|
|
|
if (!element.__i_bindings) {
|
|
return;
|
|
}
|
|
|
|
let bindings = element.__i_bindings;
|
|
|
|
if (textNodesOnly) {
|
|
for (var i = 0; i < bindings.length; i++)
|
|
if (bindings[i].type == BindingType.TextNode)
|
|
await bindings[i].render(data, radix);
|
|
} else {
|
|
// render attributes & text nodes
|
|
for (var i = 0; i < bindings.length; i++)
|
|
await bindings[i].render(data, radix);
|
|
}
|
|
|
|
// render children
|
|
for (var i = 0; i < element.children.length; i++) {
|
|
let el = element.children[i];
|
|
if (el instanceof IUIElement) {
|
|
// @TODO should check if the element depends on parent or not
|
|
if (el.dataMap != null) {
|
|
// if map function failed to call setData, we will render without it
|
|
if (!(await el.dataMap.render(data, radix))){
|
|
// @BUG @TODO this causes stackoverflow
|
|
// await el.render();
|
|
}
|
|
}
|
|
else {
|
|
await el.setData(data);
|
|
}
|
|
}
|
|
else {
|
|
if (el.dataMap != null)
|
|
await el.dataMap.render(data, radix);
|
|
else
|
|
el.data = data;
|
|
|
|
await IUI.render(el, el.data, textNodesOnly, data);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
export function iui(selector)
|
|
{
|
|
return IUI.get(selector);
|
|
|
|
/*
|
|
if ((typeof selector === 'string' || selector instanceof String) && selector.length > 0)
|
|
{
|
|
var els = document.querySelectorAll(selector);
|
|
}
|
|
else
|
|
{
|
|
var els = IUI.get(selector);
|
|
if (els != null)
|
|
|
|
}
|
|
*/
|
|
|
|
if (typeof(this) == "undefined" || this == window)
|
|
{
|
|
var o = IUI.get(selector);
|
|
if (o)
|
|
return o;
|
|
else
|
|
{
|
|
var el;
|
|
|
|
if (typeof Node === "object" ? o instanceof Node : (
|
|
selector && typeof selector === "object" && typeof selector.nodeType === "number" && typeof selector.nodeName==="string") || selector === window)
|
|
{
|
|
el = selector;
|
|
}
|
|
else if (typeof selector === 'string' || selector instanceof String)
|
|
{
|
|
if (selector[0] == ".")
|
|
el = document.getElementsByClassName(selector.substr(1));
|
|
else
|
|
el = document.getElementById(selector);
|
|
}
|
|
|
|
if (el)
|
|
{
|
|
var rt = {};
|
|
var makeFunc = function(module){
|
|
return function(){
|
|
if (el instanceof HTMLCollection)
|
|
{
|
|
let rt = [];
|
|
for(var i = 0; i < el.length; i++)
|
|
{
|
|
var args = [el[i]];
|
|
for(var j = 0; j < arguments.length; j++)
|
|
args.push(arguments[j]);
|
|
rt.push(IUI.modules[module].init.apply(this, args));
|
|
}
|
|
return rt;
|
|
}
|
|
else
|
|
{
|
|
var args = [el];
|
|
for(var i = 0; i < arguments.length; i++)
|
|
args.push(arguments[i]);
|
|
return IUI.modules[module].init.apply(this, args);
|
|
}
|
|
}
|
|
};
|
|
|
|
for(var m in IUI.modules)
|
|
rt[m] = makeFunc(m);
|
|
|
|
return rt;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
IUI.registry.push(this);
|
|
|
|
|
|
if (selector)
|
|
{
|
|
if( Object.prototype.toString.call( selector ) === '[object Array]' )
|
|
{
|
|
this.el = [];
|
|
selector.forEach(function(i){
|
|
this.el.push(query(i));
|
|
});
|
|
}
|
|
else
|
|
this.el = query(selector);
|
|
|
|
this.events = {};
|
|
this.id = this.el.id;
|
|
}
|
|
*/
|
|
}
|
|
/*
|
|
Array.prototype.each = function(func)
|
|
{
|
|
if (this instanceof Array)
|
|
{
|
|
for(var i = 0; i < this.length; i++)
|
|
if (func(this[i], i))
|
|
break;
|
|
}
|
|
else
|
|
for(var i in this)
|
|
if(func(this[i], i))
|
|
break;
|
|
}
|
|
|
|
|
|
Array.prototype.distinct = function(field)
|
|
{
|
|
var rt = [];
|
|
|
|
this.forEach(function(item)
|
|
{
|
|
if (rt.indexOf(item[field]) == -1)
|
|
rt.push(item[field]);
|
|
});
|
|
|
|
return rt;
|
|
}
|
|
/*
|
|
iui.prototype.ec = function(className, parent)
|
|
{
|
|
if (parent)
|
|
return parent.getElementsByClassName(className);
|
|
else
|
|
return document.getElementsByClassName(className);
|
|
}
|
|
|
|
iui.prototype.ne = function(tag)
|
|
{
|
|
return document.createElement(tag);
|
|
}
|
|
*/
|
|
|
|
|
|
/*
|
|
iui.prototype.destroy = function()
|
|
{
|
|
IUI.registry.splice(IUI.registry.indexOf(this.el), 1);
|
|
};
|
|
|
|
iui.prototype.register = function(event)
|
|
{
|
|
this.events[event] = [];
|
|
return this;
|
|
};
|
|
|
|
iui.prototype.emit = function(event)
|
|
{
|
|
var args = Array.prototype.slice.call(arguments, 1);
|
|
if (this.events && this.events[event])
|
|
for(var i = 0; i < this.events[event].length; i++)
|
|
this.events[event][i].apply(this, args);
|
|
|
|
return this;
|
|
};
|
|
|
|
iui.prototype.on = function(event, fn)
|
|
{
|
|
if (this.events && this.events[event])
|
|
this.events[event].push(fn);
|
|
else if (document.attachEvent)
|
|
this.el.attachEvent('on' + event, fn)
|
|
else // if (document.addEventListener)
|
|
this.el.addEventListener(event, fn, !0);
|
|
|
|
return this;
|
|
};
|
|
*/
|
|
|
|
/*
|
|
window.addEventListener("load", function(){
|
|
for(var m in IUI.modules)
|
|
{
|
|
var elements = document.getElementsByTagName(m);
|
|
|
|
}
|
|
});
|
|
*/
|