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

tagalong

v2.1.1

Published

DOM templating, done right

Downloads

12

Readme

tagalong

Tagalong is a tool for creating progressively enhanced HTML templates. Write your content in HTML, add attributes that describe how data maps to each element, and update the DOM with a single JavaScript call. Tagalong uses morphdom under the hood, minimizing the number of changes to the DOM and limiting layout thrashing.

Given this HTML:

<div id="people">
  <h1>
    <span t-text="items.length">1</span>
    <span t-text="items.length === 1 ? 'person' : 'people'">person</span>
  </h1>
  <ul>
    <li t-each="people">
      <span t-text="name">Rosie</span> the
      <span t-text="occupation">Riveter</span>
    </li>
  </ul>
</div>

You can render new data like so:

tagalong.render('#people', {
  people: [
    {name: 'Rosie', occupation: 'Riveter'},
    {name: 'Joe', occupation: 'Plumber'},
    {name: 'Jill', occupation: 'Astrophysicist'},
  ]
});

And the result should look something like this:

<div id="people">
  <h1>
    <span>3</span>
    <span>people</span>
  </h1>
  <ul>
    <li><span>Rosie</span> the <span>Riveter</span></li>
    <li><span>Joe</span> the <span>Plumber</span></li>
    <li><span>Jill</span> the <span>Astrophysicist</span></li>
  </ul>
</div>

API

tagalong.render(root [, data [, context]])

The render function accepts either an element reference or CSS selector as its root, an optional data value, and an additional, optional context object. This function renders the data into the root element and returns an update function that can be used to render new data:

var render = tagalong.render('#template', data);
// later
render(otherData);

Because tagalong modifies the DOM and removes all t- attributes, you must use the returned render function if you wish to update the element with new data.

Attributes

The following attributes are the "control structures" of tagalong template elements. You can use them on any child element of the root (see above).

t-text="expression"

Sets the text of the element to the value of the JavaScript expression:

<div id="template">
  <span t-text="name">foo</span>
</div>
<script>
tagalong.render('#template', {name: 'bar'});
</script>

Alternately, you can embed expressions in the text of any node with JavaScript wrapped in {{ and }}:

<div id="template">
  Hello, {{ world.toUpperCase() }}!
</div>
<script>
tagalong.render('#template', {world: 'Earth'});
</script>

t-if="expression" and t-else

Conditionally includes an element in the output if the JavaScript expression is truthy. An adjacent element with a t-else attribute will be shown only if the t-if expression is falsy.

<div id="template">
  <h1 t-if="error" class="error">Error: <span t-text="error"></span></h1>
  <h1 t-else>All good!</h1>
</div>
<script>
// this will show the first <h1> and hide the second
var render = tagalong.render('#template', {error: 'something bad happened'});
// and vice-versa
render({error: null});
</script>

t-each="expression"

Repeats the element for each value in the evaluated JavaScript expression:

<ul id="people">
  <li t-each="people">
    <span t-value="name">Name</span>
  </li>
</ul>
<script>
tagalong.render('#people', {
  people: ['Jill', 'Jane', 'Jack']
});
</script>

t-foreach="expression"

Repeats the element's children for each value in the evaluated JavaScript expression:

<div id="template">
  <dl t-foreach="terms">
    <dt t-text="name">Term</dt>
    <dd t-text="desc">Description</dd>
  </dl>
</div>
<script>
tagalong.render('#template', {
  terms: [
    {name: 'fubar', desc: 'F*cked Up Beyond All Recognition'},
    {name: 'foo bar', desc: 'Metasyntactic variables'}
  ]
});

t-with="expression"

Changes the context of nested expressions in the element's children.

<div id="template">
  <h1 t-with="items">
    <span t-value="length">0</span>
    item<span t-if="length !== 1">s</span>
  </h1>
  ...
</div>
<script>
tagalong.render('#template', {
  items: ['a', 'b', 'c']
});
</script>

