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

pow-templating

v1.4.1

Published

An extremely small and lightweight templating framework

Downloads

734

Readme

pow.js 💥

An extremely small and lightweight templating framework.

NPM Version pow.min.js file size in bytes

😲 Only 140 LOCs!
🤏 <2½ KiB minified script (+ header)
🧩 No other dependencies
100% test coverage

Goals

  • A very small library that can be included without additional dependencies
  • Provides clear templating and interpolation
  • Extensible functionality through function calling and templates

Functionality

pow💥 has the following functionality:

Installation

Get the npm module or import directly from any javascript module:

import pow from 'https://ifyates.github.io/pow.js/latest/pow.min.js'

Looking at CDN hosting soon.

Example

See it in action 🏃‍➡️

// examples/quickstart.html
<script type="module">
    import pow from '../src/pow.js'

    const data = {
        "url": "https://github.com/IFYates/pow.js",
        "title": "pow.js",
        "description": "An extremely small and lightweight templating framework.",
        "tags": [ "javascript", "templating", "framework" ],
        "creation": {
            "author": "IFYates",
            "date": "2024-12-09"
        }
    }
    pow.apply(document.body, data)
</script>
<body>
    <h1>{{ title }}</h1>
    <h2>{{ description }}</h2>
    <p><a href="{{ url }}">See project page</a></p>
    <p pow item="creation">
        Created by <em>{{ author }}</em> on {{ date }}
    </p>
    <p>
        Tags:
        <template pow array="tags">
            [<span>{{ *data }}</span>]<span ifnot="*last">, </span>
        </template>
    </p>
</body>

Guide

Interpolations

pow💥 uses mustache-syntax interpolations ({{ key }}) and supports deep attributes ({{ child.key }}).

Interpolations can make use of complex expressions, but must start with a variable or function reference.

It is important to be aware that values are not made HTML-safe, so any HTML tags will be inserted verbatim.

In addition to any attribute of the current object, there are some in-built values:

  • *data: The entire current object
  • *parent: The parent object
  • *path: The current binding path, for debug help
  • *root: The data passed to the apply() call
  • If we are looping through data:
    • *first: true when this is the first item
    • *index: The 0-based index of the current item
    • *last: true when this is the last item

Functions

Interpolations can make use of functions registered on the bound data hierarchy.

The simplest way to access a function is to invoke them from the global scope.

Example:

<!-- examples/functions-global.html -->
<script type="module">
    import pow from '../src/pow.js'

    const data = {
        text: 'this is my text'
    }
    pow.apply(document.body, data)
</script>
<script>
    // Global function
    function wordCount(str) {
        return str.split(' ').length
    }
</script>
<body>
    "{{ text }}" contains {{ wordCount(text) }} words
</body>

See it in action 🏃‍➡️

Functions can also be provided as part of the data hierarchy.

Example:

<!-- examples/functions-data.html -->
<script type="module">
    import pow from '../src/pow.js'

    const data = {
        text: 'this is my text',
        wordCount: (str) => str.split(' ').length
    }
    pow.apply(document.body, data)
</script>
<body>
    "{{ text }}" contains {{ *root.wordCount(text) }} words
</body>

See it in action 🏃‍➡️

Events

HTML events can be handled by binding the event attribute to a function in the data hierarchy.

Example:

<!-- examples/interaction.html -->
<script type="module">
    const data = {
        handler: function(arg, root) {
            console.log('Clicked!',
                this, // The HTML element that raised the event
                arg, // The current binding context
                root // The data used in `apply()`
            )
        }
    }
</script>
<body>
    <button onclick="{{ handler }}">Click me</button>
</body>

See it in action 🏃‍➡️

Interpolation logic and pow.safe v1.1.0

By default, pow💥 provides full Javascript logic for interpolation using dynamic functions.
Some environments block dynamic code evaluation for user security.

In these situations, the interpolation logic can be replaced to allow for a custom parser to be used that meets any security requirements of the environment.

