1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2024-11-24 01:29:36 +01:00

💥 rename signals to observables

This commit is contained in:
Jan Andrle 2023-11-24 20:41:04 +01:00
parent fc4037f3eb
commit 58b73dcbc7
Signed by: jaandrle
GPG Key ID: B3A25AED155AFFAB
44 changed files with 372 additions and 333 deletions

View File

@ -35,8 +35,8 @@ function component({ textContent, className }){
} }
``` ```
# Deka DOM Elements # Deka DOM Elements
Creating reactive elements, components and Web components using [IDL](https://developer.mozilla.org/en-US/docs/Glossary/IDL)/JavaScript DOM API and signals Creating reactive elements, components and Web components using [IDL](https://developer.mozilla.org/en-US/docs/Glossary/IDL)/JavaScript DOM API and signals/observables
([Signals — whats going on behind the scenes | by Ryan Hoffnan | ITNEXT](https://itnext.io/signals-whats-going-on-behind-the-scenes-ec858589ea63) or [The Evolution of Signals in JavaScript - DEV Community](https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob)). ([Signals — whats going on behind the scenes | by Ryan Hoffnan | ITNEXT](https://itnext.io/signals-whats-going-on-behind-the-scenes-ec858589ea63), [The Evolution of Signals in JavaScript - DEV Community](https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob) or [Observer pattern - Wikipedia](https://en.wikipedia.org/wiki/Observer_pattern)).
## Inspiration and suggested alternatives ## Inspiration and suggested alternatives
- my previous library (mostly used internaly): [jaandrle/dollar_dom_component: Functional DOM components without JSX and virtual DOM.](https://github.com/jaandrle/dollar_dom_component) - my previous library (mostly used internaly): [jaandrle/dollar_dom_component: Functional DOM components without JSX and virtual DOM.](https://github.com/jaandrle/dollar_dom_component)
@ -59,7 +59,7 @@ hopefully, help in integrating the library into existing projects.
To balance these requirements, numerous compromises have been made. To summarize: To balance these requirements, numerous compromises have been made. To summarize:
- [ ] Library size: 1015kB minified (the original goal was a maximum of 10kB) - [ ] Library size: 1015kB minified (the original goal was a maximum of 10kB)
- [x] Optional use of *signals* with the ability to register *your own signals implementation* - [x] Optional use of *observables* with the ability to register *your own signals/observables implementation*
- [x] *No build step required* - [x] *No build step required*
- [x] Preference for a *declarative/functional* approach - [x] Preference for a *declarative/functional* approach
- [x] Focus on zero/minimal dependencies - [x] Focus on zero/minimal dependencies

View File

@ -1,6 +1,6 @@
#!/usr/bin/env -S npx nodejsscript #!/usr/bin/env -S npx nodejsscript
import { bundle as bundleDTS } from "dts-bundler"; import { bundle as bundleDTS } from "dts-bundler";
const files= [ "index", "index-with-signals" ]; const files= [ "index", "index-with-observables" ];
const filesOut= (file, mark= "esm")=> "dist/"+file.replace("index", mark); const filesOut= (file, mark= "esm")=> "dist/"+file.replace("index", mark);
const css= echo.css` const css= echo.css`
.info{ color: gray; } .info{ color: gray; }

27
dist/dde-with-observables.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/dde.js vendored

File diff suppressed because one or more lines are too long

View File

@ -153,10 +153,10 @@ export const scope: {
preventDefault<T extends boolean>(prevent: T): T, preventDefault<T extends boolean>(prevent: T): T,
/** /**
* This represents reference to the current host element `scope.host()`. * This represents reference to the current host element `scope.host()`.
* It can be also used to register Addon (function to be called when component is initized) * It can be also used to register Addon(s) (functions to be called when component is initized)
* `scope.host(on.connected(console.log))`. * `scope.host(on.connected(console.log))`.
* */ * */
host: ddeElementAddon<any>, host: (...addons: ddeElementAddon<any>[])=> HTMLElement,
state: Scope[], state: Scope[],
/** Adds new child scope. All attributes are inherited by default. */ /** Adds new child scope. All attributes are inherited by default. */
@ -444,19 +444,19 @@ declare global{
interface ddeSVGUseElement extends SVGUseElement{ append: ddeAppend<ddeSVGUseElement>; } interface ddeSVGUseElement extends SVGUseElement{ append: ddeAppend<ddeSVGUseElement>; }
interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend<ddeSVGViewElement>; } interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend<ddeSVGViewElement>; }
} }
export type Signal<V, A>= (set?: V)=> V & A; export type Observable<V, A>= (set?: V)=> V & A;
type Action<V>= (this: { value: V }, ...a: any[])=> typeof S._ | void; type Action<V>= (this: { value: V }, ...a: any[])=> typeof observable._ | void;
type SymbolOnclear= Symbol; type SymbolOnclear= Symbol;
type SymbolSignal= Symbol; type SymbolObservable= Symbol;
type Actions<V>= Record<string, Action<V>>; type Actions<V>= Record<string, Action<V>>;
interface S { interface observable{
_: Symbol _: Symbol
/** /**
* Simple example: * Simple example:
* ```js * ```js
* const hello= S("Hello Signal"); * const hello= S("Hello Observable");
* ``` * ```
* simple todo signal: * simple todo observable:
* ```js * ```js
* const todos= S([], { * const todos= S([], {
* add(v){ this.value.push(S(v)); }, * add(v){ this.value.push(S(v)); },
@ -464,46 +464,47 @@ interface S {
* [S.symbols.onclear](){ S.clear(...this.value); }, * [S.symbols.onclear](){ S.clear(...this.value); },
* }); * });
* ``` * ```
* computed signal: * computed observable:
* ```js * ```js
* const name= S("Jan"); * const name= S("Jan");
* const surname= S("Andrle"); * const surname= S("Andrle");
* const fullname= S(()=> name()+" "+surname()); * const fullname= S(()=> name()+" "+surname());
* ``` * ```
* @param value Initial signal value. Or function computing value from other signals. * @param value Initial observable value. Or function computing value from other observables.
* @param actions Use to define actions on the signal. Such as add item to the array. * @param actions Use to define actions on the observable. Such as add item to the array.
* There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared * There is also a reserved function `S.symbol.onclear` which is called when the observable is cleared
* by `S.clear`. * by `S.clear`.
* */ * */
<V, A extends Actions<V>>(value: V, actions?: A): Signal<V, A>; <V, A extends Actions<V>>(value: V, actions?: A): Observable<V, A>;
/** /**
* Computations signal. This creates a signal which is computed from other signals. * Computations observable. This creates a observable which is computed from other observables.
* */ * */
<V>(computation: ()=> V): Signal<V, {}> <V>(computation: ()=> V): Observable<V, {}>
action<S extends Signal<any, Actions<any>>, A extends (S extends Signal<any, infer A> ? A : never), N extends keyof A>( action<S extends Observable<any, Actions<any>>, A extends (S extends Observable<any, infer A> ? A : never), N extends keyof A>(
signal: S, observable: S,
name: N, name: N,
...params: A[N] extends (...args: infer P)=> any ? P : never ...params: A[N] extends (...args: infer P)=> any ? P : never
): void; ): void;
clear(...signals: Signal<any, any>[]): void; clear(...observables: Observable<any, any>[]): void;
on<T>(signal: Signal<T, any>, onchange: (a: T)=> void, options?: AddEventListenerOptions): void; on<T>(observable: Observable<T, any>, onchange: (a: T)=> void, options?: AddEventListenerOptions): void;
symbols: { symbols: {
signal: SymbolSignal; observable: SymbolObservable;
onclear: SymbolOnclear; onclear: SymbolOnclear;
} }
/** /**
* Reactive element, which is rendered based on the given signal. * Reactive element, which is rendered based on the given observable.
* ```js * ```js
* S.el(signal, value=> value ? el("b", "True") : el("i", "False")); * S.el(observable, value=> value ? el("b", "True") : el("i", "False"));
* S.el(listS, list=> list.map(li=> el("li", li))); * S.el(listS, list=> list.map(li=> el("li", li)));
* ``` * ```
* */ * */
el<S extends any>(signal: Signal<S, any>, el: (v: S)=> Element | Element[]): DocumentFragment; el<S extends any>(observable: Observable<S, any>, el: (v: S)=> Element | Element[]): DocumentFragment;
attribute(name: string, initial?: string): Signal<string, {}>; attribute(name: string, initial?: string): Observable<string, {}>;
} }
export const S: S; export const observable: observable;
export const O: observable;
declare global { declare global {
type ddeSignal<T, A= {}>= Signal<T, A>; type ddeObservable<T, A= {}>= Observable<T, A>;
type ddeActions<V>= Actions<V> type ddeActions<V>= Actions<V>
} }

4
dist/esm-with-observables.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/esm.d.ts vendored
View File

@ -153,10 +153,10 @@ export const scope: {
preventDefault<T extends boolean>(prevent: T): T, preventDefault<T extends boolean>(prevent: T): T,
/** /**
* This represents reference to the current host element `scope.host()`. * This represents reference to the current host element `scope.host()`.
* It can be also used to register Addon (function to be called when component is initized) * It can be also used to register Addon(s) (functions to be called when component is initized)
* `scope.host(on.connected(console.log))`. * `scope.host(on.connected(console.log))`.
* */ * */
host: ddeElementAddon<any>, host: (...addons: ddeElementAddon<any>[])=> HTMLElement,
state: Scope[], state: Scope[],
/** Adds new child scope. All attributes are inherited by default. */ /** Adds new child scope. All attributes are inherited by default. */

2
dist/esm.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Introducing a&nbsp;library."><title>`deka-dom-el` — Introduction</title><!--<dde:mark type="component" name="metaAuthor" host="this" ssr/>--><meta name="author" content="Jan Andrle"><link type="text/plain" rel="author" href="https://jaandrle.github.io/humans.txt"><meta name="generator" content="deka-dom-el"><!--<dde:mark type="component" name="metaTwitter" host="this" ssr/>--><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="twitter:title" content="deka-dom-el"><meta name="twitter:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="twitter:creator" content="@jaandrle"><!--<dde:mark type="component" name="metaFacebook" host="this" ssr/>--><meta name="og:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="og:title" content="deka-dom-el"><meta name="og:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="og:creator" content="@jaandrle"><script src="https://flems.io/flems.html" type="text/javascript" charset="utf-8"></script><link rel="stylesheet" href="global.css"></head><body><!--<dde:mark type="component" name="page" host="this" ssr/>--><!--<dde:mark type="component" name="simplePage" host="this" ssr/>--><!--<dde:mark type="component" name="header" host="this" ssr/>--><header><h1>`deka-dom-el` — Introduction</h1><p>Introducing a&nbsp;library.</p></header><nav><a href="https://github.com/jaandrle/deka-dom-el"><svg class="icon" viewBox="0 0 32 32"><!--<dde:mark type="component" name="iconGitHub" host="parentElement" ssr/>--><path d="M 16,0.395c -8.836,0 -16,7.163 -16,16c 0,7.069 4.585,13.067 10.942,15.182c 0.8,0.148 1.094,-0.347 1.094,-0.77c 0,-0.381 -0.015,-1.642 -0.022,-2.979c -4.452,0.968 -5.391,-1.888 -5.391,-1.888c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341c -1.452,-0.993 0.11,-0.973 0.11,-0.973c 1.606,0.113 2.452,1.649 2.452,1.649c 1.427,2.446 3.743,1.739 4.656,1.33c 0.143,-1.034 0.558,-1.74 1.016,-2.14c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907c 0,-1.747 0.625,-3.174 1.649,-4.295c -0.166,-0.403 -0.714,-2.03 0.155,-4.234c 0,0 1.344,-0.43 4.401,1.64c 1.276,-0.355 2.645,-0.532 4.005,-0.539c 1.359,0.006 2.729,0.184 4.008,0.539c 3.054,-2.07 4.395,-1.64 4.395,-1.64c 0.871,2.204 0.323,3.831 0.157,4.234c 1.026,1.12 1.647,2.548 1.647,4.295c 0,6.145 -3.743,7.498 -7.306,7.895c 0.574,0.497 1.085,1.47 1.085,2.963c 0,2.141 -0.019,3.864 -0.019,4.391c 0,0.426 0.288,0.925 1.099,0.768c 6.354,-2.118 10.933,-8.113 10.933,-15.18c 0,-8.837 -7.164,-16 -16,-16Z"></path></svg>GitHub</a><a href="./" title="Introducing a&nbsp;library." class="current">1. Introduction</a><a href="p02-elements" title="Basic concepts of elements modifications and creations.">2. Elements</a><a href="p03-events" title="Using not only events in UI declaratively.">3. Events and Addons</a><a href="p04-signals" title="Handling reactivity in UI via signals.">4. Signals and reactivity</a></nav><main><p>The library tries to provide pure JavaScript tool(s) to create reactive interfaces.</p><p>We start with creating and modifying a&nbsp;static elements and end up with UI templates. <i>From <code>document.createElement</code> to <code>el</code>.</i> Then we go through the native events system and the way to include it declaratively in UI templates. <i>From <code>element.addEventListener</code> to <code>on</code>.</i></p><p>Next step is providing interactivity not only for our UI templates. We introduce signals (<code>S</code>) and how them incorporate to UI templates.</p><p>Now we will clarify how the signals are incorporated into our templates with regard to application performance. This is not the only reason the library uses <code>scope</code>s. We will look at how they work in components represented in JavaScript by functions.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-exquzbx4inc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js"; <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Introducing a&nbsp;library."><title>`deka-dom-el` — Introduction</title><!--<dde:mark type="component" name="metaAuthor" host="this" ssr/>--><meta name="author" content="Jan Andrle"><link type="text/plain" rel="author" href="https://jaandrle.github.io/humans.txt"><meta name="generator" content="deka-dom-el"><!--<dde:mark type="component" name="metaTwitter" host="this" ssr/>--><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="twitter:title" content="deka-dom-el"><meta name="twitter:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="twitter:creator" content="@jaandrle"><!--<dde:mark type="component" name="metaFacebook" host="this" ssr/>--><meta name="og:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="og:title" content="deka-dom-el"><meta name="og:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="og:creator" content="@jaandrle"><script src="https://flems.io/flems.html" type="text/javascript" charset="utf-8"></script><link rel="stylesheet" href="global.css"></head><body><!--<dde:mark type="component" name="page" host="this" ssr/>--><!--<dde:mark type="component" name="simplePage" host="this" ssr/>--><!--<dde:mark type="component" name="header" host="this" ssr/>--><header><h1>`deka-dom-el` — Introduction</h1><p>Introducing a&nbsp;library.</p></header><nav><a href="https://github.com/jaandrle/deka-dom-el"><svg class="icon" viewBox="0 0 32 32"><!--<dde:mark type="component" name="iconGitHub" host="parentElement" ssr/>--><path d="M 16,0.395c -8.836,0 -16,7.163 -16,16c 0,7.069 4.585,13.067 10.942,15.182c 0.8,0.148 1.094,-0.347 1.094,-0.77c 0,-0.381 -0.015,-1.642 -0.022,-2.979c -4.452,0.968 -5.391,-1.888 -5.391,-1.888c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341c -1.452,-0.993 0.11,-0.973 0.11,-0.973c 1.606,0.113 2.452,1.649 2.452,1.649c 1.427,2.446 3.743,1.739 4.656,1.33c 0.143,-1.034 0.558,-1.74 1.016,-2.14c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907c 0,-1.747 0.625,-3.174 1.649,-4.295c -0.166,-0.403 -0.714,-2.03 0.155,-4.234c 0,0 1.344,-0.43 4.401,1.64c 1.276,-0.355 2.645,-0.532 4.005,-0.539c 1.359,0.006 2.729,0.184 4.008,0.539c 3.054,-2.07 4.395,-1.64 4.395,-1.64c 0.871,2.204 0.323,3.831 0.157,4.234c 1.026,1.12 1.647,2.548 1.647,4.295c 0,6.145 -3.743,7.498 -7.306,7.895c 0.574,0.497 1.085,1.47 1.085,2.963c 0,2.141 -0.019,3.864 -0.019,4.391c 0,0.426 0.288,0.925 1.099,0.768c 6.354,-2.118 10.933,-8.113 10.933,-15.18c 0,-8.837 -7.164,-16 -16,-16Z"></path></svg>GitHub</a><a href="./" title="Introducing a&nbsp;library." class="current">1. Introduction</a><a href="p02-elements" title="Basic concepts of elements modifications and creations.">2. Elements</a><a href="p03-events" title="Using not only events in UI declaratively.">3. Events and Addons</a><a href="p04-observables" title="Handling reactivity in UI via observables.">4. Observables and reactivity</a></nav><main><p>The library tries to provide pure JavaScript tool(s) to create reactive interfaces.</p><p>We start with creating and modifying a&nbsp;static elements and end up with UI templates. <i>From <code>document.createElement</code> to <code>el</code>.</i> Then we go through the native events system and the way to include it declaratively in UI templates. <i>From <code>element.addEventListener</code> to <code>on</code>.</i></p><p>Next step is providing interactivity not only for our UI templates. We introduce observables (<code>O</code>) and how them incorporate to UI templates.</p><p>Now we will clarify how the observables are incorporated into our templates with regard to application performance. This is not the only reason the library uses <code>scope</code>s. We will look at how they work in components represented in JavaScript by functions.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-exquzbx4inc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
import { S } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js"; import { S } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
const clicks= S(0); const clicks= S(0);
document.body.append( document.body.append(
el().append( el().append(
@ -13,4 +13,4 @@ document.body.append(
}) })
) )
); );
</code></div><script>Flems(document.getElementById("code-example-1-exquzbx4inc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nimport { S } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst clicks= S(0);\\ndocument.body.append(\\n\\tel().append(\\n\\t\\tel(\\\"p\\\", S(()=>\\n\\t\\t\\t\\\"Hello World \\\"+\\\"🎉\\\".repeat(clicks())\\n\\t\\t)),\\n\\t\\tel(\\\"button\\\", {\\n\\t\\t\\ttype: \\\"button\\\",\\n\\t\\t\\tonclick: ()=> clicks(clicks()+1),\\n\\t\\t\\ttextContent: \\\"Fire\\\"\\n\\t\\t})\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><!--<dde:mark type="component" name="pageLink" host="this" ssr/>--><a rel="next" href="p02-elements" title="Basic concepts of elements modifications and creations."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Elements</a></div></main></body></html> </code></div><script>Flems(document.getElementById("code-example-1-exquzbx4inc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nimport { S } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst clicks= S(0);\\ndocument.body.append(\\n\\tel().append(\\n\\t\\tel(\\\"p\\\", S(()=>\\n\\t\\t\\t\\\"Hello World \\\"+\\\"🎉\\\".repeat(clicks())\\n\\t\\t)),\\n\\t\\tel(\\\"button\\\", {\\n\\t\\t\\ttype: \\\"button\\\",\\n\\t\\t\\tonclick: ()=> clicks(clicks()+1),\\n\\t\\t\\ttextContent: \\\"Fire\\\"\\n\\t\\t})\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><!--<dde:mark type="component" name="pageLink" host="this" ssr/>--><a rel="next" href="p02-elements" title="Basic concepts of elements modifications and creations."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Elements</a></div></main></body></html>

View File

@ -1,4 +1,4 @@
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Basic concepts of elements modifications and creations."><title>`deka-dom-el` — Elements</title><!--<dde:mark type="component" name="metaAuthor" host="this" ssr/>--><meta name="author" content="Jan Andrle"><link type="text/plain" rel="author" href="https://jaandrle.github.io/humans.txt"><meta name="generator" content="deka-dom-el"><!--<dde:mark type="component" name="metaTwitter" host="this" ssr/>--><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="twitter:title" content="deka-dom-el"><meta name="twitter:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="twitter:creator" content="@jaandrle"><!--<dde:mark type="component" name="metaFacebook" host="this" ssr/>--><meta name="og:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="og:title" content="deka-dom-el"><meta name="og:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="og:creator" content="@jaandrle"><script src="https://cdn.jsdelivr.net/npm/shiki" defer=""></script><script type="module" src="code.js.js"></script><script src="https://flems.io/flems.html" type="text/javascript" charset="utf-8"></script><link rel="stylesheet" href="global.css"></head><body><!--<dde:mark type="component" name="page" host="this" ssr/>--><!--<dde:mark type="component" name="simplePage" host="this" ssr/>--><!--<dde:mark type="component" name="header" host="this" ssr/>--><header><h1>`deka-dom-el` — Elements</h1><p>Basic concepts of elements modifications and creations.</p></header><nav><a href="https://github.com/jaandrle/deka-dom-el"><svg class="icon" viewBox="0 0 32 32"><!--<dde:mark type="component" name="iconGitHub" host="parentElement" ssr/>--><path d="M 16,0.395c -8.836,0 -16,7.163 -16,16c 0,7.069 4.585,13.067 10.942,15.182c 0.8,0.148 1.094,-0.347 1.094,-0.77c 0,-0.381 -0.015,-1.642 -0.022,-2.979c -4.452,0.968 -5.391,-1.888 -5.391,-1.888c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341c -1.452,-0.993 0.11,-0.973 0.11,-0.973c 1.606,0.113 2.452,1.649 2.452,1.649c 1.427,2.446 3.743,1.739 4.656,1.33c 0.143,-1.034 0.558,-1.74 1.016,-2.14c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907c 0,-1.747 0.625,-3.174 1.649,-4.295c -0.166,-0.403 -0.714,-2.03 0.155,-4.234c 0,0 1.344,-0.43 4.401,1.64c 1.276,-0.355 2.645,-0.532 4.005,-0.539c 1.359,0.006 2.729,0.184 4.008,0.539c 3.054,-2.07 4.395,-1.64 4.395,-1.64c 0.871,2.204 0.323,3.831 0.157,4.234c 1.026,1.12 1.647,2.548 1.647,4.295c 0,6.145 -3.743,7.498 -7.306,7.895c 0.574,0.497 1.085,1.47 1.085,2.963c 0,2.141 -0.019,3.864 -0.019,4.391c 0,0.426 0.288,0.925 1.099,0.768c 6.354,-2.118 10.933,-8.113 10.933,-15.18c 0,-8.837 -7.164,-16 -16,-16Z"></path></svg>GitHub</a><a href="./" title="Introducing a&nbsp;library.">1. Introduction</a><a href="p02-elements" title="Basic concepts of elements modifications and creations." class="current">2. Elements</a><a href="p03-events" title="Using not only events in UI declaratively.">3. Events and Addons</a><a href="p04-signals" title="Handling reactivity in UI via signals.">4. Signals and reactivity</a></nav><main><h2>Native JavaScript DOM elements creations</h2><p>Lets go through all patterns we would like to use and what needs to be improved for better experience.</p><div class="code" data-js="todo"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">// when NPM <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Basic concepts of elements modifications and creations."><title>`deka-dom-el` — Elements</title><!--<dde:mark type="component" name="metaAuthor" host="this" ssr/>--><meta name="author" content="Jan Andrle"><link type="text/plain" rel="author" href="https://jaandrle.github.io/humans.txt"><meta name="generator" content="deka-dom-el"><!--<dde:mark type="component" name="metaTwitter" host="this" ssr/>--><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="twitter:title" content="deka-dom-el"><meta name="twitter:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="twitter:creator" content="@jaandrle"><!--<dde:mark type="component" name="metaFacebook" host="this" ssr/>--><meta name="og:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="og:title" content="deka-dom-el"><meta name="og:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="og:creator" content="@jaandrle"><script src="https://cdn.jsdelivr.net/npm/shiki" defer=""></script><script type="module" src="code.js.js"></script><script src="https://flems.io/flems.html" type="text/javascript" charset="utf-8"></script><link rel="stylesheet" href="global.css"></head><body><!--<dde:mark type="component" name="page" host="this" ssr/>--><!--<dde:mark type="component" name="simplePage" host="this" ssr/>--><!--<dde:mark type="component" name="header" host="this" ssr/>--><header><h1>`deka-dom-el` — Elements</h1><p>Basic concepts of elements modifications and creations.</p></header><nav><a href="https://github.com/jaandrle/deka-dom-el"><svg class="icon" viewBox="0 0 32 32"><!--<dde:mark type="component" name="iconGitHub" host="parentElement" ssr/>--><path d="M 16,0.395c -8.836,0 -16,7.163 -16,16c 0,7.069 4.585,13.067 10.942,15.182c 0.8,0.148 1.094,-0.347 1.094,-0.77c 0,-0.381 -0.015,-1.642 -0.022,-2.979c -4.452,0.968 -5.391,-1.888 -5.391,-1.888c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341c -1.452,-0.993 0.11,-0.973 0.11,-0.973c 1.606,0.113 2.452,1.649 2.452,1.649c 1.427,2.446 3.743,1.739 4.656,1.33c 0.143,-1.034 0.558,-1.74 1.016,-2.14c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907c 0,-1.747 0.625,-3.174 1.649,-4.295c -0.166,-0.403 -0.714,-2.03 0.155,-4.234c 0,0 1.344,-0.43 4.401,1.64c 1.276,-0.355 2.645,-0.532 4.005,-0.539c 1.359,0.006 2.729,0.184 4.008,0.539c 3.054,-2.07 4.395,-1.64 4.395,-1.64c 0.871,2.204 0.323,3.831 0.157,4.234c 1.026,1.12 1.647,2.548 1.647,4.295c 0,6.145 -3.743,7.498 -7.306,7.895c 0.574,0.497 1.085,1.47 1.085,2.963c 0,2.141 -0.019,3.864 -0.019,4.391c 0,0.426 0.288,0.925 1.099,0.768c 6.354,-2.118 10.933,-8.113 10.933,-15.18c 0,-8.837 -7.164,-16 -16,-16Z"></path></svg>GitHub</a><a href="./" title="Introducing a&nbsp;library.">1. Introduction</a><a href="p02-elements" title="Basic concepts of elements modifications and creations." class="current">2. Elements</a><a href="p03-events" title="Using not only events in UI declaratively.">3. Events and Addons</a><a href="p04-observables" title="Handling reactivity in UI via observables.">4. Observables and reactivity</a></nav><main><h2>Native JavaScript DOM elements creations</h2><p>Lets go through all patterns we would like to use and what needs to be improved for better experience.</p><div class="code" data-js="todo"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">// when NPM
import { assign, el, elNS } from "deka-dom-el"; import { assign, el, elNS } from "deka-dom-el";
// https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm.js // https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm.js
@ -22,7 +22,7 @@ document.body.append(
{ textContent: "Elements text content.", style: "color: coral;" } { textContent: "Elements text content.", style: "color: coral;" }
) )
); );
</code></div><script>Flems(document.getElementById("code-example-1-nkz9lcdhykg"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"document.body.append(\\n\\tdocument.createElement(\\\"div\\\")\\n);\\nconsole.log(\\n\\t\\\"Emty div is generated inside <body>:\\\",\\n\\tdocument.body.innerHTML.includes(\\\"<div></div>\\\")\\n);\\n\\ndocument.body.append(\\n\\tObject.assign(\\n\\t\\tdocument.createElement(\\\"p\\\"),\\n\\t\\t{ textContent: \\\"Elements text content.\\\", style: \\\"color: coral;\\\" }\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><p>To make this easier, you can use the <code>el</code> function. Internally in basic examples, it is wrapper around <code>assign(document.createElement(…), { … })</code>.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-13lyjukvr0yk" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, assign } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js"; </code></div><script>Flems(document.getElementById("code-example-1-nkz9lcdhykg"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"document.body.append(\\n\\tdocument.createElement(\\\"div\\\")\\n);\\nconsole.log(\\n\\t\\\"Emty div is generated inside <body>:\\\",\\n\\tdocument.body.innerHTML.includes(\\\"<div></div>\\\")\\n);\\n\\ndocument.body.append(\\n\\tObject.assign(\\n\\t\\tdocument.createElement(\\\"p\\\"),\\n\\t\\t{ textContent: \\\"Elements text content.\\\", style: \\\"color: coral;\\\" }\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><p>To make this easier, you can use the <code>el</code> function. Internally in basic examples, it is wrapper around <code>assign(document.createElement(…), { … })</code>.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-13lyjukvr0yk" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, assign } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
const color= "lightcoral"; const color= "lightcoral";
document.body.append( document.body.append(
el("p", { textContent: "Hello (first time)", style: { color } }) el("p", { textContent: "Hello (first time)", style: { color } })
@ -33,7 +33,7 @@ document.body.append(
{ textContent: "Hello (second time)", style: { color } } { textContent: "Hello (second time)", style: { color } }
) )
); );
</code></div><script>Flems(document.getElementById("code-example-1-13lyjukvr0yk"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, assign } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst color= \\\"lightcoral\\\";\\ndocument.body.append(\\n\\tel(\\\"p\\\", { textContent: \\\"Hello (first time)\\\", style: { color } })\\n);\\ndocument.body.append(\\n\\tassign(\\n\\t\\tdocument.createElement(\\\"p\\\"),\\n\\t\\t{ textContent: \\\"Hello (second time)\\\", style: { color } }\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><p>The <code>assign</code> function provides improved behaviour of <code>Object.assign()</code>. You can declaratively sets any IDL and attribute of the given element. Function prefers IDL and fallback to the <code>element.setAttribute</code> if there is no writable property in the element prototype.</p><p>You can study all JavaScript elements interfaces to the corresponding HTML elements. All HTML elements inherits from <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement">HTMLElement</a>. To see all available IDLs for example for paragraphs, see <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLParagraphElement">HTMLParagraphElement</a>. Moreover, the <code>assign</code> provides a&nbsp;way to sets declaratively some convenient properties:</p><ul><li>It is possible to sets <code>data-*</code>/<code>aria-*</code> attributes using object notation.</li><li>In opposite, it is also possible to sets <code>data-*</code>/<code>aria-*</code> attribute using camelCase notation.</li><li>You can use string or object as a&nbsp;value for <code>style</code> property.</li><li><code>className</code> (IDL preffered)/<code>class</code> are ways to add CSS class to the element. You can use string (similarly to <code>class="…"</code> syntax in HTML) or array of strings. This is handy to concat conditional classes.</li><li>Use <code>classList</code> to toggle specific classes. This will be handy later when the reactivity via signals is beeing introduced.</li><li>The <code>assign</code> also accepts the <code>undefined</code> as a&nbsp;value for any property to remove it from the element declaratively. Also for some IDL the corresponding attribute is removed as it can be confusing. <em>For example, natievly the elements <code>id</code> is removed by setting the IDL to an empty string.</em></li><li>You can use <code>=</code> or <code>.</code> to force processing given key as attribute/property of the element.</li></ul><p>For processing, the <code>assign</code> internally uses <code>assignAttribute</code> and <code>classListDeclarative</code>.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-iro9ncxa4bc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { assign, assignAttribute, classListDeclarative } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js"; </code></div><script>Flems(document.getElementById("code-example-1-13lyjukvr0yk"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, assign } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst color= \\\"lightcoral\\\";\\ndocument.body.append(\\n\\tel(\\\"p\\\", { textContent: \\\"Hello (first time)\\\", style: { color } })\\n);\\ndocument.body.append(\\n\\tassign(\\n\\t\\tdocument.createElement(\\\"p\\\"),\\n\\t\\t{ textContent: \\\"Hello (second time)\\\", style: { color } }\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><p>The <code>assign</code> function provides improved behaviour of <code>Object.assign()</code>. You can declaratively sets any IDL and attribute of the given element. Function prefers IDL and fallback to the <code>element.setAttribute</code> if there is no writable property in the element prototype.</p><p>You can study all JavaScript elements interfaces to the corresponding HTML elements. All HTML elements inherits from <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement">HTMLElement</a>. To see all available IDLs for example for paragraphs, see <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLParagraphElement">HTMLParagraphElement</a>. Moreover, the <code>assign</code> provides a&nbsp;way to sets declaratively some convenient properties:</p><ul><li>It is possible to sets <code>data-*</code>/<code>aria-*</code> attributes using object notation.</li><li>In opposite, it is also possible to sets <code>data-*</code>/<code>aria-*</code> attribute using camelCase notation.</li><li>You can use string or object as a&nbsp;value for <code>style</code> property.</li><li><code>className</code> (IDL preffered)/<code>class</code> are ways to add CSS class to the element. You can use string (similarly to <code>class="…"</code> syntax in HTML) or array of strings. This is handy to concat conditional classes.</li><li>Use <code>classList</code> to toggle specific classes. This will be handy later when the reactivity via observables is beeing introduced.</li><li>The <code>assign</code> also accepts the <code>undefined</code> as a&nbsp;value for any property to remove it from the element declaratively. Also for some IDL the corresponding attribute is removed as it can be confusing. <em>For example, natievly the elements <code>id</code> is removed by setting the IDL to an empty string.</em></li><li>You can use <code>=</code> or <code>.</code> to force processing given key as attribute/property of the element.</li></ul><p>For processing, the <code>assign</code> internally uses <code>assignAttribute</code> and <code>classListDeclarative</code>.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-iro9ncxa4bc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { assign, assignAttribute, classListDeclarative } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
const paragraph= document.createElement("p"); const paragraph= document.createElement("p");
assignAttribute(paragraph, "textContent", "Hello, world!"); assignAttribute(paragraph, "textContent", "Hello, world!");
@ -66,7 +66,7 @@ console.log("paragraph.something=", paragraph.something);
document.body.append( document.body.append(
paragraph paragraph
); );
</code></div><script>Flems(document.getElementById("code-example-1-iro9ncxa4bc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { assign, assignAttribute, classListDeclarative } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst paragraph= document.createElement(\\\"p\\\");\\n\\nassignAttribute(paragraph, \\\"textContent\\\", \\\"Hello, world!\\\");\\n\\nassignAttribute(paragraph, \\\"style\\\", \\\"color: red; font-weight: bold;\\\");\\nassignAttribute(paragraph, \\\"style\\\", { color: \\\"navy\\\" });\\n\\nassignAttribute(paragraph, \\\"dataTest1\\\", \\\"v1\\\");\\nassignAttribute(paragraph, \\\"dataset\\\", { test2: \\\"v2\\\" });\\n\\nassign(paragraph, { //textContent and style see above\\n\\tariaLabel: \\\"v1\\\", //data* see above\\n\\tariaset: { role: \\\"none\\\" }, // dataset see above\\n\\t\\\"=onclick\\\": \\\"console.log(event)\\\",\\n\\tonmouseout: console.info,\\n\\t\\\".something\\\": \\\"something\\\",\\n\\tclassList: {} //see below\\n});\\n\\nclassListDeclarative(paragraph, {\\n\\tclassAdd: true,\\n\\tclassRemove: false,\\n\\tclassAdd1: 1,\\n\\tclassRemove1: 0,\\n\\tclassToggle: -1\\n});\\n\\nconsole.log(paragraph.outerHTML);\\nconsole.log(\\\"paragraph.something=\\\", paragraph.something);\\ndocument.body.append(\\n\\tparagraph\\n);\\n\"}],\"toolbar\":false}"));</script><h3 id="h-native-javascript-templating"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-native-javascript-templating" tabindex="-1">#</a> Native JavaScript templating</h3><p>By default, the native JS has no good way to define HTML template using DOM API:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-b6a1hbrxh7s" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">document.body.append( </code></div><script>Flems(document.getElementById("code-example-1-iro9ncxa4bc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { assign, assignAttribute, classListDeclarative } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst paragraph= document.createElement(\\\"p\\\");\\n\\nassignAttribute(paragraph, \\\"textContent\\\", \\\"Hello, world!\\\");\\n\\nassignAttribute(paragraph, \\\"style\\\", \\\"color: red; font-weight: bold;\\\");\\nassignAttribute(paragraph, \\\"style\\\", { color: \\\"navy\\\" });\\n\\nassignAttribute(paragraph, \\\"dataTest1\\\", \\\"v1\\\");\\nassignAttribute(paragraph, \\\"dataset\\\", { test2: \\\"v2\\\" });\\n\\nassign(paragraph, { //textContent and style see above\\n\\tariaLabel: \\\"v1\\\", //data* see above\\n\\tariaset: { role: \\\"none\\\" }, // dataset see above\\n\\t\\\"=onclick\\\": \\\"console.log(event)\\\",\\n\\tonmouseout: console.info,\\n\\t\\\".something\\\": \\\"something\\\",\\n\\tclassList: {} //see below\\n});\\n\\nclassListDeclarative(paragraph, {\\n\\tclassAdd: true,\\n\\tclassRemove: false,\\n\\tclassAdd1: 1,\\n\\tclassRemove1: 0,\\n\\tclassToggle: -1\\n});\\n\\nconsole.log(paragraph.outerHTML);\\nconsole.log(\\\"paragraph.something=\\\", paragraph.something);\\ndocument.body.append(\\n\\tparagraph\\n);\\n\"}],\"toolbar\":false}"));</script><h3 id="h-native-javascript-templating"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-native-javascript-templating" tabindex="-1">#</a> Native JavaScript templating</h3><p>By default, the native JS has no good way to define HTML template using DOM API:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-b6a1hbrxh7s" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">document.body.append(
document.createElement("div"), document.createElement("div"),
document.createElement("span"), document.createElement("span"),
document.createElement("main") document.createElement("main")
@ -77,7 +77,7 @@ const template= document.createElement("main").append(
document.createElement("span"), document.createElement("span"),
); );
console.log(typeof template==="undefined"); console.log(typeof template==="undefined");
</code></div><script>Flems(document.getElementById("code-example-1-b6a1hbrxh7s"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"document.body.append(\\n\\tdocument.createElement(\\\"div\\\"),\\n\\tdocument.createElement(\\\"span\\\"),\\n\\tdocument.createElement(\\\"main\\\")\\n);\\nconsole.log(document.body.innerHTML.includes(\\\"<div></div><span></span><main></main>\\\"));\\nconst template= document.createElement(\\\"main\\\").append(\\n\\tdocument.createElement(\\\"div\\\"),\\n\\tdocument.createElement(\\\"span\\\"),\\n);\\nconsole.log(typeof template===\\\"undefined\\\");\\n\"}],\"toolbar\":false}"));</script><p>This library therefore overwrites the <code>append</code> method of created elements to always return parent element.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-2-iro9ncxa4bc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js"; </code></div><script>Flems(document.getElementById("code-example-1-b6a1hbrxh7s"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"document.body.append(\\n\\tdocument.createElement(\\\"div\\\"),\\n\\tdocument.createElement(\\\"span\\\"),\\n\\tdocument.createElement(\\\"main\\\")\\n);\\nconsole.log(document.body.innerHTML.includes(\\\"<div></div><span></span><main></main>\\\"));\\nconst template= document.createElement(\\\"main\\\").append(\\n\\tdocument.createElement(\\\"div\\\"),\\n\\tdocument.createElement(\\\"span\\\"),\\n);\\nconsole.log(typeof template===\\\"undefined\\\");\\n\"}],\"toolbar\":false}"));</script><p>This library therefore overwrites the <code>append</code> method of created elements to always return parent element.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-2-iro9ncxa4bc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
document.head.append( document.head.append(
el("style").append( el("style").append(
"tr, td{ border: 1px solid red; padding: 1em; }", "tr, td{ border: 1px solid red; padding: 1em; }",
@ -102,7 +102,7 @@ document.body.append(
) )
); );
import { chainableAppend } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js"; import { chainableAppend } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
/** @param {keyof HTMLElementTagNameMap} tag */ /** @param {keyof HTMLElementTagNameMap} tag */
const createElement= tag=&gt; chainableAppend(document.createElement(tag)); const createElement= tag=&gt; chainableAppend(document.createElement(tag));
document.body.append( document.body.append(
@ -112,7 +112,7 @@ document.body.append(
) )
) )
); );
</code></div><script>Flems(document.getElementById("code-example-2-iro9ncxa4bc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\ndocument.head.append(\\n\\tel(\\\"style\\\").append(\\n\\t\\t\\\"tr, td{ border: 1px solid red; padding: 1em; }\\\",\\n\\t\\t\\\"table{ border-collapse: collapse; }\\\"\\n\\t)\\n);\\ndocument.body.append(\\n\\tel(\\\"p\\\", \\\"Example of a complex template. Using for example nesting lists:\\\"),\\n\\tel(\\\"ul\\\").append(\\n\\t\\tel(\\\"li\\\", \\\"List item 1\\\"),\\n\\t\\tel(\\\"li\\\").append(\\n\\t\\t\\tel(\\\"ul\\\").append(\\n\\t\\t\\t\\tel(\\\"li\\\", \\\"Nested list item 1\\\"),\\n\\t\\t\\t)\\n\\t\\t)\\n\\t),\\n\\tel(\\\"table\\\").append(\\n\\t\\tel(\\\"tr\\\").append(\\n\\t\\t\\tel(\\\"td\\\", \\\"Row 1 Col 1\\\"),\\n\\t\\t\\tel(\\\"td\\\", \\\"Row 1 Col 2\\\")\\n\\t\\t)\\n\\t)\\n);\\n\\nimport { chainableAppend } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\n/** @param {keyof HTMLElementTagNameMap} tag */\\nconst createElement= tag=> chainableAppend(document.createElement(tag));\\ndocument.body.append(\\n\\tcreateElement(\\\"p\\\").append(\\n\\t\\tcreateElement(\\\"em\\\").append(\\n\\t\\t\\t\\\"You can also use `chainableAppend`!\\\"\\n\\t\\t)\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><h3 id="h-basic-state-less-components"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-basic-state-less-components" tabindex="-1">#</a> Basic (state-less) components</h3><p>You can use functions for encapsulation (repeating) logic. The <code>el</code> accepts function as first argument. In that case, the function should return dom elements and the second argument for <code>el</code> is argument for given element.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-320zz4y072o" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js"; </code></div><script>Flems(document.getElementById("code-example-2-iro9ncxa4bc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.head.append(\\n\\tel(\\\"style\\\").append(\\n\\t\\t\\\"tr, td{ border: 1px solid red; padding: 1em; }\\\",\\n\\t\\t\\\"table{ border-collapse: collapse; }\\\"\\n\\t)\\n);\\ndocument.body.append(\\n\\tel(\\\"p\\\", \\\"Example of a complex template. Using for example nesting lists:\\\"),\\n\\tel(\\\"ul\\\").append(\\n\\t\\tel(\\\"li\\\", \\\"List item 1\\\"),\\n\\t\\tel(\\\"li\\\").append(\\n\\t\\t\\tel(\\\"ul\\\").append(\\n\\t\\t\\t\\tel(\\\"li\\\", \\\"Nested list item 1\\\"),\\n\\t\\t\\t)\\n\\t\\t)\\n\\t),\\n\\tel(\\\"table\\\").append(\\n\\t\\tel(\\\"tr\\\").append(\\n\\t\\t\\tel(\\\"td\\\", \\\"Row 1 Col 1\\\"),\\n\\t\\t\\tel(\\\"td\\\", \\\"Row 1 Col 2\\\")\\n\\t\\t)\\n\\t)\\n);\\n\\nimport { chainableAppend } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\n/** @param {keyof HTMLElementTagNameMap} tag */\\nconst createElement= tag=> chainableAppend(document.createElement(tag));\\ndocument.body.append(\\n\\tcreateElement(\\\"p\\\").append(\\n\\t\\tcreateElement(\\\"em\\\").append(\\n\\t\\t\\t\\\"You can also use `chainableAppend`!\\\"\\n\\t\\t)\\n\\t)\\n);\\n\"}],\"toolbar\":false}"));</script><h3 id="h-basic-state-less-components"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-basic-state-less-components" tabindex="-1">#</a> Basic (state-less) components</h3><p>You can use functions for encapsulation (repeating) logic. The <code>el</code> accepts function as first argument. In that case, the function should return dom elements and the second argument for <code>el</code> is argument for given element.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-320zz4y072o" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
document.head.append( document.head.append(
el("style").append( el("style").append(
".class1{ font-weight: bold; }", ".class1{ font-weight: bold; }",
@ -129,7 +129,7 @@ function component({ className, textContent }){
el("p", textContent) el("p", textContent)
); );
} }
</code></div><script>Flems(document.getElementById("code-example-1-320zz4y072o"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\ndocument.head.append(\\n\\tel(\\\"style\\\").append(\\n\\t\\t\\\".class1{ font-weight: bold; }\\\",\\n\\t\\t\\\".class2{ color: purple; }\\\"\\n\\t)\\n);\\ndocument.body.append(\\n\\tel(component, { className: \\\"class2\\\", textContent: \\\"Hello World!\\\" }),\\n\\tcomponent({ className: \\\"class2\\\", textContent: \\\"Hello World!\\\" })\\n);\\n\\nfunction component({ className, textContent }){\\n\\treturn el(\\\"div\\\", { className: [ \\\"class1\\\", className ] }).append(\\n\\t\\tel(\\\"p\\\", textContent)\\n\\t);\\n}\\n\"}],\"toolbar\":false}"));</script><p>As you can see, in case of state-less/basic components there is no difference between calling component function directly or using <code>el</code> function.</p><p class="notice">It is nice to use similar naming convention as native DOM API. This allows us to use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment" title="Destructuring assignment | MDN">the destructuring assignment syntax</a> and keep track of the native API (things are best remembered through regular use).</p><h3 id="h-creating-non-html-elements"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-creating-non-html-elements" tabindex="-1">#</a> Creating non-HTML elements</h3><p>Similarly to the native DOM API (<a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS" title="MDN"><code>document.createElementNS</code></a>) for non-HTML elements we need to tell JavaScript which kind of the element to create. We can use the <code>elNS</code> function:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-bcjydb50gdc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { elNS, assign } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js"; </code></div><script>Flems(document.getElementById("code-example-1-320zz4y072o"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.head.append(\\n\\tel(\\\"style\\\").append(\\n\\t\\t\\\".class1{ font-weight: bold; }\\\",\\n\\t\\t\\\".class2{ color: purple; }\\\"\\n\\t)\\n);\\ndocument.body.append(\\n\\tel(component, { className: \\\"class2\\\", textContent: \\\"Hello World!\\\" }),\\n\\tcomponent({ className: \\\"class2\\\", textContent: \\\"Hello World!\\\" })\\n);\\n\\nfunction component({ className, textContent }){\\n\\treturn el(\\\"div\\\", { className: [ \\\"class1\\\", className ] }).append(\\n\\t\\tel(\\\"p\\\", textContent)\\n\\t);\\n}\\n\"}],\"toolbar\":false}"));</script><p>As you can see, in case of state-less/basic components there is no difference between calling component function directly or using <code>el</code> function.</p><p class="notice">It is nice to use similar naming convention as native DOM API. This allows us to use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment" title="Destructuring assignment | MDN">the destructuring assignment syntax</a> and keep track of the native API (things are best remembered through regular use).</p><h3 id="h-creating-non-html-elements"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-creating-non-html-elements" tabindex="-1">#</a> Creating non-HTML elements</h3><p>Similarly to the native DOM API (<a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS" title="MDN"><code>document.createElementNS</code></a>) for non-HTML elements we need to tell JavaScript which kind of the element to create. We can use the <code>elNS</code> function:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-bcjydb50gdc" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { elNS, assign } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
const elSVG= elNS("http://www.w3.org/2000/svg"); const elSVG= elNS("http://www.w3.org/2000/svg");
const elMath= elNS("http://www.w3.org/1998/Math/MathML"); const elMath= elNS("http://www.w3.org/1998/Math/MathML");
document.body.append( document.body.append(
@ -140,4 +140,4 @@ document.body.append(
console.log( console.log(
document.body.innerHTML.includes("&lt;svg&gt;&lt;/svg&gt;&lt;math&gt;&lt;/math&gt;") document.body.innerHTML.includes("&lt;svg&gt;&lt;/svg&gt;&lt;math&gt;&lt;/math&gt;")
) )
</code></div><script>Flems(document.getElementById("code-example-1-bcjydb50gdc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { elNS, assign } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst elSVG= elNS(\\\"http://www.w3.org/2000/svg\\\");\\nconst elMath= elNS(\\\"http://www.w3.org/1998/Math/MathML\\\");\\ndocument.body.append(\\n\\telSVG(\\\"svg\\\"), // see https://developer.mozilla.org/en-US/docs/Web/SVG and https://developer.mozilla.org/en-US/docs/Web/API/SVGElement\\n\\telMath(\\\"math\\\") // see https://developer.mozilla.org/en-US/docs/Web/MathML and https://developer.mozilla.org/en-US/docs/Web/MathML/Global_attributes\\n);\\n\\nconsole.log(\\n\\tdocument.body.innerHTML.includes(\\\"<svg></svg><math></math>\\\")\\n)\\n\"}],\"toolbar\":false}"));</script><div class="notice"><!--<dde:mark type="component" name="mnemonicUl" 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>assign(&lt;element&gt;, ...&lt;idl-objects&gt;): &lt;element&gt;</code> — assign properties to the element</li><li><code>el(&lt;tag-name&gt;, &lt;primitive&gt;)[.append(...)]: &lt;element-from-tag-name&gt;</code> — simple element containing only text</li><li><code>el(&lt;tag-name&gt;, &lt;idl-object&gt;)[.append(...)]: &lt;element-from-tag-name&gt;</code> — element with more properties</li><li><code>el(&lt;function&gt;, &lt;function-argument(s)&gt;)[.append(...)]: &lt;element-returned-by-function&gt;</code> — using component represented by function</li><li><code>el(&lt;...&gt;, &lt;...&gt;, ...&lt;addons&gt;)</code> — see following page</li><li><code>elNS(&lt;namespace&gt;)(&lt;as-el-see-above&gt;)[.append(...)]: &lt;element-based-on-arguments&gt;</code> — typically SVG elements</li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="./" title="Introducing a&nbsp;library."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Introduction (previous)</a><a rel="next" href="p03-events" title="Using not only events in UI declaratively."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Events and Addons</a></div></main></body></html> </code></div><script>Flems(document.getElementById("code-example-1-bcjydb50gdc"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { elNS, assign } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst elSVG= elNS(\\\"http://www.w3.org/2000/svg\\\");\\nconst elMath= elNS(\\\"http://www.w3.org/1998/Math/MathML\\\");\\ndocument.body.append(\\n\\telSVG(\\\"svg\\\"), // see https://developer.mozilla.org/en-US/docs/Web/SVG and https://developer.mozilla.org/en-US/docs/Web/API/SVGElement\\n\\telMath(\\\"math\\\") // see https://developer.mozilla.org/en-US/docs/Web/MathML and https://developer.mozilla.org/en-US/docs/Web/MathML/Global_attributes\\n);\\n\\nconsole.log(\\n\\tdocument.body.innerHTML.includes(\\\"<svg></svg><math></math>\\\")\\n)\\n\"}],\"toolbar\":false}"));</script><div class="notice"><!--<dde:mark type="component" name="mnemonicUl" 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>assign(&lt;element&gt;, ...&lt;idl-objects&gt;): &lt;element&gt;</code> — assign properties to the element</li><li><code>el(&lt;tag-name&gt;, &lt;primitive&gt;)[.append(...)]: &lt;element-from-tag-name&gt;</code> — simple element containing only text</li><li><code>el(&lt;tag-name&gt;, &lt;idl-object&gt;)[.append(...)]: &lt;element-from-tag-name&gt;</code> — element with more properties</li><li><code>el(&lt;function&gt;, &lt;function-argument(s)&gt;)[.append(...)]: &lt;element-returned-by-function&gt;</code> — using component represented by function</li><li><code>el(&lt;...&gt;, &lt;...&gt;, ...&lt;addons&gt;)</code> — see following page</li><li><code>elNS(&lt;namespace&gt;)(&lt;as-el-see-above&gt;)[.append(...)]: &lt;element-based-on-arguments&gt;</code> — typically SVG elements</li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="./" title="Introducing a&nbsp;library."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Introduction (previous)</a><a rel="next" href="p03-events" title="Using not only events in UI declaratively."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Events and Addons</a></div></main></body></html>

View File

@ -1,11 +1,11 @@
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Using not only events in UI declaratively."><title>`deka-dom-el` — Events and Addons</title><!--<dde:mark type="component" name="metaAuthor" host="this" ssr/>--><meta name="author" content="Jan Andrle"><link type="text/plain" rel="author" href="https://jaandrle.github.io/humans.txt"><meta name="generator" content="deka-dom-el"><!--<dde:mark type="component" name="metaTwitter" host="this" ssr/>--><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="twitter:title" content="deka-dom-el"><meta name="twitter:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="twitter:creator" content="@jaandrle"><!--<dde:mark type="component" name="metaFacebook" host="this" ssr/>--><meta name="og:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="og:title" content="deka-dom-el"><meta name="og:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="og:creator" content="@jaandrle"><script src="https://cdn.jsdelivr.net/npm/shiki" defer=""></script><script type="module" src="code.js.js"></script><script src="https://flems.io/flems.html" type="text/javascript" charset="utf-8"></script><link rel="stylesheet" href="global.css"></head><body><!--<dde:mark type="component" name="page" host="this" ssr/>--><!--<dde:mark type="component" name="simplePage" host="this" ssr/>--><!--<dde:mark type="component" name="header" host="this" ssr/>--><header><h1>`deka-dom-el` — Events and Addons</h1><p>Using not only events in UI declaratively.</p></header><nav><a href="https://github.com/jaandrle/deka-dom-el"><svg class="icon" viewBox="0 0 32 32"><!--<dde:mark type="component" name="iconGitHub" host="parentElement" ssr/>--><path d="M 16,0.395c -8.836,0 -16,7.163 -16,16c 0,7.069 4.585,13.067 10.942,15.182c 0.8,0.148 1.094,-0.347 1.094,-0.77c 0,-0.381 -0.015,-1.642 -0.022,-2.979c -4.452,0.968 -5.391,-1.888 -5.391,-1.888c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341c -1.452,-0.993 0.11,-0.973 0.11,-0.973c 1.606,0.113 2.452,1.649 2.452,1.649c 1.427,2.446 3.743,1.739 4.656,1.33c 0.143,-1.034 0.558,-1.74 1.016,-2.14c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907c 0,-1.747 0.625,-3.174 1.649,-4.295c -0.166,-0.403 -0.714,-2.03 0.155,-4.234c 0,0 1.344,-0.43 4.401,1.64c 1.276,-0.355 2.645,-0.532 4.005,-0.539c 1.359,0.006 2.729,0.184 4.008,0.539c 3.054,-2.07 4.395,-1.64 4.395,-1.64c 0.871,2.204 0.323,3.831 0.157,4.234c 1.026,1.12 1.647,2.548 1.647,4.295c 0,6.145 -3.743,7.498 -7.306,7.895c 0.574,0.497 1.085,1.47 1.085,2.963c 0,2.141 -0.019,3.864 -0.019,4.391c 0,0.426 0.288,0.925 1.099,0.768c 6.354,-2.118 10.933,-8.113 10.933,-15.18c 0,-8.837 -7.164,-16 -16,-16Z"></path></svg>GitHub</a><a href="./" title="Introducing a&nbsp;library.">1. Introduction</a><a href="p02-elements" title="Basic concepts of elements modifications and creations.">2. Elements</a><a href="p03-events" title="Using not only events in UI declaratively." class="current">3. Events and Addons</a><a href="p04-signals" title="Handling reactivity in UI via signals.">4. Signals and reactivity</a></nav><main><h2>Listenning to the native DOM events and other Addons</h2><p>We quickly introduce helper to listening to the native DOM events. And library syntax/pattern so-called <em>Addon</em> to incorporate not only this in UI templates declaratively.</p><div class="code" data-js="todo"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">// when NPM <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Using not only events in UI declaratively."><title>`deka-dom-el` — Events and Addons</title><!--<dde:mark type="component" name="metaAuthor" host="this" ssr/>--><meta name="author" content="Jan Andrle"><link type="text/plain" rel="author" href="https://jaandrle.github.io/humans.txt"><meta name="generator" content="deka-dom-el"><!--<dde:mark type="component" name="metaTwitter" host="this" ssr/>--><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="twitter:title" content="deka-dom-el"><meta name="twitter:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="twitter:creator" content="@jaandrle"><!--<dde:mark type="component" name="metaFacebook" host="this" ssr/>--><meta name="og:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="og:title" content="deka-dom-el"><meta name="og:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="og:creator" content="@jaandrle"><script src="https://cdn.jsdelivr.net/npm/shiki" defer=""></script><script type="module" src="code.js.js"></script><script src="https://flems.io/flems.html" type="text/javascript" charset="utf-8"></script><link rel="stylesheet" href="global.css"></head><body><!--<dde:mark type="component" name="page" host="this" ssr/>--><!--<dde:mark type="component" name="simplePage" host="this" ssr/>--><!--<dde:mark type="component" name="header" host="this" ssr/>--><header><h1>`deka-dom-el` — Events and Addons</h1><p>Using not only events in UI declaratively.</p></header><nav><a href="https://github.com/jaandrle/deka-dom-el"><svg class="icon" viewBox="0 0 32 32"><!--<dde:mark type="component" name="iconGitHub" host="parentElement" ssr/>--><path d="M 16,0.395c -8.836,0 -16,7.163 -16,16c 0,7.069 4.585,13.067 10.942,15.182c 0.8,0.148 1.094,-0.347 1.094,-0.77c 0,-0.381 -0.015,-1.642 -0.022,-2.979c -4.452,0.968 -5.391,-1.888 -5.391,-1.888c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341c -1.452,-0.993 0.11,-0.973 0.11,-0.973c 1.606,0.113 2.452,1.649 2.452,1.649c 1.427,2.446 3.743,1.739 4.656,1.33c 0.143,-1.034 0.558,-1.74 1.016,-2.14c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907c 0,-1.747 0.625,-3.174 1.649,-4.295c -0.166,-0.403 -0.714,-2.03 0.155,-4.234c 0,0 1.344,-0.43 4.401,1.64c 1.276,-0.355 2.645,-0.532 4.005,-0.539c 1.359,0.006 2.729,0.184 4.008,0.539c 3.054,-2.07 4.395,-1.64 4.395,-1.64c 0.871,2.204 0.323,3.831 0.157,4.234c 1.026,1.12 1.647,2.548 1.647,4.295c 0,6.145 -3.743,7.498 -7.306,7.895c 0.574,0.497 1.085,1.47 1.085,2.963c 0,2.141 -0.019,3.864 -0.019,4.391c 0,0.426 0.288,0.925 1.099,0.768c 6.354,-2.118 10.933,-8.113 10.933,-15.18c 0,-8.837 -7.164,-16 -16,-16Z"></path></svg>GitHub</a><a href="./" title="Introducing a&nbsp;library.">1. Introduction</a><a href="p02-elements" title="Basic concepts of elements modifications and creations.">2. Elements</a><a href="p03-events" title="Using not only events in UI declaratively." class="current">3. Events and Addons</a><a href="p04-observables" title="Handling reactivity in UI via observables.">4. Observables and reactivity</a></nav><main><h2>Listenning to the native DOM events and other Addons</h2><p>We quickly introduce helper to listening to the native DOM events. And library syntax/pattern so-called <em>Addon</em> to incorporate not only this in UI templates declaratively.</p><div class="code" data-js="todo"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">// when NPM
import { on, dispatchEvent } from "deka-dom-el"; import { on, dispatchEvent } from "deka-dom-el";
// https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm.js // https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm.js
/** /**
* @type {ddeElementAddon} * @type {ddeElementAddon}
* */ * */
</code></div><h3 id="h-events-and-listenners"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-events-and-listenners" tabindex="-1">#</a> Events and listenners</h3><p>In JavaScript you can listen to the native DOM events of the given element by using <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener" title="addEventListener on MDN"><code>element.addEventListener(type, listener, options)</code></a>. The library provides an alternative (<code>on</code>) accepting the differen order of the arguments:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-1wmddlo83w68" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js"; </code></div><h3 id="h-events-and-listenners"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-events-and-listenners" tabindex="-1">#</a> Events and listenners</h3><p>In JavaScript you can listen to the native DOM events of the given element by using <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener" title="addEventListener on MDN"><code>element.addEventListener(type, listener, options)</code></a>. The library provides an alternative (<code>on</code>) accepting the differen order of the arguments:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-1wmddlo83w68" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
const log= mark=&gt; console.log.bind(console, mark); const log= mark=&gt; console.log.bind(console, mark);
const button= el("button", "Test click"); const button= el("button", "Test click");
@ -15,7 +15,7 @@ on("click", log("`on`"), { once: true })(button);
document.body.append( document.body.append(
button button
); );
</code></div><script>Flems(document.getElementById("code-example-1-1wmddlo83w68"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst log= mark=> console.log.bind(console, mark);\\n\\nconst button= el(\\\"button\\\", \\\"Test click\\\");\\nbutton.addEventListener(\\\"click\\\", log(\\\"`addEventListener`\\\"), { once: true });\\non(\\\"click\\\", log(\\\"`on`\\\"), { once: true })(button);\\n\\ndocument.body.append(\\n\\tbutton\\n);\\n\"}],\"toolbar\":false}"));</script><p>…this is actually one of the two differences. The another one is that <code>on</code> accepts only object as the <code>options</code> (but it is still optional).</p><p class="notice">The other difference is that there is <strong>no</strong> <code>off</code> function. You can remove listener declaratively using <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal" title="part of addEventListener on MDN">AbortSignal</a>:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-8r8qappf8mo" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js"; </code></div><script>Flems(document.getElementById("code-example-1-1wmddlo83w68"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst log= mark=> console.log.bind(console, mark);\\n\\nconst button= el(\\\"button\\\", \\\"Test click\\\");\\nbutton.addEventListener(\\\"click\\\", log(\\\"`addEventListener`\\\"), { once: true });\\non(\\\"click\\\", log(\\\"`on`\\\"), { once: true })(button);\\n\\ndocument.body.append(\\n\\tbutton\\n);\\n\"}],\"toolbar\":false}"));</script><p>…this is actually one of the two differences. The another one is that <code>on</code> accepts only object as the <code>options</code> (but it is still optional).</p><p class="notice">The other difference is that there is <strong>no</strong> <code>off</code> function. You can remove listener declaratively using <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal" title="part of addEventListener on MDN">AbortSignal</a>:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-8r8qappf8mo" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
const log= mark=&gt; console.log.bind(console, mark); const log= mark=&gt; console.log.bind(console, mark);
const abort_controller= new AbortController(); const abort_controller= new AbortController();
@ -28,7 +28,7 @@ on("click", log("`on`"), { signal })(button);
document.body.append( document.body.append(
button, " ", el("button", { textContent: "Off", onclick: ()=&gt; abort_controller.abort() }) button, " ", el("button", { textContent: "Off", onclick: ()=&gt; abort_controller.abort() })
); );
</code></div><script>Flems(document.getElementById("code-example-1-8r8qappf8mo"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst log= mark=> console.log.bind(console, mark);\\n\\nconst abort_controller= new AbortController();\\nconst { signal }= abort_controller;\\n\\nconst button= el(\\\"button\\\", \\\"Test click\\\");\\nbutton.addEventListener(\\\"click\\\", log(\\\"`addEventListener`\\\"), { signal });\\non(\\\"click\\\", log(\\\"`on`\\\"), { signal })(button);\\n\\ndocument.body.append(\\n\\tbutton, \\\" \\\", el(\\\"button\\\", { textContent: \\\"Off\\\", onclick: ()=> abort_controller.abort() })\\n);\\n\"}],\"toolbar\":false}"));</script><div class="notice"><p>So, there are (typically) three ways to handle events. You can use:</p><ul><li><code>el("button", { textContent: "click me", "=onclick": "console.log(event)" })</code></li><li><code>el("button", { textContent: "click me", onclick: console.log })</code></li><li><code>el("button", { textContent: "click me" }, on("click", console.log))</code></li></ul><p>In the first example we force to use HTML attribute (it corresponds to <code>&lt;button onclick="console.log(event)"&gt;click me&lt;/button&gt;</code>). <em>Side note: this can be useful in case of SSR.</em> To study difference, you can read a nice summary here: <a href="https://gist.github.com/WebReflection/b404c36f46371e3b1173bf5492acc944">GIST @WebReflection/web_events.md</a>.</p></div><h3 id="h-addons"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-addons" tabindex="-1">#</a> Addons</h3><p>From practical point of view, <em>Addons</em> are just functions that accept any html element as their first parameter. You can see that the <code>on(…)</code> fullfills this requirement.</p><p>You can use Addons as ≥3rd argument of <code>el</code> function. This way is possible to extends you templates by additional (3rd party) functionalities. But for now mainly, you can add events listeners:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-6jtsnxfqzm4" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js"; </code></div><script>Flems(document.getElementById("code-example-1-8r8qappf8mo"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst log= mark=> console.log.bind(console, mark);\\n\\nconst abort_controller= new AbortController();\\nconst { signal }= abort_controller;\\n\\nconst button= el(\\\"button\\\", \\\"Test click\\\");\\nbutton.addEventListener(\\\"click\\\", log(\\\"`addEventListener`\\\"), { signal });\\non(\\\"click\\\", log(\\\"`on`\\\"), { signal })(button);\\n\\ndocument.body.append(\\n\\tbutton, \\\" \\\", el(\\\"button\\\", { textContent: \\\"Off\\\", onclick: ()=> abort_controller.abort() })\\n);\\n\"}],\"toolbar\":false}"));</script><div class="notice"><p>So, there are (typically) three ways to handle events. You can use:</p><ul><li><code>el("button", { textContent: "click me", "=onclick": "console.log(event)" })</code></li><li><code>el("button", { textContent: "click me", onclick: console.log })</code></li><li><code>el("button", { textContent: "click me" }, on("click", console.log))</code></li></ul><p>In the first example we force to use HTML attribute (it corresponds to <code>&lt;button onclick="console.log(event)"&gt;click me&lt;/button&gt;</code>). <em>Side note: this can be useful in case of SSR.</em> To study difference, you can read a nice summary here: <a href="https://gist.github.com/WebReflection/b404c36f46371e3b1173bf5492acc944">GIST @WebReflection/web_events.md</a>.</p></div><h3 id="h-addons"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-addons" tabindex="-1">#</a> Addons</h3><p>From practical point of view, <em>Addons</em> are just functions that accept any html element as their first parameter. You can see that the <code>on(…)</code> fullfills this requirement.</p><p>You can use Addons as ≥3rd argument of <code>el</code> function. This way is possible to extends you templates by additional (3rd party) functionalities. But for now mainly, you can add events listeners:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-6jtsnxfqzm4" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
const abort_controller= new AbortController(); const abort_controller= new AbortController();
const { signal }= abort_controller; const { signal }= abort_controller;
/** @type {ddeElementAddon&lt;HTMLButtonElement&gt;} */ /** @type {ddeElementAddon&lt;HTMLButtonElement&gt;} */
@ -52,7 +52,7 @@ function update(event){
"\n" "\n"
); );
} }
</code></div><script>Flems(document.getElementById("code-example-1-6jtsnxfqzm4"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst abort_controller= new AbortController();\\nconst { signal }= abort_controller;\\n/** @type {ddeElementAddon<HTMLButtonElement>} */\\nconst onclickOff= on(\\\"click\\\", ()=> abort_controller.abort(), { signal });\\n/** @type {(ref?: HTMLOutputElement)=> HTMLOutputElement | null} */\\nconst ref= (store=> ref=> ref ? (store= ref) : store)(null);\\n\\ndocument.body.append(\\n\\tel(\\\"button\\\", \\\"Test `on`\\\",\\n\\t\\ton(\\\"click\\\", console.log, { signal }),\\n\\t\\ton(\\\"click\\\", update, { signal }),\\n\\t\\ton(\\\"mouseup\\\", update, { signal })),\\n\\t\\\" \\\",\\n\\tel(\\\"button\\\", \\\"Off\\\", onclickOff),\\n\\tel(\\\"output\\\", { style: { display: \\\"block\\\", whiteSpace: \\\"pre\\\" } }, ref)\\n);\\n/** @param {MouseEvent} event @this {HTMLButtonElement} */\\nfunction update(event){\\n\\tref().append(\\n\\t\\tevent.type,\\n\\t\\t\\\"\\\\n\\\"\\n\\t);\\n}\\n\"}],\"toolbar\":false}"));</script><p>As the example shows, you can also provide types in JSDoc+TypeScript by using global type <code>ddeElementAddon</code>. Also notice, you can use Addons to get element reference.</p><h3 id="h-life-cycle-events"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-life-cycle-events" tabindex="-1">#</a> Life-cycle events</h3><p>Addons are called immediately when the element is created, even it is not connected to live DOM yet. Therefore, you can understand the Addon to be “oncreate” event.</p><p>The library provide three additional live-cycle events corresponding to how they are named in a case of custom elements: <code>on.connected</code>, <code>on.disconnected</code> and <code>on.attributeChanged</code>.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-35hjjp3e4js" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js"; </code></div><script>Flems(document.getElementById("code-example-1-6jtsnxfqzm4"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst abort_controller= new AbortController();\\nconst { signal }= abort_controller;\\n/** @type {ddeElementAddon<HTMLButtonElement>} */\\nconst onclickOff= on(\\\"click\\\", ()=> abort_controller.abort(), { signal });\\n/** @type {(ref?: HTMLOutputElement)=> HTMLOutputElement | null} */\\nconst ref= (store=> ref=> ref ? (store= ref) : store)(null);\\n\\ndocument.body.append(\\n\\tel(\\\"button\\\", \\\"Test `on`\\\",\\n\\t\\ton(\\\"click\\\", console.log, { signal }),\\n\\t\\ton(\\\"click\\\", update, { signal }),\\n\\t\\ton(\\\"mouseup\\\", update, { signal })),\\n\\t\\\" \\\",\\n\\tel(\\\"button\\\", \\\"Off\\\", onclickOff),\\n\\tel(\\\"output\\\", { style: { display: \\\"block\\\", whiteSpace: \\\"pre\\\" } }, ref)\\n);\\n/** @param {MouseEvent} event @this {HTMLButtonElement} */\\nfunction update(event){\\n\\tref().append(\\n\\t\\tevent.type,\\n\\t\\t\\\"\\\\n\\\"\\n\\t);\\n}\\n\"}],\"toolbar\":false}"));</script><p>As the example shows, you can also provide types in JSDoc+TypeScript by using global type <code>ddeElementAddon</code>. Also notice, you can use Addons to get element reference.</p><h3 id="h-life-cycle-events"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-life-cycle-events" tabindex="-1">#</a> Life-cycle events</h3><p>Addons are called immediately when the element is created, even it is not connected to live DOM yet. Therefore, you can understand the Addon to be “oncreate” event.</p><p>The library provide three additional live-cycle events corresponding to how they are named in a case of custom elements: <code>on.connected</code>, <code>on.disconnected</code> and <code>on.attributeChanged</code>.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-35hjjp3e4js" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
const paragraph= el("p", "See live-cycle events in console.", const paragraph= el("p", "See live-cycle events in console.",
el=&gt; log({ type: "dde:created", detail: el }), el=&gt; log({ type: "dde:created", detail: el }),
on.connected(log), on.connected(log),
@ -70,7 +70,7 @@ document.body.append(
function log({ type, detail }){ function log({ type, detail }){
console.log({ _this: this, type, detail }); console.log({ _this: this, type, detail });
} }
</code></div><script>Flems(document.getElementById("code-example-1-35hjjp3e4js"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\nconst paragraph= el(\\\"p\\\", \\\"See live-cycle events in console.\\\",\\n\\tel=> log({ type: \\\"dde:created\\\", detail: el }),\\n\\ton.connected(log),\\n\\ton.disconnected(log),\\n\\ton.attributeChanged(log));\\n\\ndocument.body.append(\\n\\tparagraph,\\n\\tel(\\\"button\\\", \\\"Update attribute\\\", on(\\\"click\\\", ()=> paragraph.setAttribute(\\\"test\\\", Math.random().toString()))),\\n\\t\\\" \\\",\\n\\tel(\\\"button\\\", \\\"Remove\\\", on(\\\"click\\\", ()=> paragraph.remove()))\\n);\\n\\n/** @param {Partial<CustomEvent>} event */\\nfunction log({ type, detail }){\\n\\tconsole.log({ _this: this, type, detail });\\n}\\n\"}],\"toolbar\":false}"));</script><p>For Custom elements, we will later introduce a way to replace <code>*Callback</code> syntax with <code>dde:*</code> events. The <code>on.*</code> functions then listen to the appropriate Custom Elements events (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks">Custom element lifecycle callbacks | MDN</a>).</p><p>But, in case of regular elemnets the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver"><code>MutationObserver</code> | MDN</a> is internaly used to track these events. Therefore, there are some drawbacks:</p><ul><li>To proper listener registration, you need to use <code>on.*</code> not `on("dde:*", …)`!</li><li>Use sparingly! Internally, library must loop of all registered events and fires event properly. <strong>It is good practice to use the fact that if an element is removed, its children are also removed!</strong> In this spirit, we will introduce later the <strong>host</strong> syntax to register clean up procedures when the component is removed from the app.</li></ul><h3 id="h-final-notes"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-final-notes" tabindex="-1">#</a> Final notes</h3><p>The library also provides a&nbsp;method to dispatch the events.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-2-b6a1hbrxh7s" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on, dispatchEvent } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js"; </code></div><script>Flems(document.getElementById("code-example-1-35hjjp3e4js"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst paragraph= el(\\\"p\\\", \\\"See live-cycle events in console.\\\",\\n\\tel=> log({ type: \\\"dde:created\\\", detail: el }),\\n\\ton.connected(log),\\n\\ton.disconnected(log),\\n\\ton.attributeChanged(log));\\n\\ndocument.body.append(\\n\\tparagraph,\\n\\tel(\\\"button\\\", \\\"Update attribute\\\", on(\\\"click\\\", ()=> paragraph.setAttribute(\\\"test\\\", Math.random().toString()))),\\n\\t\\\" \\\",\\n\\tel(\\\"button\\\", \\\"Remove\\\", on(\\\"click\\\", ()=> paragraph.remove()))\\n);\\n\\n/** @param {Partial<CustomEvent>} event */\\nfunction log({ type, detail }){\\n\\tconsole.log({ _this: this, type, detail });\\n}\\n\"}],\"toolbar\":false}"));</script><p>For Custom elements, we will later introduce a way to replace <code>*Callback</code> syntax with <code>dde:*</code> events. The <code>on.*</code> functions then listen to the appropriate Custom Elements events (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks">Custom element lifecycle callbacks | MDN</a>).</p><p>But, in case of regular elemnets the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver"><code>MutationObserver</code> | MDN</a> is internaly used to track these events. Therefore, there are some drawbacks:</p><ul><li>To proper listener registration, you need to use <code>on.*</code> not `on("dde:*", …)`!</li><li>Use sparingly! Internally, library must loop of all registered events and fires event properly. <strong>It is good practice to use the fact that if an element is removed, its children are also removed!</strong> In this spirit, we will introduce later the <strong>host</strong> syntax to register clean up procedures when the component is removed from the app.</li></ul><h3 id="h-final-notes"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-final-notes" tabindex="-1">#</a> Final notes</h3><p>The library also provides a&nbsp;method to dispatch the events.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-2-b6a1hbrxh7s" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { el, on, dispatchEvent } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
document.body.append( document.body.append(
el("p", "Listenning to `test` event.", on("test", console.log)).append( el("p", "Listenning to `test` event.", on("test", console.log)).append(
el("br"), el("br"),
@ -94,4 +94,4 @@ function dde(){
function ddeOptions(){ function ddeOptions(){
dispatchEvent("test", { bubbles: true })(this, "hi"); dispatchEvent("test", { bubbles: true })(this, "hi");
} }
</code></div><script>Flems(document.getElementById("code-example-2-b6a1hbrxh7s"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on, dispatchEvent } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js\\\";\\ndocument.body.append(\\n\\tel(\\\"p\\\", \\\"Listenning to `test` event.\\\", on(\\\"test\\\", console.log)).append(\\n\\t\\tel(\\\"br\\\"),\\n\\t\\tel(\\\"button\\\", \\\"native\\\", on(\\\"click\\\", native)),\\n\\t\\t\\\" \\\",\\n\\t\\tel(\\\"button\\\", \\\"dde\\\", on(\\\"click\\\", dde)),\\n\\t\\t\\\" \\\",\\n\\t\\tel(\\\"button\\\", \\\"dde with options\\\", on(\\\"click\\\", ddeOptions))\\n\\t)\\n);\\nfunction native(){\\n\\tthis.dispatchEvent(\\n\\t\\tnew CustomEvent(\\\"test\\\",\\n\\t\\t\\t{ bubbles: true, detail: \\\"hi\\\" }\\n\\t\\t)\\n\\t);\\n}\\nfunction dde(){\\n\\tdispatchEvent(\\\"test\\\")(this.parentElement, \\\"hi\\\");\\n}\\nfunction ddeOptions(){\\n\\tdispatchEvent(\\\"test\\\", { bubbles: true })(this, \\\"hi\\\");\\n}\\n\"}],\"toolbar\":false}"));</script><div class="notice"><!--<dde:mark type="component" name="mnemonicUl" 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>on(&lt;event&gt;, &lt;listener&gt;[, &lt;options&gt;])(&lt;element&gt;)</code> — just <code>&lt;element&gt;.addEventListener(&lt;event&gt;, &lt;listener&gt;[, &lt;options&gt;])</code></li><li><code>on.&lt;live-cycle&gt;(&lt;event&gt;, &lt;listener&gt;[, &lt;options&gt;])(&lt;element&gt;)</code> — corresponds to custom elemnets callbacks <code>&lt;live-cycle&gt;Callback(...){...}</code>. To connect to custom element see following page, else it is simulated by MutationObserver.</li><li><code>dispatchEvent(&lt;event&gt;[, &lt;options&gt;])(element)</code> — just <code>&lt;element&gt;.dispatchEvent(new Event(&lt;event&gt;[, &lt;options&gt;]))</code></li><li><code>dispatchEvent(&lt;event&gt;[, &lt;options&gt;])(element, detail)</code> — just <code>&lt;element&gt;.dispatchEvent(new CustomEvent(&lt;event&gt;, { detail, ...&lt;options&gt; }))</code></li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="p02-elements" title="Basic concepts of elements modifications and creations."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Elements (previous)</a><a rel="next" href="p04-signals" title="Handling reactivity in UI via signals."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Signals and reactivity</a></div></main></body></html> </code></div><script>Flems(document.getElementById("code-example-2-b6a1hbrxh7s"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { el, on, dispatchEvent } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.body.append(\\n\\tel(\\\"p\\\", \\\"Listenning to `test` event.\\\", on(\\\"test\\\", console.log)).append(\\n\\t\\tel(\\\"br\\\"),\\n\\t\\tel(\\\"button\\\", \\\"native\\\", on(\\\"click\\\", native)),\\n\\t\\t\\\" \\\",\\n\\t\\tel(\\\"button\\\", \\\"dde\\\", on(\\\"click\\\", dde)),\\n\\t\\t\\\" \\\",\\n\\t\\tel(\\\"button\\\", \\\"dde with options\\\", on(\\\"click\\\", ddeOptions))\\n\\t)\\n);\\nfunction native(){\\n\\tthis.dispatchEvent(\\n\\t\\tnew CustomEvent(\\\"test\\\",\\n\\t\\t\\t{ bubbles: true, detail: \\\"hi\\\" }\\n\\t\\t)\\n\\t);\\n}\\nfunction dde(){\\n\\tdispatchEvent(\\\"test\\\")(this.parentElement, \\\"hi\\\");\\n}\\nfunction ddeOptions(){\\n\\tdispatchEvent(\\\"test\\\", { bubbles: true })(this, \\\"hi\\\");\\n}\\n\"}],\"toolbar\":false}"));</script><div class="notice"><!--<dde:mark type="component" name="mnemonicUl" 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>on(&lt;event&gt;, &lt;listener&gt;[, &lt;options&gt;])(&lt;element&gt;)</code> — just <code>&lt;element&gt;.addEventListener(&lt;event&gt;, &lt;listener&gt;[, &lt;options&gt;])</code></li><li><code>on.&lt;live-cycle&gt;(&lt;event&gt;, &lt;listener&gt;[, &lt;options&gt;])(&lt;element&gt;)</code> — corresponds to custom elemnets callbacks <code>&lt;live-cycle&gt;Callback(...){...}</code>. To connect to custom element see following page, else it is simulated by MutationObserver.</li><li><code>dispatchEvent(&lt;event&gt;[, &lt;options&gt;])(element)</code> — just <code>&lt;element&gt;.dispatchEvent(new Event(&lt;event&gt;[, &lt;options&gt;]))</code></li><li><code>dispatchEvent(&lt;event&gt;[, &lt;options&gt;])(element, detail)</code> — just <code>&lt;element&gt;.dispatchEvent(new CustomEvent(&lt;event&gt;, { detail, ...&lt;options&gt; }))</code></li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="p02-elements" title="Basic concepts of elements modifications and creations."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Elements (previous)</a><a rel="next" href="p04-observables" title="Handling reactivity in UI via observables."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Observables and reactivity</a></div></main></body></html>

35
docs/p04-observables.html Normal file
View File

@ -0,0 +1,35 @@
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Handling reactivity in UI via observables."><title>`deka-dom-el` — Observables and reactivity</title><!--<dde:mark type="component" name="metaAuthor" host="this" ssr/>--><meta name="author" content="Jan Andrle"><link type="text/plain" rel="author" href="https://jaandrle.github.io/humans.txt"><meta name="generator" content="deka-dom-el"><!--<dde:mark type="component" name="metaTwitter" host="this" ssr/>--><meta name="twitter:card" content="summary_large_image"><meta name="twitter:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="twitter:title" content="deka-dom-el"><meta name="twitter:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="twitter:creator" content="@jaandrle"><!--<dde:mark type="component" name="metaFacebook" host="this" ssr/>--><meta name="og:url" content="https://github.com/jaandrle/deka-dom-el"><meta name="og:title" content="deka-dom-el"><meta name="og:description" content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><meta name="og:creator" content="@jaandrle"><script src="https://cdn.jsdelivr.net/npm/shiki" defer=""></script><script type="module" src="code.js.js"></script><script src="https://flems.io/flems.html" type="text/javascript" charset="utf-8"></script><link rel="stylesheet" href="global.css"></head><body><!--<dde:mark type="component" name="page" host="this" ssr/>--><!--<dde:mark type="component" name="simplePage" host="this" ssr/>--><!--<dde:mark type="component" name="header" host="this" ssr/>--><header><h1>`deka-dom-el` — Observables and reactivity</h1><p>Handling reactivity in UI via observables.</p></header><nav><a href="https://github.com/jaandrle/deka-dom-el"><svg class="icon" viewBox="0 0 32 32"><!--<dde:mark type="component" name="iconGitHub" host="parentElement" ssr/>--><path d="M 16,0.395c -8.836,0 -16,7.163 -16,16c 0,7.069 4.585,13.067 10.942,15.182c 0.8,0.148 1.094,-0.347 1.094,-0.77c 0,-0.381 -0.015,-1.642 -0.022,-2.979c -4.452,0.968 -5.391,-1.888 -5.391,-1.888c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341c -1.452,-0.993 0.11,-0.973 0.11,-0.973c 1.606,0.113 2.452,1.649 2.452,1.649c 1.427,2.446 3.743,1.739 4.656,1.33c 0.143,-1.034 0.558,-1.74 1.016,-2.14c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907c 0,-1.747 0.625,-3.174 1.649,-4.295c -0.166,-0.403 -0.714,-2.03 0.155,-4.234c 0,0 1.344,-0.43 4.401,1.64c 1.276,-0.355 2.645,-0.532 4.005,-0.539c 1.359,0.006 2.729,0.184 4.008,0.539c 3.054,-2.07 4.395,-1.64 4.395,-1.64c 0.871,2.204 0.323,3.831 0.157,4.234c 1.026,1.12 1.647,2.548 1.647,4.295c 0,6.145 -3.743,7.498 -7.306,7.895c 0.574,0.497 1.085,1.47 1.085,2.963c 0,2.141 -0.019,3.864 -0.019,4.391c 0,0.426 0.288,0.925 1.099,0.768c 6.354,-2.118 10.933,-8.113 10.933,-15.18c 0,-8.837 -7.164,-16 -16,-16Z"></path></svg>GitHub</a><a href="./" title="Introducing a&nbsp;library.">1. Introduction</a><a href="p02-elements" title="Basic concepts of elements modifications and creations.">2. Elements</a><a href="p03-events" title="Using not only events in UI declaratively.">3. Events and Addons</a><a href="p04-observables" title="Handling reactivity in UI via observables." class="current">4. Observables and reactivity</a></nav><main><h2>Using observables to manage reactivity</h2><p>How a program responds to variable data or user interactions is one of the fundamental problems of programming. If we desire to solve the issue in a declarative manner, observables may be a viable approach.</p><div class="code" data-js="todo"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">// when NPM
import { O } from "deka-dom-el/observables";
// https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js
/**
* @type {ddeObservable}
* */
/**
* @type {ddeActions}
* */
</code></div><h3 id="h-introducing-observables"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-introducing-observables" tabindex="-1">#</a> Introducing observables</h3><p>Using observables, we split program logic into the three parts. Firstly (α), we create a&nbsp;variable (constant) representing reactive value. Somewhere later, we can register (β) a&nbsp;logic reacting to the observable value changes. Similarly, in a&nbsp;remaining part (γ), we can update the observable value.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-v1fw44pkzuo" 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";
// α — `observable` represents a&nbsp;reactive value
const observable= O(0);
// β — just reacts on observable changes
O.on(observable, console.log);
// γ — just updates the value
observable(observable()+1);
setInterval(()=&gt; observable(observable()+1), 5000);
</code></div><script>Flems(document.getElementById("code-example-1-v1fw44pkzuo"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\n// α — `observable` represents a reactive value\\nconst observable= O(0);\\n// β — just reacts on observable changes\\nO.on(observable, console.log);\\n// γ — just updates the value\\nobservable(observable()+1);\\nsetInterval(()=> observable(observable()+1), 5000);\\n\"}],\"toolbar\":false}"));</script><p>All this is just an&nbsp;example of <a href="https://en.wikipedia.org/wiki/Event-driven_programming" title="Wikipedia: Event-driven programming">Event-driven programming</a> and <a href="https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern" title="Wikipedia: Publishsubscribe pattern">Publishsubscribe pattern</a> (compare for example with <a href="https://www.npmjs.com/package/fpubsub" title="NPM package: fpubsub">fpubsub library</a>). All three parts can be in some manner independent and still connected to the same reactive entity.</p><p>Observables are implemented in the library as functions. To see current value of observable, just call it without any arguments <code>console.log(observable())</code>. To update the observable value, pass any argument <code>observable('a new value')</code>. For listenning the observable value changes, use <code>O.on(observable, console.log)</code>.</p><p>Similarly to the <code>on</code> function to register DOM events listener. You can use <code>AbortController</code>/<code>AbortSignal</code> to <em>off</em>/stop listenning. For representing “live” piece of code computation pattern:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-1-1ti9ynadhw5c" 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 observable= O(0);
// computation pattern
const double= O(()=&gt; 2*observable());
const ac= new AbortController();
O.on(observable, v=&gt; console.log("observable", v), { signal: ac.signal });
O.on(double, v=&gt; console.log("double", v), { signal: ac.signal });
observable(observable()+1);
const interval= 5000;
const id= setInterval(()=&gt; observable(observable()+1), interval);
ac.signal.addEventListener("abort",
()=&gt; setTimeout(()=&gt; clearInterval(id), 2*interval));
setTimeout(()=&gt; ac.abort(), 3*interval)
</code></div><script>Flems(document.getElementById("code-example-1-1ti9ynadhw5c"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst observable= O(0);\\n// computation pattern\\nconst double= O(()=> 2*observable());\\n\\nconst ac= new AbortController();\\nO.on(observable, v=> console.log(\\\"observable\\\", v), { signal: ac.signal });\\nO.on(double, v=> console.log(\\\"double\\\", v), { signal: ac.signal });\\n\\nobservable(observable()+1);\\nconst interval= 5000;\\nconst id= setInterval(()=> observable(observable()+1), interval);\\nac.signal.addEventListener(\\\"abort\\\",\\n\\t()=> setTimeout(()=> clearInterval(id), 2*interval));\\n\\nsetTimeout(()=> ac.abort(), 3*interval)\\n\"}],\"toolbar\":false}"));</script><div class="notice"><!--<dde:mark type="component" name="mnemonicUl" 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(&lt;value&gt;)</code> — observable: reactive value</li><li><code>O(()=&gt; &lt;computation&gt;)</code> — observable: reactive value dependent on calculation using other observables</li><li><code>O.on(&lt;observable&gt;, &lt;listener&gt;[, &lt;options&gt;])</code> — listen to the observable value changes</li><li><code>O.clear(...&lt;observables&gt;)</code> — off and clear observables</li><li><code>O(&lt;value&gt;, &lt;actions&gt;)</code> — observable: pattern to create complex reactive objects/arrays</li><li><code>O.action(&lt;observable&gt;, &lt;action-name&gt;, ...&lt;action-arguments&gt;)</code> — invoke an&nbsp;action for given observable</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><!--<dde:mark type="component" name="pageLink" host="this" ssr/>--></div></main></body></html>

View File

@ -26,7 +26,7 @@ import { relative } from "node:path";
export function example({ src, language= "js", page_id }){ export function example({ src, language= "js", page_id }){
registerClientPart(page_id); registerClientPart(page_id);
const content= s.cat(src).toString() const content= s.cat(src).toString()
.replaceAll(/ from "deka-dom-el(\/signals)?";/g, ' from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";'); .replaceAll(/ from "deka-dom-el(\/observables)?";/g, ' from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";');
const id= "code-example-"+generateCodeId(src); const id= "code-example-"+generateCodeId(src);
return el().append( return el().append(
el(code, { id, content, language, className: example.name }), el(code, { id, content, language, className: example.name }),

View File

@ -1,5 +1,5 @@
import { el } from "deka-dom-el"; import { el } from "deka-dom-el";
import { S } from "deka-dom-el/signals"; import { S } from "deka-dom-el/observables";
const clicks= S(0); const clicks= S(0);
document.body.append( document.body.append(
el().append( el().append(

View File

@ -0,0 +1,16 @@
import { O } from "deka-dom-el/observables";
const observable= O(0);
// computation pattern
const double= O(()=> 2*observable());
const ac= new AbortController();
O.on(observable, v=> console.log("observable", v), { signal: ac.signal });
O.on(double, v=> console.log("double", v), { signal: ac.signal });
observable(observable()+1);
const interval= 5000;
const id= setInterval(()=> observable(observable()+1), interval);
ac.signal.addEventListener("abort",
()=> setTimeout(()=> clearInterval(id), 2*interval));
setTimeout(()=> ac.abort(), 3*interval)

View File

@ -1,9 +1,9 @@
// when NPM // when NPM
import { S } from "deka-dom-el/signals"; import { O } from "deka-dom-el/observables";
// https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js // https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js
/** /**
* @type {ddeSignal} * @type {ddeObservable}
* */ * */
/** /**
* @type {ddeActions} * @type {ddeActions}

View File

@ -0,0 +1,8 @@
import { O } from "deka-dom-el/observables";
// α — `observable` represents a reactive value
const observable= O(0);
// β — just reacts on observable changes
O.on(observable, console.log);
// γ — just updates the value
observable(observable()+1);
setInterval(()=> observable(observable()+1), 5000);

View File

@ -1,16 +0,0 @@
import { S } from "deka-dom-el/signals";
const signal= S(0);
// computation pattern
const double= S(()=> 2*signal());
const ac= new AbortController();
S.on(signal, v=> console.log("signal", v), { signal: ac.signal });
S.on(double, v=> console.log("double", v), { signal: ac.signal });
signal(signal()+1);
const interval= 5000;
const id= setInterval(()=> signal(signal()+1), interval);
ac.signal.addEventListener("abort",
()=> setTimeout(()=> clearInterval(id), 2*interval));
setTimeout(()=> ac.abort(), 3*interval)

View File

@ -1,8 +0,0 @@
import { S } from "deka-dom-el/signals";
// α — `signal` represents a reactive value
const signal= S(0);
// β — just reacts on signal changes
S.on(signal, console.log);
// γ — just updates the value
signal(signal()+1);
setInterval(()=> signal(signal()+1), 5000);

View File

@ -23,10 +23,10 @@ export function page({ pkg, info }){
el("p").append( el("p").append(
"Next step is providing interactivity not only for our UI templates.", "Next step is providing interactivity not only for our UI templates.",
" ", " ",
"We introduce signals (", el("code", "S"), ") and how them incorporate to UI templates.", "We introduce observables (", el("code", "O"), ") and how them incorporate to UI templates.",
), ),
el("p").append( el("p").append(
"Now we will clarify how the signals are incorporated into our templates with regard ", "Now we will clarify how the observables are incorporated into our templates with regard ",
"to application performance. This is not the only reason the library uses ", "to application performance. This is not the only reason the library uses ",
el("code", "scope"), "s. We will look at how they work in components represented ", el("code", "scope"), "s. We will look at how they work in components represented ",
"in JavaScript by functions." "in JavaScript by functions."

View File

@ -64,7 +64,7 @@ export function page({ pkg, info }){
"This is handy to concat conditional classes." "This is handy to concat conditional classes."
), ),
el("li").append( el("li").append(
"Use ", el("code", "classList"), " to toggle specific classes. This will be handy later when the reactivity via signals is beeing introduced.", "Use ", el("code", "classList"), " to toggle specific classes. This will be handy later when the reactivity via observables is beeing introduced.",
), ),
el("li").append( el("li").append(
"The ", el("code", "assign"), " also accepts the ", el("code", "undefined"), " as a value for any property to remove it from the element declaratively. ", "The ", el("code", "assign"), " also accepts the ", el("code", "undefined"), " as a value for any property to remove it from the element declaratively. ",

View File

@ -12,24 +12,24 @@ const fileURL= url=> new URL(url, import.meta.url);
export function page({ pkg, info }){ export function page({ pkg, info }){
const page_id= info.id; const page_id= info.id;
return el(simplePage, { info, pkg }).append( return el(simplePage, { info, pkg }).append(
el("h2", "Using signals to manage reactivity"), el("h2", "Using observables to manage reactivity"),
el("p").append( el("p").append(
"How a program responds to variable data or user", "How a program responds to variable data or user",
" interactions is one of the fundamental problems of programming.", " interactions is one of the fundamental problems of programming.",
" If we desire to solve the issue in a declarative manner,", " If we desire to solve the issue in a declarative manner,",
" signals may be a viable approach.", " observables may be a viable approach.",
), ),
el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }), el(code, { src: fileURL("./components/examples/observables/intro.js"), page_id }),
el(h3, "Introducing signals"), el(h3, "Introducing observables"),
el("p").append( el("p").append(
"Using signals, we split program logic into the three parts.", "Using observables, we split program logic into the three parts.",
" Firstly (α), we create a variable (constant) representing reactive", " Firstly (α), we create a variable (constant) representing reactive",
" value. Somewhere later, we can register (β) a logic reacting", " value. Somewhere later, we can register (β) a logic reacting",
" to the signal value changes. Similarly, in a remaining part (γ), we", " to the observable value changes. Similarly, in a remaining part (γ), we",
" can update the signal value." " can update the observable value."
), ),
el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }), el(example, { src: fileURL("./components/examples/observables/observables.js"), page_id }),
el("p").append( el("p").append(
"All this is just an example of ", "All this is just an example of ",
el("a", { textContent: "Event-driven programming", href: "https://en.wikipedia.org/wiki/Event-driven_programming", title: "Wikipedia: Event-driven programming" }), el("a", { textContent: "Event-driven programming", href: "https://en.wikipedia.org/wiki/Event-driven_programming", title: "Wikipedia: Event-driven programming" }),
@ -40,35 +40,35 @@ export function page({ pkg, info }){
" to the same reactive entity." " to the same reactive entity."
), ),
el("p").append( el("p").append(
"Signals are implemented in the library as functions. To see current value", "Observables are implemented in the library as functions. To see current value",
" of signal, just call it without any arguments ", el("code", "console.log(signal())"), ".", " of observable, just call it without any arguments ", el("code", "console.log(observable())"), ".",
" To update the signal value, pass any argument ", el("code", "signal('a new value')"), ".", " To update the observable value, pass any argument ", el("code", "observable('a new value')"), ".",
" For listenning the signal value changes, use ", el("code", "S.on(signal, console.log)"), "." " For listenning the observable value changes, use ", el("code", "O.on(observable, console.log)"), "."
), ),
el("p").append( el("p").append(
"Similarly to the ", el("code", "on"), " function to register DOM events listener.", "Similarly to the ", el("code", "on"), " function to register DOM events listener.",
" You can use ", el("code", "AbortController"), "/", el("code", "AbortSignal"), " to", " You can use ", el("code", "AbortController"), "/", el("code", "AbortSignal"), " to",
" ", el("em", "off"), "/stop listenning. For representing “live” piece of code computation pattern:" " ", el("em", "off"), "/stop listenning. For representing “live” piece of code computation pattern:"
), ),
el(example, { src: fileURL("./components/examples/signals/computations-abort.js"), page_id }), el(example, { src: fileURL("./components/examples/observables/computations-abort.js"), page_id }),
el(mnemonicUl).append( el(mnemonicUl).append(
el("li").append( el("li").append(
el("code", "S(<value>)"), " — signal: reactive value", el("code", "O(<value>)"), " — observable: reactive value",
), ),
el("li").append( el("li").append(
el("code", "S(()=> <computation>)"), " — signal: reactive value dependent on calculation using other signals", el("code", "O(()=> <computation>)"), " — observable: reactive value dependent on calculation using other observables",
), ),
el("li").append( el("li").append(
el("code", "S.on(<signal>, <listener>[, <options>])"), " — listen to the signal value changes", el("code", "O.on(<observable>, <listener>[, <options>])"), " — listen to the observable value changes",
), ),
el("li").append( el("li").append(
el("code", "S.clear(...<signals>)"), " — off and clear signals", el("code", "O.clear(...<observables>)"), " — off and clear observables",
), ),
el("li").append( el("li").append(
el("code", "S(<value>, <actions>)"), " — signal: pattern to create complex reactive objects/arrays", el("code", "O(<value>, <actions>)"), " — observable: pattern to create complex reactive objects/arrays",
), ),
el("li").append( el("li").append(
el("code", "S.action(<signal>, <action-name>, ...<action-arguments>)"), " — invoke an action for given signal" el("code", "O.action(<observable>, <action-name>, ...<action-arguments>)"), " — invoke an action for given observable"
) )
), ),
); );

View File

@ -6,7 +6,7 @@ export const pages= [
{ id: "index", href: "./", title: "Introduction", description: "Introducing a library." }, { id: "index", href: "./", title: "Introduction", description: "Introducing a library." },
{ id: "p02-elements", href: "p02-elements", title: "Elements", description: "Basic concepts of elements modifications and creations." }, { id: "p02-elements", href: "p02-elements", title: "Elements", description: "Basic concepts of elements modifications and creations." },
{ id: "p03-events", href: "p03-events", title: "Events and Addons", description: "Using not only events in UI declaratively." }, { id: "p03-events", href: "p03-events", title: "Events and Addons", description: "Using not only events in UI declaratively." },
{ id: "p04-signals", href: "p04-signals", title: "Signals and reactivity", description: "Handling reactivity in UI via signals." }, { id: "p04-observables", href: "p04-observables", title: "Observables and reactivity", description: "Handling reactivity in UI via observables." },
]; ];
/** /**
* @typedef registerClientFile * @typedef registerClientFile

View File

@ -1,4 +1,4 @@
import { style, el, elNS, on, S, scope } from '../exports.js'; import { style, el, elNS, on, O, scope } from '../exports.js';
const className= style.host(fullNameComponent).css` const className= style.host(fullNameComponent).css`
:host form{ :host form{
display: flex; display: flex;
@ -7,8 +7,8 @@ const className= style.host(fullNameComponent).css`
`; `;
export function fullNameComponent(){ export function fullNameComponent(){
const labels= [ "Name", "Surname" ]; const labels= [ "Name", "Surname" ];
const name= labels.map(_=> S("")); const name= labels.map(_=> O(""));
const full_name= S(()=> const full_name= O(()=>
name.map(l=> l()).filter(Boolean).join(" ") || "-"); name.map(l=> l()).filter(Boolean).join(" ") || "-");
scope.host( scope.host(
on.connected(()=> console.log(fullNameComponent)), on.connected(()=> console.log(fullNameComponent)),

View File

@ -1,4 +1,4 @@
import { style, el, dispatchEvent, on, S, scope } from '../exports.js'; import { style, el, dispatchEvent, on, O, scope } from '../exports.js';
const className= style.host(todosComponent).css` const className= style.host(todosComponent).css`
:host{ :host{
display: flex; display: flex;
@ -16,23 +16,23 @@ const className= style.host(todosComponent).css`
`; `;
/** @param {{ todos: string[] }} */ /** @param {{ todos: string[] }} */
export function todosComponent({ todos= [ "Task A" ] }= {}){ export function todosComponent({ todos= [ "Task A" ] }= {}){
const todosS= S(todos.map(t=> S(t)), { const todosS= O(todos.map(t=> O(t)), {
add(v){ this.value.push(S(v)); }, add(v){ this.value.push(O(v)); },
remove(i){ S.clear(this.value.splice(i, 1)[0]); } remove(i){ O.clear(this.value.splice(i, 1)[0]); }
}); });
const name= "todoName"; const name= "todoName";
const onsubmitAdd= on("submit", event=> { const onsubmitAdd= on("submit", event=> {
const el= event.target.elements[name]; const el= event.target.elements[name];
event.preventDefault(); event.preventDefault();
S.action(todosS, "add", el.value); O.action(todosS, "add", el.value);
el.value= ""; el.value= "";
}); });
const onremove= on("remove", event=> const onremove= on("remove", event=>
S.action(todosS, "remove", event.detail)); O.action(todosS, "remove", event.detail));
const ul_todos= el("ul").append( const ul_todos= el("ul").append(
S.el(todosS, ts=> ts O.el(todosS, ts=> ts
.map((textContent, value)=> .map((textContent, value)=>
el(todoComponent, { textContent, value, className }, onremove)) el(todoComponent, { textContent, value, className }, onremove))
)); ));
@ -40,7 +40,7 @@ export function todosComponent({ todos= [ "Task A" ] }= {}){
el("div").append( el("div").append(
el("h2", "Todos:"), el("h2", "Todos:"),
el("h3", "List of todos:"), el("h3", "List of todos:"),
S.el(todosS, ts=> !ts.length O.el(todosS, ts=> !ts.length
? el("p", "No todos yet") ? el("p", "No todos yet")
: ul_todos), : ul_todos),
el("p", "Click to the text to edit it.") el("p", "Click to the text to edit it.")
@ -54,7 +54,7 @@ export function todosComponent({ todos= [ "Task A" ] }= {}){
), ),
el("div").append( el("div").append(
el("h3", "Output (JSON):"), el("h3", "Output (JSON):"),
el("output", S(()=> JSON.stringify(todosS, null, "\t"))) el("output", O(()=> JSON.stringify(todosS, null, "\t")))
) )
) )
} }
@ -69,13 +69,13 @@ function todoComponent({ textContent, value }){
event.stopPropagation(); event.stopPropagation();
dispatchEvent("remove")(host(), value); dispatchEvent("remove")(host(), value);
}); });
const is_editable= S(false); const is_editable= O(false);
const onedited= on("change", ev=> { const onedited= on("change", ev=> {
textContent(ev.target.value); textContent(ev.target.value);
is_editable(false); is_editable(false);
}); });
return el("li").append( return el("li").append(
S.el(is_editable, is=> is O.el(is_editable, is=> is
? el("input", { value: textContent(), type: "text" }, onedited) ? el("input", { value: textContent(), type: "text" }, onedited)
: el("span", { textContent, onclick: is_editable.bind(null, true) }), : el("span", { textContent, onclick: is_editable.bind(null, true) }),
), ),

View File

@ -1,5 +1,5 @@
import { el, on, scope } from "../../index.js"; import { el, on, scope } from "../../index.js";
import { S } from "../../signals.js"; import { O } from "../../observables.js";
/** /**
* Compatible with `npx -y web-component-analyzer examples/components/webComponent.js` * Compatible with `npx -y web-component-analyzer examples/components/webComponent.js`
@ -24,8 +24,8 @@ export class CustomHTMLTestElement extends HTMLElement{
on.attributeChanged(e=> console.log(e)), on.attributeChanged(e=> console.log(e)),
on.disconnected(()=> console.log(CustomHTMLTestElement)) on.disconnected(()=> console.log(CustomHTMLTestElement))
); );
const name= S.attribute("name"); const name= O.attribute("name");
const preName= S.attribute("pre-name"); const preName= O.attribute("pre-name");
console.log({ name, test, preName}); console.log({ name, test, preName});
return el("p").append( return el("p").append(

View File

@ -1,10 +1,10 @@
import * as dde_dom from "../index.js"; import * as dde_dom from "../index.js";
export * from "../index.js"; export * from "../index.js";
import * as dde_s from "../signals.js"; import * as dde_s from "../observables.js";
export * from "../signals.js"; export * from "../observables.js";
Object.assign(globalThis, dde_dom, dde_s); Object.assign(globalThis, dde_dom, dde_s);
//import * as dde_dom from "../dist/esm-with-signals.js"; //import * as dde_dom from "../dist/esm-with-observables.js";
//export * from "../dist/esm-with-signals.js"; //export * from "../dist/esm-with-observables.js";
//Object.assign(globalThis, dde_dom); //Object.assign(globalThis, dde_dom);
export const style= createStyle(); export const style= createStyle();

2
index-with-observables.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
export * from "./index";
export * from "./observables";

View File

@ -0,0 +1,2 @@
export * from "./index.js";
export * from "./observables.js";

View File

@ -1,2 +0,0 @@
export * from "./index";
export * from "./signals";

View File

@ -1,2 +0,0 @@
export * from "./index.js";
export * from "./signals.js";

4
index.d.ts vendored
View File

@ -152,10 +152,10 @@ export const scope: {
preventDefault<T extends boolean>(prevent: T): T, preventDefault<T extends boolean>(prevent: T): T,
/** /**
* This represents reference to the current host element `scope.host()`. * This represents reference to the current host element `scope.host()`.
* It can be also used to register Addon (function to be called when component is initized) * It can be also used to register Addon(s) (functions to be called when component is initized)
* `scope.host(on.connected(console.log))`. * `scope.host(on.connected(console.log))`.
* */ * */
host: ddeElementAddon<any>, host: (...addons: ddeElementAddon<any>[])=> HTMLElement,
state: Scope[], state: Scope[],
/** Adds new child scope. All attributes are inherited by default. */ /** Adds new child scope. All attributes are inherited by default. */

64
observables.d.ts vendored Normal file
View File

@ -0,0 +1,64 @@
export type Observable<V, A>= (set?: V)=> V & A;
type Action<V>= (this: { value: V }, ...a: any[])=> typeof observable._ | void;
type SymbolOnclear= Symbol;
type SymbolObservable= Symbol;
type Actions<V>= Record<string, Action<V>>;
interface observable{
_: Symbol
/**
* Simple example:
* ```js
* const hello= S("Hello Observable");
* ```
* simple todo observable:
* ```js
* const todos= S([], {
* add(v){ this.value.push(S(v)); },
* remove(i){ this.value.splice(i, 1); },
* [S.symbols.onclear](){ S.clear(...this.value); },
* });
* ```
* computed observable:
* ```js
* const name= S("Jan");
* const surname= S("Andrle");
* const fullname= S(()=> name()+" "+surname());
* ```
* @param value Initial observable value. Or function computing value from other observables.
* @param actions Use to define actions on the observable. Such as add item to the array.
* There is also a reserved function `S.symbol.onclear` which is called when the observable is cleared
* by `S.clear`.
* */
<V, A extends Actions<V>>(value: V, actions?: A): Observable<V, A>;
/**
* Computations observable. This creates a observable which is computed from other observables.
* */
<V>(computation: ()=> V): Observable<V, {}>
action<S extends Observable<any, Actions<any>>, A extends (S extends Observable<any, infer A> ? A : never), N extends keyof A>(
observable: S,
name: N,
...params: A[N] extends (...args: infer P)=> any ? P : never
): void;
clear(...observables: Observable<any, any>[]): void;
on<T>(observable: Observable<T, any>, onchange: (a: T)=> void, options?: AddEventListenerOptions): void;
symbols: {
observable: SymbolObservable;
onclear: SymbolOnclear;
}
/**
* Reactive element, which is rendered based on the given observable.
* ```js
* S.el(observable, value=> value ? el("b", "True") : el("i", "False"));
* S.el(listS, list=> list.map(li=> el("li", li)));
* ```
* */
el<S extends any>(observable: Observable<S, any>, el: (v: S)=> Element | Element[]): DocumentFragment;
attribute(name: string, initial?: string): Observable<string, {}>;
}
export const observable: observable;
export const O: observable;
declare global {
type ddeObservable<T, A= {}>= Observable<T, A>;
type ddeActions<V>= Actions<V>
}

4
observables.js Normal file
View File

@ -0,0 +1,4 @@
export { observable, O, isObservable } from "./src/observables-lib.js";
import { observables_config } from "./src/observables-lib.js";
import { registerReactivity } from "./src/observables-common.js";
registerReactivity(observables_config);

View File

@ -24,13 +24,13 @@
"import": "./jsdom.js", "import": "./jsdom.js",
"types": "./jsdom.d.ts" "types": "./jsdom.d.ts"
}, },
"./signals": { "./observables": {
"import": "./signals.js", "import": "./observables.js",
"types": "./signals.d.ts" "types": "./observables.d.ts"
}, },
"./src/signals-lib": { "./src/observables-lib": {
"import": "./src/signals-lib.js", "import": "./src/observables-lib.js",
"types": "./src/signals.d.ts" "types": "./src/observables.d.ts"
} }
}, },
"files": [ "files": [
@ -62,7 +62,7 @@
"gzip": false "gzip": false
}, },
{ {
"path": "./signals.js", "path": "./observables.js",
"limit": "11.5 kB", "limit": "11.5 kB",
"gzip": false "gzip": false
}, },

63
signals.d.ts vendored
View File

@ -1,63 +0,0 @@
export type Signal<V, A>= (set?: V)=> V & A;
type Action<V>= (this: { value: V }, ...a: any[])=> typeof S._ | void;
type SymbolOnclear= Symbol;
type SymbolSignal= Symbol;
type Actions<V>= Record<string, Action<V>>;
interface S {
_: Symbol
/**
* Simple example:
* ```js
* const hello= S("Hello Signal");
* ```
* simple todo signal:
* ```js
* const todos= S([], {
* add(v){ this.value.push(S(v)); },
* remove(i){ this.value.splice(i, 1); },
* [S.symbols.onclear](){ S.clear(...this.value); },
* });
* ```
* computed signal:
* ```js
* const name= S("Jan");
* const surname= S("Andrle");
* const fullname= S(()=> name()+" "+surname());
* ```
* @param value Initial signal value. Or function computing value from other signals.
* @param actions Use to define actions on the signal. Such as add item to the array.
* There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared
* by `S.clear`.
* */
<V, A extends Actions<V>>(value: V, actions?: A): Signal<V, A>;
/**
* Computations signal. This creates a signal which is computed from other signals.
* */
<V>(computation: ()=> V): Signal<V, {}>
action<S extends Signal<any, Actions<any>>, A extends (S extends Signal<any, infer A> ? A : never), N extends keyof A>(
signal: S,
name: N,
...params: A[N] extends (...args: infer P)=> any ? P : never
): void;
clear(...signals: Signal<any, any>[]): void;
on<T>(signal: Signal<T, any>, onchange: (a: T)=> void, options?: AddEventListenerOptions): void;
symbols: {
signal: SymbolSignal;
onclear: SymbolOnclear;
}
/**
* Reactive element, which is rendered based on the given signal.
* ```js
* S.el(signal, value=> value ? el("b", "True") : el("i", "False"));
* S.el(listS, list=> list.map(li=> el("li", li)));
* ```
* */
el<S extends any>(signal: Signal<S, any>, el: (v: S)=> Element | Element[]): DocumentFragment;
attribute(name: string, initial?: string): Signal<string, {}>;
}
export const S: S;
declare global {
type ddeSignal<T, A= {}>= Signal<T, A>;
type ddeActions<V>= Actions<V>
}

View File

@ -1,4 +0,0 @@
export { S, isSignal } from "./src/signals-lib.js";
import { signals_config } from "./src/signals-lib.js";
import { registerReactivity } from "./src/signals-common.js";
registerReactivity(signals_config);

View File

@ -1,4 +1,4 @@
import { signals } from "./signals-common.js"; import { observables } from "./observables-common.js";
import { enviroment } from './dom-common.js'; import { enviroment } from './dom-common.js';
/** @type {{ scope: object, prevent: boolean, host: function }[]} */ /** @type {{ scope: object, prevent: boolean, host: function }[]} */
@ -32,11 +32,11 @@ export function chainableAppend(el){ if(el.append===append) return el; el.append
let namespace; let namespace;
export function createElement(tag, attributes, ...addons){ export function createElement(tag, attributes, ...addons){
/* jshint maxcomplexity: 15 */ /* jshint maxcomplexity: 15 */
const s= signals(this); const s= observables(this);
let scoped= 0; let scoped= 0;
let el, el_host; let el, el_host;
//TODO Array.isArray(tag) ⇒ set key (cache els) //TODO Array.isArray(tag) ⇒ set key (cache els)
if(Object(attributes)!==attributes || s.isSignal(attributes)) if(Object(attributes)!==attributes || s.isObservable(attributes))
attributes= { textContent: attributes }; attributes= { textContent: attributes };
switch(true){ switch(true){
case typeof tag==="function": { case typeof tag==="function": {
@ -168,11 +168,11 @@ function assignContext(element, _this){
if(assign_context.has(element)) return assign_context.get(element); if(assign_context.has(element)) return assign_context.get(element);
const is_svg= element instanceof SVGElement; const is_svg= element instanceof SVGElement;
const setRemoveAttr= (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute"); const setRemoveAttr= (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
const s= signals(_this); const s= observables(_this);
return { setRemoveAttr, s }; return { setRemoveAttr, s };
} }
export function classListDeclarative(element, toggle){ export function classListDeclarative(element, toggle){
const s= signals(this); const s= observables(this);
forEachEntries(s, toggle, forEachEntries(s, toggle,
(class_name, val)=> (class_name, val)=>
element.classList.toggle(class_name, val===-1 ? undefined : Boolean(val))); element.classList.toggle(class_name, val===-1 ? undefined : Boolean(val)));

View File

@ -1,4 +1,4 @@
export { registerReactivity } from './signals-common.js'; export { registerReactivity } from './observables-common.js';
export function dispatchEvent(name, options, host){ export function dispatchEvent(name, options, host){
if(!options) options= {}; if(!options) options= {};

13
src/observables-common.js Normal file
View File

@ -0,0 +1,13 @@
export const observables_global= {
isObservable(attributes){ return false; },
processReactiveAttribute(obj, key, attr, set){ return attr; },
};
export function registerReactivity(def, global= true){
if(global) return Object.assign(observables_global, def);
Object.setPrototypeOf(def, observables_global);
return def;
}
/** @param {unknown} _this @returns {typeof observables_global} */
export function observables(_this){
return observables_global.isPrototypeOf(_this) && _this!==observables_global ? _this : observables_global;
}

View File

@ -1,25 +1,25 @@
export const mark= Symbol.for("Signal"); export const mark= Symbol.for("observable");
export function isSignal(candidate){ export function isObservable(candidate){
try{ return Reflect.has(candidate, mark); } try{ return Reflect.has(candidate, mark); }
catch(e){ return false; } catch(e){ return false; }
} }
/** @type {function[]} */ /** @type {function[]} */
const stack_watch= []; const stack_watch= [];
/** /**
* ### `WeakMap<function, Set<ddeSignal<any, any>>>` * ### `WeakMap<function, Set<ddeObservable<any, any>>>`
* The `Set` is in the form of `[ source, ...depended signals (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 (`S.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`.
* @type {WeakMap<function|object,Set<ddeSignal<any, any>>|function>} * @type {WeakMap<function|object,Set<ddeObservable<any, any>>|function>}
* */ * */
const deps= new WeakMap(); const deps= new WeakMap();
export function S(value, actions){ export function observable(value, actions){
if(typeof value!=="function") if(typeof value!=="function")
return create(value, actions); return create(value, actions);
if(isSignal(value)) return value; if(isObservable(value)) return value;
const out= create(); const out= create();
const contextReWatch= function(){ const contextReWatch= function(){
@ -32,9 +32,9 @@ export function S(value, actions){
if(!deps_old.length) return; if(!deps_old.length) return;
const deps_curr= deps.get(contextReWatch); const deps_curr= deps.get(contextReWatch);
for (const dep_signal of deps_old){ for (const dep_observable of deps_old){
if(deps_curr.has(dep_signal)) continue; if(deps_curr.has(dep_observable)) continue;
removeSignalListener(dep_signal, contextReWatch); removeObservableListener(dep_observable, contextReWatch);
} }
}; };
deps.set(out[mark], contextReWatch); deps.set(out[mark], contextReWatch);
@ -42,44 +42,45 @@ export function S(value, actions){
contextReWatch(); contextReWatch();
return out; return out;
} }
S.action= function(signal, name, ...a){ export { observable as O };
const s= signal[mark], { actions }= s; observable.action= function(observable, name, ...a){
const s= observable[mark], { actions }= s;
if(!actions || !Reflect.has(actions, name)) if(!actions || !Reflect.has(actions, name))
throw new Error(`'${signal}' has no action with name '${name}'!`); throw new Error(`'${observable}' has no action with name '${name}'!`);
actions[name].apply(s, a); actions[name].apply(s, a);
if(s.skip) return Reflect.deleteProperty(s, "skip"); if(s.skip) return Reflect.deleteProperty(s, "skip");
s.listeners.forEach(l=> l(s.value)); s.listeners.forEach(l=> l(s.value));
}; };
S.on= function on(signals, listener, options= {}){ observable.on= function on(observables, listener, options= {}){
const { signal: as }= options; const { observable: as }= options;
if(as && as.aborted) return; if(as && as.aborted) return;
if(Array.isArray(signals)) return signals.forEach(s=> on(s, listener, options)); if(Array.isArray(observables)) return observables.forEach(s=> on(s, listener, options));
addSignalListener(signals, listener); addObservableListener(observables, listener);
if(as) as.addEventListener("abort", ()=> removeSignalListener(signals, listener)); if(as) as.addEventListener("abort", ()=> removeObservableListener(observables, listener));
//TODO cleanup when signal removed //TODO cleanup when observable removed
}; };
S.symbols= { observable.symbols= {
signal: mark, observable: mark,
onclear: Symbol.for("Signal.onclear") onclear: Symbol.for("Observable.onclear")
}; };
S.clear= function(...signals){ observable.clear= function(...observables){
for(const signal of signals){ for(const observable of observables){
Reflect.deleteProperty(signal, "toJSON"); Reflect.deleteProperty(observable, "toJSON");
const s= signal[mark]; const s= observable[mark];
s.onclear.forEach(f=> f.call(s)); s.onclear.forEach(f=> f.call(s));
clearListDeps(signal, s); clearListDeps(observable, s);
Reflect.deleteProperty(signal, mark); Reflect.deleteProperty(observable, mark);
} }
function clearListDeps(signal, s){ function clearListDeps(observable, s){
s.listeners.forEach(l=> { s.listeners.forEach(l=> {
s.listeners.delete(l); s.listeners.delete(l);
if(!deps.has(l)) return; if(!deps.has(l)) return;
const ls= deps.get(l); const ls= deps.get(l);
ls.delete(signal); ls.delete(observable);
if(ls.size>1) return; if(ls.size>1) return;
S.clear(...ls); observable.clear(...ls);
deps.delete(l); deps.delete(l);
}); });
} }
@ -87,7 +88,7 @@ S.clear= function(...signals){
const key_reactive= "__dde_reactive"; const key_reactive= "__dde_reactive";
import { el, elementAttribute } from "./dom.js"; import { el, elementAttribute } from "./dom.js";
import { scope } from "./dom.js"; import { scope } from "./dom.js";
S.el= function(signal, map){ observable.el= function(observable, map){
const mark_start= el.mark({ type: "reactive" }, false); const mark_start= el.mark({ type: "reactive" }, false);
const mark_end= mark_start.end; const mark_end= mark_start.end;
const out= document.createDocumentFragment(); const out= document.createDocumentFragment();
@ -95,7 +96,7 @@ S.el= function(signal, map){
const { current }= scope; const { current }= scope;
const reRenderReactiveElement= v=> { const reRenderReactiveElement= v=> {
if(!mark_start.parentNode || !mark_end.parentNode) if(!mark_start.parentNode || !mark_end.parentNode)
return removeSignalListener(signal, reRenderReactiveElement); return removeObservableListener(observable, reRenderReactiveElement);
scope.push(current); scope.push(current);
let els= map(v); let els= map(v);
scope.pop(); scope.pop();
@ -106,16 +107,16 @@ S.el= function(signal, map){
el_r.remove(); el_r.remove();
mark_start.after(...els); mark_start.after(...els);
}; };
addSignalListener(signal, reRenderReactiveElement); addObservableListener(observable, reRenderReactiveElement);
removeSignalsFromElements(signal, reRenderReactiveElement, mark_start, map); removeObservablesFromElements(observable, reRenderReactiveElement, mark_start, map);
reRenderReactiveElement(signal()); reRenderReactiveElement(observable());
return out; return out;
}; };
import { on } from "./events.js"; import { on } from "./events.js";
const key_attributes= "__dde_attributes"; const key_attributes= "__dde_attributes";
S.attribute= function(name, initial= null){ observable.attribute= function(name, initial= null){
//TODO host=element & reuse existing //TODO host=element & reuse existing
const out= S(initial); const out= observable(initial);
let element; let element;
scope.host(el=> { scope.host(el=> {
element= el; element= el;
@ -127,17 +128,17 @@ S.attribute= function(name, initial= null){
return; return;
} }
element[key_attributes]= { [name]: out }; element[key_attributes]= { [name]: out };
on.attributeChanged(function attributeChangeToSignal({ detail }){ on.attributeChanged(function attributeChangeToObservable({ detail }){
/*! This maps attributes to signals (`S.attribute`). /*! This maps attributes to observables (`S.attribute`).
* 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 curr(value);
})(element); })(element);
on.disconnected(function(){ on.disconnected(function(){
/*! This removes all signals mapped to attributes (`S.attribute`). /*! This removes all observables mapped to attributes (`S.attribute`).
* Investigate `__dde_attributes` key of the element.*/ * Investigate `__dde_attributes` key of the element.*/
S.clear(...Object.values(element[key_attributes])); observable.clear(...Object.values(element[key_attributes]));
})(element); })(element);
}); });
return new Proxy(out, { return new Proxy(out, {
@ -150,17 +151,17 @@ S.attribute= function(name, initial= null){
}; };
import { typeOf } from './helpers.js'; import { typeOf } from './helpers.js';
export const signals_config= { export const observables_config= {
isSignal, isObservable,
processReactiveAttribute(element, key, attrs, set){ processReactiveAttribute(element, key, attrs, set){
if(!isSignal(attrs)) return attrs; if(!isObservable(attrs)) return attrs;
const l= attr=> set(key, attr); const l= attr=> set(key, attr);
addSignalListener(attrs, l); addObservableListener(attrs, l);
removeSignalsFromElements(attrs, l, element, key); removeObservablesFromElements(attrs, l, element, key);
return attrs(); return attrs();
} }
}; };
function removeSignalsFromElements(signal, listener, ...notes){ function removeObservablesFromElements(observable, listener, ...notes){
const { current }= scope; const { current }= scope;
if(current.prevent) return; if(current.prevent) return;
current.host(function(element){ current.host(function(element){
@ -168,28 +169,28 @@ function removeSignalsFromElements(signal, listener, ...notes){
element[key_reactive]= []; element[key_reactive]= [];
on.disconnected(()=> on.disconnected(()=>
/*! /*!
* Clears all signals listeners added in the current scope/host (`S.el`, `assign`, ?). * Clears all Observables listeners added in the current scope/host (`S.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(([ [ signal, listener ] ])=> element[key_reactive].forEach(([ [ observable, listener ] ])=>
removeSignalListener(signal, listener, signal[mark]?.host() === element)) removeObservableListener(observable, listener, observable[mark]?.host() === element))
)(element); )(element);
} }
element[key_reactive].push([ [ signal, listener ], ...notes ]); element[key_reactive].push([ [ observable, listener ], ...notes ]);
}); });
} }
function create(value, actions){ function create(value, actions){
const signal= (...value)=> const observable= (...value)=>
value.length ? write(signal, ...value) : read(signal); value.length ? write(observable, ...value) : read(observable);
return toSignal(signal, value, actions); return toObservable(observable, value, actions);
} }
const protoSigal= Object.assign(Object.create(null), { const protoSigal= Object.assign(Object.create(null), {
stopPropagation(){ stopPropagation(){
this.skip= true; this.skip= true;
} }
}); });
class SignalDefined extends Error{ class ObservableDefined extends Error{
constructor(){ constructor(){
super(); super();
const [ curr, ...rest ]= this.stack.split("\n"); const [ curr, ...rest ]= this.stack.split("\n");
@ -197,64 +198,64 @@ class SignalDefined extends Error{
this.stack= rest.find(l=> !l.includes(curr_file)); this.stack= rest.find(l=> !l.includes(curr_file));
} }
} }
function toSignal(signal, value, actions){ function toObservable(observable, value, actions){
const onclear= []; const onclear= [];
if(typeOf(actions)!=="[object Object]") if(typeOf(actions)!=="[object Object]")
actions= {}; actions= {};
const { onclear: ocs }= S.symbols; const { onclear: ocs }= observable.symbols;
if(actions[ocs]){ if(actions[ocs]){
onclear.push(actions[ocs]); onclear.push(actions[ocs]);
Reflect.deleteProperty(actions, ocs); Reflect.deleteProperty(actions, ocs);
} }
const { host }= scope; const { host }= scope;
Reflect.defineProperty(signal, mark, { Reflect.defineProperty(observable, mark, {
value: { value: {
value, actions, onclear, host, value, actions, onclear, host,
listeners: new Set(), listeners: new Set(),
defined: new SignalDefined() defined: new ObservableDefined()
}, },
enumerable: false, enumerable: false,
writable: false, writable: false,
configurable: true configurable: true
}); });
signal.toJSON= ()=> signal(); observable.toJSON= ()=> observable();
Object.setPrototypeOf(signal[mark], protoSigal); Object.setPrototypeOf(observable[mark], protoSigal);
return signal; return observable;
} }
function currentContext(){ function currentContext(){
return stack_watch[stack_watch.length - 1]; return stack_watch[stack_watch.length - 1];
} }
function read(signal){ function read(observable){
if(!signal[mark]) return; if(!observable[mark]) return;
const { value, listeners }= signal[mark]; const { value, listeners }= observable[mark];
const context= currentContext(); const context= currentContext();
if(context) listeners.add(context); if(context) listeners.add(context);
if(deps.has(context)) deps.get(context).add(signal); if(deps.has(context)) deps.get(context).add(observable);
return value; return value;
} }
function write(signal, value, force){ function write(observable, value, force){
if(!signal[mark]) return; if(!observable[mark]) return;
const s= signal[mark]; const s= observable[mark];
if(!force && s.value===value) return; if(!force && s.value===value) return;
s.value= value; s.value= value;
s.listeners.forEach(l=> l(value)); s.listeners.forEach(l=> l(value));
return value; return value;
} }
function addSignalListener(signal, listener){ function addObservableListener(observable, listener){
if(!signal[mark]) return; if(!observable[mark]) return;
return signal[mark].listeners.add(listener); return observable[mark].listeners.add(listener);
} }
function removeSignalListener(signal, listener, clear_when_empty){ function removeObservableListener(observable, listener, clear_when_empty){
const s= signal[mark]; const s= observable[mark];
if(!s) return; if(!s) return;
const out= s.listeners.delete(listener); const out= s.listeners.delete(listener);
if(clear_when_empty && !s.listeners.size){ if(clear_when_empty && !s.listeners.size){
S.clear(signal); observable.clear(observable);
if(!deps.has(s)) return out; if(!deps.has(s)) return out;
const c= deps.get(s); const c= deps.get(s);
if(!deps.has(c)) return out; if(!deps.has(c)) return out;
deps.get(c).forEach(sig=> removeSignalListener(sig, c, true)); deps.get(c).forEach(sig=> removeObservableListener(sig, c, true));
} }
return out; return out;
} }

View File

@ -1,13 +0,0 @@
export const signals_global= {
isSignal(attributes){ return false; },
processReactiveAttribute(obj, key, attr, set){ return attr; },
};
export function registerReactivity(def, global= true){
if(global) return Object.assign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
/** @param {unknown} _this @returns {typeof signals_global} */
export function signals(_this){
return signals_global.isPrototypeOf(_this) && _this!==signals_global ? _this : signals_global;
}