class TemplateElement {
static XMLNS = {
svg: "http://www.w3.org/2000/svg",
xlink: "https://www.w3.org/1999/xlink"
};
static HTML = HTML.reduce((r, v) => {
r[v] = true;
return r;
}, {});
static SVG = SVG.reduce((r, v) => {
if (TemplateElement.HTML[v] === undefined) {
r[v] = "http://www.w3.org/2000/svg";
}
return r;
}, {});
constructor(name, attributes = {}, children = []) {
this.name = name;
this.namespace = attributes?.xmlns || TemplateElement.SVG[name];
this.attributes = {};
this.children = [];
this.set(attributes, ...children);
this._node = undefined;
}
get node() {
this._node = this._node ? this._node : this.toDOM();
return this._node;
}
set(attributes, ...rest) {
if (attributes instanceof Array) {
return this.set({}, attributes.concat(rest));
} else if (
attributes instanceof Node ||
attributes instanceof TemplateElement ||
attributes instanceof Slot ||
attributes instanceof Effector ||
attributes instanceof Selector
) {
return this.set({}, [attributes, ...rest]);
} else if (!(attributes instanceof Object)) {
return this.set({}, [attributes, ...rest]);
} else {
// We normalize child slots to be effects
this.attributes = Object.entries(attributes || {}).reduce((r, [k, v]) => {
r[k] =
v instanceof Slot
? k.match(RE_ATTRIBUTE_HANDLER)
? // The attribute is a handler, and the handler callback come from a cell
new InteractionEffector(v, null)
: new AttributeEffector(v, k)
: v instanceof Function
? new InteractionEffector(null, v)
: v;
return r;
}, this.attributes);
this.children = rest
.flat()
.map((v, k) =>
v instanceof Slot
? new ContentEffector(v)
: v instanceof Selector
? v.compile()
: v
);
return this;
}
}
walk(functor, path = []) {
functor(this, path);
for (let k in this.attributes) {
const v = this.attributes[k];
if (v instanceof Effector) {
functor(path.concat([k]));
}
}
this.children.forEach((v, k) => {
if (v instanceof TemplateElement) {
v.walk(functor, path.concat([k]));
} else if (v instanceof Effector) {
functor(v, path.concat([k]));
// TODO: We should probably recurse with the effectors's sub effectors.
/*v.templates.forEach((t, i) => {
t.walk(functor, path.concat([k, i]));
});*/
} else {
functor(v, path.concat([k]));
}
});
}
getEffects(element = this) {
// This extracts the effectors defined in the template and returns a list list of effects.
const slots = [];
element.walk((node, path) => {
if (
node === null ||
node === undefined ||
typeof node === "string" ||
typeof node === "number"
) {
// Nothing, it's a straight up node.
} else if (node instanceof Effector) {
slots.push(new TemplateEffect(node, path, slots.length));
} else {
Object.entries(node.attributes || {}).forEach(([k, v]) => {
if (v instanceof Effector) {
slots.push(new TemplateEffect(v, [...path, k], slots.length));
}
});
}
});
return slots;
}
getXXXEffectors(element = this) {
throw new Error("This is not implemented");
return this.getSlots(element).reduce((r, _) => {
// FIXME: We're not using mapping inputs
const m = _.effect.mapping.inputs;
if (!m) {
// Nothing
} else if (m instanceof Array) {
r = m.reduce((r, v) => {
r[v.name] = v;
return r;
}, r);
} else if (m instanceof Slot) {
r[m.name] = m;
} else {
r = Object.entries(m).reduce((r, [k, v]) => {
r[k] = v;
return r;
}, r);
}
return r;
}, {});
}
toDOM(element = this) {
return TemplateElement.ToDOM(element);
}
static ToDOM(element) {
let node = null;
if (typeof element === "string" || typeof element === "number") {
node = document.createTextNode(`${element}`);
} else if (element === null || element === undefined) {
node = document.createComment("");
} else if (element instanceof Effector) {
node = document.createComment("◌");
} else if (element instanceof TemplateElement) {
node = element.namespace
? document.createElementNS(
TemplateElement.XMLNS[element.namespace] || element.namespace,
element.name
)
: document.createElement(element.name);
Object.entries(element.attributes || {}).forEach(([k, v]) => {
switch (k) {
case "style":
if (!(v instanceof Effector)) {
Object.assign(node.style, v);
}
break;
// FIXME: We should probably have jsut one
case "class":
case "className":
case "_":
if (!(v instanceof Effector)) {
node.className = `${v}`;
}
break;
default:
const qualname = k.split(":");
qualname.length === 1
? node.setAttribute(k, v instanceof Effector ? "" : `${v}`)
: node.setAttributeNS(
TemplateElement.NS[qualname[0]] || qualname[0],
qualname[1],
v instanceof Effector ? "" : `${v}`
);
}
});
} else if (element instanceof Node) {
return element;
} else {
onError(
`TemplateElement: unsupported child of type '${typeof element}': ${element}`,
{ element }
);
}
return (element.children || []).reduce((r, v) => {
r.appendChild(TemplateElement.ToDOM(v));
return r;
}, node);
}
}