2
0
mirror of https://github.com/esiur/iui.git synced 2026-04-04 06:58:22 +00:00
Files
iui/src/Core/Binding.js
2024-07-10 23:36:25 +03:00

361 lines
11 KiB
JavaScript

import IUIElement from "./IUIElement.js";
import { IUI } from "./IUI.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, scope) {
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) {
if (!nodeOrAttributeOrIUIElement.wholeText.match(/\${.*}/))
return null;
type = BindingType.TextNode;
isAsync = nodeOrAttributeOrIUIElement.parentElement.hasAttribute("async");
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);
// skip scope
// if (attrKey == "scope")
// return null;
}
else if (nodeOrAttributeOrIUIElement.name.startsWith(":")) {
isAsync = false;
attrType = AttributeBindingDestination.Field;
attrKey = nodeOrAttributeOrIUIElement.name.substr(1);
// skip scope
// if (attrKey == "scope")
// return null;
}
else {
return null;
}
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
let scopeKeys = Object.keys(scope);
let scopeValues = Object.values(scope);
try {
let args = ["data", "d", "radix", "context", "_test",
...scopeKeys]
if (isAsync)
func = new AsyncFunction(...args, code);
else
func = new Function(...args, 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, scopeKeys, scopeValues });
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, null, {}, true
, ...this.scopeValues]);
this.map = map;
return d;
}
catch (ex) {
//console.log("Proxy failed", ex);
this.map = map;
}
}
async _execute(thisArg, data, radix) {
if (!this.checked)
this._findMap(thisArg);
let context = {};
var rt = this.func.apply(thisArg, [data, data, radix, context, false,
...this.scopeValues]);
if (rt instanceof Promise)
await rt;
if (context.error != undefined)
{
if (thisArg instanceof IUIElement){
thisArg.setError(context.error);
}
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) {
if (thisArg instanceof IUIElement){
thisArg.setError(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, radix) {
// @TODO: Checking properties bindings moved here
if (data != this.data)
this.unbind();
try {
if (this.type === BindingType.IUIElement) {
let d = await this._execute(this.target, data, radix);
await this.target.setData(d);
}
else if (this.type === BindingType.TextNode) {
try {
let d = await this._execute(this.target.parentElement, data, radix);
if (d === undefined)
return false;
this.target.data = 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 targetElement = this.target.ownerElement;
let d = await this._execute(targetElement, data, radix);
if (d === undefined)
return false;
targetElement.innerHTML = d;
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);
await IUI.render(targetElement, targetElement._data, true);
}
}
else if (this.type == BindingType.IfAttribute)
{
let d = await this._execute(this.target.ownerElement, data, radix);
this.target.ownerElement.style.display = d ? "" : "none";
}
else if (this.type == BindingType.RevertAttribute)
{
let d = await this._execute(this.target.ownerElement, data, radix);
if (d === undefined)
return false;
}
// Attribute
else if (this.type === BindingType.Attribute) {
let d = await this._execute(this.target.ownerElement, data, radix);
if (d === undefined)
return false;
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, radix);
// radix is data
await this.target.ownerElement.setData(d, data);
}
// Data Attribute of HTML Element
else if (this.type == BindingType.HTMLElementDataAttribute) {
let d = await this._execute(this.target.ownerElement, data, radix);
if (d === undefined)
return false;
this.target.ownerElement.data = d;
}
return true;
}
catch (ex) {
return false;
}
}
}