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

impera-js

v0.7.8

Published

Tiny, Proxy based App State Managment for custom-elements

Downloads

20

Readme

ImperaJS

Tiny, Proxy based App State Management for custom-elements.

Main Features

I know what you are thinking... Yet another framework, hurray!

This library is inspired to State Management frameworks that we all know (Redux, MobX, Effector). It tries to put together the flow, the "store" breakup philosophy of Effector and the syntax simplicity of MobX, while being minimalistic and having custom-elements in mind as its first citizens. The main features are:

  • It's tiny, only about 5 kB minified (and 1.9 kB g-zipped).
  • It uses Proxy under the hood for a little :sparkler:
  • It is meant for custom-elements.
  • Supports Lit-Element.
  • Works with Vanilla-Js or frameworks like Brick.
  • Implements the usual flow: ACTION->REDUCER->STORE but with A LOOOOT less painful syntax.
  • You can break the STORE in parts as small as you like.
  • Works with async out of the box.
  • Saves the state to localStorage automatically.

If you are not familiar with the ACTION->REDUCER->STORE pattern and why is a good idea, have a look at the Redux Docs where it is very well explained.

Demos

Simple to-do App demo made with ImperaJs and Lit-Element.

While here the same demo made with Brick-Element instead.

Quick Links

Getting Started

Of course... new framework, new sets of names for the same things... So let's first get the naming right:

  • Here the closest thing to the Redux STORE we call it StateVariable
  • The equivalent of a Redux ACTION + REDUCER we call it StateTransition

