mirror of
https://github.com/jaandrle/deka-dom-el
synced 2024-11-22 16:55:23 +01:00
Compare commits
No commits in common. "97f8c2eed3247b736a6841aea3eba0fdcc434983" and "8dc3e49d980eb5bca1d470fca8ea2bbd6b566ec0" have entirely different histories.
97f8c2eed3
...
8dc3e49d98
@ -2,7 +2,7 @@
|
|||||||
/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */
|
/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */
|
||||||
echo("Building static documentation files…");
|
echo("Building static documentation files…");
|
||||||
echo("Preparing…");
|
echo("Preparing…");
|
||||||
import { path_target, pages as pages_registered, styles, dispatchEvent, t } from "../docs_src/ssr.js";
|
import { path_target, pages as pages_registered, styles, dispatchEvent } from "../docs_src/ssr.js";
|
||||||
import { createHTMl } from "./docs/jsdom.js";
|
import { createHTMl } from "./docs/jsdom.js";
|
||||||
import { register } from "../jsdom.js";
|
import { register } from "../jsdom.js";
|
||||||
const pkg= s.cat("package.json").xargs(JSON.parse);
|
const pkg= s.cat("package.json").xargs(JSON.parse);
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -10,7 +10,7 @@ S.observedAttributes;
|
|||||||
|
|
||||||
// “internal” utils
|
// “internal” utils
|
||||||
import { lifecyclesToEvents } from "deka-dom-el";
|
import { lifecyclesToEvents } from "deka-dom-el";
|
||||||
</code></div><h3 id="h-custom-elements-introduction"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-custom-elements-introduction" tabindex="-1">#</a> Custom Elements Introduction</h3><p><a title="MDN documentation page for Custom Elements" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements">Using custom elements</a></p><div class="code" data-js="todo"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">class CustomHTMLElement extends HTMLElement{
|
</code></div><h3 id="h-custom-elements-introduction"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-custom-elements-introduction" tabindex="-1">#</a> Custom Elements Introduction</h3><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements" title="Article about custom elements on MDN">Using custom elements</a></p><div class="code" data-js="todo"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">class CustomHTMLElement extends HTMLElement{
|
||||||
static tagName = "custom-element"; // just suggestion, we can use `el(CustomHTMLElement.tagName)`
|
static tagName = "custom-element"; // just suggestion, we can use `el(CustomHTMLElement.tagName)`
|
||||||
static observedAttributes= [ "custom-attribute" ];
|
static observedAttributes= [ "custom-attribute" ];
|
||||||
constructor(){
|
constructor(){
|
||||||
@ -31,4 +31,4 @@ import { lifecyclesToEvents } from "deka-dom-el";
|
|||||||
set customAttribute(value){ this.setAttribute("custom-attribute", value); }
|
set customAttribute(value){ this.setAttribute("custom-attribute", value); }
|
||||||
}
|
}
|
||||||
customElements.define(CustomHTMLElement.tagName, CustomHTMLElement);
|
customElements.define(CustomHTMLElement.tagName, CustomHTMLElement);
|
||||||
</code></div><p><a title="Ideas and tips from WebReflection" href="https://gist.github.com/WebReflection/ec9f6687842aa385477c4afca625bbf4">Handy Custom Elements' Patterns</a></p><div class="notice"><!--<dde:mark type="component" name="mnemonic" host="parentElement" ssr/>--><h3 id="h-mnemonic"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-mnemonic" tabindex="-1">#</a> Mnemonic</h3><ul><li><code>customElementRender(<custom-element>, <render-function>[, <properties>])</code> — use function to render DOM structure for given <custom-element></li><li><code>customElementWithDDE(<custom-element>)</code> — register <custom-element> to DDE library, see also `lifecyclesToEvents`, can be also used as decorator</li><li><code>observedAttributes(<custom-element>)</code> — returns record of observed attributes (keys uses camelCase)</li><li><code>S.observedAttributes(<custom-element>)</code> — returns record of observed attributes (keys uses camelCase and values are signals)</li><li><code>lifecyclesToEvents(<class-declaration>)</code> — convert lifecycle methods to events, can be also used as decorator</li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="p05-scopes" title="Organizing UI into components"><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Scopes and components (previous)</a><!--<dde:mark type="component" name="pageLink" host="this" ssr/>--></div></main></body></html>
|
</code></div><p><a href="https://gist.github.com/WebReflection/ec9f6687842aa385477c4afca625bbf4" title="Ideas and tips from WebReflection">Handy Custom Elements' Patterns</a></p><div class="notice"><!--<dde:mark type="component" name="mnemonic" host="parentElement" ssr/>--><h3 id="h-mnemonic"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-mnemonic" tabindex="-1">#</a> Mnemonic</h3><ul><li><code>customElementRender(<custom-element>, <render-function>[, <properties>])</code> — use function to render DOM structure for given <custom-element></li><li><code>customElementWithDDE(<custom-element>)</code> — register <custom-element> to DDE library, see also `lifecyclesToEvents`, can be also used as decorator</li><li><code>observedAttributes(<custom-element>)</code> — returns record of observed attributes (keys uses camelCase)</li><li><code>S.observedAttributes(<custom-element>)</code> — returns record of observed attributes (keys uses camelCase and values are signals)</li><li><code>lifecyclesToEvents(<class-declaration>)</code> — convert lifecycle methods to events, can be also used as decorator</li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="p05-scopes" title="Organizing UI into components"><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Scopes and components (previous)</a><!--<dde:mark type="component" name="pageLink" host="this" ssr/>--></div></main></body></html>
|
@ -1,15 +1,15 @@
|
|||||||
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/signals";
|
||||||
const clicks= S(0); // A
|
const clicks= S(0);
|
||||||
document.body.append(
|
document.body.append(
|
||||||
el().append(
|
el().append(
|
||||||
el("p", S(()=>
|
el("p", S(()=>
|
||||||
"Hello World "+"🎉".repeat(clicks()) // B
|
"Hello World "+"🎉".repeat(clicks())
|
||||||
)),
|
)),
|
||||||
el("button", {
|
el("button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
onclick: ()=> clicks(clicks()+1), // C
|
onclick: ()=> clicks(clicks()+1),
|
||||||
textContent: "Fire",
|
textContent: "Fire"
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
@ -1,6 +0,0 @@
|
|||||||
// pseudo code!
|
|
||||||
const onchage=
|
|
||||||
event=>
|
|
||||||
console.log("Reacting to the:", event); // A
|
|
||||||
input.addEventListener("change", onchange); // B
|
|
||||||
input.dispatchEvent(new Event("change")); // C
|
|
@ -1,59 +1,41 @@
|
|||||||
import { t, T } from "./utils/index.js";
|
|
||||||
export const info= {
|
export const info= {
|
||||||
href: "./",
|
href: "./",
|
||||||
title: t`Introduction`,
|
title: "Introduction",
|
||||||
description: t`Introducing a library.`,
|
description: "Introducing a library.",
|
||||||
};
|
};
|
||||||
|
|
||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
import { simplePage } from "./layout/simplePage.html.js";
|
import { simplePage } from "./layout/simplePage.html.js";
|
||||||
import { h3 } from "./components/pageUtils.html.js";
|
|
||||||
import { example } from "./components/example.html.js";
|
import { example } from "./components/example.html.js";
|
||||||
import { code } from "./components/code.html.js";
|
|
||||||
/** @param {string} url */
|
|
||||||
const fileURL= url=> new URL(url, import.meta.url);
|
|
||||||
const references= {
|
|
||||||
w_mvv:{
|
|
||||||
title: t`Wikipedia: Model–view–viewmodel`,
|
|
||||||
href: "https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel",
|
|
||||||
},
|
|
||||||
w_mvc: {
|
|
||||||
title: t`Wikipedia: Model–view–controller`,
|
|
||||||
href: "https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
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("p", t`The library tries to provide pure JavaScript tool(s) to create reactive interfaces using …`),
|
el("p", "The library tries to provide pure JavaScript tool(s) to create reactive interfaces."),
|
||||||
el(h3, t`Event-driven programming (3 parts separation ≡ 3PS)`),
|
el("p").append(
|
||||||
el("p").append(t`
|
"We start with creating and modifying a static elements and end up with UI templates.",
|
||||||
Let's introduce the basic principle on which the library is built. We'll use the JavaScript listener as
|
" ",
|
||||||
a starting point.
|
el("i").append(
|
||||||
`),
|
"From ", el("code", "document.createElement"), " to ", el("code", "el"), "."
|
||||||
el(code, { src: fileURL("./components/examples/introducing/3ps.js"), page_id }),
|
),
|
||||||
el("p").append(...T`
|
" ",
|
||||||
As we can see, in the code at location “A” we define ${el("em", t`how to react`)} when the function
|
"Then we go through the native events system and the way to include it declaratively in UI templates.",
|
||||||
is called with any event as an argument. At that moment, we ${el("em", t`don't care who/why/how`)}
|
" ",
|
||||||
the function was called. Similarly, at point “B”, we reference to a function to be called on the event
|
el("i").append(
|
||||||
${el("em", t`without caring`)} what the function will do at that time. Finally, at point “C”, we tell
|
"From ", el("code", "element.addEventListener"), " to ", el("code", "on"), "."
|
||||||
the application that a change has occurred, in the input, and we ${el("em", t`don't care if/how someone`)}
|
),
|
||||||
is listening for the event.
|
),
|
||||||
`),
|
el("p").append(
|
||||||
el(example, { src: fileURL("./components/examples/introducing/helloWorld.js"), page_id }),
|
"Next step is providing interactivity not only for our UI templates.",
|
||||||
el("p").append(...T`
|
" ",
|
||||||
The library introduces a new “type” of variable/constant called ${el("em", t`signal`)} allowing us to
|
"We introduce signals (", el("code", "S"), ") and how them incorporate to UI templates.",
|
||||||
to use introduced 3PS pattern in our applications. As you can see it in the example above.
|
),
|
||||||
`),
|
el("p").append(
|
||||||
el("p").append(...T`
|
"Now we will clarify how the signals are incorporated into our templates with regard ",
|
||||||
Also please notice that there is very similar 3PS pattern used for separate creation of UI and business logic.
|
"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("p").append(...T`
|
"in JavaScript by functions."
|
||||||
The 3PS is very simplified definition of the pattern. There are more deep/academic definitions more precisely
|
),
|
||||||
describe usage in specific situations, see for example ${el("a", { textContent: t`MVVM`, ...references.w_mvv })}
|
el(example, { src: new URL("./components/examples/helloWorld.js", import.meta.url), page_id }),
|
||||||
or ${el("a", { textContent: t`MVC`, ...references.w_mvc })}.
|
|
||||||
`),
|
|
||||||
el(h3, t`Organization of the documentation`),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { T, t } from "./utils/index.js";
|
|
||||||
export const info= {
|
export const info= {
|
||||||
title: t`Elements`,
|
title: "Elements",
|
||||||
description: t`Basic concepts of elements modifications and creations.`,
|
description: "Basic concepts of elements modifications and creations.",
|
||||||
};
|
};
|
||||||
|
|
||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
@ -12,142 +11,113 @@ import { mnemonic } from "./components/mnemonic/elements-init.js";
|
|||||||
import { code } from "./components/code.html.js";
|
import { code } from "./components/code.html.js";
|
||||||
/** @param {string} url */
|
/** @param {string} url */
|
||||||
const fileURL= url=> new URL(url, import.meta.url);
|
const fileURL= url=> new URL(url, import.meta.url);
|
||||||
const references= {
|
|
||||||
/** document.createElement() */
|
|
||||||
mdn_create: {
|
|
||||||
title: t`MDN documentation page for document.createElement()`,
|
|
||||||
href: "https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement",
|
|
||||||
},
|
|
||||||
/** Interface Description Language (`el.textContent`) */
|
|
||||||
mdn_idl: {
|
|
||||||
title: t`MDN page about Interface Description Language`,
|
|
||||||
href: "https://developer.mozilla.org/en-US/docs/Glossary/IDL",
|
|
||||||
},
|
|
||||||
/** HTMLElement */
|
|
||||||
mdn_el: {
|
|
||||||
title: t`MDN documentation page for HTMLElement`,
|
|
||||||
href: "https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement"
|
|
||||||
},
|
|
||||||
/** HTMLParagraphElement */
|
|
||||||
mdn_p: {
|
|
||||||
title: t`MDN documentation page for HTMLParagraphElement (\`p\` tag)`,
|
|
||||||
href: "https://developer.mozilla.org/en-US/docs/Web/API/HTMLParagraphElement"
|
|
||||||
},
|
|
||||||
/** `[a, b] = [1, 2]` */
|
|
||||||
mdn_destruct: {
|
|
||||||
title: t`MDN page about destructuring assignment syntax (e.g. \`[a, b] = [1, 2]\`)`,
|
|
||||||
href: "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment",
|
|
||||||
},
|
|
||||||
/** document.createElementNS() */
|
|
||||||
mdn_ns: {
|
|
||||||
title: t`MDN documentation page for document.createElementNS() (e.g. for SVG elements)`,
|
|
||||||
href: "https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
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", t`Native JavaScript DOM elements creations`),
|
el("h2", "Native JavaScript DOM elements creations"),
|
||||||
el("p", t`
|
el("p", "Let’s go through all patterns we would like to use and what needs to be improved for better experience."),
|
||||||
Let’s go through all patterns we would like to use and what needs to be improved for better experience.
|
|
||||||
`),
|
|
||||||
|
|
||||||
el(code, { src: fileURL("./components/examples/elements/intro.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/elements/intro.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Creating element(s) (with custom attributes)`),
|
el(h3, "Creating element(s) (with custom attributes)"),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
You can create a native DOM element by using the ${el("a", references.mdn_create).append(
|
"You can create a native DOM element by using the ",
|
||||||
el("code", "document.createElement()") )}. It is also possible to provide a some attribute(s) declaratively
|
el("a", { href: "https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement", title: "MDN documentation for document.createElement()" }).append(
|
||||||
using ${el("code", "Object.assign()")}. More precisely, this way you can sets some
|
el("code", "document.createElement()")
|
||||||
${el("a", references.mdn_idl).append( el("abbr", { textContent: "IDL", title: "Interface Description Language" }))}
|
), ". ",
|
||||||
also known as a JavaScript property.
|
"It is also possible to provide a some attribute(s) declaratively using ", el("code", "Object.assign()"), ". ",
|
||||||
`),
|
"More precisely, this way you can sets some ",
|
||||||
el(example, { src: fileURL("./components/examples/elements/nativeCreateElement.js"), page_id }),
|
el("a", {
|
||||||
el("p").append(...T`
|
href: "https://developer.mozilla.org/en-US/docs/Glossary/IDL",
|
||||||
To make this easier, you can use the ${el("code", "el")} function. Internally in basic examples,
|
title: "MDN page about Interface Description Language"
|
||||||
it is wrapper around ${el("code", "assign(document.createElement(…), { … })")}.
|
}).append(
|
||||||
`),
|
el("abbr", { textContent: "IDL", title: "Interface Description Language" })
|
||||||
el(example, { src: fileURL("./components/examples/elements/dekaCreateElement.js"), page_id }),
|
), " also known as a JavaScript property."
|
||||||
el("p").append(...T`
|
),
|
||||||
The ${el("code", "assign")} function provides improved behaviour of ${el("code", "Object.assign()")}.
|
el(example, { src: fileURL("./components/examples/elements/nativeCreateElement.js"), page_id }),
|
||||||
You can declaratively sets any IDL and attribute of the given element. Function prefers IDL and fallback
|
el("p").append(
|
||||||
to the ${el("code", "element.setAttribute")} if there is no writable property in the element prototype.
|
"To make this easier, you can use the ", el("code", "el"), " function. ",
|
||||||
`),
|
"Internally in basic examples, it is wrapper around ", el("code", "assign(document.createElement(…), { … })"), "."
|
||||||
el("p").append(...T`
|
),
|
||||||
You can study all JavaScript elements interfaces to the corresponding HTML elements. All HTML elements
|
el(example, { src: fileURL("./components/examples/elements/dekaCreateElement.js"), page_id }),
|
||||||
inherits from ${el("a", { textContent: "HTMLElement", ...references.mdn_el })}. To see
|
el("p").append(
|
||||||
all available IDLs for example for paragraphs, see ${el("a", { textContent: "HTMLParagraphElement", ...references.mdn_p })}.
|
"The ", el("code", "assign"), " function provides improved behaviour of ", el("code", "Object.assign()"), ". ",
|
||||||
Moreover, the ${el("code", "assign")} provides a way to sets declaratively some convenient properties:
|
"You can declaratively sets any IDL and attribute of the given element. ",
|
||||||
`),
|
"Function prefers IDL and fallback to the ", el("code", "element.setAttribute"), " if there is no writable property in the element prototype."
|
||||||
el("ul").append(
|
),
|
||||||
el("li").append(...T`
|
el("p").append(
|
||||||
It is possible to sets ${el("code", "data-*")}/${el("code", "aria-*")} attributes using object notation.
|
"You can study all JavaScript elements interfaces to the corresponding HTML elements. ",
|
||||||
`),
|
"All HTML elements inherits from ", el("a", { textContent: "HTMLElement", href: "https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement" }), ". ",
|
||||||
el("li").append(...T`
|
"To see all available IDLs for example for paragraphs, see ", el("a", { textContent: "HTMLParagraphElement", href: "https://developer.mozilla.org/en-US/docs/Web/API/HTMLParagraphElement" }), ". ",
|
||||||
In opposite, it is also possible to sets ${el("code", "data-*")}/${el("code", "aria-*")} attribute
|
"Moreover, the ", el("code", "assign"), " provides a way to sets declaratively some convenient properties:"
|
||||||
using camelCase notation.
|
),
|
||||||
`),
|
el("ul").append(
|
||||||
el("li").append(...T`You can use string or object as a value for ${el("code", "style")} property.`),
|
el("li").append(
|
||||||
el("li").append(...T`
|
"It is possible to sets ", el("code", "data-*"), "/", el("code", "aria-*"), " attributes using object notation."
|
||||||
${el("code", "className")} (IDL – preffered)/${el("code", "class")} are ways to add CSS classes
|
),
|
||||||
to the element. You can use string (similarly to ${el("code", "class=\"…\"")} syntax in HTML) or
|
el("li").append(
|
||||||
array of strings. This is handy to concat conditional classes.
|
"In opposite, it is also possible to sets ", el("code", "data-*"), "/", el("code", "aria-*"), " attribute using camelCase notation."
|
||||||
`),
|
),
|
||||||
el("li").append(...T`
|
el("li").append(
|
||||||
Use ${el("code", "classList")} to toggle specific classes. This will be handy later when
|
"You can use string or object as a value for ", el("code", "style"), " property."
|
||||||
the reactivity via signals is beeing introduced.
|
),
|
||||||
`),
|
el("li").append(
|
||||||
el("li").append(...T`
|
el("code", "className"), " (IDL – preffered)/", el("code", "class"), " are ways to add CSS class to the element. ",
|
||||||
The ${el("code", "assign")} also accepts the ${el("code", "undefined")} as a value for any property
|
"You can use string (similarly to ", el("code", "class=\"…\"") ," syntax in HTML) or array of strings. ",
|
||||||
to remove it from the element declaratively. Also for some IDL the corresponding attribute is removed
|
"This is handy to concat conditional classes."
|
||||||
as it can be confusing. ${el("em").append(...T`For example, natievly the element’s ${el("code", "id")}
|
),
|
||||||
is removed by setting the IDL to an empty string.`)}
|
el("li").append(
|
||||||
`),
|
"Use ", el("code", "classList"), " to toggle specific classes. This will be handy later when the reactivity via signals is beeing introduced.",
|
||||||
el("li").append(...T`
|
),
|
||||||
You can use ${el("code", "=")} or ${el("code", ".")} to force processing given key as attribute/property
|
el("li").append(
|
||||||
of the element.
|
"The ", el("code", "assign"), " also accepts the ", el("code", "undefined"), " as a 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. ",
|
||||||
|
el("em").append(
|
||||||
|
"For example, natievly the element’s ", el("code", "id"), " is removed by setting the IDL to an empty string."
|
||||||
|
)
|
||||||
|
),
|
||||||
|
el("li").append(
|
||||||
|
"You can use ", el("code", "="), " or ", el("code", "."), " to force processing given key as attribute/property of the element."
|
||||||
|
)
|
||||||
|
),
|
||||||
|
el("p").append(
|
||||||
|
"For processing, the ", el("code", "assign"), " internally uses ", el("code", "assignAttribute"), " and ", el("code", "classListDeclarative"), "."
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
|
||||||
For processing, the ${el("code", "assign")} internally uses ${el("code", "assignAttribute")} and
|
|
||||||
${el("code", "classListDeclarative")}.
|
|
||||||
`),
|
|
||||||
el(example, { src: fileURL("./components/examples/elements/dekaAssign.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/elements/dekaAssign.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Native JavaScript templating`),
|
el(h3, "Native JavaScript templating"),
|
||||||
el("p", t`By default, the native JS has no good way to define HTML template using DOM API:`),
|
el("p", "By default, the native JS has no good way to define HTML template using DOM API:"),
|
||||||
el(example, { src: fileURL("./components/examples/elements/nativeAppend.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/elements/nativeAppend.js"), page_id }),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
This library therefore overwrites the ${el("code", "append")} method of created elements to always return
|
"This library therefore overwrites the ", el("code", "append"), " method of created elements to always return parent element."
|
||||||
parent element.
|
),
|
||||||
`),
|
|
||||||
el(example, { src: fileURL("./components/examples/elements/dekaAppend.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/elements/dekaAppend.js"), page_id }),
|
||||||
|
|
||||||
|
|
||||||
el(h3, t`Basic (state-less) components`),
|
el(h3, "Basic (state-less) components"),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
You can use functions for encapsulation (repeating) logic. The ${el("code", "el")} accepts function
|
"You can use functions for encapsulation (repeating) logic. ",
|
||||||
as first argument. In that case, the function should return dom elements and the second argument for
|
"The ", el("code", "el"), " accepts function as first argument. ",
|
||||||
${el("code", "el")} is argument for given element.
|
"In that case, the function should return dom elements and the second argument for ", el("code", "el"), " is argument for given element."
|
||||||
`),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/elements/dekaBasicComponent.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/elements/dekaBasicComponent.js"), page_id }),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
As you can see, in case of state-less/basic components there is no difference between calling component
|
"As you can see, in case of state-less/basic components there is no difference",
|
||||||
function directly or using ${el("code", "el")} function.
|
" between calling component function directly or using ", el("code", "el"), " function.",
|
||||||
`),
|
),
|
||||||
el("p", { className: "notice" }).append(...T`
|
el("p", { className: "notice" }).append(
|
||||||
It is nice to use similar naming convention as native DOM API. This allows us to use
|
"It is nice to use similar naming convention as native DOM API. ",
|
||||||
${el("a", { textContent: t`the destructuring assignment syntax`, ...references.mdn_destruct })} and keep
|
"This allows us to use ", el("a", { textContent: "the destructuring assignment syntax", href: "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment", title: "Destructuring assignment | MDN" }),
|
||||||
track of the native API (things are best remembered through regular use).
|
" and keep track of the native API (things are best remembered through regular use).",
|
||||||
`),
|
),
|
||||||
|
|
||||||
el(h3, t`Creating non-HTML elements`),
|
el(h3, "Creating non-HTML elements"),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
Similarly to the native DOM API (${el("a", references.mdn_ns).append(el("code", "document.createElementNS"))})
|
"Similarly to the native DOM API (", el("a", { href: "https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS", title: "MDN" }).append(el("code", "document.createElementNS")), ") for non-HTML elements",
|
||||||
for non-HTML elements we need to tell JavaScript which kind of the element to create. We can use
|
" we need to tell JavaScript which kind of the element to create.",
|
||||||
the ${el("code", "elNS")} function:
|
" We can use the ", el("code", "elNS"), " function:"
|
||||||
`),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/elements/dekaElNS.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/elements/dekaElNS.js"), page_id }),
|
||||||
|
|
||||||
el(mnemonic)
|
el(mnemonic)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { T, t } from "./utils/index.js";
|
|
||||||
export const info= {
|
export const info= {
|
||||||
title: t`Events and Addons`,
|
title: "Events and Addons",
|
||||||
description: t`Using not only events in UI declaratively.`,
|
description: "Using not only events in UI declaratively.",
|
||||||
};
|
};
|
||||||
|
|
||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
@ -12,136 +11,121 @@ import { mnemonic } from "./components/mnemonic/events-init.js";
|
|||||||
import { code } from "./components/code.html.js";
|
import { code } from "./components/code.html.js";
|
||||||
/** @param {string} url */
|
/** @param {string} url */
|
||||||
const fileURL= url=> new URL(url, import.meta.url);
|
const fileURL= url=> new URL(url, import.meta.url);
|
||||||
const references= {
|
|
||||||
/** element.addEventListener() */
|
|
||||||
mdn_listen: {
|
|
||||||
title: t`MDN documentation page for elemetn.addEventListener`,
|
|
||||||
href: "https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener",
|
|
||||||
},
|
|
||||||
/** AbortSignal+element.addEventListener */
|
|
||||||
mdn_abortListener: {
|
|
||||||
title: t`MDN documentation page for using AbortSignal with element.addEventListener`,
|
|
||||||
href: "https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal",
|
|
||||||
},
|
|
||||||
/** comparison listening options by WebReflection */
|
|
||||||
web_events: {
|
|
||||||
href: "https://gist.github.com/WebReflection/b404c36f46371e3b1173bf5492acc944",
|
|
||||||
},
|
|
||||||
/** Custom Element lifecycle callbacks */
|
|
||||||
mdn_customElement: {
|
|
||||||
href: "https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks"
|
|
||||||
},
|
|
||||||
/** MutationObserver */
|
|
||||||
mdn_mutation: {
|
|
||||||
href: "https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver",
|
|
||||||
},
|
|
||||||
/** Readding the element to the DOM fix by Vue */
|
|
||||||
vue_fix: {
|
|
||||||
title: t`Vue and Web Components, lifecycle implementation readding the element to the DOM`,
|
|
||||||
href: "https://vuejs.org/guide/extras/web-components.html#lifecycle",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
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", t`Listenning to the native DOM events and other Addons`),
|
el("h2", "Listenning to the native DOM events and other Addons"),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
We quickly introduce helper to listening to the native DOM events. And library syntax/pattern so-called
|
"We quickly introduce helper to listening to the native DOM events.",
|
||||||
${el("em", t`Addon`)} to incorporate not only this in UI templates declaratively.
|
" ",
|
||||||
`),
|
"And library syntax/pattern so-called ", el("em", "Addon"), " to",
|
||||||
|
" incorporate not only this in UI templates declaratively."
|
||||||
|
),
|
||||||
|
|
||||||
el(code, { src: fileURL("./components/examples/events/intro.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/events/intro.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Events and listenners`),
|
el(h3, "Events and listenners"),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
In JavaScript you can listen to the native DOM events of the given element by using
|
"In JavaScript you can listen to the native DOM events of the given element by using ",
|
||||||
${el("a", references.mdn_listen).append( el("code", "element.addEventListener(type, listener, options)") )}.
|
el("a", { href: "https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener", title: "addEventListener on MDN" }).append(
|
||||||
The library provides an alternative (${el("code", "on")}) accepting the differen order of the arguments:
|
el("code", "element.addEventListener(type, listener, options)")
|
||||||
`),
|
), ".",
|
||||||
|
" ",
|
||||||
|
"The library provides an alternative (", el("code", "on"), ") accepting the differen order",
|
||||||
|
" of the arguments:"
|
||||||
|
),
|
||||||
el(example, { src: fileURL("./components/examples/events/compare.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/events/compare.js"), page_id }),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
…this is actually one of the two differences. The another one is that ${el("code", "on")} accepts only
|
"…this is actually one of the two differences. The another one is that ", el("code", "on"),
|
||||||
object as the ${el("code", "options")} (but it is still optional).
|
" accepts only object as the ", el("code", "options"), " (but it is still optional)."
|
||||||
`),
|
),
|
||||||
el("p", { className: "notice" }).append(...T`
|
el("p", { className: "notice" }).append(
|
||||||
The other difference is that there is ${el("strong", "no")} ${el("code", "off")} function. You can remove
|
"The other difference is that there is ", el("strong", "no"), " ", el("code", "off"), " function.",
|
||||||
listener declaratively using ${el("a", { textContent: "AbortSignal", ...references.mdn_abortListener })}:
|
" ",
|
||||||
`),
|
"You can remove listener declaratively using ", el("a", { textContent: "AbortSignal", href: "https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal", title: "part of addEventListener on MDN" }),
|
||||||
|
":"
|
||||||
|
),
|
||||||
el(example, { src: fileURL("./components/examples/events/abortSignal.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/events/abortSignal.js"), page_id }),
|
||||||
el("div", { className: "notice" }).append(
|
el("div", { className: "notice" }).append(
|
||||||
el("p", t`So, there are (typically) three ways to handle events. You can use:`),
|
el("p", "So, there are (typically) three ways to handle events. You can use:"),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li").append( el("code", `el("button", { textContent: "click me", "=onclick": "console.log(event)" })`)),
|
el("li").append( el("code", `el("button", { textContent: "click me", "=onclick": "console.log(event)" })`)),
|
||||||
el("li").append( el("code", `el("button", { textContent: "click me", onclick: console.log })`)),
|
el("li").append( el("code", `el("button", { textContent: "click me", onclick: console.log })`)),
|
||||||
el("li").append( el("code", `el("button", { textContent: "click me" }, on("click", console.log))`))
|
el("li").append( el("code", `el("button", { textContent: "click me" }, on("click", console.log))`))
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
In the first example we force to use HTML attribute (it corresponds to
|
"In the first example we force to use HTML attribute (it corresponds to ", el("code", `<button onclick="console.log(event)">click me</button>`), ").",
|
||||||
${el("code", `<button onclick="console.log(event)">click me</button>`)}). ${el("em", t`Side note:
|
" ",
|
||||||
this can be useful in case of SSR.`)} To study difference, you can read a nice summary here:
|
el("em", "Side note: this can be useful in case of SSR."),
|
||||||
${el("a", { textContent: "GIST @WebReflection/web_events.md", ...references.web_events })}.
|
" ",
|
||||||
`)
|
"To study difference, you can read a nice summary here: ", el("a", { href: "https://gist.github.com/WebReflection/b404c36f46371e3b1173bf5492acc944", textContent: "GIST @WebReflection/web_events.md" }), "."
|
||||||
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Addons`),
|
el(h3, "Addons"),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
From practical point of view, ${el("em", t`Addons`)} are just functions that accept any HTML element as
|
"From practical point of view, ", el("em", "Addons"), " are just functions that accept any html element",
|
||||||
their first parameter. You can see that the ${el("code", "on(…)")} fullfills this requirement.
|
" as their first parameter. You can see that the ", el("code", "on(…)"), " fullfills this requirement."
|
||||||
`),
|
),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
You can use Addons as ≥3rd argument of ${el("code", "el")} function. This way is possible to extends your
|
"You can use Addons as ≥3rd argument of ", el("code", "el"), " function. This way is possible to extends",
|
||||||
templates by additional (3rd party) functionalities. But for now mainly, you can add events listeners:
|
" you templates by additional (3rd party) functionalities. But for now mainly, you can add events listeners:"
|
||||||
`),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/events/templateWithListeners.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/events/templateWithListeners.js"), page_id }),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
As the example shows, you can also provide types in JSDoc+TypeScript by using global type
|
"As the example shows, you can also provide types in JSDoc+TypeScript by using global type ", el("code", "ddeElementAddon"), ".",
|
||||||
${el("code", "ddeElementAddon")}. Also notice, you can use Addons to get element reference.
|
" ",
|
||||||
`),
|
"Also notice, you can use Addons to get element reference.",
|
||||||
el(h3, t`Life-cycle events`),
|
),
|
||||||
el("p").append(...T`
|
el(h3, "Life-cycle events"),
|
||||||
Addons are called immediately when the element is created, even it is not connected to live DOM yet.
|
el("p").append(
|
||||||
Therefore, you can understand the Addon to be “oncreate” event.
|
"Addons are called immediately when the element is created, even it is not connected to live DOM yet.",
|
||||||
`),
|
" ",
|
||||||
el("p").append(...T`
|
"Therefore, you can understand the Addon to be “oncreate” event."
|
||||||
The library provide three additional live-cycle events corresponding to how they are named in a case of
|
),
|
||||||
custom elements: ${el("code", "on.connected")}, ${el("code", "on.disconnected")} and ${el("code", "on.attributeChanged")}.
|
el("p").append(
|
||||||
`),
|
"The library provide three additional live-cycle events corresponding to how they are named in",
|
||||||
el(example, { src: fileURL("./components/examples/events/live-cycle.js"), page_id }),
|
" a case of custom elements: ", el("code", "on.connected"), ", ", el("code", "on.disconnected"),
|
||||||
el("p").append(...T`
|
" and ", el("code", "on.attributeChanged"), "."
|
||||||
For Custom elements, we will later introduce a way to replace ${el("code", "*Callback")} syntax with
|
),
|
||||||
${el("code", "dde:*")} events. The ${el("code", "on.*")} functions then listen to the appropriate
|
el(example, { src: fileURL("./components/examples/events/live-cycle.js"), page_id }),
|
||||||
Custom Elements events (see ${el("a", { textContent: t`Custom element lifecycle callbacks | MDN`, ...references.mdn_customElement })}).
|
el("p").append(
|
||||||
`),
|
"For Custom elements, we will later introduce a way to replace ", el("code", "*Callback"),
|
||||||
el("p").append(...T`
|
" syntax with ", el("code", "dde:*"), " events. The ", el("code", "on.*"), " functions then",
|
||||||
But, in case of regular elemnets the ${el("a", references.mdn_mutation).append(el("code", "MutationObserver"), " | MDN")}
|
" listen to the appropriate Custom Elements events (see ", el("a", { textContent: "Custom element lifecycle callbacks | MDN", href: "https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks" }), ")."
|
||||||
is internaly used to track these events. Therefore, there are some drawbacks:
|
),
|
||||||
`),
|
el("p").append(
|
||||||
el("ul").append(
|
"But, in case of regular elemnets the ", el("a", { href: "https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver" }).append(el("code", "MutationObserver"), " | MDN"),
|
||||||
el("li").append(...T`
|
" is internaly used to track these events. Therefore, there are some drawbacks:",
|
||||||
To proper listener registration, you need to use ${el("code", "on.*")} not \`on("dde:*", …)\`!
|
|
||||||
`),
|
|
||||||
el("li").append(...T`
|
|
||||||
Use sparingly! Internally, library must loop of all registered events and fires event properly.
|
|
||||||
${el("strong", t`It is good practice to use the fact that if an element is removed, its children are
|
|
||||||
also removed!`)} In this spirit, we will introduce later the ${el("strong", t`host`)} syntax to
|
|
||||||
register, clean up procedures when the component is removed from the app.
|
|
||||||
`),
|
|
||||||
),
|
),
|
||||||
el("p").append(...T`
|
|
||||||
To provide intuitive behaviour, similar also to how the life-cycle events works in other
|
|
||||||
frameworks/libraries, deka-dom-el library ensures that ${el("code", "on.connected")} and
|
|
||||||
${el("code", "on.disconnected")} are called only once and only when the element, is (dis)connected to
|
|
||||||
live DOM. The solution is inspired by ${el("a", { textContent: "Vue", ...references.vue_fix })}. For using
|
|
||||||
native behaviour re-(dis)connecting element, use:
|
|
||||||
`),
|
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li").append(...T`custom ${el("code", "MutationObserver")} or logic in (dis)${el("code", "connectedCallback")} or…`),
|
el("li").append(
|
||||||
el("li").append(...T`re-add ${el("code", "on.connected")} or ${el("code", "on.disconnected")} listeners.`)
|
"To proper listener registration, you need to use ", el("code", "on.*"), " not `on(\"dde:*\", …)`!"
|
||||||
|
),
|
||||||
|
el("li").append(
|
||||||
|
"Use sparingly! Internally, library must loop of all registered events and fires event properly.",
|
||||||
|
" ",
|
||||||
|
el("strong", "It is good practice to use the fact that if an element is removed, its children are also removed!"),
|
||||||
|
" ",
|
||||||
|
"In this spirit, we will introduce later the ", el("strong", "host"), " syntax to register",
|
||||||
|
" clean up procedures when the component is removed from the app."
|
||||||
|
),
|
||||||
|
),
|
||||||
|
el("p").append(
|
||||||
|
"To provide intuitive behaviour, similar also to how the life-cycle events works in other",
|
||||||
|
" frameworks/libraries, deka-dom-el library ensures that ", el("code", "on.connected"),
|
||||||
|
" and ", el("code", "on.disconnected"), " are called only once and only when the element",
|
||||||
|
" is (dis)connected to live DOM. The solution is inspired by ", el("a", { textContent: "Vue", href: "https://vuejs.org/guide/extras/web-components.html#lifecycle", title: "Vue and Web Components | Lifecycle" }), ".",
|
||||||
|
" For using native behaviour re-(dis)connecting element, use:"
|
||||||
|
),
|
||||||
|
el("ul").append(
|
||||||
|
el("li").append("custom ", el("code", "MutationObserver"), " or logic in (dis)", el("code", "connectedCallback"), " or…"),
|
||||||
|
el("li").append("re-add ", el("code", "on.connected"), " or ", el("code", "on.disconnected"), " listeners.")
|
||||||
),
|
),
|
||||||
|
|
||||||
el(h3, t`Final notes`),
|
el(h3, "Final notes"),
|
||||||
el("p", t`The library also provides a method to dispatch the events.`),
|
el("p", "The library also provides a method to dispatch the events."),
|
||||||
el(example, { src: fileURL("./components/examples/events/compareDispatch.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/events/compareDispatch.js"), page_id }),
|
||||||
|
|
||||||
el(mnemonic)
|
el(mnemonic)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { T, t } from "./utils/index.js";
|
|
||||||
export const info= {
|
export const info= {
|
||||||
title: t`Signals and reactivity`,
|
title: "Signals and reactivity",
|
||||||
description: t`Handling reactivity in UI via signals.`,
|
description: "Handling reactivity in UI via signals.",
|
||||||
};
|
};
|
||||||
|
|
||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
@ -12,115 +11,95 @@ import { mnemonic } from "./components/mnemonic/signals-init.js";
|
|||||||
import { code } from "./components/code.html.js";
|
import { code } from "./components/code.html.js";
|
||||||
/** @param {string} url */
|
/** @param {string} url */
|
||||||
const fileURL= url=> new URL(url, import.meta.url);
|
const fileURL= url=> new URL(url, import.meta.url);
|
||||||
const references= {
|
|
||||||
/** Event-driven programming */
|
|
||||||
wiki_event_driven: {
|
|
||||||
title: t`Wikipedia: Event-driven programming`,
|
|
||||||
href: "https://en.wikipedia.org/wiki/Event-driven_programming",
|
|
||||||
},
|
|
||||||
/** Publish–subscribe pattern */
|
|
||||||
wiki_pubsub: {
|
|
||||||
title: t`Wikipedia: Publish–subscribe pattern`,
|
|
||||||
href: "https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern",
|
|
||||||
},
|
|
||||||
/** NPM package: fpubsub */
|
|
||||||
fpubsub: {
|
|
||||||
title: t`NPM package: fpubsub`,
|
|
||||||
href: "https://www.npmjs.com/package/fpubsub",
|
|
||||||
},
|
|
||||||
/** JS Primitives | MDN */
|
|
||||||
mdn_primitive: {
|
|
||||||
title: t`Primitive | MDN`,
|
|
||||||
href: "https://developer.mozilla.org/en-US/docs/Glossary/Primitive",
|
|
||||||
},
|
|
||||||
/** useReducer */
|
|
||||||
mdn_use_reducer: {
|
|
||||||
title: t`useReducer hook | React docs`,
|
|
||||||
href: "https://react.dev/reference/react/useReducer",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
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", t`Using signals to manage reactivity`),
|
el("h2", "Using signals to manage reactivity"),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
How a program responds to variable data or user interactions is one of the fundamental problems of
|
"How a program responds to variable data or user",
|
||||||
programming. If we desire to solve the issue in a declarative manner, signals may be a viable approach.
|
" interactions is one of the fundamental problems of programming.",
|
||||||
`),
|
" If we desire to solve the issue in a declarative manner,",
|
||||||
|
" signals may be a viable approach.",
|
||||||
|
),
|
||||||
el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/signals/intro.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Introducing signals`),
|
el(h3, "Introducing signals"),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
Let’s re-introduce
|
"Using signals, we split program logic into the three parts.",
|
||||||
${el("a", { textContent: t`3PS principle`, href: "./#h-event-driven-programming--parts-separation--ps" })}.
|
" Firstly (α), we create a variable (constant) representing reactive",
|
||||||
`),
|
" value. Somewhere later, we can register (β) a logic reacting",
|
||||||
el("p").append(...T`
|
" to the signal value changes. Similarly, in a remaining part (γ), we",
|
||||||
Using signals, we split program logic into the three parts. Firstly (α), we create a variable (constant)
|
" can update the signal value."
|
||||||
representing reactive value. Somewhere later, we can register (β) a logic reacting to the signal value
|
),
|
||||||
changes. Similarly, in a remaining part (γ), we can update the signal value.
|
|
||||||
`),
|
|
||||||
el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/signals/signals.js"), page_id }),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
All this is just an example of
|
"All this is just an example of ",
|
||||||
${el("a", { textContent: t`Event-driven programming`, ...references.wiki_event_driven })} and
|
el("a", { textContent: "Event-driven programming", href: "https://en.wikipedia.org/wiki/Event-driven_programming", title: "Wikipedia: Event-driven programming" }),
|
||||||
${el("a", { textContent: t`Publish–subscribe pattern`, ...references.wiki_pubsub })} (compare for example
|
" and ",
|
||||||
with ${el("a", { textContent: t`fpubsub library`, ...references.fpubsub })}). All three parts can be in
|
el("a", { textContent: "Publish–subscribe pattern", href: "https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern", title: "Wikipedia: Publish–subscribe pattern" }),
|
||||||
some manner independent and still connected to the same reactive entity.
|
" (compare for example with ", el("a", { textContent: "fpubsub library", href: "https://www.npmjs.com/package/fpubsub", title: "NPM package: fpubsub" }), ").",
|
||||||
`),
|
" All three parts can be in some manner independent and still connected",
|
||||||
el("p").append(...T`
|
" to the same reactive entity."
|
||||||
Signals are implemented in the library as functions. To see current value of signal, just call it without
|
),
|
||||||
any arguments ${el("code", "console.log(signal())")}. To update the signal value, pass any argument
|
el("p").append(
|
||||||
${el("code", `signal('${t`a new value`}')`)}. For listenning the signal value changes, use
|
"Signals are implemented in the library as functions. To see current value",
|
||||||
${el("code", "S.on(signal, console.log)")}.
|
" of signal, just call it without any arguments ", el("code", "console.log(signal())"), ".",
|
||||||
`),
|
" To update the signal value, pass any argument ", el("code", "signal('a new value')"), ".",
|
||||||
el("p").append(...T`
|
" For listenning the signal value changes, use ", el("code", "S.on(signal, console.log)"), "."
|
||||||
Similarly to the ${el("code", "on")} function to register DOM events listener. You can use
|
),
|
||||||
${el("code", "AbortController")}/${el("code", "AbortSignal")} to ${el("em", "off")}/stop listenning. In
|
el("p").append(
|
||||||
example, you also found the way for representing “live” piece of code computation pattern (derived signal):
|
"Similarly to the ", el("code", "on"), " function to register DOM events listener.",
|
||||||
`),
|
" You can use ", el("code", "AbortController"), "/", el("code", "AbortSignal"), " to",
|
||||||
|
" ", el("em", "off"), "/stop listenning. In example, you also found the way for representing",
|
||||||
|
" “live” piece of code computation pattern (derived signal):"
|
||||||
|
),
|
||||||
el(example, { src: fileURL("./components/examples/signals/computations-abort.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/signals/computations-abort.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Signals and actions`),
|
el(h3, "Signals and actions"),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
${el("code", `S(/* ${t`primitive`} */)`)} allows you to declare simple reactive variables, typically, around
|
el("code", "S(/* primitive */)"), " allows you to declare simple reactive variables, typically",
|
||||||
${el("em", t`immutable`)} ${el("a", { textContent: t`primitive types`, ...references.mdn_primitive })}.
|
" around ", el("em", "immutable"), " ", el("a", { textContent: "primitive types", title: "Primitive | MDN", href: "https://developer.mozilla.org/en-US/docs/Glossary/Primitive" }), ".",
|
||||||
However, it may also be necessary to use reactive arrays, objects, or other complex reactive structures.
|
" ",
|
||||||
`),
|
"However, it may also be necessary to use reactive arrays, objects, or other complex reactive structures."
|
||||||
|
),
|
||||||
el(example, { src: fileURL("./components/examples/signals/actions-demo.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/signals/actions-demo.js"), page_id }),
|
||||||
el("p", t`…but typical user-case is object/array (maps, sets and other mutable objects):`),
|
el("p", "…but typical user-case is object/array (maps, sets and other mutable objects):"),
|
||||||
el(example, { src: fileURL("./components/examples/signals/actions-todos.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/signals/actions-todos.js"), page_id }),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
In some way, you can compare it with ${el("a", { textContent: "useReducer", ...references.mdn_use_reducer })}
|
"In some way, you can compare it with ", el("a", { textContent: "useReducer", href: "https://react.dev/reference/react/useReducer", title: "useReducer hook | React docs" }),
|
||||||
hook from React. So, the ${el("code", "S(<data>, <actions>)")} pattern creates a store “machine”. We can
|
" hook from React. So, the ", el("code", "S(<data>, <actions>)"), " pattern creates",
|
||||||
then invoke (dispatch) registered action by calling ${el("code", "S.action(<signal>, <name>, ...<args>)")}
|
" a store “machine”. We can then invoke (dispatch) registered action by calling",
|
||||||
after the action call the signal calls all its listeners. This can be stopped by calling
|
" ", el("code", "S.action(<signal>, <name>, ...<args>)"), " after the action call",
|
||||||
${el("code", "this.stopPropagation()")} in the method representing the given action. As it can be seen in
|
" the signal calls all its listeners. This can be stopped by calling ", el("code", "this.stopPropagation()"),
|
||||||
examples, the “store” value is available also in the function for given action (${el("code", "this.value")}).
|
" in the method representing the given action. As it can be seen in examples, the “store” value is",
|
||||||
`),
|
" available also in the function for given action (", el("code", "this.value"), ")."
|
||||||
|
),
|
||||||
|
|
||||||
el(h3, t`Reactive DOM attributes and elements`),
|
el(h3, "Reactive DOM attributes and elements"),
|
||||||
el("p", t`There are on basic level two distinc situation to mirror dynamic value into the DOM/UI`),
|
el("p", "There are on basic level two distinc situation to mirror dynamic value into the DOM/UI"),
|
||||||
el("ol").append(
|
el("ol").append(
|
||||||
el("li", t`to change some attribute(s) of existing element(s)`),
|
el("li", "to change some attribute(s) of existing element(s)"),
|
||||||
el("li", t`to generate elements itself dynamically – this covers conditions and loops`)
|
el("li", "to generate elements itself dynamically – this covers conditions and loops")
|
||||||
),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
To derived attribute based on value of signal variable just use the signal as a value of the attribute
|
"To derived attribute based on value of signal variable just use the signal as",
|
||||||
(${el("code", "assign(element, { attribute: S('value') })")}). ${el("code", "assign")}/${el("code", "el")}
|
" a value of the attribute (", el("code", "assign(element, { attribute: S('value') })"), ").",
|
||||||
provides ways to glue reactive attributes/classes more granularly into the DOM. Just use dedicated build-in
|
" ", el("code", "assign"), "/", el("code", "el"), " provides ways to glue reactive attributes/classes",
|
||||||
attributes ${el("code", "dataset")}, ${el("code", "ariaset")} and ${el("code", "classList")}.
|
" more granularly into the DOM. Just use dedicated build-in attributes ", el("code", "dataset"), ", ",
|
||||||
`),
|
el("code", "ariaset"), " and ", el("code", "classList"), "."
|
||||||
el("p").append(...T`
|
),
|
||||||
For computation, you can use the “derived signal” (see above) like
|
el("p").append(
|
||||||
${el("code", "assign(element, { textContent: S(()=> 'Hello '+WorldSignal()) })")}. This is read-only signal
|
"For computation, you can use the “derived signal” (see above) like ", el("code", "assign(element, { textContent: S(()=> 'Hello '+WorldSignal()) })"), ".",
|
||||||
its value is computed based on given function and updated when any signal used in the function changes.
|
" ",
|
||||||
`),
|
"This is read-only signal its value is computed based on given function and updated when any signal used in the function changes."
|
||||||
el("p").append(...T`
|
),
|
||||||
To represent part of the template filled dynamically based on the signal value use
|
el("p").append(
|
||||||
${el("code", "S.el(signal, DOMgenerator)")}. This was already used in the todo example above or see:
|
"To represent part of the template filled dynamically based on the signal value use ", el("code", "S.el(signal, DOMgenerator)"), ".",
|
||||||
`),
|
" This was already used in the todo example above or see:"
|
||||||
|
),
|
||||||
el(example, { src: fileURL("./components/examples/signals/dom-el.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/signals/dom-el.js"), page_id }),
|
||||||
|
|
||||||
el(mnemonic)
|
el(mnemonic)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { T, t } from "./utils/index.js";
|
|
||||||
export const info= {
|
export const info= {
|
||||||
title: t`Scopes and components`,
|
title: "Scopes and components",
|
||||||
description: t`Organizing UI into components`,
|
description: "Organizing UI into components",
|
||||||
};
|
};
|
||||||
|
|
||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
@ -12,74 +11,67 @@ import { mnemonic } from "./components/mnemonic/scopes-init.js";
|
|||||||
import { code } from "./components/code.html.js";
|
import { code } from "./components/code.html.js";
|
||||||
/** @param {string} url */
|
/** @param {string} url */
|
||||||
const fileURL= url=> new URL(url, import.meta.url);
|
const fileURL= url=> new URL(url, import.meta.url);
|
||||||
const references= {
|
|
||||||
/** Garbage collection on MDN */
|
|
||||||
garbage_collection: {
|
|
||||||
title: t`MDN documentation page for Garbage collection`,
|
|
||||||
href: "https://developer.mozilla.org/en-US/docs/Glossary/Garbage_collection",
|
|
||||||
},
|
|
||||||
/** Signals */
|
|
||||||
signals: {
|
|
||||||
title: t`Signals section on this library`,
|
|
||||||
href: "./p04-signals#h-introducing-signals",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
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", t`Using functions as UI components`),
|
el("h2", "Using functions as UI components"),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
For state-less components we can use functions as UI components (see “Elements” page). But in real life,
|
"For state-less components we can use functions as UI components (see “Elements” page).",
|
||||||
we may need to handle the component live-cycle and provide JavaScript the way to properly use
|
" But in real life, we may need to handle the component live-cycle and provide",
|
||||||
the ${el("a", { textContent: t`Garbage collection`, ...references.garbage_collection })}.
|
" 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(code, { src: fileURL("./components/examples/scopes/intro.js"), page_id }),
|
||||||
el("p").append(...T`The library therefore use ${el("em", t`scopes`)} to provide these functionalities.`),
|
el("p").append(
|
||||||
|
"The library therefore use ", el("em", "scopes"), " to provide these functionalities.",
|
||||||
|
),
|
||||||
|
|
||||||
el(h3, t`Scopes and hosts`),
|
el(h3, "Scopes and hosts"),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
The ${el("strong", "host")} is the name for the element representing the component. This is typically
|
"The ", el("strong", "host"), " is the name for the element representing the component.",
|
||||||
element returned by function. To get reference, you can use ${el("code", "scope.host()")} to applly addons
|
" This is typically element returned by function. To get reference, you can use ",
|
||||||
just use ${el("code", "scope.host(...<addons>)")}.
|
el("code", "scope.host()"), " to applly addons just use ", el("code", "scope.host(...<addons>)"), "."
|
||||||
`),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/scopes/scopes-and-hosts.js"), page_id }),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
To better understanding we implement function ${el("code", "elClass")} helping to create component as
|
"To better understanding we implement function ", el("code", "elClass"), " helping to create",
|
||||||
class instances.
|
" component as class instances."
|
||||||
`),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/scopes/class-component.js"), page_id }),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
As you can see, the ${el("code", "scope.host()")} is stored temporarily and synchronously. Therefore, at
|
"As you can see, the ", el("code", "scope.host()"), " is stored temporarily and synchronously.",
|
||||||
least in the beginning of using library, it is the good practise to store ${el("code", "host")} in the root
|
" Therefore, at least in the beginning of using library, it is the good practise to store",
|
||||||
of your component. As it may be changed, typically when there is asynchronous code in the component.
|
" ", el("code", "host"), " in the root of your component. As it may be changed, typically when",
|
||||||
`),
|
" there is asynchronous code in the component."
|
||||||
|
),
|
||||||
el(code, { src: fileURL("./components/examples/scopes/good-practise.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/scopes/good-practise.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Scopes, signals and cleaning magic`),
|
el(h3, "Scopes, signals and cleaning magic"),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
The ${el("code", "host")} is internally used to register the cleaning procedure, when the component
|
"The ", el("code", "host"), " is internally used to register the cleaning procedure,",
|
||||||
(${el("code", "host")} element) is removed from the DOM.
|
" when the component (", el("code", "host"), " element) is removed from the DOM."
|
||||||
`),
|
),
|
||||||
el(example, { src: fileURL("./components/examples/scopes/cleaning.js"), page_id }),
|
el(example, { src: fileURL("./components/examples/scopes/cleaning.js"), page_id }),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
The text content of the paragraph is changing when the value of the signal ${el("code", "textContent")}
|
"The text content of the paragraph is changing when the value of the signal ", el("code", "textContent"),
|
||||||
is changed. Internally, there is association between ${el("code", "textContent")} and the paragraph,
|
" is changed. Internally, there is association between ", el("code", "textContent"), " and the paragraph",
|
||||||
similar to using ${el("code", `S.on(textContent, /* ${t`update the paragraph`} */)`)}.
|
" similar to using ", el("code", "S.on(textContent, /* update the paragraph */)"), "."
|
||||||
`),
|
),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
This listener must be removed when the component is removed from the DOM. To do it, the library assign
|
"This listener must be removed when the component is removed from the DOM. To do it, the library",
|
||||||
internally ${el("code", `on.disconnected(/* ${t`remove the listener`} */)(host())`)} to the host element.
|
" assign internally ", el("code", "on.disconnected(/* remove the listener */)(host())"), " to the host element."
|
||||||
`),
|
),
|
||||||
el("p", { className: "notice" }).append(...T`
|
el("p", { className: "notice" }).append(
|
||||||
The library DOM API and signals works ideally when used declaratively. It means, you split your app logic
|
"The library DOM API and signals works ideally when used declaratively.",
|
||||||
into three parts as it was itroduced in ${el("a", { textContent: "Signals", ...references.signals })}.
|
" It means, you split your app logic into three parts as it was itroduced in ", el("a", { textContent: "Signals", href: "http://localhost:40911/docs/p04-signals#h-introducing-signals" }), "."
|
||||||
`),
|
),
|
||||||
el(code, { src: fileURL("./components/examples/scopes/declarative.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/scopes/declarative.js"), page_id }),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
Strictly speaking, the imperative way of using the library is not prohibited. Just be careful (rather avoid)
|
"Strictly speaking, the imperative way of using the library is not prohibited.",
|
||||||
mixing declarative approach (using signals) and imperative manipulation of elements.
|
" Just be careful (rather avoid) mixing declarative approach (using signals)",
|
||||||
`),
|
" and imperative manipulation of elements.",
|
||||||
|
),
|
||||||
el(code, { src: fileURL("./components/examples/scopes/imperative.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/scopes/imperative.js"), page_id }),
|
||||||
|
|
||||||
el(mnemonic)
|
el(mnemonic)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { T, t } from "./utils/index.js";
|
|
||||||
export const info= {
|
export const info= {
|
||||||
title: t`Custom elements`,
|
title: "Custom elements",
|
||||||
description: t`Using custom elements in combinantion with DDE`,
|
description: "Using custom elements in combinantion with DDE",
|
||||||
};
|
};
|
||||||
|
|
||||||
import { el } from "deka-dom-el";
|
import { el } from "deka-dom-el";
|
||||||
@ -12,35 +11,25 @@ import { mnemonic } from "./components/mnemonic/customElement-init.js";
|
|||||||
import { code } from "./components/code.html.js";
|
import { code } from "./components/code.html.js";
|
||||||
/** @param {string} url */
|
/** @param {string} url */
|
||||||
const fileURL= url=> new URL(url, import.meta.url);
|
const fileURL= url=> new URL(url, import.meta.url);
|
||||||
const references= {
|
|
||||||
/** Custom Elements on MDN */
|
|
||||||
custom_elements: {
|
|
||||||
title: t`MDN documentation page for Custom Elements`,
|
|
||||||
href: "https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements",
|
|
||||||
},
|
|
||||||
/** Custom Elements tips from WebReflection */
|
|
||||||
custom_elements_tips: {
|
|
||||||
title: t`Ideas and tips from WebReflection`,
|
|
||||||
href: "https://gist.github.com/WebReflection/ec9f6687842aa385477c4afca625bbf4",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
/** @param {import("./types.d.ts").PageAttrs} attrs */
|
||||||
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", t`Using custom elements in combinantion with DDE`),
|
el("h2", "Using custom elements in combinantion with DDE"),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
`),
|
|
||||||
|
),
|
||||||
el(code, { src: fileURL("./components/examples/customElement/intro.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/customElement/intro.js"), page_id }),
|
||||||
|
|
||||||
el(h3, t`Custom Elements Introduction`),
|
el(h3, "Custom Elements Introduction"),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
${el("a", { textContent: t`Using custom elements`, ...references.custom_elements })}
|
el("a", { textContent: "Using custom elements", href: "https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements", title: "Article about custom elements on MDN" })
|
||||||
`),
|
),
|
||||||
el(code, { src: fileURL("./components/examples/customElement/native-basic.js"), page_id }),
|
el(code, { src: fileURL("./components/examples/customElement/native-basic.js"), page_id }),
|
||||||
el("p").append(...T`
|
el("p").append(
|
||||||
${el("a", { textContent: t`Handy Custom Elements' Patterns`, ...references.custom_elements_tips })}
|
el("a", { textContent: "Handy Custom Elements' Patterns", href: "https://gist.github.com/WebReflection/ec9f6687842aa385477c4afca625bbf4", title: "Ideas and tips from WebReflection" })
|
||||||
`),
|
),
|
||||||
|
|
||||||
el(mnemonic)
|
el(mnemonic)
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
export { t } from "./utils/index.js";
|
|
||||||
export const path_target= {
|
export const path_target= {
|
||||||
root: "docs/",
|
root: "docs/",
|
||||||
css: "docs/"
|
css: "docs/"
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
/**
|
|
||||||
* This is helper to write texts in code more readable
|
|
||||||
* and doesn’t inpact the finally generated text in HTML.
|
|
||||||
*
|
|
||||||
* ```js
|
|
||||||
* t`
|
|
||||||
* Hello ${el("b", "world")}!
|
|
||||||
* How are you?
|
|
||||||
* ` === "Hello <b>world</b>! How are you?"
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* In future, this can be expanded to allow translations.
|
|
||||||
*
|
|
||||||
* ```js
|
|
||||||
* t(key)`text`; // for example
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param {TemplateStringsArray} strings
|
|
||||||
* @param {...(string|Node)} values
|
|
||||||
* @returns {(string|Node)[]}
|
|
||||||
* */
|
|
||||||
export function T(strings, ...values){
|
|
||||||
const out= [];
|
|
||||||
tT(s=> out.push(s), strings, ...values);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Similarly to {@link T}, but for only strings.
|
|
||||||
* @param {TemplateStringsArray} strings
|
|
||||||
* @param {...string} values
|
|
||||||
* @returns {string}
|
|
||||||
* */
|
|
||||||
export function t(strings, ...values){
|
|
||||||
let out= "";
|
|
||||||
tT(s=> out+= s, strings, ...values);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {(input: string|Node)=> void} callback
|
|
||||||
* @param {TemplateStringsArray} strings
|
|
||||||
* @param {...(string|Node)} values
|
|
||||||
* */
|
|
||||||
function tT(callback, strings, ...values){
|
|
||||||
const { length }= strings;
|
|
||||||
const last= length-1;
|
|
||||||
for(let i= 0; i<length; i++){
|
|
||||||
const out= strings[i].replace(/\n\s+/g, " ");
|
|
||||||
callback(!i ? out.trimStart() : i===last ? out.trimEnd() : out);
|
|
||||||
if(i<values.length) callback(values[i]);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user