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

bi-st

v0.0.7

Published

Provide declarative binding of DOM/Custom Elements to history.state

Downloads

7

Readme

Published on webcomponents.org

bi-st

bi-st binds elements to the window.history.state object. Its purpose is to take the long, drawn-out 99% unambiguously unidirectional markup seen here, and roll it up into a nice compact sushi roll. To help visualize this analogy, see Figure 1 below.

Figure 1

What we get is more compact and more flexible, which is good, but less clearly unidirectional, and a little less declarative, which, in my book, is not so good.

Syntax:

The syntax for binding a UI element is shown below.

<input data-st="draft.key">

<bi-st level="global"><script nomodule>
({
    '[data-st]':{
        onPopState: ({bist, el}) =>{
            el.value = bist.pullFromPath(el.dataset.st, 'Volt ikh mit dir gefloygn vu du vilst');
        },
        on:{
            input: ({event, bist}) => bist.merge(event.target.dataset.st, event.target.value, 'push');
        }
    }
})
</script></bi-st>
<p-d on="sync-history" to="{histEvent:detail.value}"></p-d>
<purr-sist write></purr-sist>

The demo shown here does the following:

  1. Allows the user to enter arbitary key value pairs into an object.
  2. The data the user enters is put into history.state.
  3. History.state is persisted to a remote datastore.

The total number of lines of markup is only slightly fewer than the example shown here (view source), that does the same thing without the benefit of this component. In particular, the number of lines drops from 129 lines to 110 lines.

However, as a page increases in complexity, with large numbers of UI elements, this component should help significantly reduce the amount of boilerplate. Markup shown below.

What's so great about history.state?

One of the trappings of a component library, like Vueactulitymber, is that each such library ships with a state manager / render binding. But what if you want to combine components together using different libraries? A tempting choice is to say "use Redux, or MobX, or RxJs, or CycleJS" to unify the state management. But this tends to enforce a "library / framework" choice of its own. For small applications / teams this may be fine, but what if you are working with a large application, that spans multiple generations of component libraries, involving loosely coupled teams?

Why not use the platform, and utilize something that will forevermore (?) ship with every browser? That supports time travel, and routing?

It should be noted that AMP components (like amp-bind) seem to ba based on the same principle.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Example 1</title>
</head>

<body>
    <div style="display:flex;flex-direction: column">

        <!-- Parse the address bar -->
        <xtal-state-parse disabled="3" parse="location.href" level="global" 
            with-url-pattern="id=(?<storeId>[a-z0-9-]*)">
        </xtal-state-parse>

        <!-- If no id found in address bar, "pass-down (p-d)" message to purr-sist-myjson writer 
            and xtal-state-update history initializer  to  create a new record ("session") 
            in both history and remote store -->
        <p-d on="no-match-found" to="purr-sist-myjson[write],xtal-state-update[init]" prop="new" val="target.noMatch" m="2" skip-init></p-d>

        <!-- If id found in address bar, pass it to 
            the persistence reader if history is null -->
        <p-d on="match-found" if="[data-history-was-null]" to="purr-sist-myjson[read]" prop="storeId" val="target.value.storeId" m="1" skip-init></p-d>

        <!-- If id found in address bar, pass it to the 
            persistence writer whether or not history is null -->
        <p-d on="match-found" to="purr-sist-myjson[write]" prop="storeId" 
            val="target.value.storeId" m="1" skip-init></p-d>

       <!-- Read stored history.state from remote database if 
            id found in address bar and history starts out null -->
        <purr-sist-myjson read disabled></purr-sist-myjson>

        <!-- If persisted history.state found, repopulate history.state-->
        <p-d on="value-changed" to="history" prop="target.value"></p-d>
        <xtal-state-update init rewrite level="global"></xtal-state-update>

        <!-- ==========================  UI Input Fields ===================================-->
        <!-- Manage binding between global history.state and UI elements -->
        <bi-st disabled id="bist" level="global" url-search="(?<store>(.*?))" replace-url-value="?id=$<store>"><script nomodule >({
            '[data-st]': {
                onPopState: (bist, el) => {
                    el.value = bist.pullFromPath(el.dataset.st, el.value);
                },
                on: {
                    input: (event, bist ) => bist.merge(event.target.dataset.st, event.target.value, 'push'),
                }
            },
            '[insert]':{
                on:{
                    click: (event, bist) => bist.merge('submitted', event.target.obj, 'push'),
                }
            }
        })</script></bi-st>
        <p-d on="history-changed" to="purr-sist-myjson" prop="newVal" val="target.value" m="1"></p-d>

        <!-- Add a new key (or replace existing one) -->
        <input type="text" disabled placeholder="key" data-st="draft.key">
        <!-- Pass key to aggregator that creates key / value object -->
        <p-d on="input" to="aggregator-fn" prop="key" val="target.value" m="1"></p-d>

        <!-- Edit (JSON) value -->
        <textarea disabled placeholder="value (JSON optional)" data-st="draft.value"></textarea>
        <!-- Pass (JSON) value to key / value aggregator -->
        <p-d on="input" prop="val" val="target.value"></p-d>
        <!-- ============================  End UI Input fields =============================== -->

        <!-- Combine key / value fields into one object -->
        <aggregator-fn><script nomodule>
            fn = ({ key, val }) => {
                if (key === undefined || val === undefined) return null;
                try {
                    return { [key]: JSON.parse(val) };
                } catch (e) {
                    return { [key]: val };
                }
            }
        </script></aggregator-fn>
        <!-- Pass Aggregated Object to button's "obj" property -->
        <p-d on="value-changed" to="button" prop="obj" val="target.value" m="1"></p-d>

        <button insert>Insert Key/Value pair</button>        

        <!-- Persist history.state to remote store-->
        <purr-sist-myjson write></purr-sist-myjson>

        <!-- Pass store ID up one element so xtal-state-update knows how to update the address bar -->
        <p-u on="new-store-id" to="bist" prop="url"></p-u>

        <!-- Pass persisted object to JSON viewer -->
        <p-d on="value-changed" prop="input"></p-d>
        <xtal-json-editor options="{}" height="300px"></xtal-json-editor>

        <!-- Reload window to see if changes persist -->
        <button onclick="window.location.reload()">Reload Window</button>

        <script src="https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
        <script type="module" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/purr-sist-myjson.iife.js"></script>
        <script type="module" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/p-all.iife.js"></script>
        <script type="module" src="https://cdn.jsdelivr.net/npm/[email protected]/xtal-json-editor.js"></script>
        <script type="module" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/aggregator-fn.iife.js"></script>
        <script type="module" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/xtal-state-update.iife.js"></script>
        <script type="module" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/xtal-state-parse.iife.js"></script>
        <script type="module" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bi-st.iife.js"></script>
    </div>
</body>
</html>