So basically the state of your App is kept by State-Variables (which do a little bit more than just keeping the state, but we'll see), while a transition from one app state to another is implemented by State-Transitions, hope it makes sense so far... State-Variables and Transitions can be hooked to custom-elements, so that on StateVariable change, or on dispatch of a Transition, the custom-element can apply its own UI-related changes.

Install

npm i impera-js

State Variables

A StateVariable hold the state of the App, its content can be a String, Object, Number and Boolean. Its DEFAULT value is passed at creation time and defines the type of the variable, the type cannot be changed later. A StateVariable is automatically stored in localStorage, if a value already exist it is automatically loaded. You can have a look at a more complete example here.

import {StateVariable} from 'impera-js'

// Initialization to an empty list
var todos = new StateVariable("todos",[]);

// Attach/Detach watchers
// Target is a custom-element and the callback is a function 
// that modify its UI (it will work with any object really)
todos.attachWatcher( target:HTMLElement, callback:Function )
todos.detachWatcher( target:HTMLElement )

// modifying the state is easy,
// this will fire the watchers callbacks
todos.value.push({txt:"first todo", isComplete:false})

// Note that **value** returns a proxy!
// this will also fire the watchers callbacks
let myProxy = todos.value[0]
myProxy.txt = "modified todo" 

The property value of a StateVariable returns a proxy to the content of the StateVariable, whenever it is set (directly or indirectly using Array.push for example) will run the callback for all watchers.

Local State Transitions

Transitions must be Pure Functions, they only compute a final state, they are defined by initial state and input data only, they reproduce always the same result for same inputs. Here below we are talking about local state transition, i.e. related to a single StateVariable, but technically there is very little difference with the global ones.


// Adding a Transition to a StateVariable
todos.addTransition("addTodo",(text)=>{
    let todo = {txt : text, isComplete : false}
    todos.value.push(todo)
})


// State change via transition
// this will fire the watchers callbacks
todos.applyTransition("addTodo", "new Todo")


// Note that now this will throw, this is to make 
// sure one can only change state by defined transitions
todos.value.push(some_todo)

// this protection can be overridden
todos.allowStandaloneAssign = true
todos.value.push(some_todo) // now will not throw

The idea here is that once you add a transition to a StateVariable you limit its allowed change space, the framework makes sure that now you are only allowed to change the StateVariable via transitions. You can of course override this behavior with the allowStandaloneAssign property. Note that transition owned by a state variable will bind this to the state variable itself, however make sure to use a named function then and NOT an arrow function as in the example.

Global State Transition

import {StateTransition} from 'impera-js'

// Global transitions definition
var removeTodo = new StateTransition("removeTodo",(index)=>{
    todos.value.splice(index,1)
    
    // any other StateVariable change can go below here
    // ....
});

// Dispatch the transition, and call watchers callbacks.
// Watcher can be attached in same way as for StateVariable
removeTodo.applyTransition( 1 )

A global StateTransition is a global function that is meant to apply simultaneously an overall state change, this can be made of just one variable change or multiple StateVariable changes at the same time, so that the initial and final states are always well defined, it guarantees that UI updates are made at transition completion (final state) only.

State Mixins

The StateMixins are a way to attach custom-element callbacks to a StateVariable or a StateTransition in an easy way. The callbacks get attached and detached automatically when the custom-element is connected/disconnected from the DOM.

import {statesMixin} from 'impera-js'

/**
 *  USAGE:
 * @param StatesList Array of StateVariable or StateTransition
 * @param baseClass  Class to inherith from
 * @return A mixed-in class
 */
statesMixin( StatesList, baseClass ) 


// Mixin applied to generic a custom-element
class myTodo extends statesMixin([todos,removeTodo], HTMLElement){
    constructor(){
        super()
        
        // the element has a read-only prop connected 
        // to each StateVariable in the list above
        let myTodos = this.todos[0]
        // This property is safe to use, you cannot 
        // modify the state accidentally.
        myTodos.txt = "new todo"  // this will throw
    }
    
    // override callback that fires on "todos" changes
    on_todos_update(){
        // do something to update the UI
    }
    
    // override callback that fires on transition "removeTodo"
    on_removeTodo(){
        // do something here
    }

    onclick(){
        // the element now has a hook to all transition of 
        // states in the list.
        this.applyTransition("addTodo", "new todo")
        this.applyTransition("removeTodo", 1 )
    }
}

For any StateVariable in the list a read-only property named as the StateVariable will be added to the element. Also an applyTransition method to dispatch the added transitions (either of a StateVariable or of a global StateTransition) will be added. Callbacks to react on StateVariable change needs to be overwritten by the user and have a predefined naming scheme: on_"stateVarName"_update. Callbacks to react to transitions are instead called on_"stateTransitionName", in the latter case also the transition input data are passed.

Usage with Lit-Element

The usage with Lit-Element is very similar to what shown above, with the exception that each update of any StateVariable or dispatch of Transition will request a render of the element. You can have a look at a more complete example here, while a demo can be found here.

import {litStatesMixin} from 'impera-js'

class myTodo extends litStatesMixin([todos,removeTodo],LitElement){
    
    static get properties() { return { index: Number } }
    
    render(){
        return html`
            <span @click="${this.toggle}">
                ${this.todos[this.index].isComplete === "true" ? "Done" : "Pending"}
            </span>
            <span class="text">${ this.todos[this.index].txt }</span>
            <a class="tag is-delete is-light" @click="${this.remove}"></a>
            </div>
        `;
    }
    toggle(){
        this.applyTransition("toggleTodo",this.index);
    }
    remove(){
        this.applyTransition('removeTodo',this.index)
    }
    on_todos_update(){
        // run here anything that has to be done before the render,
        // it can be used also for property/attribute reflection to state variables.
        console.log("updating todos");
    }
}

customElements.define("my-todo",myTodo);

Note the this.todos[...], a read only property with the name of the StateVariable has been added to the element, this is again a safe property to use: it cannot change the state of the app, it's just a getter. As above, also the transitions are added to the element. A hook callback function is added for each StateVariable and can be overriden by the user, it has a predefined naming scheme: on_"stateVarName"_update, this hooks runs every time the variable is updated and runs before the "render()", one interesting use case for this is property/attribute reflection to state variables.