Find a file
2024-09-07 08:54:25 +02:00
.editorconfig Add basic editorconfig 2024-06-24 12:27:05 +02:00
domProxy.js Add meta-tag domProxy 2024-02-06 22:56:08 +01:00
jsconfig.json Add jsdoc class annotations to skooma module 2024-02-12 13:38:57 +01:00
license.md Update metadata 2023-10-04 10:35:56 +02:00
mini.js Add mini.js as minimalist node creation helper 2024-09-07 08:54:25 +02:00
observable.js More code documentation 2024-06-24 13:04:42 +02:00
package.json Rename skooma module to "render" 2024-03-18 11:49:38 +01:00
readme.md Add code example and goals section in readme 2024-08-02 09:24:50 +02:00
ref.js Extract Ref class into separate module 2024-02-29 15:33:26 +01:00
render.js Fix handling of document fragments in renderer 2024-07-31 09:30:17 +02:00

Skooma

import {html} from "skooma/render.js"

document.body.append(
	html.p(
		"This is a paragraph with some text ",
		html.b("and some bold text "),
		html.img({
			alt: "And an image",
			href: "http://picsum.photos/200/200"
		})
	)
)

Goals

  1. skooma/render should stay small enough to use it as just a helper library to generate some dom nodes in any sort of web environment.
  2. skooma/observable should likewise function as a standalone reactive state management library to be used with or without a framework
  3. A developer who doesn't use skooma should be able to read any code using it and piece together what it does based on structure and function names
  4. Skooma should be easy to gradually introduce into an application that uses a different framework or no framework at all
  5. Skooma should make it easy to gradually replace it with a different solution should it prove unfit for a project it is being used in
  6. The library should be hackable so that developers can tweak it for different environments like SSR or frameworks

Warning

This branch is in the process of being aggressively refactored and improved. This readme file may not reflect the latest state of the interface.

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 generatio

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/state.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"),
]))