npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

dom-controller

v0.4.4

Published

Adds controller logic to elements through attributes

Downloads

9

Readme

dom-controller

Attach behavior to any element in a clear and debuggable way.

Less than 2KB minified + zipped

Example Usage

<!-- index.html -->
<head>
    <!-- include the library -->
    <script src="https://unpkg.com/[email protected]/bundle.min.js" defer></script>

    <!-- reference the controller -->
    <link controller-name="fancy" href="/controllers/to-do/fancy.mjs" />
</head>
<body>
    <!-- controller will attach to this ul -->	
    <ul controller="fancy">
        <li>Caviar</li>
        <li>Champagne</li>
    </ul>
</body>
// controllers/to-do/fancy.mjs
export default class FancyController {

    // called when [controller="fancy"] is set
    async attach(element) { 
        add_save_events(element);
        load_items(element);
    }

    // called when [controller] is changed or removed
    async detach(element) {
        cancel_load_items(element);
        remove_save_events(element);
    }

}

You can also use TypeScript.

// controllers/to-do/fancy.ts
import { IController } from 'dom-controller/IController';

export default class FancyController implements IController<HTMLElement> {
    element!: HTMLElement;
    async attach(element: HTMLElement): Promise<void> { }
    async detach(element: HTMLElement): Promise<void> { }
}

Lifecycle calls

attach() is called when a controller is attached to an element. i.e. When the controller attribute is set to the name of a controller.

detach() is called when a controller is detached. This happens when an element is removed or when the controller attribute is changed to something new.

Load as module

Sometimes you need a .js to act like .mjs. To do this add type-is="module"

example:

<link
    type-is="module"
    controller-name="fancy"
    href="/controllers/to-do/fancy.js"
/>

Using preload

Preload will tell your browser to load the scripts in advance. This is especially useful if you add immutable headers & a cache busting parameter.

<link
    type-is="module"
    controller-name="fancy"
    href="/controllers/to-do/fancy.js"
    rel="preload" as="script"
    crossOrigin="anonymous"
/>

Old-school loading, in order

<!-- index.html -->
<head>
    <!-- include the library -->
    <script src="https://unpkg.com/[email protected]/bundle.min.js" defer></script>

    <!-- reference the controller -->
    <!-- it must come after loading dom-controller.js -->
    <script src="/controllers/to-do/fancy.js" defer></script>
</head>
<body>
    <!-- controller will attach to this ul -->	
    <ul controller="fancy">
        <li>Caviar</li>
        <li>Champagne</li>
    </ul>
</body>
// controllers/to-do/fancy.js
class FancyController {
    async attach(element) { }
    async detach(element) { }
}

DomController.registerController(FancyController, 'fancy');
// controllers/to-do/fancy.ts
import { IController } from 'dom-controller/IController';

class FancyController implements IController<HTMLElement> {
    element!: HTMLElement;
    async attach(element: HTMLElement): Promise<void> { }
    async detach(element: HTMLElement): Promise<void> { }
}

// @ts-ignore
DomController.registerController(FancyController, 'fancy');

Script Placement

If you are using modules, i.e. *.mjs/import/export default, and loading using a link controller alias, then the dom-controller.js script can be loaded anytime anywhere.

If you aren't using modules, or you're using the window.DomController.registerController() call then the library needs to be loaded before any controller. You can speed up loading by using the defer attribute <script defer ...>.

Decoupling

Here's how you can efficiently reuse a controller for multiple related elements. Elements keep internal state and produce events, controllers talk to the server and update element data.

// interfaces/todo-element.ts
export interface TodoElement extends HTMLUListElement {
    add(item: Item): void;
    remove(id: string): void;
}
// elements/basic-todo.ts
import { TodoElement } from 'interfaces/todo-element.ts';

class BasicTodo implements TodoElement extends HTMLUListElement {
    constructor() {...}
    add(item: Item): void { }
    remove(id: string): void { }
}

window.customElements.define('basic-todo', BasicTodo);
// elements/premium-todo.ts
import { TodoElement } from 'interfaces/todo-element.ts';

class PremiumTodo implements TodoElement extends HTMLUListElement {
    constructor() {...}
    add(item: Item): void { }
    remove(id: string): void { }
}

window.customElements.define('premium-todo', PremiumTodo);
// controllers/todo.ts
import { IController } from 'dom-controller/IController';
import { TodoElement } from 'interfaces/todo-element.ts';

export default class TodoController implements IController<TodoElement> {
    element!: TodoElement;
    async attach(element: TodoElement): Promise<void> {
        element.add({ id: 1, text: 'Tada 🎉' })
    }
    async detach(element: TodoElement): Promise<void> { }
}
<!-- index.html -->
<head>
    <!-- include the library -->
    <script src="https://unpkg.com/[email protected]/bundle.min.js" defer></script>

    <!-- reference the controller -->
    <link
        type-is="module"
        controller-name="todo"
        href="/controllers/todo.js"
    />
</head>
<body>
    <basic-todo controller="todo">
        <li>Cheez Wiz</li>
        <li>Grape Soda</li>
    </basic-todo>

    <premium-todo controller="todo">
        <li>Caviar</li>
        <li>Champagne</li>
    </premium-todo>
</body>

Pros:

  • Helps track breaking changes.
  • Intellisense works well.
  • Interfaces don't add to transpiled size, they are "free".
  • Code reuse / DRY.

Cons:

  • Increased complexity, don't use unless you need it.