mirror of
https://github.com/jaandrle/deka-dom-el
synced 2024-11-22 16:55:23 +01:00
💥 observedAttributes
This commit is contained in:
parent
6f5e13e66a
commit
2e4e6adec1
43
dist/dde-with-observables.js
vendored
43
dist/dde-with-observables.js
vendored
File diff suppressed because one or more lines are too long
29
dist/dde.js
vendored
29
dist/dde.js
vendored
File diff suppressed because one or more lines are too long
8
dist/esm-with-observables.js
vendored
8
dist/esm-with-observables.js
vendored
File diff suppressed because one or more lines are too long
2
dist/esm.js
vendored
2
dist/esm.js
vendored
File diff suppressed because one or more lines are too long
@ -121,7 +121,7 @@ document.head.append(
|
|||||||
const interval= 5 * 1000;
|
const interval= 5 * 1000;
|
||||||
setTimeout(clearInterval, 10*interval,
|
setTimeout(clearInterval, 10*interval,
|
||||||
setInterval(()=> count(count()+1), interval));
|
setInterval(()=> count(count()+1), interval));
|
||||||
</code></div><script>Flems(document.getElementById("code-example-1-ehcq40v0h5k"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst count= O(0);\\n\\nimport { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.body.append(\\n\\tel(\\\"p\\\", O(()=> \\\"Currently: \\\"+count())),\\n\\tel(\\\"p\\\", { classList: { red: O(()=> count()%2) }, dataset: { count }, textContent: \\\"Attributes example\\\" })\\n);\\ndocument.head.append(\\n\\tel(\\\"style\\\", \\\".red { color: red; }\\\")\\n);\\n\\nconst interval= 5 * 1000;\\nsetTimeout(clearInterval, 10*interval,\\n\\tsetInterval(()=> count(count()+1), interval));\\n\"}],\"toolbar\":false}"));</script><p>To derived attribute based on value of observable variable just use the observable as a value of the attribute (<code>assign(element, { attribute: O('value') })</code>). <code>assign</code>/<code>el</code> provides ways to glue reactive attributes/classes more granularly into the DOM. Just use dedicated build-in attributes <code>dataset</code>, <code>ariaset</code> and <code>classList</code>.</p><p>For computation, you can use the derived observable (see above) like <code>assign(element, { textContent: O(()=> 'Hello '+WorldObservable()) })</code>.</p><p>To represent part of the template filled dynamically based on the observable value use <code>O.el(observable, DOMgenerator)</code>. This was already used in the todo example above or see:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-2-8r8qappf8mo" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { O } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
</code></div><script>Flems(document.getElementById("code-example-1-ehcq40v0h5k"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst count= O(0);\\n\\nimport { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.body.append(\\n\\tel(\\\"p\\\", O(()=> \\\"Currently: \\\"+count())),\\n\\tel(\\\"p\\\", { classList: { red: O(()=> count()%2) }, dataset: { count }, textContent: \\\"Attributes example\\\" })\\n);\\ndocument.head.append(\\n\\tel(\\\"style\\\", \\\".red { color: red; }\\\")\\n);\\n\\nconst interval= 5 * 1000;\\nsetTimeout(clearInterval, 10*interval,\\n\\tsetInterval(()=> count(count()+1), interval));\\n\"}],\"toolbar\":false}"));</script><p>To derived attribute based on value of observable variable just use the observable as a value of the attribute (<code>assign(element, { attribute: O('value') })</code>). <code>assign</code>/<code>el</code> provides ways to glue reactive attributes/classes more granularly into the DOM. Just use dedicated build-in attributes <code>dataset</code>, <code>ariaset</code> and <code>classList</code>.</p><p>For computation, you can use the “derived observable” (see above) like <code>assign(element, { textContent: O(()=> 'Hello '+WorldObservable()) })</code>. This is read-only observable its value is computed based on given function and updated when any observable used in the function changes.</p><p>To represent part of the template filled dynamically based on the observable value use <code>O.el(observable, DOMgenerator)</code>. This was already used in the todo example above or see:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-2-8r8qappf8mo" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { O } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
|
||||||
const count= O(0, {
|
const count= O(0, {
|
||||||
add(){ this.value= this.value + Math.round(Math.random()*10); }
|
add(){ this.value= this.value + Math.round(Math.random()*10); }
|
||||||
});
|
});
|
||||||
@ -147,4 +147,4 @@ setTimeout(clearInterval, 10*interval, setInterval(function(){
|
|||||||
O.action(count, "add");
|
O.action(count, "add");
|
||||||
O.action(numbers, "push", count());
|
O.action(numbers, "push", count());
|
||||||
}, interval));
|
}, interval));
|
||||||
</code></div><script>Flems(document.getElementById("code-example-2-8r8qappf8mo"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst count= O(0, {\\n\\tadd(){ this.value= this.value + Math.round(Math.random()*10); }\\n});\\nconst numbers= O([ count() ], {\\n\\tpush(next){ this.value.push(next); }\\n});\\n\\nimport { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.body.append(\\n\\tO.el(count, count=> count%2\\n\\t\\t? el(\\\"p\\\", \\\"Last number is odd.\\\")\\n\\t\\t: el()\\n\\t),\\n\\tel(\\\"p\\\", \\\"Lucky numbers:\\\"),\\n\\tel(\\\"ul\\\").append(\\n\\t\\tO.el(numbers, numbers=> numbers.toReversed()\\n\\t\\t\\t.map(n=> el(\\\"li\\\", n)))\\n\\t)\\n);\\n\\nconst interval= 5*1000;\\nsetTimeout(clearInterval, 10*interval, setInterval(function(){\\n\\tO.action(count, \\\"add\\\");\\n\\tO.action(numbers, \\\"push\\\", count());\\n}, interval));\\n\"}],\"toolbar\":false}"));</script><div class="notice"><!--<dde:mark type="component" name="mnemonic" host="parentElement" ssr/>--><h3 id="h-mnemonic"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-mnemonic" tabindex="-1">#</a> Mnemonic</h3><ul><li><code>O(<value>)</code> — observable: reactive value</li><li><code>O(()=> <computation>)</code> — observable: reactive value dependent on calculation using other observables</li><li><code>O.on(<observable>, <listener>[, <options>])</code> — listen to the observable value changes</li><li><code>O.clear(...<observables>)</code> — off and clear observables</li><li><code>O(<value>, <actions>)</code> — observable: pattern to create complex reactive objects/arrays</li><li><code>O.action(<observable>, <action-name>, ...<action-arguments>)</code> — invoke an action for given observable</li><li><code>O.el(<observable>, <function-returning-dom>)</code> — render partial dom structure (template) based on the current observable value</li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="p03-events" title="Using not only events in UI declaratively."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Events and Addons (previous)</a><a rel="next" href="p05-scopes" title="Organizing UI into components"><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Scopes and components</a></div></main></body></html>
|
</code></div><script>Flems(document.getElementById("code-example-2-8r8qappf8mo"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst count= O(0, {\\n\\tadd(){ this.value= this.value + Math.round(Math.random()*10); }\\n});\\nconst numbers= O([ count() ], {\\n\\tpush(next){ this.value.push(next); }\\n});\\n\\nimport { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.body.append(\\n\\tO.el(count, count=> count%2\\n\\t\\t? el(\\\"p\\\", \\\"Last number is odd.\\\")\\n\\t\\t: el()\\n\\t),\\n\\tel(\\\"p\\\", \\\"Lucky numbers:\\\"),\\n\\tel(\\\"ul\\\").append(\\n\\t\\tO.el(numbers, numbers=> numbers.toReversed()\\n\\t\\t\\t.map(n=> el(\\\"li\\\", n)))\\n\\t)\\n);\\n\\nconst interval= 5*1000;\\nsetTimeout(clearInterval, 10*interval, setInterval(function(){\\n\\tO.action(count, \\\"add\\\");\\n\\tO.action(numbers, \\\"push\\\", count());\\n}, interval));\\n\"}],\"toolbar\":false}"));</script><div class="notice"><!--<dde:mark type="component" name="mnemonic" host="parentElement" ssr/>--><h3 id="h-mnemonic"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-mnemonic" tabindex="-1">#</a> Mnemonic</h3><ul><li><code>O(<value>)</code> — observable: reactive value</li><li><code>O(()=> <computation>)</code> — read-only observable: reactive value dependent on calculation using other observables</li><li><code>O.on(<observable>, <listener>[, <options>])</code> — listen to the observable value changes</li><li><code>O.clear(...<observables>)</code> — off and clear observables</li><li><code>O(<value>, <actions>)</code> — observable: pattern to create complex reactive objects/arrays</li><li><code>O.action(<observable>, <action-name>, ...<action-arguments>)</code> — invoke an action for given observable</li><li><code>O.el(<observable>, <function-returning-dom>)</code> — render partial dom structure (template) based on the current observable value</li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="p03-events" title="Using not only events in UI declaratively."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Events and Addons (previous)</a><a rel="next" href="p05-scopes" title="Organizing UI into components"><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Scopes and components</a></div></main></body></html>
|
@ -12,26 +12,30 @@ export class CustomHTMLTestElement extends HTMLElement{
|
|||||||
}
|
}
|
||||||
connectedCallback(){
|
connectedCallback(){
|
||||||
if(!this.hasAttribute("pre-name")) this.setAttribute("pre-name", "default");
|
if(!this.hasAttribute("pre-name")) this.setAttribute("pre-name", "default");
|
||||||
console.log(observedAttributes(this));
|
|
||||||
this.attachShadow({ mode: "open" }).append(
|
this.attachShadow({ mode: "open" }).append(
|
||||||
customElementRender(this, this.render)
|
customElementRender(this, this.render, this.attributes)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render(test){
|
attributes(element){
|
||||||
|
const observed= O.observedAttributes(element);
|
||||||
|
return Object.assign({ test: element.test }, observed);
|
||||||
|
}
|
||||||
|
render({ name, preName, test }){
|
||||||
console.log(scope.state);
|
console.log(scope.state);
|
||||||
scope.host(
|
scope.host(
|
||||||
on.connected(()=> console.log(CustomHTMLTestElement)),
|
on.connected(()=> console.log(CustomHTMLTestElement)),
|
||||||
on.attributeChanged(e=> console.log(e)),
|
on.attributeChanged(e=> console.log(e)),
|
||||||
on.disconnected(()=> console.log(CustomHTMLTestElement))
|
on.disconnected(()=> console.log(CustomHTMLTestElement))
|
||||||
);
|
);
|
||||||
const name= O.attribute("name");
|
const text= text=> el().append(
|
||||||
const preName= O.attribute("pre-name");
|
el("#text", text),
|
||||||
|
" | "
|
||||||
console.log({ name, test, preName});
|
);
|
||||||
return el("p").append(
|
return el("p").append(
|
||||||
el("#text", name),
|
text(test),
|
||||||
el("#text", preName),
|
text(name),
|
||||||
|
text(preName),
|
||||||
el("button", { type: "button", textContent: "pre-name", onclick: ()=> preName("Ahoj") })
|
el("button", { type: "button", textContent: "pre-name", onclick: ()=> preName("Ahoj") })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "deka-dom-el",
|
"name": "deka-dom-el",
|
||||||
"version": "0.7.5",
|
"version": "0.7.6",
|
||||||
"description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.",
|
"description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.",
|
||||||
"author": "Jan Andrle <andrle.jan@centrum.cz>",
|
"author": "Jan Andrle <andrle.jan@centrum.cz>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -46,6 +46,7 @@
|
|||||||
"browser": true,
|
"browser": true,
|
||||||
"undef": "true",
|
"undef": "true",
|
||||||
"latedef": "true",
|
"latedef": "true",
|
||||||
|
"-W014": true,
|
||||||
"maxparams": 5,
|
"maxparams": 5,
|
||||||
"maxdepth": 3,
|
"maxdepth": 3,
|
||||||
"maxcomplexity": 14,
|
"maxcomplexity": 14,
|
||||||
@ -58,7 +59,7 @@
|
|||||||
"size-limit": [
|
"size-limit": [
|
||||||
{
|
{
|
||||||
"path": "./index.js",
|
"path": "./index.js",
|
||||||
"limit": "9.75 kB",
|
"limit": "9.85 kB",
|
||||||
"gzip": false,
|
"gzip": false,
|
||||||
"brotli": false
|
"brotli": false
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ export function customElementRender(custom_element, render, props= observedAttri
|
|||||||
host: (...c)=> c.length ? c.forEach(c=> c(custom_element)) : custom_element,
|
host: (...c)=> c.length ? c.forEach(c=> c(custom_element)) : custom_element,
|
||||||
custom_element
|
custom_element
|
||||||
});
|
});
|
||||||
if(typeof props==="function") props= props(custom_element);
|
if(typeof props==="function") props= props.call(custom_element, custom_element);
|
||||||
const out= render.call(custom_element, props);
|
const out= render.call(custom_element, props);
|
||||||
scope.pop();
|
scope.pop();
|
||||||
return out;
|
return out;
|
||||||
@ -33,20 +33,7 @@ function wrapMethod(obj, method, apply){
|
|||||||
obj[method]= new Proxy(obj[method] || (()=> {}), { apply });
|
obj[method]= new Proxy(obj[method] || (()=> {}), { apply });
|
||||||
}
|
}
|
||||||
|
|
||||||
function observedAttribute(instance, name){
|
import { observedAttributes as oA } from "./helpers.js";
|
||||||
const out= (...args)=> !args.length
|
|
||||||
? instance.getAttribute(name)
|
|
||||||
: instance.setAttribute(name, ...args);
|
|
||||||
out.attribute= name;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
export function observedAttributes(instance){
|
export function observedAttributes(instance){
|
||||||
const { observedAttributes= [] }= instance.constructor;
|
return oA(instance, (i, n)=> i.getAttribute(n));
|
||||||
return observedAttributes
|
|
||||||
.reduce(function(out, name){
|
|
||||||
Reflect.set(out, kebabToCamel(name), observedAttribute(instance, name));
|
|
||||||
return out;
|
|
||||||
}, {});
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
function kebabToCamel(name){ return name.replace(/-./g, x=> x[1].toUpperCase()); }
|
|
||||||
|
@ -84,6 +84,7 @@ on.attributeChanged= function(listener, options){
|
|||||||
});
|
});
|
||||||
const c= onAbort(options.signal, ()=> observer.disconnect());
|
const c= onAbort(options.signal, ()=> observer.disconnect());
|
||||||
if(c) observer.observe(element, { attributes: true });
|
if(c) observer.observe(element, { attributes: true });
|
||||||
|
//TODO: clean up when element disconnected
|
||||||
return element;
|
return element;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -15,3 +15,12 @@ export function onAbort(signal, listener){
|
|||||||
signal.removeEventListener("abort", listener);
|
signal.removeEventListener("abort", listener);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
export function observedAttributes(instance, observedAttribute){
|
||||||
|
const { observedAttributes= [] }= instance.constructor;
|
||||||
|
return observedAttributes
|
||||||
|
.reduce(function(out, name){
|
||||||
|
Reflect.set(out, kebabToCamel(name), observedAttribute(instance, name));
|
||||||
|
return out;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
function kebabToCamel(name){ return name.replace(/-./g, x=> x[1].toUpperCase()); }
|
||||||
|
@ -9,7 +9,7 @@ const stack_watch= [];
|
|||||||
/**
|
/**
|
||||||
* ### `WeakMap<function, Set<ddeObservable<any, any>>>`
|
* ### `WeakMap<function, Set<ddeObservable<any, any>>>`
|
||||||
* The `Set` is in the form of `[ source, ...depended observables (DSs) ]`.
|
* The `Set` is in the form of `[ source, ...depended observables (DSs) ]`.
|
||||||
* When the DS is cleaned (`S.clear`) it is removed from DSs,
|
* When the DS is cleaned (`O.clear`) it is removed from DSs,
|
||||||
* if remains only one (`source`) it is cleared too.
|
* if remains only one (`source`) it is cleared too.
|
||||||
* ### `WeakMap<object, function>`
|
* ### `WeakMap<object, function>`
|
||||||
* This is used for revesed deps, the `function` is also key for `deps`.
|
* This is used for revesed deps, the `function` is also key for `deps`.
|
||||||
@ -113,41 +113,38 @@ observable.el= function(o, map){
|
|||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
import { on } from "./events.js";
|
import { on } from "./events.js";
|
||||||
|
import { observedAttributes } from "./helpers.js";
|
||||||
|
function observedAttribute(instance, name){
|
||||||
|
const out= (...args)=> !args.length
|
||||||
|
? instance.getAttribute(name)
|
||||||
|
: instance.setAttribute(name, ...args);
|
||||||
|
out.attribute= name;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
const key_attributes= "__dde_attributes";
|
const key_attributes= "__dde_attributes";
|
||||||
observable.attribute= function(name, initial= null){
|
observable.observedAttributes= function(element){
|
||||||
//TODO host=element & reuse existing
|
const attrs= observedAttributes(element, observedAttribute);
|
||||||
const out= observable(initial);
|
const store= element[key_attributes]= {};
|
||||||
let element;
|
const actions= {
|
||||||
scope.host(el=> {
|
_set(value){ this.value= value; },
|
||||||
element= el;
|
};
|
||||||
if(elementAttribute(element, "has", name)) out(elementAttribute(element, "get", name));
|
Object.keys(attrs).forEach(name=> {
|
||||||
else if(initial!==null) elementAttribute(element, "set", name, initial);
|
const attr= attrs[name]= toObservable(attrs[name], attrs[name](), actions);
|
||||||
|
store[attr.attribute]= attr;
|
||||||
if(el[key_attributes]){
|
});
|
||||||
el[key_attributes][name]= out;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
element[key_attributes]= { [name]: out };
|
|
||||||
on.attributeChanged(function attributeChangeToObservable({ detail }){
|
on.attributeChanged(function attributeChangeToObservable({ detail }){
|
||||||
/*! This maps attributes to observables (`S.attribute`).
|
/*! This maps attributes to observables (`O.observedAttributes`).
|
||||||
* Investigate `__dde_attributes` key of the element.*/
|
* Investigate `__dde_attributes` key of the element.*/
|
||||||
const [ name, value ]= detail;
|
const [ name, value ]= detail;
|
||||||
const curr= element[key_attributes][name];
|
const curr= element[key_attributes][name];
|
||||||
if(curr) return curr(value);
|
if(curr) return observable.action(curr, "_set", value);
|
||||||
})(element);
|
})(element);
|
||||||
on.disconnected(function(){
|
on.disconnected(function(){
|
||||||
/*! This removes all observables mapped to attributes (`S.attribute`).
|
/*! This removes all observables mapped to attributes (`O.observedAttributes`).
|
||||||
* Investigate `__dde_attributes` key of the element.*/
|
* Investigate `__dde_attributes` key of the element.*/
|
||||||
observable.clear(...Object.values(element[key_attributes]));
|
observable.clear(...Object.values(element[key_attributes]));
|
||||||
})(element);
|
})(element);
|
||||||
});
|
return attrs;
|
||||||
return new Proxy(out, {
|
|
||||||
apply(target, _, args){
|
|
||||||
if(!args.length) return target();
|
|
||||||
const value= args[0];
|
|
||||||
return elementAttribute(element, "set", name, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
import { typeOf } from './helpers.js';
|
import { typeOf } from './helpers.js';
|
||||||
@ -169,7 +166,7 @@ function removeObservablesFromElements(o, listener, ...notes){
|
|||||||
element[key_reactive]= [];
|
element[key_reactive]= [];
|
||||||
on.disconnected(()=>
|
on.disconnected(()=>
|
||||||
/*!
|
/*!
|
||||||
* Clears all Observables listeners added in the current scope/host (`S.el`, `assign`, …?).
|
* Clears all Observables listeners added in the current scope/host (`O.el`, `assign`, …?).
|
||||||
* You can investigate the `__dde_reactive` key of the element.
|
* You can investigate the `__dde_reactive` key of the element.
|
||||||
* */
|
* */
|
||||||
element[key_reactive].forEach(([ [ o, listener ] ])=>
|
element[key_reactive].forEach(([ [ o, listener ] ])=>
|
||||||
|
Loading…
Reference in New Issue
Block a user