Web Dev Diaries

A blog and resource for modern web development

HTMLApp - a simple JavaScript library for building simple web apps!

HTMLApp v1.0.0 is now available at html-app.com!
HTMLApp v1.0.0 is now available at html-app.com!

React, Angular, Vue, Aurelia, Ember, and so on. These are just a small handful of the different JavaScript frameworks and libraries out there that, broadly speaking, do one thing: give developers the means to build ambitious front-end heavy web applications. Developers now rely so heavily on libraries like these along with hosts of other packages for work and side-projects.

So what about those of us that aren’t building the next big user management portal, trading platform, CMS, social network or other? Should we also pick one of these tools that will bring us a virtual DOM, functional components or full-blown MVC to our humble little apps?

Introducing: HTMLApp

Enter the new kid on the block, HTMLApp: a super-minimal library for stitching together HTML & JavaScript to build web applications.

HTMLApp is a pet project that I’ve been working on for about 3-4 weeks now. It went live just a few days ago with its first release, v1.0.0. It’s the first “serious” library project I’ve released and I’m excited to share a bit about it.

Another JavaScript library??

If the leading paragraphs of this post weren’t enough of a hint: I’ve grown just a tad tired with being expected to use a sledgehammer to break an egg. If I’m throwing together a prototype or a small-scale web project, I would expect to spend the least amount of time possible on setting up a project, getting it SEO-ready and publishing to whatever hosting service is required or most convenient.

Those who’ve known me a while may know that I also worked on a little side-project in 2016 called IOWho?. I recently put it back online via Netlify to motivate me to give it some TLC in the near future. IOWho is an app I created for fun, but also for teaching myself about how best to go about setting up and using React and Redux in a new project from scratch. Not that I regret using those technologies in a fun project, but it was certainly overkill for a one-page app. I could have built what I did in perhaps a fraction of the time and with less boilerplate code using good old vanilla JavaScript.

It could be age, experience or something else, but hype-driven decisions don’t impress me any more. This, along with the generally weighty baggage that comes with using one of the big libraries and the time needed to setup and maintain them, lead me to want to forge a new path.

The path I chose with HTMLApp remains as close to vanilla as possible, whilst also providing structure: a valuable asset that web apps of any size should have.

HTMLApp’s features

HTMLApp doesn’t have a large gamut of features, because it doesn’t need them. At a bit below 2kb gzipped, it’s also a very compact library, partly because it has zero external dependencies.

HTML-JavaScript linkages via element attributes

Since HTML files are used as the “view” of the app, there’s two important attributes that are required to make the necessary connections to the JavaScript side of the app.

data-htmlapp is the first attribute to be aware of. This is an attribute that tells HTMLApp: “this is the root element in the app’s view”. This is required to ensure that bindings and actions are captured only within the scope of this element. Elements outside of this root element can coexist perfectly fine with the application code. A value can be provided to the attribute, allowing for multiple apps to be initialised at once with different names, or it can be left blank.

data-ha is the second important attribute. This is used to attach to any elements inside a root element that need to be listened to for user interactions, or to have dynamic content populated with your application code. This attribute must always have a value, and the value should be unique within an application root element. It’s OK to have duplicate values in different root elements, though.

Here’s an example bit of markup that demonstrates two independent root elements, each with their own child elements:

<body>
  <header>Awesome App!</header>

  <form data-htmlapp="userSignup">
    <h3>User Signup</h3>
    <input data-ha="email">
    <input data-ha="password">
    <button data-ha="submit">Submit</button>
  </form>

  <footer>
    <div data-htmlapp="newsletter">
      <input data-ha="email">
      <button data-ha="submit">Submit</button>
    </div>
  </footer>

  <script src="https://unpkg.com/html-app/dist/html-app.browser.min.js"></script>
  <script src="app.js"></script>
</body>

Then with JavaScript, the two apps can be initialised:

new HTMLApp({
  appName: 'userSignup'
});

new HTMLApp({
  appName: 'newsletter'
});

Adding event handlers

When instantiating a new HTMLApp instance, one of the available options that can be passed in is eventHandlers. This property should be an array of objects that each point to a specific target element in the DOM. By target element, we mean any element with the data-ha attribute assigned to it.

