2
0
mirror of https://github.com/esiur/iui.git synced 2025-05-06 06:42:58 +00:00
iui/src/Core/IUI.js
2024-07-10 23:36:25 +03:00

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);
}
});
*/