Skip to content

Repository files navigation

✨weld.js

build npm Version npm License npm Downloads

A small and powerful library for declarative JavaScript binding. Dead simple to use, no build required.

What is weld.js?

weld.js is a small, fast, and powerful library for declaratively binding JavaScript to the DOM. It's designed to be simple, lightweight, and easy to use. It's perfect for server-side rendered (SSR) applications, where you want to add interactivity without the need for a full-blown JavaScript framework. It pairs brilliantly with libraries like htmx.

💡 I want to be able to declaratively bind JavaScript to the DOM, and have it just work.

The beauty is, there really isn't much to it. It's just a few attributes and functions to bind JavaScript to the DOM. At which point you're just writing vanilla JavaScript. You can use it with any JavaScript framework, or even without one. In fact, you can go a very long way using just weld.js with a single script.

Installation

weld.js is less than 200LOC so you can easily copy + paste it in your project. But it's also available via npm and CDN. Designed for no build client-side development, but also supports ES6 modules and bundlers.

CDN

<script src="https://unpkg.com/weld.js/weld.js"></script>

npm

npm install weld.js --save

Quick Start

Below is a basic example demonstrating most of the concepts in weld.js. It shows how to create a binding that greets the user. The binding takes a greeting parameter from the server, 'Hello' in this case. The binding also has two named targets, output and input, which are used to display the greeting and capture the user input on change. Note the use of wd-apply on the script reference.

<div wd-bind="greeter" wd-attr="{ greeting: 'Hello' }">
    <div wd-target="output">Hello world</div>
    <input wd-target="input" placeholder="enter name">
</div>

<script src="weld.js" wd-apply></script>
<script>
    weld.bind('greeter', (el, attr, targets) => {
        const setGreeting = (name = 'world') =>
            weld.dom.set(targets.output, [
                `${attr.greeting} `,
                weld.el('strong', name)
            ]);

        weld.el(targets.input, {
            oninput: e => setGreeting(e.target.value)
        });
    });
</script>

Note: if you omit the wd-apply attribute, you need to call weld.apply() manually. This is typically done in a DOMContentLoaded event listener.

<script src="weld.js"></script>
<script>
    // ... bindings

    document.addEventListener('DOMContentLoaded', () => {
        weld.apply();
    });
</script>

Creating Bindings

Attaching functionality to DOM elements is achieved using the custom attribute wd-bind="name", where "name" is the identifer for a binder defined using weld.bind().

You can think of these bindings as components. They are reusable, self-contained pieces of functionality that can be attached to any element in the DOM. The binder function is called with the element, any parameters passed to it, and any named targets within the binding scope.

<div wd-bind="greet"></div>
<script src="weld.js" wd-apply></script>
<script>
    weld.bind('greet', (el) => {
        weld.el(el, 'Hello world');
    });
</script>

Passing Parameters

You can also pass parameters to the binder using the wd-attr attribute. This can be a string, integer, array, object literal, or JSON.

This is useful for passing data from the server to the client, or for configuring the behavior of the binder.

<!-- passing a string -->
<div wd-bind="greetValue" wd-attr="pim"></div>

<!-- passing an integer  -->
<div wd-bind="greetValue" wd-attr="1"></div>

<!-- using an object literal -->
<div wd-bind="greetComplex" wd-attr="{name:'pim'}"></div>

<!-- using an array literal -->
<div wd-bind="greetArray" wd-attr="['pim', 'john']"></div>

<!-- using JSON -->
<div wd-bind="greetComplex" wd-attr='{"name":"pim"}'></div>

<script src="weld.js" wd-apply></script>
<script>
    weld.bind('greetValue', (el, attr) => {
        const message = 'Hello ' + attr;
        weld.el(el, message);
    });

    weld.bind('greetArray', (el, attr) => {
        const message = 'Hello ' + attr.join(', ');
        weld.el(el, message);
    });

    weld.bind('greetComplex', (el, attr) => {
        const message = 'Hello ' + attr.name;
        weld.el(el, message);
    });
</script>

Defining Targets

Declaratively define named targets using the wd-target attribute. This gives you keyed access to elements within the binding scope. This is useful when you need to manipulate or identify elements within a binding.

<!-- designating a named target -->
<div wd-bind="greetTarget" wd-attr="pim">
    <div wd-target=greeting></div>
</div>

<!-- using multiple attrbutes and targets -->
<div wd-bind="greetMulti" wd-attr="{name:'pim', greeting:'Howdy', intVal: 1, boolVal: true}">
    <div>
        <span wd-target=greeting></span>
        <span wd-target=name></span>
    </div>
</div>

<script src="weld.js" wd-apply></script>
<script>
    weld.bind('greetTarget', (el, attr, targets) => {
        const message = 'Hello ' + attr;
        targets.greeting ?
            weld.el(targets.greeting, message) :
            weld.el(el, message);
    });

    weld.bind('greetMulti', (el, attr, targets) => {
        const greeting = attr.greeting ? attr.greeting : "Hello";
        const name = attr.name ? attr.name : "world";
        weld.el(targets.greeting, greeting);
        weld.el(targets.name, name);
    });