Event handlers in HTMLApp are very easy to define and are processed to ensure that the minimum number of native event listeners are attached to the DOM, relying heavily on event delegation instead. This is done to ensure that a large number of events can be listened to for many elements at once without slowing down the browser.

Here’s an extension of the above JavaScript code that shows what the event handlers might look like for the userSignup form:

var userSignupForm = { email: '', password: '' };

new HTMLApp({
  appName: 'userSignup'
  eventHandlers: [
    {
      id: 'email',
      onChange: function(e, el, app) {
        userSignupForm.email = e.target.value;
      }
    },
    {
      id: 'password',
      onChange: function(e, el, app) {
        userSignupForm.password = e.target.value;
      }
    },
    {
      id: 'submit',
      onClick: function(e, el, app) {
        e.preventDefault();

        fetch('/api/userSignup', {
          method: 'POST',
          body: userSignupForm
        });
      }
    }
  ]
});

As you will hopefully agree, the event handlers API is very easy to read and understand at a glance and provides simple separation of concerns between different event handlers.

This is not the only way of handling form events, but it at least shows how multiple events handlers can be bound. There’s no restrictions on the event types handled, onClick, onScroll, onKeyPress and so on are all supported. MDN has a pretty comprehensive list of events types supported by the majority of browsers, you just need to make the first letter of the event name uppercase and prefix it with “on”, e.g.: click -> onClick, keydown -> onKeyDown.

It’s also perfectly acceptable to add multiple event types to one element in one event handler object, like so:

eventHandlers: [
  {
    id: 'email',
    onChange: function(e, el, app) {},
    onClick: function(e, el, app) {},
    onKeyDown: function(e, el, app) {}
  }
]

If you did wish to share logic between different element event handlers, that’s very easily achieved using a common handler function and the element helper methods API, exposed via the second argument passed to all event handler callbacks:

function onChange(e, el, app) {
  // dynamically set the form state from the element name
  userSignupForm[el.id] = e.target.value;
}

new HTMLApp({
  eventHandlers: [
    { id: 'email', onChange },
    { id: 'password', onChange }
  ]
});

el.id is a reference to the element’s data-ha attribute name, which will match the event handler’s id property. The el object also has other useful helper properties and methods, explained in the next section.

Element helper methods

One of the things I felt it important to include was an easy way to access element getters and setters. Since HTMLApp wasn’t designed to control the view, but merely supplement it, redrawing whole elements isn’t a built-in feature, at least not in the current version.

Instead, the intention was to expose just enough shortcut methods to painlessly access the more important features already built-in to the native JavaScript DOM API. Here’s an example showing a few of the helper methods in use to display a validation error when the email address field is empty after being changed:

new HTMLApp({
  appName: 'userSignup'
  eventHandlers: [
    {
      id: 'email',
      onChange: function(e, el, app) {
        if (e.target.value.length === 0) {
          el.addClass('has-error');

          app
            .getEl('emailError')
            .setText('Invalid email address')
            .addClass('has-error');
        } else {
          el.removeClass('has-error');

          app
            .getEl('emailError')
            .setText('')
            .removeClass('has-error');
        }
      }
    }
  ]
});

app.getEl() is a method that will return a specific element tagged with the data-ha attribute for the app instance. In the above example, because we reference a new element called emailError, this needs to be added to the HTML, too, otherwise an error will be thrown:

<form data-htmlapp="userSignup">
  <h3>User Signup</h3>
  <input data-ha="email">
  <span data-ha="emailError"></span>

A few of the most useful methods exposed for elements are: addClass, removeClass, setText, setAttribute, along with a bunch more, explained in the docs.

You can also just get at the raw JavaScript DOM element via: el.el, or app.getEl().el, and use the full set of native browser DOM element properties & methods.

Go use HTMLApp in your next little hustle!

That’ll about wrap up the things I wanted to cover about HTMLApp for now. This post only touches on the more stand-out features of the library, so do check out the documentation website for a much greater coverage of the available API. As of right now though, I do ask for your forgiveness for any gaps in the documentation. It’s taking me a bit more time than I anticipated to ensure I cover everything in sufficient detail. I’m tracking the things still to add to the docs in this GitHub issue.

I’m really excited for you to try it out and provide feedback. Better yet, if you have any suggestions about how to improve the existing API or even make feature contributions to the project on GitHub, I’d more than welcome the support.

comments powered by Disqus