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

brutaldom

v1.0.0-alpha4

Published

Classes to wrap DOM APIs to make writing your app's front-end easier

Downloads

1

Readme

BrutalDOM - Treat your web pages for what they are, not what you'd like them to be

BrutalDOM is not ready for production USE - I've only used it on two projects and so it is evolving.

A web page is a bunch of HTML and CSS. It can include JavaScript that responds to events. The browser includes many powerful APIs for building just about any user interface you might need. Thus, you can build whatever you need without any third party framework. But the browser's API is complex and contains many more features and behaviors than your app is going to need.

The goal of BrutalDOM is to provide tools to make it easier to model your app. For example, you may have a button that shows a message to the user when clicked.

<main>
  <button>Show Message</button>
  <p style="display:none">Hello there!</p>
</main>

You can implement this with the browser directly, like so:

const setup = () {
  const button = document.querySelector("main button")
  const message = document.querySelector("main p")
  if (button && message) {
    button.addEventListener("click", () => {
      message.style.display = "block"
    })
  }
  else {
    if (!button) {
      throw `Could not find button`
    }
    if (!message) {
      throw `Could not find message`
    }
  }
}

window.addEventListener("DOMContentLoaded",setup)

This is not so bad, however with more complicated needs, this can become quite difficult:

  • Your code has to check that the elements its going to decorate are there
  • The actual behavior relies on magical strings like "click" and "block". If you mis-type these, no error occurs, your app just doesn't work.

While you could certainly use a big framework, BrutalDOM provides classes to wrap DOM elements and use them as the basis for richer components and objects that respond to the needs of your app.

For example:

window.addEventListener("DOMContentLoaded",() => {
  const body = new Body()
  const button = new Button(body.$selector("main button"))
  const message = new Message(body.$selector("main p"))

  button.onClick( () => message.show() )
})

This is the core logic that glues together the app. Body is provided by BrutalDOM, but Button and Message are defined by you. Before we see those, note that it's a bit more clear what does what. When the button is clicked, we show the message.

Here's Button

import { Component } from "brutaldom"

class Button extends Component {
  whenCreated() {
    EventManager.defineEvents(this, "click")
    this.element.addEventListener("click", (event) => {
      event.preventDefault()
      this.clickEventManager.fireEvent()
    })
  }
}

That…looks like regular JavaScript. It's a bit more code, but all the stuff inside whenCreated() is providing a richer event than what yo uget with addEventListener. It provides the onClick method. If you mis-type it, you'll get an error as opposed to silent failure.

Message doesn't need much logic and can use the builtin Component:

class Message extends Component {
}

This provides the show() method.

This may seem like more code. For this simple case, it certainly is. But, when you have a complex UI that has a lot of events and interactions, it can be help to create high-level components that don't stray too much from the base APIs that are part othe browser.

General Overview

Any DOM element you want to interact with should have a new class that extends Component. That class is then designed by you to provide the exact API yoru app needs to interact with it.

Here are the main features:

  • DOM element location without if statements. Every Component provides several methods to locate DOM elements. These methods will fail if the located element is not found (or if a collection of elements results in zero elements). The Body class allows you to locate elements from the <body>.

    // BAD: if there is no button, you don't get notified.
    //      if there is more than one match, you just get the first one
    const button = document.querySelector("button[data-doit]")
    
    const body = new Body()
    
    // GOOD: If there is not exactly one match, you get an exception
    const button = body.$selector("button[data-doit]")
    
    
    // BAD: if there are no buttons, you get an empty list, which is likely 
    //      not what you want
    const buttons = document.querySelectorAll("button[data-doit]")
    
    // GOOD: if there is not one or more matches, you get an exception
    const buttons = body.$selectors("button[data-doit]")
  • Create Richer, Higher-Level Events. addEventListener is fine, exception that it takes a string for the name and produces only an Event. This means lots of translating concepts. Instead, you can easily define events using methods—not strings—whose payloads, when fired, produce objects in your domain, or which trigger other events.

    // BAD: "input" can be mistyped, plus you have to examine
    //      .checked
    checkboxElement.addEventListener("input", () => {
      if (checkboxElement.checked) {
        // do one thing
      }
      else {
        // do another
      }
    })
    
    // GOOD: explicit events, no introspection of the DOM element,
    //       and no strings
    checkbox = new Checkbox(checkboxElement)
    checkbox.onChecked( () => {
      // do on thing
    })
    checkbox.onChecked( () => {
      // do another
    })
  • Templates and Slots without Shadow DOM. The <template> and <slot> elements standard, but Web Components provides a somewhat clunky API to these. Plus, using Web Components with templates requires adopting the Shadown DOM which you may not want to do. Instead, any Component can locate a Template, then create a newNode that fills in any <slot> elements with data you define in code.

    <div data-my-content>
      <template>
        <p><slot name="message"></slot></p>
        <code><slot name="link"></slot></code>
      </template>
    </div>
    
    class MyContent extends Component {
      wasCreated() {
        this.messageTemplate = this.template()
      }
    
      addMessage(message, link) {
        const node = this.messageTemplate.newNode({
          fillSlots: {
            message: message,
            link: link,
          }
        })
        this.element.appendChild(node)
      }
    }
    
    const content = new MyContent(body.$("my-content"))
    content.addMessage("Hello!","www.exampe.com")
    content.addMessage("Hello Again!","www.exampe.net")
  • Simplified Animation. The browser's animation API is powerful but flexible and clunky. Animator allows a more streamlined way to animate between two sets of styles. It can't handle every single animation need, but certainly handles most of them.

Developing

To work on brutaldom:

  1. Install Docker
  2. dx/build
  3. dx/start
  4. In another terminal, dx/exec bin/setup
  5. In another terminal, dx/exec bin/test
  6. Open up the browser wherever the output of bin/test says to, and the tests will run in the browser.