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

vjs-reactivity

v1.0.24

Published

Create bindings between a JS variable and a DOM element. Also allows reactivity when a bound JS variable or DOM element changes its value.

Downloads

8

Readme

vjs-reactivity

This simple library is an interface to allow you to interact with your DOM elements as JS variables using vanilla javascript.

Installing

The quickest way to install this library is to use the unpkg cdn.

<script src="https://unpkg.com/vjs-reactivity/dist/bundle.js"></script>
<script>
    const { Binding, $$, reactive, claritive } = vjs_reactivity;
</script>

Examples

Here are a couple of examples of how this library can be used.

Simple binding

Simple bindings are ways to interact with your DOM elements as a JS variable by referencing .value on your bound variable. This example shows how you can create a binding that maintains its value from the DOM element initial value.

<input type="number" id="some-number" value="10">
const x = new Binding("#some-number");

x.value = 12;

console.log(document.querySelector("#some-number").value); // outputs: "12"

Simple binding with initializer

This example shows how you can create a binding that gives an initial value when the binding is created.

<input type="number" id="some-number" value="10">
const x = new Binding("#some-number", 12);

console.log(document.querySelector("#some-number").value); // outputs: "15"

x.value = 15;

console.log(document.querySelector("#some-number").value); // outputs: "12"

Bindings with different triggers

All bindings created have two default triggers: change and input. However, sometimes you may want your bindings to only change on a certain trigger. For example, if you are validating an input for percentage and only want it to appear as some number between 0 and 1, having the trigger of input would be problematic.

You can change this by adding in a third and last argument to your Binding constructor. This argument is an array of strings where each string is the type of event trigger.

Here is an example using the percentage validation as mentioned:

const bX = new Binding("#percent", 0, ["change"]);

$$(bX, (x) => {
    // using $value avoids infinite invalidation. You can read more about this below in the "Reactivity and Reassigning Back" section.
    bX.$value = x > 1 ? x / 100 : x; // tada! it works!
});

Claritive binding

Claritive bindings are intended to be used when you want to store your bound variables in an object. This will eliminate the need to use .value to access your bound variable, and instead just reference the variable itself. Here is an example of a claritive binding.

<input type="number" id="some-number-x">
<input type="number" id="some-number-y">
const o = claritive({
    x: new Binding("#some-number-x", 5),
    y: new Binding("#some-number-y", 10)
});

console.log(document.querySelector("#some-number-x").value); // outputs: "5"

o.x = 15;

console.log(document.querySelector("#some-number-x").value); // outputs: "15"

Accessing the proxy in a claritive binding

Sometimes, you may actually want to get the Binding object itself, instead of just the value that is held by the binding. TypeScript will show you that you have access to all of your properties, plus n more (where n is the number of initial properties you had) prepended with $. Referencing a property with $ prepended will instead give you the Binding object.

NOTE: Because of this, the properties you give to your object in a claritive binding should not be prepended with $. An error will be thrown if this happens.

Here is an example of referencing the Binding object instead of the value.

<input type="number" id="some-number-x">
<input type="number" id="some-number-y">
const o = claritive({
    x: new Binding("#some-number-x", 5),
    y: new Binding("#some-number-y", 10)
});

console.log(o.$x); // outputs: "Binding { ... }".

Reactivity

With these bindings, you can create reactive events that fire when certain bindings change. This can be used to easily make a reactive web application. You can create reactivity by using the function reactive exported from this module. (Another synonym would be $$) The arguments passed into reactive are Binding objects or anonymous functions. All binding objects passed are used as a reference. If the binding changes, then all Functions that were passed in will trigger. The functions that you pass in will take n arguments (where n is the number of Bindings passed in). Each argument will read from left to right in order of what the bindings were passed in as. These arguments will be the newest values from the latest change.

Here is an example of using reactivity.

<input type="number" id="some-number-x">
<input type="number" id="some-number-y">
const o = claritive({
    x: new Binding("#some-number-x", 5),
    y: new Binding("#some-number-y", 10)
});

// since the binding itself needs to be passed in, we need to use the `$_` extension of the property created from claritive.
reactive(o.$x, (x) => {
    o.y = x + 5;
});

console.log(document.querySelector("#some-number-x").value); // outputs: "5"
console.log(document.querySelector("#some-number-y").value); // outputs: "10"

o.x = 10;

console.log(document.querySelector("#some-number-x").value); // outputs: "10"
console.log(document.querySelector("#some-number-y").value); // outputs: "15"

Above was an example using a claritive binding. Here is an example of just two simple Bindings and using the $$ synonym.

const bX = new Binding("#some-number-x", 5);
const bY = new Binding("#some-number-y", 10);

$$(bX, (x) => {
    bY.value = x + 5;
});

console.log(document.querySelector("#some-number-x").value); // outputs: "5"
console.log(document.querySelector("#some-number-y").value); // outputs: "10"

bX.value = 10;

console.log(document.querySelector("#some-number-x").value); // outputs: "10"
console.log(document.querySelector("#some-number-y").value); // outputs: "15"

NOTE: Avoid using reactive statements that create a circular reference.

Reactivity and Reassigning back

As noted in the previous section, you want to avoid circular references, but what if you want to assign your variable back as a form of validation?

Here's an example of what might be applicable, where you, the user, would want to make sure a number passed in as a percentage is actually a number between 0 and 1.

