mirror of
https://github.com/jaandrle/deka-dom-el
synced 2024-11-21 23:39:37 +01:00
💥 Events and README idea presentation
This commit is contained in:
parent
dc3d8133bb
commit
31563c4f7b
109
README.md
109
README.md
@ -1,6 +1,113 @@
|
|||||||
**WIP** (the experimentation phase) | [source code on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el) | [*mirrored* on GitHub](https://github.com/jaandrle/deka-dom-el)
|
**WIP** (the experimentation phase) | [source code on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el) | [*mirrored* on GitHub](https://github.com/jaandrle/deka-dom-el)
|
||||||
|
|
||||||
# Deka DOM Elements
|
# Deka DOM Elements
|
||||||
This is reimplementation of [jaandrle/dollar_dom_component: Functional DOM components without JSX and virtual DOM. Subrepository for https://github.com/jaandrle/jaaJSU ($dom namespace)](https://github.com/jaandrle/dollar_dom_component).
|
This is reimplementation of [jaandrle/dollar_dom_component: Functional DOM components without JSX and virtual DOM.](https://github.com/jaandrle/dollar_dom_component).
|
||||||
|
|
||||||
The goal is to be even more close to the native JavaScript.
|
The goal is to be even more close to the native JavaScript.
|
||||||
|
|
||||||
|
# Native JavaScript DOM elements creations
|
||||||
|
Let’s go through all patterns we would like to use and what needs to be improved for better experience.
|
||||||
|
|
||||||
|
## Creating element and DOM templates natively
|
||||||
|
```js
|
||||||
|
document.body.append(
|
||||||
|
document.createElement("div"),
|
||||||
|
document.createElement("span"),
|
||||||
|
document.createElement("main")
|
||||||
|
);
|
||||||
|
//=> HTML output: <div></div><span></span><main></main>
|
||||||
|
const template= document.createElement("main").append(
|
||||||
|
document.createElement("div"),
|
||||||
|
document.createElement("span"),
|
||||||
|
);
|
||||||
|
//=> ★:: typeof template==="undefined"
|
||||||
|
```
|
||||||
|
**Pitfalls**:
|
||||||
|
- there is lots of text
|
||||||
|
- `.append` methdo returns `void`⇒ it cannot be chained (see ★)
|
||||||
|
|
||||||
|
## Set properties of created element
|
||||||
|
```js
|
||||||
|
const element= Object.assign(document.createElement("p"), { className: "red", textContent: "paragraph" });
|
||||||
|
document.body.append(element);
|
||||||
|
//=> HTML output: <p class="red">paragraph</p>
|
||||||
|
```
|
||||||
|
**Pitfalls**:
|
||||||
|
- there is lots of text
|
||||||
|
- `Object.assign` isn’t ideal as it can set only (some) [IDL](https://developer.mozilla.org/en-US/docs/Glossary/IDL)
|
||||||
|
|
||||||
|
# Events and dynamic parts
|
||||||
|
```js
|
||||||
|
const input= document.createElement("input");
|
||||||
|
const output= document.createElement("output");
|
||||||
|
input.addEventListener("change", function(event){
|
||||||
|
output.value= event.target.value;
|
||||||
|
});
|
||||||
|
document.body.append(
|
||||||
|
output,
|
||||||
|
input
|
||||||
|
);
|
||||||
|
//=> HTML output: <output></output><input>
|
||||||
|
```
|
||||||
|
**Pitfalls**:
|
||||||
|
- there is lots of text
|
||||||
|
- very hard to organize code
|
||||||
|
|
||||||
|
# Helpers and modifications
|
||||||
|
Now, let's introduce library helpers and modifications.
|
||||||
|
|
||||||
|
## `.append`
|
||||||
|
The `.append` is owerwrote to always returns element. This seem to be the best way to do it as it is very hard
|
||||||
|
to create Proxy around `HTMLElement`, ….
|
||||||
|
```js
|
||||||
|
document.body.append(
|
||||||
|
document.createElement("main").append(
|
||||||
|
document.createElement("div"),
|
||||||
|
document.createElement("span"),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
//=> HTML output: <main><div></div><span></span></main>
|
||||||
|
```
|
||||||
|
|
||||||
|
## `el` and `assign` functions
|
||||||
|
```js
|
||||||
|
const element= assign(document.createElement("a"), {
|
||||||
|
className: "red",
|
||||||
|
dataTest: "test",
|
||||||
|
href: "www.seznam.cz",
|
||||||
|
textContent: "Link",
|
||||||
|
style: { color: "blue" }
|
||||||
|
});
|
||||||
|
document.body.append(element);
|
||||||
|
assign(element, { style: undefined });
|
||||||
|
//=> HTML output: <a href="www.seznam.cz" data-test="test" class="red">Link</a>
|
||||||
|
```
|
||||||
|
…but for elements/template creations `el` is even better:
|
||||||
|
```js
|
||||||
|
document.body.append(
|
||||||
|
el("div").append(
|
||||||
|
el("p").append(
|
||||||
|
el("#text", { textContent: "Link: " }),
|
||||||
|
el("a", {
|
||||||
|
href: "www.seznam.cz",
|
||||||
|
textContent: "example",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Events and dynamic parts
|
||||||
|
```js
|
||||||
|
const output_dynamic= (function(){
|
||||||
|
const element= el("span", { style: { fontWeight: "bold" }, textContent: "" });
|
||||||
|
return {
|
||||||
|
element,
|
||||||
|
onchange: listen("change", event=> assign(element, { textContent: event.target.value }))
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
document.body.append(
|
||||||
|
output_dynamic.element,
|
||||||
|
el("input", { type: "text" }, output_dynamic.onchange)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
73
index.js
73
index.js
@ -2,74 +2,5 @@
|
|||||||
const { append }= c.prototype;
|
const { append }= c.prototype;
|
||||||
c.prototype.append= function(...els){ append.apply(this, els); return this; };
|
c.prototype.append= function(...els){ append.apply(this, els); return this; };
|
||||||
});
|
});
|
||||||
|
export * from "./src/dom.js";
|
||||||
export function createElement(tag, attributes){
|
export * from "./src/events.js";
|
||||||
if(tag==="<>") return document.createDocumentFragment();
|
|
||||||
if(tag==="") return document.createTextNode(attributes.textContent ?? attributes.innerText ?? attributes.innerHTML);
|
|
||||||
return assign(document.createElement(tag), attributes);
|
|
||||||
}
|
|
||||||
export { createElement as el };
|
|
||||||
export function createElementNS(tag, attributes, attributes_todo){
|
|
||||||
let namespace= "svg";
|
|
||||||
if(typeof attributes_todo !== "undefined"){
|
|
||||||
namespace= tag; tag= attributes; attributes= attributes_todo; }
|
|
||||||
if(tag==="<>") return document.createDocumentFragment();
|
|
||||||
if(tag==="") return document.createTextNode(attributes.textContent ?? attributes.innerText ?? attributes.innerHTML);
|
|
||||||
return assign(document.createElementNS(namespace==="svg" ? "http://www.w3.org/2000/svg" : namespace, tag), attributes);
|
|
||||||
}
|
|
||||||
export { createElementNS as elNS };
|
|
||||||
|
|
||||||
export function assign(element, ...attributes){
|
|
||||||
// prefers https://developer.mozilla.org/en-US/docs/Glossary/IDL
|
|
||||||
if(!attributes.length) return element;
|
|
||||||
const is_svg= element instanceof SVGElement;
|
|
||||||
const setRemoveAttr= (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
|
|
||||||
|
|
||||||
Object.entries(Object.assign({}, ...attributes)).forEach(function([ key, attr ]){
|
|
||||||
if(key[0]==="=") return setRemoveAttr(key.slice(1), attr);
|
|
||||||
if(key[0]===".") return setDelete(element, key.slice(1), attr);
|
|
||||||
if(typeof attr === "object"){
|
|
||||||
switch(key){
|
|
||||||
case "style": return forEachEntries(attr, setRemove.bind(null, element.style, "Property"))
|
|
||||||
case "dataset": return forEachEntries(attr, setDelete.bind(null, element.dataset));
|
|
||||||
case "ariaset": return forEachEntries(attr, (key, val)=> setRemoveAttr("aria-"+key, val));
|
|
||||||
case "classList": return classListDeclartive(element, attr);
|
|
||||||
default: return Reflect.set(element, key, attr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(/(aria|data)([A-Z])/.test(key)){
|
|
||||||
key= key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
||||||
return setRemoveAttr(key, attr);
|
|
||||||
}
|
|
||||||
switch(key){
|
|
||||||
case "href" || "src" || "style":
|
|
||||||
return setRemoveAttr(key, attr);
|
|
||||||
case "xlink:href":
|
|
||||||
return setRemoveAttr(key, attr, "http://www.w3.org/1999/xlink");
|
|
||||||
case "textContent" || "innerText":
|
|
||||||
if(!is_svg) break;
|
|
||||||
return element.appendChild(document.createTextNode(attr));
|
|
||||||
}
|
|
||||||
if(key in element && !is_svg)
|
|
||||||
return setDelete(element, key, attr);
|
|
||||||
return setRemoveAttr(key, attr);
|
|
||||||
});
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
export function classListDeclartive(element, toggle){
|
|
||||||
if(typeof toggle !== "object") return element;
|
|
||||||
|
|
||||||
forEachEntries(toggle,
|
|
||||||
(class_name, val)=>
|
|
||||||
element.classList.toggle(class_name, val===-1 ? undefined : Boolean(val)))
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function empty(el){ Array.from(el.children).forEach(el=> el.remove()); return el; }
|
|
||||||
|
|
||||||
function forEachEntries(obj, cb){ return Object.entries(obj).forEach(([ key, val ])=> cb(key, val)); }
|
|
||||||
function isUndef(value){ return typeof value==="undefined"; }
|
|
||||||
|
|
||||||
function setRemove(obj, prop, key, val){ return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, val); }
|
|
||||||
function setRemoveNS(obj, prop, key, val, ns= null){ return obj[ (isUndef(val) ? "remove" : "set") + prop + "NS" ](ns, key, val); }
|
|
||||||
function setDelete(obj, prop, val){ return Reflect[ isUndef(val) ? "deleteProperty" : "set" ](obj, prop, val); }
|
|
||||||
|
75
src/dom.js
Normal file
75
src/dom.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
export function createElement(tag, attributes, ...connect){
|
||||||
|
let el;
|
||||||
|
switch(true){
|
||||||
|
case typeof tag==="function": el= tag(attributes || undefined); break;
|
||||||
|
case tag==="<>": el= document.createDocumentFragment(); break;
|
||||||
|
case tag==="#text": el= assign(document.createTextNode(""), attributes); break;
|
||||||
|
default: el= assign(document.createElement(tag), attributes);
|
||||||
|
}
|
||||||
|
connect.forEach(c=> c(el));
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
export { createElement as el };
|
||||||
|
export function createElementNS(tag, attributes, attributes_todo){
|
||||||
|
let namespace= "svg";
|
||||||
|
if(typeof attributes_todo !== "undefined"){
|
||||||
|
namespace= tag; tag= attributes; attributes= attributes_todo; }
|
||||||
|
if(tag==="<>") return document.createDocumentFragment();
|
||||||
|
if(tag==="") return document.createTextNode(attributes.textContent ?? attributes.innerText ?? attributes.innerHTML);
|
||||||
|
return assign(document.createElementNS(namespace==="svg" ? "http://www.w3.org/2000/svg" : namespace, tag), attributes);
|
||||||
|
}
|
||||||
|
export { createElementNS as elNS };
|
||||||
|
|
||||||
|
export function assign(element, ...attributes){
|
||||||
|
if(!attributes.length) return element;
|
||||||
|
const is_svg= element instanceof SVGElement;
|
||||||
|
const setRemoveAttr= (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
|
||||||
|
|
||||||
|
Object.entries(Object.assign({}, ...attributes)).forEach(function([ key, attr ]){
|
||||||
|
if(key[0]==="=") return setRemoveAttr(key.slice(1), attr);
|
||||||
|
if(key[0]===".") return setDelete(element, key.slice(1), attr);
|
||||||
|
if(typeof attr === "object"){
|
||||||
|
switch(key){
|
||||||
|
case "style": return forEachEntries(attr, setRemove.bind(null, element.style, "Property"))
|
||||||
|
case "dataset": return forEachEntries(attr, setDelete.bind(null, element.dataset));
|
||||||
|
case "ariaset": return forEachEntries(attr, (key, val)=> setRemoveAttr("aria-"+key, val));
|
||||||
|
case "classList": return classListDeclartive(element, attr);
|
||||||
|
default: return Reflect.set(element, key, attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(/(aria|data)([A-Z])/.test(key)){
|
||||||
|
key= key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
||||||
|
return setRemoveAttr(key, attr);
|
||||||
|
}
|
||||||
|
switch(key){
|
||||||
|
case "href" || "src" || "style":
|
||||||
|
return setRemoveAttr(key, attr);
|
||||||
|
case "xlink:href":
|
||||||
|
return setRemoveAttr(key, attr, "http://www.w3.org/1999/xlink");
|
||||||
|
case "textContent" || "innerText":
|
||||||
|
if(!is_svg) break;
|
||||||
|
return element.appendChild(document.createTextNode(attr));
|
||||||
|
}
|
||||||
|
if(key in element && !is_svg)
|
||||||
|
return setDelete(element, key, attr);
|
||||||
|
return setRemoveAttr(key, attr);
|
||||||
|
});
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
export function classListDeclartive(element, toggle){
|
||||||
|
if(typeof toggle !== "object") return element;
|
||||||
|
|
||||||
|
forEachEntries(toggle,
|
||||||
|
(class_name, val)=>
|
||||||
|
element.classList.toggle(class_name, val===-1 ? undefined : Boolean(val)))
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function empty(el){ Array.from(el.children).forEach(el=> el.remove()); return el; }
|
||||||
|
|
||||||
|
function forEachEntries(obj, cb){ return Object.entries(obj).forEach(([ key, val ])=> cb(key, val)); }
|
||||||
|
function isUndef(value){ return typeof value==="undefined"; }
|
||||||
|
|
||||||
|
function setRemove(obj, prop, key, val){ return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, val); }
|
||||||
|
function setRemoveNS(obj, prop, key, val, ns= null){ return obj[ (isUndef(val) ? "remove" : "set") + prop + "NS" ](ns, key, val); }
|
||||||
|
function setDelete(obj, prop, val){ return Reflect[ isUndef(val) ? "deleteProperty" : "set" ](obj, prop, val); }
|
8
src/events.js
Normal file
8
src/events.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export function listen(event, listener, options){
|
||||||
|
return element=> element.addEventListener(event, listener, options);
|
||||||
|
}
|
||||||
|
export function dispatch(event, detail){
|
||||||
|
if(typeof event === "string")
|
||||||
|
event= typeof detail==="undefined" ? new Event(event) : new CustomEvent(event, { detail });
|
||||||
|
return element=> element.dispatchEvent(event);
|
||||||
|
}
|
@ -1,17 +1,63 @@
|
|||||||
import { el, elNS, assign } from "../index.js";
|
import { el, elNS, assign, listen, dispatch } from "../index.js";
|
||||||
Object.assign(globalThis, { el, elNS, assign });
|
Object.assign(globalThis, { el, elNS, assign, listen, dispatch });
|
||||||
|
|
||||||
console.log(el("p", { className: "red", textContent: "Hello "}));
|
const { style, css }= createStyle();
|
||||||
console.log(el("p", { className: "red", textContent: "Hello "}) instanceof HTMLParagraphElement);
|
globalThis.test= console.log;
|
||||||
|
const app= el(component, null, listen("change", globalThis.test));
|
||||||
|
dispatch("change", "Peter")(app);
|
||||||
|
console.log(app, app instanceof HTMLDivElement);
|
||||||
|
|
||||||
document.head.append(
|
document.head.append(style);
|
||||||
el("style", { textContent: `
|
document.body.append(app);
|
||||||
.red{ color: red; }
|
|
||||||
` })
|
function component({ value= "World" }= {}){
|
||||||
)
|
const name= "naiveForm";
|
||||||
document.body.append(
|
css`
|
||||||
el("p", { className: "red" }).append(
|
.${name}{
|
||||||
el("", { textContent: "Hello " }),
|
display: flex;
|
||||||
el("strong", { textContent: "World" })
|
flex-flow: column nowrap;
|
||||||
|
}
|
||||||
|
.${name} input{
|
||||||
|
margin-inline-start: .5em;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const output= (function(){
|
||||||
|
const element= el("strong", { textContent: value });
|
||||||
|
return {
|
||||||
|
element,
|
||||||
|
onchange: listen("change", function(event){
|
||||||
|
assign(element, { textContent: event.target.value });
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
const input= (function(){
|
||||||
|
const element= el("input", { type: "text", value }, output.onchange);
|
||||||
|
return {
|
||||||
|
element,
|
||||||
|
onchange: listen("change", function(event){
|
||||||
|
assign(element, { value: event.detail });
|
||||||
|
dispatch("change")(element);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
return el("div", { className: name }, input.onchange).append(
|
||||||
|
el("p").append(
|
||||||
|
el("#text", { textContent: "Hello " }),
|
||||||
|
output.element,
|
||||||
|
),
|
||||||
|
el("label").append(
|
||||||
|
el("#text", { textContent: "Set name:" }),
|
||||||
|
input.element
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
}
|
||||||
|
function createStyle(){
|
||||||
|
const style= el("style");
|
||||||
|
return {
|
||||||
|
style,
|
||||||
|
css(...args){
|
||||||
|
style.appendChild(el("#text", { textContent: String.raw(...args) }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user