</script>

Working with the DOM

weld.js comes with a few utilies to make creating and manipulating DOM elements easier.

Creating Elements

The first is weld.el() which is a DOM swiss army knife. It can create new elements and modify existing ones. When the first argument is a string literal tag name, a new element will be creating. Attributes are provided as an object literal and can include event listeners, denoted by prefixing the attribute name with on.

Below is an example of creating a button element with an ID, class, text content, and an onclick event listener.

const button = weld.el('button', {
    id: 'myButton',
    class: 'myClass',
    textContent: 'Click me',
    onclick: () => alert('Hello world')
});

button.outerHTML // <button id="myButton" class="myClass">Click me</button>
button.click() // alerts 'Hello world'

There is also some shortcuts to make common tasks, like assigning IDs, classes and text content. Below is the same example as above, but using the shortcuts.

const button = weld.el('button#myButton.myClass', 'Click me', {
    onclick: () => alert('Hello world')
});

weld.el() can also be used to augment existing elements. By passing an element as the first argument, the attributes and content are applied to the element. This is useful when working with named targets.

<div wd-bind="counter" wd-attr="99">
    <p>You clicked the button <span wd-target="count">0</span> times</p>
    <button wd-target="clicker">Click me</button>
</div>

<script src="weld.js" wd-apply></script>
<script>
    weld.bind('counter', (el, attr, targets) => {
        let count = attr;
        weld.el(targets.count, count);

        weld.el(targets.clicker, {
            onclick: () => {
                count++;
                weld.el(targets.count, count);
            }
        });
    });
</script>

Manipulating Elements

When manipulating element content, you are typically either appending content or replacing it. weld.js provides two functions for this, weld.dom.append() and weld.dom.set(). Both functions take an element as the first parameter and one or more elements as the second parameter.

const container = weld.el('div');
const button = weld.el('button', 'Click me', { onclick: () => alert('Hello world') });

weld.dom.set(container, button);
container.outerHTML; // <div><button>Click me</button></div>

const para = weld.el('p', 'Hello world');
weld.dom.append(container, para);
container.outerHTML; // <div><button>Click me</button><p>Hello world</p></div>

Finding Elements

There is a utility function for finding a single element (first match) or multiple elements (all matches) using a CSS selector. weld.dom.get() and weld.dom.find() respectively.

const container = weld.el('div');
weld.dom.append(container, weld.el('div.classfind'));
weld.dom.append(container, weld.el('div.classfind'));

const first = weld.dom.get('div.classfind', container);
const all = weld.dom.find('div.classfind', container);

Examples

Lazy Loading Images

<img wd-bind="lazyLoad" wd-attr="image.jpg" src="placeholder.jpg">

<script src="weld.js" wd-apply></script>
<script>
    weld.bind('lazyLoad', (el, src) => {
        weld.el(el, { src });
    });
</script>

External Link Handler

<main wd-bind=externalLink>
    <p>This is an external link: <a href="https://www.github.com/eastcitysoftware">click here</a>.</p>
    <p>If you click it a new tab will open. Click <a href="https://github.com/eastcitysoftware/weld">this one</a> to also open a new tab.</p>
</main>

<script src="weld.js" wd-apply></script>
<script>
    weld.bind('externalLink', (el) => {
        for (const anchor of weld.dom.find('a', el)) {
            if (anchor.href
                && anchor.href.startsWith('http')) {
                anchor.target = '_blank';
            }
        }
    });
</script>

Usage with JavaScript Frameworks

See the samples directory for more examples.

Many JavaScript libraries angle their value proposition at single-page application (SPA) development. And most are viable options for multi-page application (MPA) development as well.

When building a SPA you typically create a root element, <div id="root"></div>, pass it to the framework of choice and it takes over from there. Effectively eliminating the brittle CSS-to-JS relationship. But in MPA development there isn't a clean entry-point like this, since the markup is primarily generated server-side. Thus, you often turn to using existing (or creating new) classes to begin attaching JavaScript.

Instead, using weld you can declaratively attach functionality without the reliance on selectors for activation.

An example using Mithril.js

mithril.js is an amazing JavaScript project, so it will be used to demonstrate the concept in the example below.

<div wd-bind="counter" wd-attr="weld"></div>

<script src="https://unpkg.com/mithril@2.2.14/mithril.min.js"></script>
<script src="weld.js" wd-apply></script>
<script>
    // A mithril component
    function HelloWorld() {
        let count = 0;

        function OnClick() {
            count++;
        }

        return {
            view: function (vnode) {
                var msg = 'Hello ' + vnode.attrs.name;
                return m('div', [
                    m('p', `${msg}. You clicked the button ${count} times.`),
                    m('button', { onclick: OnClick }, 'Click me',),
                ]);
            }
        };
    }

    // Mount the mithril component using a weld binding
    weld.bind('counter', function (el, name) {
        m.mount(el, { view: () => m(HelloWorld, { name: name }) });
    });
</script>

Find a bug?

There's an issue for that.

License

Licensed under MIT.

About

Don't select it, weld it.

Resources

Stars

7 stars

Watchers

1 watching

Forks

Used by

Contributors

Languages