The logic is specified by providing a new pow._eval() function, which takes 2 arguments: the string to be parsed and the current context data.

Example:

<script type="module">
pow._eval = (expr, ctxt) => {
    // expr: 'child.text'
    // ctxt: { child: { text: "my value" } }
}
pow.apply(document.body, {
    child: { text: "my value" }
})
</script>
<body>
    {{ child.text }}
</body>

The pow.safe.js file is provided with a suggested alternative parser with some basic capabilities.

See the quickstart example using pow.safe in action 🏃‍➡️

As of v1.3.0, pow.safe also rebinds element events after every apply() to remove dynamic code.

See the interaction example using pow.safe in action 🏃‍➡️

Bindings

Any element can be used to control a binding, as long as it has the pow attribute.

If the binding element is a template, it will be replaced in its entirety. Other elements will only have their contents replaced.
The other advantage of using template is that it will not render anything until apply() has completed.

The default context will be the data provided to the apply() function, but the context can be changed by binding to a child element.

Example:

<!-- pow.apply(document.body, data) -->
<body><!-- *data = data -->
    <p><!-- *data = data -->
        <template pow item="child"><!-- *data = data.child -->
            <p><!-- *data = data.child -->

Attributes v1.4.0

In general, all placeholders in bound HTML are interpolated, so there is nothing special needed for setting attribute values.

However, attributes on bound elements (with the pow attribute) have more logic applied to them:

  • :attributes (names beginning :) will have the : removed and be fully interpolated, only setting/overwriting the attribute if it is truthy, and
  • If an attribute template resolves to a null value ('', null, undefined), it will be removed. Note that false and 0 will be displayed verbatim.

Examples:

  • Bound element with a truthy attribute value:
    <!-- data: { isChecked: 'truthy' } -->
    <input pow type="checkbox" checked="{{ isChecked }}" />
    <!-- Result --><input type="checkbox" checked="truthy" />
  • Bound element with a null/missing attribute:
    <!-- data: { } -->
    <input pow type="checkbox" checked="{{ isChecked }}" />
    <!-- Result --><input type="checkbox" />
  • Unbound element with a null/missing attribute:
    <!-- data: { } -->
    <input type="checkbox" checked="{{ isChecked }}" />
    <!-- Result --><input type="checkbox" checked="" />
  • Unbound element with an attribute that evaluates to false:
    <!-- data: { isChecked: false } -->
    <input type="checkbox" checked="{{ isChecked }}" />
    <!-- Result --><input type="checkbox" checked="false" />
  • Bound element with a truthy :attribute value, which overwrites an existing attribute:
    <!-- data: { value: 'replaced' } -->
    <div pow class="existing" :class="value">
    <!-- Result --><div class="replaced">
  • Bound element with a :attribute that evaluates to a null value, leaving the existing attribute:
    <!-- data: { } -->
    <div pow class="existing" :class="value">
    <!-- Result --><div class="existing">

Conditional

pow💥 bindings support a basic set of conditions:

  • if="cond", ifnot="cond"
  • else-if="cond", else-ifnot="cond"
  • else

The else* conditions are only evaluated if placed as siblings to a lead if template.

<div pow if="cond">Always checked</div> <!-- Not part of following set -->
<div pow if="cond">Always checked; new condition set</div>
<div pow else-if="cond">Only checked if prior sibling "if" was false</div>
<div pow else-if="cond">Only checked if all prior sibling "if" and "else-if"s were false</div>
<div pow else>Only checked if all prior sibling "if" and "else-if"s were false</div>

Loops

To loop through an item's elements with pow💥, just bind it as an array.

The bound element will be repeated per element.

Example:

<!-- pow.apply(document.body, { list: [1, 2, 3] }) -->
<body>
    <ul>
        <li pow array="list">{{ *data }}</li>
<!-- Output -->
<body>
   <ul>
       <li>1</li>
       <li>2</li>
       <li>3</li>

If you are already in the array context, omit the binding value to achieve the same effect:

