From fb14d51cb4507e611df81b2bcb90d481022709f3 Mon Sep 17 00:00:00 2001 From: Jan Andrle Date: Thu, 30 Nov 2023 17:05:19 +0100 Subject: [PATCH] :tada: docs/p05-scopes --- bs/docs.js | 11 ++- bs/docs/jsdom.js | 22 ++++-- docs/index.html | 2 +- docs/p02-elements.html | 2 +- docs/p03-events.html | 2 +- docs/p04-observables.html | 4 +- docs/p05-scopes.html | 78 +++++++++++++++++++ .../examples/scopes/class-component.js | 41 ++++++++++ docs_src/components/examples/scopes/intro.js | 3 + .../examples/scopes/scopes-and-hosts.js | 33 ++++++++ docs_src/components/mnemonic/scopes-init.js | 16 ++++ docs_src/p05-scopes.html.js | 41 ++++++++++ docs_src/ssr.js | 1 + 13 files changed, 240 insertions(+), 16 deletions(-) create mode 100644 docs/p05-scopes.html create mode 100644 docs_src/components/examples/scopes/class-component.js create mode 100644 docs_src/components/examples/scopes/intro.js create mode 100644 docs_src/components/examples/scopes/scopes-and-hosts.js create mode 100644 docs_src/components/mnemonic/scopes-init.js create mode 100644 docs_src/p05-scopes.html.js diff --git a/bs/docs.js b/bs/docs.js index f87bb61..bb2cb9b 100755 --- a/bs/docs.js +++ b/bs/docs.js @@ -10,16 +10,19 @@ const pkg= s.cat("package.json").xargs(JSON.parse); for(const info of pages){ const { id }= info; echo(`Generating ${id}.html…`); - const ssr= createHTMl(""); - const { el }= await register(ssr.dom); + const serverDOM= createHTMl(""); + serverDOM.registerGlobally( + "HTMLScriptElement" + ); + const { el }= await register(serverDOM.dom); const { page }= await import(`../docs_src/${id}.html.js`); //→ TODO: important to mention in docs!!! - document.body.append( + serverDOM.document.body.append( el(page, { pkg, info }), ); echo.use("-R", `Writing ${id}.html…`); dispatchEvent("oneachrender", document); - s.echo(ssr.serialize()).to(path_target.root+id+".html"); + s.echo(serverDOM.serialize()).to(path_target.root+id+".html"); } s.echo(styles.content).to(path_target.css+styles.name); dispatchEvent("onssrend"); diff --git a/bs/docs/jsdom.js b/bs/docs/jsdom.js index 339cde3..bad3f28 100644 --- a/bs/docs/jsdom.js +++ b/bs/docs/jsdom.js @@ -4,6 +4,7 @@ let keys= []; let dom= null; import { relative } from 'node:path'; import { writeFileSync } from 'node:fs'; +/** @param {string} html */ export function createHTMl(html, options= {}){ if(!html) html= html_default; if(dom) cleanHTML(); @@ -12,23 +13,30 @@ export function createHTMl(html, options= {}){ dom= new JSDOM(html, options); const window= dom.window; - if(!keys.length) - keys= Object.getOwnPropertyNames(window).filter((k) => !k.startsWith('_') && !(k in globalThis)); - keys.forEach(key=> globalThis[key]= window[key]); - globalThis.document= window.document - globalThis.window= window - window.console= globalThis.console return { dom, writeToFS({ html, css }){ if(css) this.style.writeToFile(css, relative(html, css).slice(1)); if(html) writeFileSync(html, dom.serialize()); }, - serialize(){ return dom.serialize(); } + /** @param {string[]} [names] */ + registerGlobally(...names){ + keys.push(...names); + if(!keys.length) + keys= Object.getOwnPropertyNames(window).filter((k) => !k.startsWith('_') && !(k in globalThis)); + keys.forEach(key=> globalThis[key]= window[key]); + globalThis.document= window.document + globalThis.window= window + window.console= globalThis.console + return this; + }, + serialize(){ return dom.serialize(); }, + window, document: window.document }; } export function cleanHTML(){ if(!dom) return; keys.forEach(key=> delete globalThis[key]); + keys.length= 0; dom= false; } diff --git a/docs/index.html b/docs/index.html index 66c95b1..dbaa66f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,4 +1,4 @@ -`deka-dom-el` — Introduction

`deka-dom-el` — Introduction

Introducing a library.

The library tries to provide pure JavaScript tool(s) to create reactive interfaces.

We start with creating and modifying a static elements and end up with UI templates. From document.createElement to el. Then we go through the native events system and the way to include it declaratively in UI templates. From element.addEventListener to on.

Next step is providing interactivity not only for our UI templates. We introduce observables (O) and how them incorporate to UI templates.

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 scopes. We will look at how they work in components represented in JavaScript by functions.

import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js"; +`deka-dom-el` — Introduction

`deka-dom-el` — Introduction

Introducing a library.

The library tries to provide pure JavaScript tool(s) to create reactive interfaces.

We start with creating and modifying a static elements and end up with UI templates. From document.createElement to el. Then we go through the native events system and the way to include it declaratively in UI templates. From element.addEventListener to on.

Next step is providing interactivity not only for our UI templates. We introduce observables (O) and how them incorporate to UI templates.

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 scopes. We will look at how they work in components represented in JavaScript by functions.

import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js"; import { O } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js"; const clicks= O(0); document.body.append( diff --git a/docs/p02-elements.html b/docs/p02-elements.html index 5f8e9ea..ed6b19e 100644 --- a/docs/p02-elements.html +++ b/docs/p02-elements.html @@ -1,4 +1,4 @@ -`deka-dom-el` — Elements

`deka-dom-el` — Elements

Basic concepts of elements modifications and creations.

Native JavaScript DOM elements creations

Let’s go through all patterns we would like to use and what needs to be improved for better experience.

// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm.js +`deka-dom-el` — Elements

`deka-dom-el` — Elements

Basic concepts of elements modifications and creations.

Native JavaScript DOM elements creations

Let’s go through all patterns we would like to use and what needs to be improved for better experience.

// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm.js import { assign, el, createElement, diff --git a/docs/p03-events.html b/docs/p03-events.html index eeeb376..55f7ded 100644 --- a/docs/p03-events.html +++ b/docs/p03-events.html @@ -1,4 +1,4 @@ -`deka-dom-el` — Events and Addons

`deka-dom-el` — Events and Addons

Using not only events in UI declaratively.

Listenning to the native DOM events and other Addons

We quickly introduce helper to listening to the native DOM events. And library syntax/pattern so-called Addon to incorporate not only this in UI templates declaratively.

// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm.js +`deka-dom-el` — Events and Addons

`deka-dom-el` — Events and Addons

Using not only events in UI declaratively.

Listenning to the native DOM events and other Addons

We quickly introduce helper to listening to the native DOM events. And library syntax/pattern so-called Addon to incorporate not only this in UI templates declaratively.

// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm.js import { on, dispatchEvent } from "deka-dom-el"; /** @type {ddeElementAddon} */ diff --git a/docs/p04-observables.html b/docs/p04-observables.html index ce28451..85f730a 100644 --- a/docs/p04-observables.html +++ b/docs/p04-observables.html @@ -1,4 +1,4 @@ -`deka-dom-el` — Observables and reactivity

`deka-dom-el` — Observables and reactivity

Handling reactivity in UI via observables.

Using observables to manage reactivity

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.

// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js +`deka-dom-el` — Observables and reactivity

`deka-dom-el` — Observables and reactivity

Handling reactivity in UI via observables.

Using observables to manage reactivity

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.

// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js import { O, observable } from "deka-dom-el/observables"; O===observable /** @type {ddeObservable} */ @@ -147,4 +147,4 @@ setTimeout(clearInterval, 10*interval, setInterval(function(){ O.action(count, "add"); O.action(numbers, "push", count()); }, interval)); -

# Mnemonic

  • O(<value>) — observable: reactive value
  • O(()=> <computation>) — observable: reactive value dependent on calculation using other observables
  • O.on(<observable>, <listener>[, <options>]) — listen to the observable value changes
  • O.clear(...<observables>) — off and clear observables
  • O(<value>, <actions>) — observable: pattern to create complex reactive objects/arrays
  • O.action(<observable>, <action-name>, ...<action-arguments>) — invoke an action for given observable
  • O.el(<observable>, <function-returning-dom>) — render partial dom structure (template) based on the current observable value
\ No newline at end of file +

# Mnemonic

  • O(<value>) — observable: reactive value
  • O(()=> <computation>) — observable: reactive value dependent on calculation using other observables
  • O.on(<observable>, <listener>[, <options>]) — listen to the observable value changes
  • O.clear(...<observables>) — off and clear observables
  • O(<value>, <actions>) — observable: pattern to create complex reactive objects/arrays
  • O.action(<observable>, <action-name>, ...<action-arguments>) — invoke an action for given observable
  • O.el(<observable>, <function-returning-dom>) — render partial dom structure (template) based on the current observable value
\ No newline at end of file diff --git a/docs/p05-scopes.html b/docs/p05-scopes.html new file mode 100644 index 0000000..2ab2fa7 --- /dev/null +++ b/docs/p05-scopes.html @@ -0,0 +1,78 @@ +`deka-dom-el` — Scopes and components

`deka-dom-el` — Scopes and components

Organizing UI into components

Using functions as UI components

For state-less components we can use functions as UI components (see “Elements” page). But in real life, we may need to handle the component live-cycle and provide JavaScript the way to properly use the Garbage collection.

// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js +import { scope, el, on } from "deka-dom-el"; +/** @type {ddeElementAddon} */ +

The library therefore use scopes to provide these functionalities.

# Scopes and hosts

The host is the name for the element representing the component. This is typically element returned by function. To get reference, you can use scope.host() to applly addons just use scope.host(...<addons>).

import { el, on, scope } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js"; +const { host }= scope; +host( + element=> console.log( + "This represents Addon/oninit for root", + element.outerHTML + ) +); +console.log( + "This represents the reference to the host element of root", + host().outerHTML +); +document.body.append( + el(component) +); +function component(){ + const { host }= scope; + host( + element=> console.log( + "This represents Addon/oninit for the component", + element.outerHTML + ) + ); + const onclick= on("click", function(ev){ + console.log( + "This represents the reference to the host element of the component", + host().outerHTML + ); + }) + return el("div", null, onclick).append( + el("strong", "Component") + ); +} +

To better understanding we implement function elClass helping to create component as class instances.

import { chainableAppend, el, scope } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js"; +class Test { + constructor(params){ + this._params= params; + } + render(){ + return el("div").append( + this._params.textContent + ); + } +} +document.body.append( + elClass(Test, { textContent: "Hello World" }) +); + +function elClass(c, props, ...addons){ + let element, element_host; + scope.push({ + scope: c, //just informative purposes + host: (...c)=> c.length + ? (!element + ? addons.unshift(...c) + : c.forEach(c=> c(element_host)), undefined) + : element_host + }); + const C= new c(props); + element= C.render(); + const is_fragment= el instanceof DocumentFragment; + const el_mark= el.mark({ //this creates html comment `<dde:mark …/>` + type: "class-component", + name: C.name, + host: is_fragment ? "this" : "parentElement", + }); + element.prepend(el_mark); + if(is_fragment) element_host= el_mark; + + chainableAppend(element); + addons.forEach(c=> c(element_host)); + scope.pop(); + return element; +} +

# Mnemonic

  • el(<function>, <function-argument(s)>)[.append(...)]: <element-returned-by-function> — using component represented by function
  • scope.host() — get current component reference
  • scope.host(...<addons>) — use addons to current component
\ No newline at end of file diff --git a/docs_src/components/examples/scopes/class-component.js b/docs_src/components/examples/scopes/class-component.js new file mode 100644 index 0000000..5e88740 --- /dev/null +++ b/docs_src/components/examples/scopes/class-component.js @@ -0,0 +1,41 @@ +import { chainableAppend, el, scope } from "deka-dom-el"; +class Test { + constructor(params){ + this._params= params; + } + render(){ + return el("div").append( + this._params.textContent + ); + } +} +document.body.append( + elClass(Test, { textContent: "Hello World" }) +); + +function elClass(c, props, ...addons){ + let element, element_host; + scope.push({ + scope: c, //just informative purposes + host: (...c)=> c.length + ? (!element + ? addons.unshift(...c) + : c.forEach(c=> c(element_host)), undefined) + : element_host + }); + const C= new c(props); + element= C.render(); + const is_fragment= el instanceof DocumentFragment; + const el_mark= el.mark({ //this creates html comment `` + type: "class-component", + name: C.name, + host: is_fragment ? "this" : "parentElement", + }); + element.prepend(el_mark); + if(is_fragment) element_host= el_mark; + + chainableAppend(element); + addons.forEach(c=> c(element_host)); + scope.pop(); + return element; +} diff --git a/docs_src/components/examples/scopes/intro.js b/docs_src/components/examples/scopes/intro.js new file mode 100644 index 0000000..d0f1ab0 --- /dev/null +++ b/docs_src/components/examples/scopes/intro.js @@ -0,0 +1,3 @@ +// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js +import { scope, el, on } from "deka-dom-el"; +/** @type {ddeElementAddon} */ diff --git a/docs_src/components/examples/scopes/scopes-and-hosts.js b/docs_src/components/examples/scopes/scopes-and-hosts.js new file mode 100644 index 0000000..115816e --- /dev/null +++ b/docs_src/components/examples/scopes/scopes-and-hosts.js @@ -0,0 +1,33 @@ +import { el, on, scope } from "deka-dom-el"; +const { host }= scope; +host( + element=> console.log( + "This represents Addon/oninit for root", + element.outerHTML + ) +); +console.log( + "This represents the reference to the host element of root", + host().outerHTML +); +document.body.append( + el(component) +); +function component(){ + const { host }= scope; + host( + element=> console.log( + "This represents Addon/oninit for the component", + element.outerHTML + ) + ); + const onclick= on("click", function(ev){ + console.log( + "This represents the reference to the host element of the component", + host().outerHTML + ); + }) + return el("div", null, onclick).append( + el("strong", "Component") + ); +} diff --git a/docs_src/components/mnemonic/scopes-init.js b/docs_src/components/mnemonic/scopes-init.js new file mode 100644 index 0000000..61ff544 --- /dev/null +++ b/docs_src/components/mnemonic/scopes-init.js @@ -0,0 +1,16 @@ +import { el } from "deka-dom-el"; +import { mnemonicUl } from "../mnemonicUl.html.js"; + +export function mnemonic(){ + return mnemonicUl().append( + el("li").append( + el("code", "el(, )[.append(...)]: "), " — using component represented by function", + ), + el("li").append( + el("code", "scope.host()"), " — get current component reference" + ), + el("li").append( + el("code", "scope.host(...)"), " — use addons to current component", + ) + ); +} diff --git a/docs_src/p05-scopes.html.js b/docs_src/p05-scopes.html.js new file mode 100644 index 0000000..7a48be0 --- /dev/null +++ b/docs_src/p05-scopes.html.js @@ -0,0 +1,41 @@ +import { simplePage } from "./layout/simplePage.html.js"; + +import { el } from "deka-dom-el"; +import { example } from "./components/example.html.js"; +import { h3 } from "./components/pageUtils.html.js"; +import { mnemonic } from "./components/mnemonic/scopes-init.js"; +import { code } from "./components/code.html.js"; +/** @param {string} url */ +const fileURL= url=> new URL(url, import.meta.url); + +/** @param {import("./types.d.ts").PageAttrs} attrs */ +export function page({ pkg, info }){ + const page_id= info.id; + return el(simplePage, { info, pkg }).append( + el("h2", "Using functions as UI components"), + el("p").append( + "For state-less components we can use functions as UI components (see “Elements” page).", + " But in real life, we may need to handle the component live-cycle and provide", + " JavaScript the way to properly use the ", el("a", { textContent: "Garbage collection", href: "https://developer.mozilla.org/en-US/docs/Glossary/Garbage_collection", title: "Garbage collection | MDN" }), "." + ), + el(code, { src: fileURL("./components/examples/scopes/intro.js"), page_id }), + el("p").append( + "The library therefore use ", el("em", "scopes"), " to provide these functionalities.", + ), + + el(h3, "Scopes and hosts"), + el("p").append( + "The ", el("strong", "host"), " is the name for the element representing the component.", + " This is typically element returned by function. To get reference, you can use ", + el("code", "scope.host()"), " to applly addons just use ", el("code", "scope.host(...)"), "." + ), + el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js"), page_id }), + el("p").append( + "To better understanding we implement function ", el("code", "elClass"), " helping to create", + " component as class instances." + ), + el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }), + + el(mnemonic) + ); +} diff --git a/docs_src/ssr.js b/docs_src/ssr.js index cb5278c..817bbaa 100644 --- a/docs_src/ssr.js +++ b/docs_src/ssr.js @@ -7,6 +7,7 @@ export const pages= [ { 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: "p04-observables", href: "p04-observables", title: "Observables and reactivity", description: "Handling reactivity in UI via observables." }, + { id: "p05-scopes", href: "p05-scopes", title: "Scopes and components", description: "Organizing UI into components" }, ]; /** * @typedef registerClientFile