<link rel="stylesheet" href="style.css">

<script type="module" src="codeblock.js"></script>
<script type="module" src="filesize.js"></script>
<script type="module" src="scrollToTop.js"></script>

<scroll-to-top>
</scroll-to-top>

<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 =&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>The magic <code>dataset</code> attribute can be used to set values in the object's data-set</p>
		<p></p>
		<code-block>
			return html.div({ dataset: { name: "user" } })
		</code-block>
		<code-block>
			let div = document.createElement("div")
			div.dataset.name = "user"
			return div
		</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>
			Function arguments will be called on the new element.<br>
			This can be used to easily add custom initialisation logic to elements.
		</p>
		<p></p>
		<code-block>
			return html.p("Hello", console.log, ", world!")
		</code-block>
		<code-block>
			const element = document.createElement("p")
			element.innerText = "Hello"
			console.log(element)
			element.innerText += ", world!"
			return element
		</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>

		<p>Custom built-ins can be created with the <code>is</code> attribute.</p>
		<p></p>
		<code-block>
			return html.span({is: "my-span"})
			// Also sets the `is` attribute, useful for selectors
			// like span[is="my-span"]
		</code-block>
		<code-block>
			return document.createElement("span", {is: "my-span"})
			// No actual `is` attribute. GL styling these.
		</code-block>
	</div>
</section>

<section>
	<h2>The <code>svg</code> helper</h2>

	<p>
		This works exactly the same as the <code>html</code> helper,
		except that it creates elements with the appropriate namespace
		and does <em>not</em> convert camelCase to kebab-case.
	</p>
</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>

	<dl>
		<code>
			<dt>bind</dt>
			<dd>transform-function &xrarr; update-function</dd>
		</code>
	</dl>

	<p>
		<dl>
			<dt>Transform function</dt>
			<code>
				<dd>...data &xrarr; new-element</dd>
			</code>
			<dd>
				A function that takes the current state and returns a new HTML element.
				If the function returns a non-truthy value, the element won't be replaced.
				<div>
					<strong>Note:</strong> the function must return a single <em>element</em>.
					Therefore one cannot use tagged template literals with <code>text</code>
					as this would return a document fragment which cannot be replaced.
				</div>
			</dd>
			<dt>Update function</dt>
			<code>
				<dd>...data &xrarr; new-element</dd>
			</code>
			<dd>
				A function that passes its arguments to the transform function and
				returns its results while also taking care of replacing the old
				element with the new one and injecting the <code>current</code>
				attribute into it.
			</dd>
		</dl>
	</p>

	<p>
		A simple self-contained incrementing counter button could be implemented like this:
	</p>
	<code-block>
		let update = bind(count =&gt; html.button(`Count: ${count}`, {click: event =&gt; update(count+1)}))
		document.body.append(update(1))
	</code-block>
	<p>
		The initial call of <code>update</code> sets the initial count of the
		button, and the attached event handler updates the button every time it
		is clicked, thereby replacing it with a new one.
	</p>
	<p>
		For this next example, imagine a <code>counter</code> object that works like this:
		<ul>
			<li> <code>counter.count</code> returns the current count
			<li> <code>counter.onUpdate</code> lets the user register a callback that will be called with the new count whenever the counter updates
			<li> The counter will be updated periodically by some other part of the application
		</ul>
		The following code could be used to display the current count in the application:
	</p>
	<code-block>
		let update = bind(text)
		counter.onIncrement(update)
		return text`Current count: ${update(counter.count)}`
	</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>

<section>
	<h2>The <code>handle</code> helper</h2>

	<p>
		This helper function takes an event handler and wraps it in a new
		function that calls <code>preventDefault</code> on the event before
		passing it to the original function.
	</p>
	<code-block>
		html.form(html.button({
			click: handle(event =&gt; console.log("I'm not submitting anything"))
		}))
	</code-block>
</section>

<section>
	<h2>The <code>empty</code> constant</h2>

	<p>
		This symbol will be completely ignored when it appears as a children in any skooma generator.
	</p>
	<code-block>
		const name = undefined
		html.div("name: ", name ?? "") // This will generate an (unnecessary) empty text note
		html.div("name: ", name ?? null) // This will print a warning to the console (same with undefined)
		html.div("name: ", name ?? empty) // This will only generate the first text node
	</code-block>
</section>