t-as="symbol"

Aliases whatever data it gets (or "creates", as in t-each, t-foreach, or t-with elements) to the named symbol, which can be referred to in child expressions by name. This can be used to make nested expressions easier to read, or simplify accessing numeric indices or properties that contain odd characters:

<div id="template">
  <ul t-foreach="items" t-as="item">
    <li t-value="item[0]"></li>
  </ul>
</div>
<script>
tagalong.render('#template', {
  items: [
    ['foo', 1],
    ['bar', 2],
    ['baz', 0]
  ]
});
</script>

Behind the scenes, this just sets the property named by symbol on the context object passed to tagalong.render() while that element is rendering.

t-class="expression"

Class name expressions can evaluate to either an object or an array. If the expression evaluates to an object, then the keys are used as class names if their values are truthy:

<section t-class="{regional: true, onshore: onshore}">
</section>

If the result is an array, the values are joined with spaces and used as-is.

t-style="expression"

Style expressions can evaluate to either an object or an array. If the expression evaluates to an object, then the key/value pairs are formatted as CSS properties, separated with semicolons:

<ol id="toc">
  <li t-each="sessions" t-style="{color: color}">
    <a t-href="'#' + id" t-text="text">Section</a>
  </li>
</ol>
<script>
tagalong.render('#toc', {
  sessions: [
    {id: 'intro', name: 'Welcome'},
    {id: 'breakout', name: 'Breakout Session', color: 'pink'},
    {id: 'speaker1',  name: 'Speaker: Jane Goodall'}
  ]
});
</script>

If the result is an array, the values are assumed to be property: value pairs and are joined with semicolons.

Other Attributes

Any other attribute with a t- prefix is evaluated as a JavaScript expression and has its t- prefix stripped in the output, e.g.

<input name="first_name" t-value="first_name">
<input name="attending" t-checked="attending ? 'checked' : null">

Events

Event handling can happen in three different ways:

  1. Use event delegation to capture bubbling events at the top level of your template, which obviates the need for listeners on individual elements, e.g.
var delegate = function(selector, callback) {
  return function(e) {
    if (e.target.matches(selector)) {
      callback.apply(this, arguments);
    }
  };
};
root.addEventListener('click', delegate('a', function(e) {
  alert('clicked:', e.target.href);
}));

:warning: This won't work with events that don't bubble, such as focus and blur.

  1. Use t-on* attributes to bind your listeners to template data:
<ul id="root">
  <li t-each="items">
    <a t-onclick="(item, event) => click(item)">{{ . }}</a>
  </li>
</ul>
<script>
var render = tagalong.render('#root', {
  items: [
    'foo',
    'bar',
    'baz'
  ]
}, {
  click: function(d) {
    alert('clicked', d);
  }
});
</script>

Note that "unqualified" listener references (such as click(), above) must be present in the context (3rd) argument to tagalong.render(). If you want to call a function of the data, you can do it like this:

<a id="link" t-onclick="(d, e) => d.click(e)">hi</a>
<script>
tagalong.render('#link', {
  click: function(e) {
  }
});
</script>
  1. Use on* attributes. This is not suggested.

<t-template> Custom Element

Tagalong ships with a <t-template> custom element:

<t-template id="people">
  <h1>
    <span t-text="items.length">0</span>
    Member<span t-if="items.length !== 1">s</span>
  </h1>
  <ul>
    <li t-each="items">
      <span t-text="name">Rosie</span> the
      <span t-text="occupation">Riveter</span>
    </li>
  </ul>
</t-template>

Then, set the data either by setting the data attribute (any JavaScript or JSON expression will do), or set the data property directly:

document.getElementById('people').data = {
  people: [
    {name: 'Rosie', occupation: 'Riveter'},
    {name: 'Joe', occupation: 'Waiter'},
    {name: 'Jill', occupation: 'Welder'},
  ]
};