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

mithril-hobbit-decorator

v0.3.3

Published

Set of utility components to boost your base components capabilities.

Downloads

4

Readme

Mithril Hobbit Decorator

NPM Version NPM License NPM Downloads

Hobbit decorator is part of the Hobbit framework for mithril. This package contains a set of components to improve the functionalities for micro-components and introduce better programming habits to mithril.

The component set is easy to use and extremely light - weighing less than 1KB - bringing the whole mithril package to just about 9 KB.

How to install

To install Hobbit decorator, either install the hobbit framework or this package individually through npm:

npm install --save mithril-hobbit-decorator

Hobbit decorator is also available as a UMD module in a minified or unminified format.

How to use

Hobbit decorator exports a set of 10 decorators - referred as enhancers in this documentation - to improve Mithril components. All enhancers work as closures, they take their initial parameters and return a function to be called again with the component they should enhance.

import { withState } from 'mithril-hobbit-decorator';

const Component = {
    view: function() {
        ...
    }
};

const enhanced = withState(...)(Component);

This pattern allows the functions to be easily composed and called together as to enhance one component with multiple components.

import { withAttrsValidation, withDefaultAttrs, compose } from 'mithril-hobbit-decorator';

const Component = {
    view: function() {
        ...
    }
};

const composed = compose(
    withAttrsValidation(...),
    withDefaultAttrs(...)
)(Component);

Note that attrs flow from top to bottom, so in the previous example, withAttrsValidation would change the attributes of the vnode before withDefaultAttrs did.

This pattern also allows creating enhancers and keeping them as modules to be used elsewhere, improving an application's modularity.

// module.js
import { withDefaultAttrs } from 'mithril-hobbit-decorator';

export withDefaultAttrs(...);

// index.js
import module from 'module.js';

const Component = {
    view: function() {
        ...
    }
};

const enhanced = module(Component);

Of course, exported modules can be composed as well.

// module1.js
import { withAttrsValidation } from 'mithril-hobbit-decorator';

export withAttrsValidation(...);

// module2.js
import { withDefaultAttrs } from 'mithril-hobbit-decorator';

export withDefaultAttrs(...);

// index.js
import { compose } from 'mithril-hobbit-decorator';
import module1 from 'module1.js';
import module2 from 'module2.js';

const Component = {
    view: function() {
        ...
    }
};

const enhanced = compose(
    module1,
    module2
)(Component);

Why?

Hobbit decorator is meant as a lightweight utility belt for mithril, adding some mush needed functionalities for component. For those used to Recompose/Recompact for React, hobbit decorator will feel very similar to those libraries.

To quickly illustrate why developer should use hobbit decorator, lets looks at a standard component in mithril.

const Component = {
    view(vnode) {
        return m('div', vnode.attrs.children);
    }
};

As functionalities are added to this component, it can quickly get bloated with a variety of functionalities that have very specific conditions which makes component hard to test and read. For example;

const Component = {
    loading: true,
    error: null,
    data: undefined,
    oninit: function(vnode) {
        m.request(vnode.attrs.request).then((result) => {
            vnode.state.loading = false;
            vnode.state.data = result;
        }).catch((error) => {
            vnode.state.loading = false;
            vnode.state.error = error;
        });
    },
    view: function(vnode) {
        const { onChange, inputValue } = vnode.attrs;
        const { loading, error, data } = vnode.state;
        
        if (loading) {
            return m('div', 'loading....');
        }
        
        return error != null ? 
            m('div', 'Error') :
            m('div', [
                m('span', data),
                m('input', {
                    value: inputValue || 'Please enter a value',
                    oninput: onChange,
                }),
                vnode.attrs.children
            ]);
    }
};

What was before a 'dumb' component - a component who did not handle its state - has now become a 'smart' and bloated component. What if we could bring this component back to its original micro-state while still giving it the desired behavior? Hobbit decorated can be used to add those functionalities to a micro-component.

import { compose, toEnhancer, withFetch, withTernary, withDefaultAttrs } from 'mithril-hobbit-decorator';
import loader from './loader';

const Component = {
    view: function(vnode) {
        const { onChange, inputValue, error, data } = vnode.attrs;
        
        return error != null ? 
            m('div', 'Error') :
            m('div', [
                m('span', data),
                m('input', {
                    value: inputValue,
                    oninput: onChange,
                }),
                vnode.attrs.children
            ]);
    }
};

const enhanced = compose(
    withDefaultAttrs({
        onChange: () => {},
        inputValue: 'Please enter a value',
    }),
    withFetch((attrs) => attrs.request),
    withTernary(
        (attrs) => attrs.loading,
        toEnhancer(loader),
        Component
    ),
)(Component);

The component itself is kept pure and dumb, making it very easily testable and predictable. We could further modularize this component by sending all the enhancers to their own file, making them as easily testable as the base component.

Recipes

Todo

API

compose(...enhancers)(component)

compose executes all the given enhancers on the component from top to bottom, passing the enhanced props to the next enhancers up to the component itself. The utility is useful to simplify calling multiple enhancers one after the other.

//This
const enhanced = enhancer2(...)(enhancer1(...)(Component))

