0.0.3 • Published 5 months ago

@sprs/dom v0.0.3

Weekly downloads
-
License
MIT
Repository
-
Last release
5 months ago

@sprs/dom

@sprs/dom is a minimal library for dealing with DOM elements.

The feature set aims to be intentional, pointed, and free from unnecessary complexity and mental overhead.

It integrates directly with @sprs/state, which provides primitives for reactive states.

Utilities

render

The render function accepts a single parameter, which may be:

  • a primitive
  • a DOM node
  • a NodeProducer (see below)
  • a reactive state from @sprs/state
  • an array of any of these items

The output of render is a DocumentFragment containing the rendered DOM nodes. Use HTMLElement.appendChild to add the resulting nodes to the DOM at runtime.

Example:

import { render, NodeProducer } from '@sprs/dom';

/*
 * Trivial example class to show that `NodeProducer` is
 * understood by `render`.
 */
class RedDiv extends NodeProducer {
  constructor() {
    this.div = document.createElement('div');
    this.div.style.backgroundColor = 'red';
  }

  getNode() {
    return this.div;
  }
}

// Make a small fragment
const fragment = render([
  document.createElement('body'),
  [
    new RedDiv(),
    123e2,
    'a string',
    true,
  ],
]);

// Attach it to the body of the page
document.querySelector('body').appendChild(fragment);

NodeProducer

NodeProducer is a base class which serves as a hook into render. It contains one method: getNode(): Node, which render will use to delegate the responsibility of getting or generating DOM elements.

Example:

import { NodeProducer } from '@sprs/dom';

class MakesDivsOnTheFly extends NodeProducer {
  getNode() {
    return document.createElement('div');
  }
}

const docFragment = render(new MakesDivsOnTheFly());

This is mostly useful for building structures that add extra functionality to an element in the DOM by wrapping it instead of adding new properties to it.

A real-life example of this is the Peg class from this package.

html, svg, and mathml

The html export is a thin wrapper around document.createElement which supports inline attribute, listener, and child assignment. It just returns DOM nodes, and so does not require render except when combined with other types of renderable structures.

This is useful for expressive construction of DOM elements in JS. It is quite similar to HyperScript, with the exception that it does a little bit less (in a good way, I think).

html may be invoked in two basic forms:

Form 1: Creating elements from scratch
Pass a tag name as a string as the first parameter to create an element of that type

const myDiv = html('div');

Form 2: Creating elements using other elements as a template
Pass an existing element instead of a tag name as the first parameter to deeply clone that element, assign new properties to it, and append new children to it.

const myBlueDiv = html('div', { style: { backgroundColor: 'blue' });
const myBlueDivWithPadding = html(myBlueDiv, { style: { padding: '6px' } });
myBlueDiv === myBlueDivWithPadding; // -> false
myBlueDiv.style.backgroundColor = 'red';
myBlueDivWithPadding.style.backgroundColor; // -> 'blue'

The options object, which may be specified as the second parameter, contains attribute names and values to assign to the resulting element. Any attribute may be assigned a state from @sprs/state and it will be automatically bound to that state value.

There are two keys available on the options object which are treated specially.

style accepts either a string, which sets the styles wholesale, overwriting any existing styles, or an object with the same properties as a CSSStyleDeclaration.

const myDiv1 = html('div', { style: 'background-color: blue; padding: 6px;' });
const myDiv2 = html('div', { style: { backgroundColor: 'blue', padding: '6px' } });

on accepts an object which associates event names with event handlers, and adds those event handlers as listeners on the element.

const myButton = html('button', { on: { click: () => alert('Clicked!') } });

svg and mathml are nearly identical to html, with the exception that they assign the appropriate XML namespace when creating the node type they represent.

Because these utilities just return Nodes, their results can be directly appended to the document:

import { html } from '@sprs/dom';

// Make an array of 1000 <li /> elements
const listItems = new Array(1000)
  .fill(null)
  .map((_, i) => `This is list item: ${i}`)
  .map((text) => html('li', [text]));

// Make a page with a header and a list
const myPage = html('section', [
  html('h1', ['This is a heading']),
  html('p', ['This is some text']),

  html('label', ['This is a list of items']),
  html('ul', listItems),
]);

document
  .querySelector('#root')
  .appendChild(myPage);

Peg

The Peg class represents a known position in the DOM. It maintains an internal reference to a hidden <div />, which it uses to mark a location in the document. The affix and clear methods accept any kind of DOM node, and may be used to insert or remove content at the location maintained by the Peg.

Following is an example that uses Peg to add and remove elements from a fixed position in the document.

Note that this is a toy example to demonstrate direct usage of Peg, and if you were actually to build this, you would probably be better off using @sprs/state and relying on the data binding capabilities of render and html/svg/mathml.

import { render, html, Peg } from '@sprs/dom';

const content = html('p', ['This is some simple content in a <p /> tag']);
const contentPeg = new Peg();

let attached = false;
const toggleContent = () => {
  if (attached) contentPeg.clear();
  else          contentPeg.affix(content);

  attached = !attached;
};

const toggleButton = html(
  'button',
  { on: { click: toggleContent } },
);

const myApp = render([
  html('h1', ['This example shows how a Peg helps to make interactivity easier to implement']),

  contentPeg,
  toggleButton,
]);

document
  .querySelector('#root')
  .appendChild(myApp);

Many use cases for Pegs are wrapped in other helpers in the @sprs suite. See the Reactive class from this package, or the drag-and-drop utilities in @sprs/dnd for some examples.

setAttrs

The setAttrs utility applies the same attribute logic as html and svg, and can be used to modify an existing element with the same data structure.

import { html, setAttrs } from '@sprs/dom';

const div1 = html('div');
const div2 = document.createElement('div');

setAttrs(div1, { style: 'background-color: red;' });
setAttrs(div2, { style: 'background-color: black;' });

applyListeners

In the same vein as setAttrs, applyListeners applies event listeners to an element using the same data structure accepted by html and svg. Additionally, it accepts listeners at the root level, without the need for the on key.

import { html, applyListeners } from '@sprs/dom';

const button1 = html('button');
const button2 = document.createElement('button');

applyListeners(div1, { on: { click: () => alert('Clicked') }});
applyListeners(div2, { on: { click: () => alert('Clicked') }});

applyListeners(div1, { click: () => alert('Clicked') });
applyListeners(div2, { click: () => alert('Clicked') });
0.0.3

5 months ago

0.0.2

5 months ago

0.0.1

5 months ago