Find a file
DarkWiiPlayer 3f838821e4 Add support for child generators as skooma args
Function arguments to skooma generators are now only treated as
initialisers if they take at least one named argument. Otherwise they
are called and their return value is evaluated as their replacement.

The process is recursive and can happen repeatedly for functions that
themselves return functions.
2024-02-01 14:00:26 +01:00
domLense.js Change domLense method semantics 2024-02-01 13:35:57 +01:00
license.md Update metadata 2023-10-04 10:35:56 +02:00
package.json Add subscribe method to state class 2024-01-17 11:39:16 +01:00
readme.md Move description above import block in readme 2023-12-23 18:25:05 +01:00
skooma.js Add support for child generators as skooma args 2024-02-01 14:00:26 +01:00
state.js Refactor get/set to use arguments instead of ... 2024-02-01 13:35:57 +01:00

Skooma

A functional-friendly helper library for procedural DOM generation and templating.

import {html} from "skooma.js"

Overview

const text = new State({value: "Skooma is cool"})
setTimeout(() => {text.value = "Skooma is awesome!"}, 1e5)

document.body.append(html.div(
    html.h1("Hello, World!"),
    html.p(text, {class: "amazing"}),
    html.button("Show Proof", {click: event => { alert("It's true!") }})
))

Interface / Examples

Basic DOM generation

Accessing the html proxy with any string key returns a new node generator function:

html.div("Hello, World!")

Attributes can be set by passing objects to the generator:

html.div("Big Text", {style: "font-size: 1.4em"})

Complex structures can easily achieved by nesting generator functions:

html.div(
    html.p(
        html.b("Bold Text")
    )
)

For convenience, arrays assigned as attributes will be joined with spaces:

html.a({class: ["button", "important"]})

Assigning a function as an attribute will instead attach it as an event listener:

html.button("Click me!", {click: event => {
    alert("You clicked the button.")
}})

Generators can be called with many arguments. Arrays get iterated recursively as if they were part of a flat argument list.

Generating Text Nodes

text("Hello, World")
// Wraps document.createTextNode
text()
// Defaults to empty string instead of erroring
text(null)
// Non-string arguments still error

text`Hello, World!`
// returns a new document fragment containing the text node "Hello, World!"
text`Hello, ${user}!`
// returns a document fragment containing 3 nodes:
// "Hello, ", the interpolated value of `user` and "!"
text`Hello, ${html.b(user)}!`
// Text node for Hello, the <b> tag with the user's name, and a text node for !

handle

import {handle} from 'skooma.js'

Since it is common for event handlers to call preventDefault(), skooma provides a helper function called handle with the following definition:

fn => event => { event.preventDefault(); return fn(event) }

A few more examples:

Create a Button that deletes itself:

document.body.append(
	html.button("Delete Me", {click: event => event.target.remove()})
)

Turn a two-dimensional array into an HTML table:

const table = rows =>
	html.table(html.tbody(rows.map(
		row => html.tr(row.map(
			cell => html.rd(cell, {dataset: {
				content: cell.toLowerCase(),
			}})
		))
	)))

A list that you can add items to

let list, input = ""
document.body.append(html.div([
	list=html.ul(),
	html.input({type: 'text', input: e => input = e.target.value}),
	html.button({click: event => list.append(html.li(input))}, "Add"),
]))

A list that you can also delete items from

const listItem = content => html.li(
	html.span(content), " ", html.a("[remove]", {
		click: event => event.target.closest("li").remove(),
		style: { cursor: 'pointer', color: 'red' },
	})
)
let list, input = ""
document.body.append(html.div([
	list=html.ul(),
	html.input({type: 'text', input: e => input = e.target.value}),
	html.button({click: event => list.append(listItem(input))}, "Add"),
]))