<!-- pow.apply(document.body, { list: [1, 2, 3] }) -->
<body>
    <ul pow item="list">
        <li pow array>{{ *data }}</li>

Objects

If an object is looped, the keys and values are iterated as key and value.

Example:

<!-- pow.apply(document.body, { steps: { first: 1, second: 2, third: 3 } }) -->
<body>
    <div pow array="steps">{{ key }}: {{ value }}</div>
<!-- Output -->
<body>
    <div>first: 1</div>
    <div>second: 2</div>
    <div>third: 3</div>

Post-loop conditions v1.2.0

The else logic can be applied to loops, used when the loop does not produce an output.

Example:

<!-- pow.apply(document.body, { list: [] }) -->
<body>
    <div pow array="list">{{ *data }}</div>
    <div pow else>This is nothing in your list</div>

Reusable templates v1.2.0

If you have a particularly complex template, pow💥 can insert a copy of it using the template binding, replacing the bound element.
Although this can be applied to any element, it is recommended to use source as this is a self-closing tag that cannot have any contents.

The item/array bindings can be used to specify what data the template receives.

Note: Reusable templates should always be stored outside of any bound elements, otherwise they will be modified like anything else.

Example:

<!-- pow.apply(document.body, { list: [ 1, 2, 3, 4, 5 ] }) -->
<body>
    <source pow array="list" template="example" />
</body>
<template id="example">{{ *data }}, </template>
<!-- Output -->
<body>
    1, 2, 3, 4, 5, 
</body>
<template id="example">{{ *data }}, </template>

Stop v1.4.0

The stop binding will prevent an element and it's children from being processed further by the current apply().

Note that the binding logic is destructive to elements, even with stop, so any element references taken before will be void.

Example:

<body>
    {{ text1 }}
    <div pow stop>{{ ignore }}</div>
    {{ text2 }}

Reactivity

pow💥 does not provide true reactivity out-of-the-box, in the aim of keeping the library small.

Some reactivity can be achieved through re-applying or refreshing a binding. Note that this does destroy the elements, so any code referencing the output will need to be reapplied.

Example:

<!-- examples/reactivity/index.html -->
<script type="module">
    import pow from '../src/pow.js'

    const binding = pow.bind(document.body)
    const data = {
        count: 0,
        increment: () => {
            data.count += 1
            binding.refresh()
        }
    }
    binding.apply(data)
</script>
<body>
    Current value: {{ count }}
    <button onclick="{{ increment }}">Add 1</button>
</body>

See it in action 🏃‍➡️

Troubleshooting

Malformed HTML

Since the templating is applied to the DOM structure, malformed HTML may cause what appears to be unexpected behaviour.
The *path interpolation is intended to help in these situations.

A particularly common mistake is not closing tags correctly or incorrect nestings (e.g., div tags cannot be inside p tags).

Example:

{{* path }}<!-- '*root' -->
<p pow item="list">
    {{* path }}<!-- '*root.list' -->
    <div pow array><!-- 'div' element cannot be inside a 'p' and will be shifted outside -->
        {{* path }}<!-- '*root' -->
    </div>
</p>

Possible future features

  • Attributes
    • Dynamic attributes: Adding an attribute based on interpolation (with conditions)
    • Aggregating attributes: Adding a dynamic value to a static attribute (e.g., class)
    • Possible syntax: <param pow (if?) name="interpolated" mode="create|replace|append" value="interpolated" />
    • Already have create (attr="{{ expr }}") and replace (:attr="expr"). Append could be :add:attr="expr"? Dynamic ::interpolate::="expr"? or :attr="'+' + expr"?
  • async support on function resolution
  • Switch statement
    <template pow switch="token">
        <div case="{{ token }}">...</div>
        <div case="literal">...</div>
    </template>
  • Review the * syntax for accessing meta-context - makes certain expressions impossible (*data == *root.value)
    Likely just always expose $data, $root, $path, $index, etc.