2
0
mirror of https://github.com/esiur/iui.git synced 2025-06-27 09:23:12 +00:00

initial commit

This commit is contained in:
2021-02-22 11:39:50 +03:00
commit e82f4bc4cf
87 changed files with 14463 additions and 0 deletions

24
src/Core/App.js Normal file
View File

@ -0,0 +1,24 @@
import IUIElement from "../Core/IUIElement.js";
import { IUI } from "../Core/IUI.js";
export default IUI.module(class App extends IUIElement {
constructor() {
super();
}
create() {
this._register("load");
window.app = this;
}
created() {
this.updateBindings();
this.render();
this._emit("load", { app: this });
this.loaded = true;
}
});

351
src/Core/Binding.js Normal file
View File

@ -0,0 +1,351 @@
import IUIElement from "./IUIElement.js";
export const BindingType = {
IUIElement: 0, // this will never happen !
TextNode: 1,
ContentAttribute: 2,
Attribute: 3,
HTMLElementDataAttribute: 4,
IUIElementDataAttribute: 5,
IfAttribute: 6,
RevertAttribute: 7
};
export const AttributeBindingDestination = {
Field: 0,
Attribute: 1
};
const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
export class Binding {
static create(nodeOrAttributeOrIUIElement) {
var code, isAsync, type, attrType, attrKey, func, script;
//if (nodeOrAttributeOrIUIElement.created)
// debugger;
if (nodeOrAttributeOrIUIElement instanceof IUIElement) {
isAsync = nodeOrAttributeOrIUIElement.hasAttribute("async");
type = BindingType.IUIElement;
} else if (nodeOrAttributeOrIUIElement instanceof Text) {// nodeOrAttribute.nodeType == 3) {
if (!nodeOrAttributeOrIUIElement.wholeText.match(/\${.*}/))
return null;
type = BindingType.TextNode;
isAsync = nodeOrAttributeOrIUIElement.parentElement.hasAttribute("async");
//code = "return `" + nodeOrAttributeOrIUIElement.wholeText + "`;";
script = nodeOrAttributeOrIUIElement.wholeText;
code = `try {\r\n context.value = \`${script}\`\r\n}\r\n catch(ex) { context.error = ex; }`
nodeOrAttributeOrIUIElement.data = "";
nodeOrAttributeOrIUIElement.created = true;
} else if (nodeOrAttributeOrIUIElement instanceof Attr) {
if (nodeOrAttributeOrIUIElement.name.startsWith("async::")) {
isAsync = true;
attrType = AttributeBindingDestination.Attribute;
attrKey = nodeOrAttributeOrIUIElement.name.substr(7);
}
else if (nodeOrAttributeOrIUIElement.name.startsWith("::")) {
isAsync = false;
attrType = AttributeBindingDestination.Attribute;
attrKey = nodeOrAttributeOrIUIElement.name.substr(2);
}
else if (nodeOrAttributeOrIUIElement.name.startsWith("async:")) {
isAsync = true;
attrType = AttributeBindingDestination.Field;
attrKey = nodeOrAttributeOrIUIElement.name.substr(6);
}
else if (nodeOrAttributeOrIUIElement.name.startsWith(":")) {
isAsync = false;
attrType = AttributeBindingDestination.Field;
attrKey = nodeOrAttributeOrIUIElement.name.substr(1);
}
else {
return null;
}
// isAsync = nodeOrAttributeOrIUIElement.value.search("await");
// code = "return " + nodeOrAttributeOrIUIElement.value + ";";
script = nodeOrAttributeOrIUIElement.value
code = `try {\r\n context.value = ${script}; \r\n}\r\n catch(ex) { context.error = ex; }`
let sentence = attrKey.split("-");
for (var i = 1; i < sentence.length; i++)
sentence[i] = sentence[i].charAt(0).toUpperCase() + sentence[i].slice(1);
attrKey = sentence.join("");
if (attrKey == "content")
type = BindingType.ContentAttribute;
else if (attrKey == "if") {
type = BindingType.IfAttribute;
//displayMode =
}
else if (attrKey == "revert")
type = BindingType.RevertAttribute;
else if (attrKey != "data")
type = BindingType.Attribute;
else if (nodeOrAttributeOrIUIElement.ownerElement instanceof IUIElement)
type = BindingType.IUIElementDataAttribute;
else
type = BindingType.HTMLElementDataAttribute;
}
// test the function
try {
if (isAsync)
func = new AsyncFunction("data", "d", "context", "_test", code);
else
func = new Function("data", "d", "context", "_test", code);
}
catch (ex) {
console.log("Test failed: " + ex, code);
return null;
}
let rt = new Binding();
Object.assign(rt, { isAsync, type, attrType, attrKey, func, target: nodeOrAttributeOrIUIElement, checked: false, script });
return rt;
}
constructor() {
this.watchList = [];
let self = this;
this.listener = function (name, value) {
self.render(self.data);
};
}
_findMap(thisArg) {
// @TODO: Map thisArg too
let map = {};
let detector = {
get: function (obj, prop) {
if (typeof prop == "string") {
obj[prop] = {};
return new Proxy(obj[prop], detector);
}
}
};
this.checked = true;
let proxy = new Proxy(map, detector);
try {
let d = this.func.apply(thisArg, [proxy, proxy, {}, true]);
this.map = map;
return d;
}
catch (ex) {
//console.log("Proxy failed", ex);
this.map = map;
}
}
async _execute(thisArg, data) {
if (!this.checked)
this._findMap(thisArg);
let context = {};
var rt = this.func.apply(thisArg, [data, data, context, false]);
//console.log(rt);
if (rt instanceof Promise)
await rt;
if (context.error != undefined)
{
console.log("Execution failed", context.error.name + ": " + context.error.message, this.script, this.target);
return;
}
else if (context.value == undefined)
{
return;
}
else if (context.value instanceof Promise)
{
try
{
return await context.value;
} catch(ex) {
console.log("Execution failed", ex.name + ": " + ex.message, this.script, this.target);
}
}
else
{
return context.value;
}
}
unbind() {
this.data = null;
for (var i = 0; i < this.watchList.length; i++)
this.watchList[i].data.off(this.watchList[i].event, this.listener);
this.watchList = [];
}
bind(data, map) {
if (data == null)
return;
if (data?.on) {
for (var p in map) {
let event = ":" + p;
data.on(":" + p, this.listener);
this.watchList.push({ data, event});
this.bind(data[p], map[p]);
}
//if (this.watchList.includes(data))
// this.watchList.push({ data, event : });
}
else {
for (var p in map) {
this.bind(data[p], map[p]);
}
}
}
async render(data) {
// @TODO: Checking properties bindings moved here
if (data != this.data)
this.unbind();
try {
if (this.type === BindingType.IUIElement) {
let d = this.func.apply(this.target, [data, data]);
if (d instanceof Promise)
d = await d;
await this.target.setData(d);
}
else if (this.type === BindingType.TextNode) {
try {
let d = await this._execute(this.target.parentElement, data);
if (d === undefined)
return false;
//if (d instanceof Promise)
// d = await d;
this.target.data = d;// (d === undefined) ? "" : d;
if (data != this.data) {
this.data = data;
this.bind(data, this.map);
}
}
catch (ex) {
this.target.data = "";
}
}
// Content Attribute
else if (this.type == BindingType.ContentAttribute) {
let d = await this._execute(this.target.ownerElement, data);
if (d === undefined)
return false;
//if (d instanceof Promise)
// d = await d;
this.target.ownerElement.innerHTML = d;
}
else if (this.type == BindingType.IfAttribute)
{
let d = await this._execute(this.target.ownerElement, data);
if (d === undefined)
return false;
//if (d instanceof Promise)
// d = await d;
this.target.ownerElement.style.display = d ? "" : "none";
}
else if (this.type == BindingType.RevertAttribute)
{
let d = await this._execute(this.target.ownerElement, data);
if (d === undefined)
return false;
//if (d instanceof Promise)
// d = await d;
}
// Attribute
else if (this.type === BindingType.Attribute) {
//if (this.target.ownerElement.hasAttribute("debug"))
// debugger;
let d = await this._execute(this.target.ownerElement, data);
if (d === undefined)
return false;
//if (d instanceof Promise)
// d = await d;
if (this.attrType == AttributeBindingDestination.Field)
this.target.ownerElement[this.attrKey] = d;
else
this.target.ownerElement.setAttribute(this.attrKey, d);
if (data != this.data) {
this.data = data;
this.bind(data, this.map);
}
}
// Data Attribute of IUI Element
else if (this.type === BindingType.IUIElementDataAttribute) {
let d = await this._execute(this.target.ownerElement, data);
//if (d === undefined)
// return false;
//if (d instanceof Promise)
// d = await d;
await this.target.ownerElement.setData(d);
}
// Data Attribute of HTML Element
else if (this.type == BindingType.HTMLElementDataAttribute) {
let d = await this._execute(this.target.ownerElement, data);
if (d === undefined)
return false;
//if (d instanceof Promise)
// d = await d;
this.target.ownerElement.data = d;
}
return true;
}
catch (ex) {
// console.log(ex);
return false;
}
}
}

