js/page/skooma.html

253 lines
7.6 KiB
HTML
Raw Normal View History

2022-01-01 12:05:43 +00:00
<link rel="stylesheet" href="style.css">
<script type="module" src="codeblock.js"></script>
<script type="module" src="filesize.js"></script>
2022-01-01 12:05:43 +00:00
<h1>Skooma.js</h1>
<code-block>import {html} from 'skooma.js'</code-block>
2022-01-01 12:05:43 +00:00
<section>
<h2>Introduction & Scope</h2>
<p>
Skooma.js is a library for generating DOM nodes within JavaScript.
</p>
<h3>What are the benefits of Skooma?</h3>
<p>
Skooma is only a small <file-size file="../skooma.js"></file-size> ES6
module that uses meta-programming to turn JavaScript into a
<span title="Domain-Specific Language">DSL</span> that generates HTML
and XML subtrees.
2022-01-01 12:05:43 +00:00
<br>
This means you're writing plain JavaScript code that needs no additional
transpilation steps and runs directly in the browser.
</p>
</section>
<section>
<h2>Showcase</h2>
<p>Here's a few examples of how things are done in Skooma.js and how it compares to vanilla JavaScript.</p>
<div class="columns">
<h3>Skooma.js</h3>
<h3>Vanilla JavaScript</h3>
<p>Generating a single empty HTML element. The <code>html</code> namespace creates generator functions dynamically.</p>
<p>Using the browser API, this is a bit more verbose, but still looks similar.</p>
<code-block>
return html.h1()
</code-block>
<code-block>
return document.createElement("h1")
</code-block>
<p>String arguments to the generator function will be inserted as <strong>text nodes</strong>.</p>
<p>Without Skooma.js this would already require using a variable since <code>createElement</code> cannot insert text content into a new node.</p>
<code-block>
return html.h1("Hello, World!")
</code-block>
<code-block>
let h1 = document.createElement("h1")
h1.innerText = "Hello, World!"
return h1
</code-block>
<p>DOM Nodes can also be passed into the generator function to add them as <strong>child-elements.</strong></p>
<p>This would normally require two separate variables, one for each element.</p>
<code-block>
return html.div(html.b("Hello!"))
</code-block>
<code-block>
let div = document.createElement("div")
let b = document.createElement("b")
b.innerText = "Hello!"
div.append(b)
return div
</code-block>
<p>When passing an object, its key/value pairs will be added as <strong>attributes</strong> to the new element.</p>
<p>Once again, in plain JS this requires a variable.</p>
<code-block>
return html.div({attribute: "value"})
</code-block>
<code-block>
let div = document.createElement("div")
div.setAttribute("attribute", "value")
return div
</code-block>
<p>When an object value is a function, it will instead be added as an <strong>event handler</strong>. The corresponding key will be used as the event name.</p>
<p>You guessed it: variable.</p>
<code-block>
return html.button("Click Me!", {
click: event =&gt; console.log(event)
})
</code-block>
<code-block>
let button document.createElement("button")
button.innerText = "Click Me!"
button.addEventListener(
"click", event =&gt; console.log(event)
)
return button
</code-block>
<p>Adding a <strong>shadow-root</strong> to the new element can be done with the magic <code>shadowRoot</code> property.</p>
<p></p>
<code-block>
return html.div({
shadowRoot: html.p("Shadow-DOM text content")
}, "Light-DOM text content")
</code-block>
<code-block>
let div = document.createElement("div")
let p = document.createElement("p")
p.innerText = "Shadow-DOM text content"
div.attachShadow({mode: "open"}).append(p)
div.innerText = "Light-DOM text content"
return div
</code-block>
<p>Object can be <strong>styled</strong> inline via the magic <code>style</code> property.</p>
<p>Meanwhile in Vanilla JS styling properties have to be added one by one</p>
<code-block>
return html.div("Hello, World!" {
class: 'button', style: {
color, // some constant
border: '1px solid currentcolor
}
})
</code-block>
<code-block>
let div = document.createElement("div")
div.innerHTML = "Hello, World!"
div.style.color: color // some constant
div.style.border: '1px solid currentcolor'
return div
</code-block>
<p>Custom elements with hyphenated names can be created easily</p>
<p></p>
<code-block>
return html.myComponent()
</code-block>
<code-block>
return document.createElement("my-component")
</code-block>
</div>
</section>
<section>
<h2>The <code>text</code> helper</h2>
<div class="columns">
<p>The <code>text</code> helper provides a convenient wrapper around the
<code>document.createTextNode</code> function</p>
<p>In its simplest form, it's only a shorthand for its vanilla counterpart</p>
<code-block>
return text("Hello, World!")
</code-block>
<code-block>
return document.createTextNode("Hello, World!")
</code-block>
<p>However, you don't need to pass an argument to it.</p>
<p></p>
<code-block>
return text()
</code-block>
<code-block>
return document.createTextNode("")
</code-block>
<p>It also acts as a tag function for template literals, returning a
document fragment containing a list of text nodes.</p>
<p></p>
<code-block>
return text`Hello, ${name}!`
</code-block>
<code-block>
let fragment = new DocumentFragment()
fragment.append("Hello, ")
fragment.append(name)
fragment.append("!")
return fragment
</code-block>
<p>You can even interpolate actual DOM nodes in the string</p>
<p></p>
<code-block>
return text`Hello, ${html.b(name)}!`
</code-block>
<code-block>
let fragment = new DocumentFragment()
fragment.append("Hello, ")
let bold = document.createElement("b")
bold.innerHTML = name
fragment.append(bold)
fragment.append("!")
return fragment
</code-block>
</div>
</section>
<section>
<h2>The <code>bind</code> helper</h2>
<p>
<dl>
<dt>Callback registration function</dt>
<dd>
A function that takes a callback as its single argument and returns
an initial state as an array of elements. The inital state will be
used to generate the bound element for the first time. The callback
function should be called whenever an update in the UI is desired
and the new state should be passed as its argument.
</dd>
<dt>Transform function</dt>
<dd>
A function that takes the initial or updated state and returns a new
HTML element.
<details><summary>Planned feature</summary>
When no HTML element is returned, nothing happens. This can be
used to update the existing element instead; however, the
function currently provides no means to access the old element in
the transform function.
</details>
</dd>
</dl>
</p>
<p>
Imagine <code>counter</code> to be an object with a <code>count</code>
attribute representing the current count and <code>onIncrement</code> to
be a function to register a callback to be called whenever the counter
gets updated.
</p>
<div class="columns">
<h3>Creating a new Element</h3>
<h3>Updating the old Element</h3>
<code-block>
let counterMessage = count =&gt;
text`Current count: ${html.b(count)}`
// onIncrement doesn't return an initial
// state, so we have to wrap it:
let onIncrement = bind(callback =&gt;
counter.onIncrement(callback) || counter.count)
return boundElement = onIncrement(counterMessage)
</code-block>
<code-block>
// Not yet implemented
</code-block>
</div>
<p>
When an element gets replaced with a newer version of itself, any variable
containing the old element will become "stale". For this reason, the
function injects a <code>current</code> property into every element it
creates that will always point to the newest version of the element.
</p>
</section>