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

html-extender

v0.5.1

Published

JavaScript template engine that runs in the browser without the need for compilation. It's an extension chord for HTML.

Downloads

10

Readme

Deprecation

Deprecating this project in favor of already existing projects such as AlpineJS or Slim.js

HTMLExtender

An "extension chord" library for HTML templating.

Purpose

This is a vanilla JavaScript library that adds template abilities to HTML - including easy updating of element values and innerHTML, "when" conditions that hide/show elements, bind events, and set attributes and class names dynamically, all based on a passed in context. This is different than Angular in that values are not "bound" and do not automatically update. It's more like React in that the rendering occurs when the context is updated.

Important Note

Please be aware that this is not a 1.0.0 release, so the API is subject to change. The track to 1.0.0 is still undetermined, but will be described here when complete.

Contributing

Please submit issues and PRs if bugs are found, or to submit issues for discussion. As stated above, the goal is to make templating in HTML available using vanilla JavaScript. It should be able to work in any framework so long as Custom Elements and the other dependencies below are available.

Code of Conduct

Please be respectful and courteous to everyone. Discrimination, rude behavior, bigotry, etc will not be tolerated; especially from contributors and maintainers. Basically: just be a good person and cooperate!

Installation

npm install --save html-extender

This includes a browser ready version of JSON Query which is a dependency of the library. You may include different versions of this library, but HTMLExtender has only been tested with version 2.2.2 at the time of this writing.

Dependencies

  • JSON Query v2.2.2 or greater
  • Custom Elements API, Shadow DOM API
  • Map, Set, Iterable, ES7+ Array and Object methods
  • HTMLTemplateElement

Viewing the Docs

Documentation can be generated with ESDoc using the commands:

npm run docs:generate
npm run docs

It will start a server on port 9000 by default. Set the PORT environment variable to change the port.

Usage

Syntax

There isn't any syntactical difference with the HTML, just some new attributes that can be added to any HTML element:

<div ext-when="shouldShow">
  <span ext-model="path.to.value"></span>
  <a ext-href="dynamic[0].href">Dynamic HREF!</a>
  <button ext-bind="click:clickHandler">Click</button>
</div>

To render this, a new instance of HTMLExtender is created and used:

const extender = HTMLExtender.create(options); // defaults to searching the entire document.body
extender.update({
  shouldShow() {
    return true;
  },
  path: {
    to: {
      value: 'this value is rendered in the HTML of the span'
    }
  },
  dynamic: [
    {
      href: '/some/link'
    }
  ],
  clickHandler(evt) {
    // handle click event here...context is HTMLExtender
  }
});

Attributes

ext-model=""

Updates the element's value or innerHTML property if the provided JSON query resolves in a value. Example:

<body>
  <span ext-model="path.value">
  <input type="text" ext-model="inputValue">
  <script type="module">
    import HTMLExtender from './HTMLExtender.js';
    const extender = HTMLExtender.create();
    extender.update({
      path: {
        value: "hello value"
      },
      inputValue: "some text"
    });
    console.log(document.querySelector('span').innerHTML); // prints "hello value"
    console.log(document.querySelector('input').value); // prints "some text"
  </script>
</body>

ext-bind="[eventName]:[JSONQuery] [, [eventName]:[JSONQuery], ...]"

Binds the defined event handlers to events. This is the only attribute that has special syntax. It's a space-delimited string of eventName:eventHandler where the eventHandler is a JSON Query path that must result in a function. This is where the second option to the update method is helpful:

<body>
  <button ext-bind="click:..clickHandler mouseover:..path.to.overHandler">Click or hover!</button>
  <script type="module">
    import HTMLExtender from './HTMLExtender.js';
    const extender = HTMLExtender.create();
    extender.update({}, {
      parent: {
        clickHandler(evt) {
          // called when button is clicked
        },
        path: {
          to: {
            overHandler(evt) {
              // called when mouse hovers the button
            }
          }
        }
      }
    });
  </script>
</body>

The second option (as described in the API), is an options object that can take a parent object. This allows the context to be separate from a component or some other instance. The .. syntax tells JSON Query to look in the parent object instead of the main object.

ext-when="[JSONQuery]"

Adds or removes the ext-hidden attribute based on the result of the method that the JSON Query returns. If the method returns truthy then the ext-hidden attribute is removed, otherwise the attribute is added. Note that this does not hide or display the element; only the attribute is toggled so that the developer can decide the specific styling of elements with that attribute. The context and options that are passed to the update method are passed to the callback.

