249 lines
7.5 KiB
HTML
249 lines
7.5 KiB
HTML
<link rel="stylesheet" href="style.css">
|
|
|
|
<script type="module" src="codeblock.js"></script>
|
|
<script type="module" src="filesize.js"></script>
|
|
|
|
<h1>Skooma.js</h1>
|
|
|
|
<code-block>import {html} from 'skooma.js'</code-block>
|
|
|
|
<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.
|
|
<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 => console.log(event)
|
|
})
|
|
</code-block>
|
|
<code-block>
|
|
let button document.createElement("button")
|
|
button.innerText = "Click Me!"
|
|
button.addEventListener(
|
|
"click", event => 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.
|
|
If the function returns a non-truthy value, the element won't be
|
|
replaced.
|
|
</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>
|
|
<code-block>
|
|
// onIncrement doesn't return an initial state, so we have to wrap it:
|
|
let bindCount = bind(callback => counter.onIncrement(callback) || [counter.count])
|
|
|
|
let counterMessage = count => text`Current count: ${html.b(count)}`
|
|
|
|
return bindCount(counterMessage)
|
|
</code-block>
|
|
|
|
<p>
|
|
This can also be broken down to text nodes for more atomic updates.
|
|
</p>
|
|
<code-block>
|
|
// onIncrement doesn't return an initial state, so we have to wrap it:
|
|
let bindCount = bind(callback => counter.onIncrement(callback) || [counter.count])
|
|
|
|
return text`Current count: ${bindCount(text)}`
|
|
</code-block>
|
|
|
|
<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>
|