//Becomes this, note that the order goes from top to bottom
const compose(
    enhancer1(...),
    enhancer2(...),
)(Component)
  • enhancers is a set of functions that must return another function for compose to work.

expand(object)(component)

expand is a utility that allows functions in an object to monkey-path the original functions of component. For example, with expand, lifecycle methods can now be called multiple times, adding more freedom to library developers. The original function will always be called last with the result from the previous call as an added parameter.

const Component = {
    view: function(vnode, prev) {
        // called last, prev is the value returned from extraExpanded
    }
};

const expanded = expand({
    view: function(vnode) {
        // called first
    }
})(Component);

const extraExpanded = expand({
    view: function(vnode, prev) {
        // called second, prev is the value returned from expanded
    }
})(expanded);
  • objet is an object of functions. It the element of object exists in the expanded component, it will be monkey-patched.

toEnhancer(component)

toEnhancer is a simple utility that transforms a normal component into an enhancer, making it possible to be called into compose or other enhancers. It is especially useful when combined with withTernary.

const Component = {
    view: function(vnode) {
        ...
    }
};

//Will only render if the condition is true
const enhanced = withTernary(
    () => { ... },
    toEnhancer(Component),
);
  • component is the component to transform into an enhancer, it must be a valid component. Note that calling it as an enhancer will end the chain.

withAttrsRename(renames)

withAttrsRename is an enhancer that can rename attributes to make them easier to use within the components. Its parameter is an object where the keys are the old name and the value associated with the key is the new name to give to that attribute. If the value is a function, it will be called with all the attributes as its parameter to make renaming more dynamic.

const enhanced = withAttrsRename({
    foo: 'bar', //will rename any attribute 'foo' to 'bar'
})(Component);

withAttrsValidation(validators)

withAttrsValidation is an enhancer that adds the functionalities of react prop-types to a component where the validators given can ensure that the attributes the enhancer receives follow the specific set of rules defined by the developer. The validators parameter is an object of functions mapped under the name of the attribute they should validate. Each function will receive three parameters, the attribute itself, its name and all the attributes of the component. All functions should return a value that can be evaluated as a boolean.

If a validator does not pass, it will trigger a console error whether on production or development environments, but will not lead to any crash or loss of functionalities.

const enhanced = withAttrsRename({
    foo: (value) => value !== "",
})(Component);

withDefaultAttrs(defaults)

withDefaultAttrs is an enhancer that can replace undefined attributes with a default value to ensure they are always set to a predictable value. The defaults object is a set of default values mapped under the name of the attribute they should ensure the default value. The default is applied only if the exact type of the attribute is undefined. The default can also be a function in which case it is called with all the attributes as its parameters and should return the value to set as default.

const enhanced = withDefaultAttrs({
    foo: "bar", //Will make the value of attribute foo equal to bar if it was undefined
})(Component);

withFetch(request)

withFetch is an enhancer that wraps the base fetch functionalities of mithril in an enhancer for ease of use.

When the enhancer is called, the enhanced component will be returned with a loading attribute set to true. It will also receive an error and data attribute, all undefined.

Once the request ends, if it triggered an error the loading attribute will be set to false and the error set to whatever error was caught. If it succeeded, the loading attribute will be set to false and the data attribute set to whatever value the fetch returned.

withHandle(handlers)

withHandle is an enhancer that adds a set of functions to the attributes of a component. If handlers is a function, it will be called with the attributes as its parameters an expect an object of functions in return. If an object, it should be an object of the functions to add to attributes.

withHandle is very likely to change for a version that is not simply a "functions only" version of withTransform in the future

const state;

const enhanced = withHandle({
    onChange: (value) => state = value,
})(Component);

withState(valueName, setterName, initialState = null)

withState is an enhancer that adds an external piece of state to a components attribute. It will add an attribute under the valueName key as well ass a setter function under the setterName key. An initial state can be given to preset the value of the state, otherwise it will be set to null.

const Component = {
    view: function(vnode) {
        return m('input', {
            value: vnode.attrs.foo, //Will default to 'bar'
            oninput: vnode.attrs.setFoo,
        });
    },
};

const enhanced = withState('foo', 'setFoo', 'bar')(Component);

withTernary(condition, first, second = null)

withTernary is an enhancer that will render one of two enhancers with the base component depending on the condition. Condition must be a function that will receive the attributes as its parameter and should return a value that can be evaluated to a boolean. If the condition is true, the first enhancer is rendered, otherwise the second will be rendered. The second enhancer can be ignored to render nothing in case the condition is false.

const enhanced = withTernary(
    (attrs) => attrs.type === "string",
    withDefaultAttrs({foo: ""}),
    withDefaultAttrs({foo: 0}),
)(Component);

withTransform(transformators)

withTransform is an enhancer that allows attributes to be transformed before they are passed to the base component. Transformators is an object where each key is the name of the attribute to transform and the value a function to be used for transforming the attribute. If transformators is a function, it is first called with all the attributes as its parameters and should return an object with the same specifications.

All transform functions are called with three arguments, the attribute itself, its name and all the attributes given to the component.

const enhanced = withTransform({
    foo: (foo) => foo + "_bar"
})(Component);

Browsers support

Not tested yet.