simpledom

The simpledom library lets you easily access and manipulate the DOM in a typescript-oriented API.

Find it on GitHub: sufianrhazi/simpledom
Install it via: npm install @srhazi/simpledom

Motivation

While typescript is a fantastic language, the DOM APIs were not designed for type safety. For example, let's say you want to write a very simple todo list using typescript in strict mode.

Let's assume we have the following html:

<h2>Todo list</h2>
<div id="todo-app">
    <form id="todo-add" action="">
        <label>
            New item:
            <input type="text" name="item" />
        </label>
        <button type="submit" name="add">Add</button>
        <button type="button" name="clear">
            Clear Completed
        </button>
    </form>
    <ul id="todo-list"></ul>
</div>

You could either use the raw DOM API, and be riddled with awkward type coercion/not-null assertions, and annoyingly verbose DOM construction:

const todoListEl = document.querySelector('#todo-list')! as HTMLElement;
const formEl = document.querySelector('#todo-add')! as HTMLElement;
const inputEl = formEl.querySelector('input[name="item"]')! as HTMLInputElement;
const addEl = formEl.querySelector('button[name="add"]')! as HTMLButtonElement;
const clearEl = formEl.querySelector('button[name="clear"]')! as HTMLButtonElement;

todoListEl.addEventListener('click', (event) => {
    const target = event.target as Node;
    for (let itemEl of Array.from(todoListEl.children)) {
        if (itemEl.querySelector('.delete')!.contains(target)) {
            todoListEl.removeChild(itemEl);
        }
    }
});

formEl.addEventListener('submit', event => {
    event.preventDefault();
    if (inputEl.value.trim().length > 0) {
        const todoItem = document.createElement('li');
        todoItem.classList.add('todo-list-item');
        const label = document.createElement('label');
        const check = document.createElement('input');
        check.type = 'checkbox';
        const close = document.createElement('button');
        close.classList.add('delete');
        close.tabIndex = 0;
        close.textContent = '✘';
        close.setAttribute('aria-label', 'close');
        label.appendChild(check);
        label.appendChild(document.createTextNode(` ${inputEl.value}`));
        todoItem.appendChild(label);
        todoItem.appendChild(close);
        todoListEl.appendChild(todoItem);
        inputEl.value = '';
    }
});

clearEl.addEventListener('click', event => {
    const toRemove = [];
    for (let todoItem of Array.from(todoListEl.children)) {
        let checkbox = todoItem.querySelector('input[type="checkbox"]')! as HTMLInputElement;
        if (checkbox.checked) {
            todoListEl.removeChild(todoItem);
        }
    }
});

Or you could use simpledom:

import * as dom from "@srhazi/simpledom";

const todoListEl = dom.one(HTMLElement, '#todo-list');
const formEl = dom.one(HTMLFormElement, '#todo-add');
const inputEl = dom.oneFrom(formEl, HTMLInputElement, 'input[name="item"]');

dom.on(todoListEl, 'click', Element, '.delete', (e, target) => {
    for (let itemEl of Array.from(todoListEl.children)) {
        if (itemEl.contains(target)) {
            todoListEl.removeChild(itemEl);
        }
    }
});

formEl.addEventListener('submit', e => {
    e.preventDefault();
    if (inputEl.value.trim().length > 0) {
        todoListEl.appendChild(dom.build(`
            <li class="todo-list-item">
                <label>
                    <input type="checkbox"> ${inputEl.value}
                </label>
                <button class="delete" aria-label="close">✘</button>
            </li>
        `));
        inputEl.value = '';
    }
});

dom.on(formEl, 'click', Element, 'button[name="clear"]', () => {
    for (let todoItem of Array.from(todoListEl.children)) {
        const checkbox = dom.oneFrom(todoItem, HTMLInputElement, 'input[type="checkbox"]');
        if (checkbox.checked) {
            todoListEl.removeChild(todoItem);
        }
    }
});

Todo list