310
src/Core/IUI.js Normal file
View File

@ -0,0 +1,310 @@
import IUIElement from "./IUIElement.js";
export const IUI = {
format: function (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 [];
},
_menus: [],
views: [],
modules: {},
registry : [],
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] }),
created: async function (element) {
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);
}
},
create: async function(element)
{
for (let i = 0; i < element.children.length; i++) {
let e = element.children[i];
if (e instanceof IUIElement) {
await e.create();
// e.updateBindings();
}
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;
},
get : function(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;
},
put: function(o)
{
IUI.registry.push(o);
},
remove: function(id)
{
for(var i = 0; i < IUI.registry.length; i++)
if (IUI.registry[i].el.id == id)
{
IUI.registry.splice(i, 1);
break;
}
},
module: function(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;
},
extend: function(properties, defaults, force)
{
if (properties == null)
properties = defaults;
else
for(var i in defaults)
if (force)
properties[i] = defaults[i];
else if (properties[i] === undefined)
properties[i] = defaults[i];
return properties;
}
};
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);
}
});
*/

313
src/Core/IUIElement.js Normal file
View File

@ -0,0 +1,313 @@
import { IUI } from "./IUI.js";
import { Binding, BindingType, AttributeBindingDestination } from "./Binding.js";
export default class IUIElement extends HTMLElement {
constructor(defaults) {
super();
this._events = [];
this._data = null;
for (var i in defaults)
if (this[i] == undefined)
this[i] = defaults[i];
this._register("data");
}
static get moduleName(){
return this.name.toLowerCase();
}
get cssClass(){
if (this.hasAttribute("css-class"))
return this.getAttribute("css-class");
//else
// return this.constructor.moduleName;
}
set cssClass(value)
{
this.classList.remove(this.cssClass);
this.setAttribute("css-class", value);
this.classList.add(value);
}
async render() {
await this._renderElement(this, this._data);
}
_getParentData() {
var p = this.parentElement;
do {
if (p.data !== undefined)
return p.data;
} while (p = p.parentElement);
return undefined;
}
async setData(value) {
//if (this.bindings === undefined)
// this.updateBindings();
this._data = value;
this._emit("data", {data: value});
await this._renderElement(this, value);
//console.log("IUI: SetData", value, this.tagName);
}
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 {
var p = e.parentElement;
if (e.revertMap != null)
await e.revertMap.render(p?.data);
} while (e = p);
//}
}
async update(data) {
if (data == undefined) {
// get parent data
if (this.dataMap != null) {
await this.dataMap.render(this._getParentData());
} else
await this.setData(this.data);
}
else {
// apply specified data
if (this.dataMap != null) {
await this.dataMap.render(data);
} else
await this.setData(data);
}
}
async _renderElement(element, data) {
if (!element.bindings) {
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);
// 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 this._renderElement(e, e.data);
}
}
}
// this meant to be inherited
modified() {
}
get data() {
return this._data;
}
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);
}
}
disconnectedCallback() {
// console.log("removed", this);
}
adoptedCallback() {
//console.log("adopted", this);
}
//appendChild(node) {
// // do some bindings
// super.appendChild(node);
//}
created() {
}
create() {
//this.updateBindings();
}
destroy() {
console.log("Destroy", this);
IUI.registry.splice(IUI.registry.indexOf(this), 1);
if (this.parentNode)
this.parentNode.removeChild(this);
}
_make_bindings(element) {
// ::Attribute
// : Field
// async:: Async Attribute
// async: Async Field
// @ Event
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)
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
this._make_bindings(e);
}
else if (e instanceof HTMLElement) {
this._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);
for (var i in values) {
if (e[i] === undefined)
e[i] = values[i];
}
try
{
return this.dispatchEvent(e);
}
catch(ex)
{
console.log(ex);
}
}
updateBindings() {
this._make_bindings(this);
}
_encapsulateEvent(code){
return `try {\r\n ${code} \r\n}\r\n catch(ex) { console.log(ex.name + ":" + ex.message, this); }`;
}
_register(event) {
this._events.push(event);
if (this.hasAttribute("@" + event)) {
let handler = this.getAttribute("@" + event);
if (handler.match(/^[A-Za-z\$_]+(?:[\$_][A-Za-z0-9]+)*$/g) === null) {
try
{
let func = new Function("event", this._encapsulateEvent(this.getAttribute("@" + event)));
this.addEventListener(event, func);
} catch (ex)
{
console.log(ex);
}
}
else {
let func = this[handler];
if (func instanceof Function) {
this.addEventListener(event, func, false);
}
else {
// might be added in the future
let func = new Function("event", `this["${handler}"](event)`);
this.addEventListener(event, func, false);
}
}
}
}
off(event, fn) {
this.removeEventListener(event, fn);
return this;
}
on(event, fn) {
this.addEventListener(event, fn, false);
return this;
}
}