<body>
  <div ext-when="conditionCallback">Click or hover!</div>
  <script type="module">
    import HTMLExtender from './HTMLExtender.js';
    const extender = HTMLExtender.create();
    extender.update({
      conditionCallback(context, opts) {
        return true; // causes the ext-hidden attribute to be removed from the element
      }
    }, {});
  </script>
</body>

ext-classes="[JSONQuery]"

This attribute expects the resulting value from the JSON Query to be either an object or a Map<string, boolean> with the key being the name of the class and the value being a boolean. If true, the class is added to the element, otherwise the class is removed. This was done to avoid an ugly attribute string and improve flexibility when dynamically selecting class names.

<body>
  <div ext-classes="classlist">Style me!</div>
  <script type="module">
    import HTMLExtender from './HTMLExtender.js';
    const extender = HTMLExtender.create();
    extender.update({
      classlist: {
        btn: true,
        'btn-primary': true,
        'btn-error': false
      }
    }, {});
    console.log(document.querySelector('div').classList); // prints 'btn btn-primary'
  </script>
</body>

The syntax described for ext-attr (below) also applies to ext-classes.

ext-attr="[ATTR_NAME]:[JSONQuery];[,ATTR_NAME:JSONQuery]"

Other attributes can be dynamically set by using the ext-attr syntax. For example, dynamically setting href can be done by using ext-attr="attr:query":

<body>
  <a ext-attr="href:dynamicHref">Link</div>
  <script type="module">
    import HTMLExtender from './HTMLExtender.js';
    const extender = HTMLExtender.create();
    extender.update({
      dynamicHref: '/some/link' 
    });
  </script>
</body>

Multiple attributes can be modified by separating each definition with a semicolon ;: ext-attr="href:path;disabled:is.disabled"

This library also defines the element <ext-repeat> which can be used to repeat a set of elements based on an iterable. Example:

<ext-repeat iterable="some.iterable">
  <template>
    <h2 ext-model="repeatKey"></h2>
    <a ext-href="repeatValue" ext-model="..upperContext.value"></a>
  </template>
</ext-repeat>

<script type="module">
  import HTMLExtender from './HTMLExtender.js';
  const extender = HTMLExtender.create();
  extender.update({
    some: {
      iterable: new Map([
        ['key', 'value'],
        ['anotherKey', 'anotherValue'],
      ])
    },
    upperContext: {
      value: 'hi'
    }
  });
</script>

A couple of important notes: this element will only repeat the content of the first <template> tag that's found. This template must be a child of <ext-repeat>. The elements that are created are given two new contexts: the main context is an object that contains repeatKey and repeatValue:

{
  repeatKey: *,
  repeatValue: *
}

This is the key/value from the iterable. The parent context is the context that's passed to <ext-repeat>; e.g. the context where the iterable is found. Under-the-hood, this element uses HTMLExtender to render each group of elements that are created.

NOTE: the generated elements are added to the parent element. These elements are created with a unique ID and removed when the repeater is next updated.

Attribute iterable="[JSONQuery]"

This is an optional attribute, if the count attribute is provided. The JSON Query must resolve in an iterable: Set, Array, or Map. The only exception is Objects are allowed. When an object is recieved, Object.entries is run and the repeater runs against that.

Attribute for="[ELEMENT ID]"

Sometimes it's desirable to have the generated elements be added to a different element. The for attribute takes an id of another element and the generated elements will be placed there. This works in the same way that the for attribute for the <label> element works.

Attribute count="[Number|JSONQuery]"

This is an optional attribute, if the iterable attribute is provided. If a number is provided, the template contents will be repeated that number of times. If a JSON query is provided, it should resolve to a Number that will be used to repeat the template contents.

Nested Elements

Nested elements are able to access ancestor element context objects by using the repeaters property:

<ext-repeat iterable="someIterable" name="parentIterable">
  <template>
    <ext-repeat count="3">
      <template>
        <span ext-model="repeaters[1].repeatValue.value"></span>
      </template>
    </ext-repeat>
  </template>
</ext-repeat>

<script>
  const extender = HTMLExtender.create();
  extender.update({
    someIterable: [{
      value: 'boom!'
    }]
  });
</script>

The spans will display the value property from the first iteration (boom!). The parent iterable can be accessed index value (1 in this case). Why is the index 1? The current context (0) refers to the inner most <ext-repeat> - in this case, the ext-repeat with the count=3 attribute.

Note: Accessing parent iterables by name is not currently supported. This is due to the limitation of how JSON Query strictly treats Objects and Arrays. This is planned to be supported in the future.