const bX = new Binding("#percent");

$$(bX, (x) => {
    bX.value = x > 1 ? x / 100 : x; // error
});

There's just one problem with this-- When you assign bX.value inside of the reactive statement, you get a Maximum Call Size Exceeded error, since the reactive statement is infinitely called.

To avoid this infinite invalidation, you can use .$value to change the value without invalidating the binding. It may not be intuitive, but it gets the job done.

Here is the same example using .$value instead to avoid invalidation.

const bX = new Binding("#percent", 0, ["change"]);

$$(bX, (x) => {
    bX.$value = x > 1 ? x / 100 : x; // tada! it works!
});

Manual invalidation

Above, I explained how you can avoid invalidation, but sometimes you may want to invalidate a variable without changing the value.

You can simply do this by either reassigning the value back to itself, or use the built-in .invalidate() function.

Here is an example of calculating a net income only when a button is clicked using .invalidate().


const bIncome = new Binding("#income");
const bTaxRate = new Binding("#tax-rate");
const bNet = new Binding("#net");

$$(bTaxRate, (percent) => {
    bTaxRate.$value = percent > 1 ? percent / 100 : percent;
    bIncome.value = bIncome.value * percent;
});

$$(bIncome, (inc) => {
    bNet.value = bIncome.value * bTaxRate.value;
});

const btn = document.querySelector("#calculate");

btn.addEventListener("click", () => {
    bIncome.invalidate(); // now the bNet will only ever be calculated when the "#net" button is clicked, instead of when bIncome changes.
});

Full example

Here is a full example with the application being a calculator for income/expenses

<script src="https://unpkg.com/vjs-reactivity/dist/bundle.js"></script>

<div>
    <h3>Income</h3>
    <label>Total Income: <input type="number" id="amount"></label>
    <select id="income-basis">
        <option value="daily">daily</option>
        <option value="weekly">weekly</option>
        <option value="bi-weekly">bi-weekly</option>
        <option value="monthly">monthly</option>
        <option value="yearly">yearly</option>
    </select>
</div>
<div>
    <h3>Expenses</h3>
    <label>Groceries: <input type="number" id="groceries" value="300"></label>
    <label>Bills: <input type="number" id="bills" value="1000"></label>
    <label>Entertainment: <input type="number" id="entertainment" value="200"></label>
    <select id="expenses-basis">
        <option value="daily">daily</option>
        <option value="weekly">weekly</option>
        <option value="bi-weekly">bi-weekly</option>
        <option value="monthly">monthly</option>
        <option value="yearly">yearly</option>
    </select>
</div>
<div>
    <h3>Outcome</h3>
    <label>Per day: <input type="number" id="result-per-day" disabled="disabled"></label>
    <label>Per week: <input type="number" id="result-per-week" disabled="disabled"></label>
    <label>Per month: <input type="number" id="result-per-month" disabled="disabled"></label>
    <label>Per year: <input type="number" id="result-per-year" disabled="disabled"></label>
</div>

<script>
    const { Binding, $$, reactive, claritive } = vjs_reactivity;

    const income = claritive({
        amount: new Binding("#amount"),
        basis: new Binding("#income-basis")
    });

    const expenses = claritive({
        groceries: new Binding("#groceries"),
        bills: new Binding("#bills"),
        entertainment: new Binding("#entertainment"),
        basis: new Binding("#expenses-basis")
    });

    const result = claritive({
        perDay: new Binding("#result-per-day"),
        perWeek: new Binding("#result-per-week"),
        perMonth: new Binding("#result-per-month"),
        perYear: new Binding("#result-per-year")
    });

    $$(income.$amount, income.$basis, expenses.$groceries, expenses.$bills, expenses.$entertainment, expenses.$basis, 
        (inc, incomeBasis, groc, bills, ent, expensesBasis) => {
            let incomePerMonth, expensesPerMonth;
            switch(incomeBasis) {
                case "daily": {
                    incomePerMonth = inc * 30;
                    break;
                };
                case "weekly": {
                    incomePerMonth = inc * 4;
                    break;
                };
                case "bi-weekly": {
                    incomePerMonth = inc * 2;
                    break;
                };
                case "monthly": {
                    incomePerMonth = inc;
                    break;
                };
                case "yearly": {
                    incomePerMonth = inc / 12;
                    break;
                }
                default: throw new Error(`The basis you entered is incorrect.`);
            }
            switch (expensesBasis) {
                case "daily": {
                    expensesPerMonth = (groc + bills + ent) * 30;
                    break;
                };
                case "weekly": {
                    expensesPerMonth = (groc + bills + ent) * 4;
                    break;
                };
                case "bi-weekly": {
                    expensesPerMonth = (groc + bills + ent) * 2;
                    break;
                };
                case "monthly": {
                    expensesPerMonth = (groc + bills + ent);
                    break;
                };
                case "yearly": {
                    expensesPerMonth = (groc + bills + ent) / 12;
                    break;
                }
                default: throw new Error(`The basis you entered is incorrect.`);
            }
            
            result.perDay = (incomePerMonth - expensesPerMonth) / 30;
            result.perWeek = (incomePerMonth - expensesPerMonth) / 4;
            result.perMonth = (incomePerMonth - expensesPerMonth);
            result.perYear = (incomePerMonth - expensesPerMonth) * 12;
        }
    );
</script>