This package has been deprecated

Author message:

package name changed to @okwolo/state

goo-state

2.3.5 • Public • Published

GOO Build Status Coverage Status NPM version gzipped size

Goo.js is a small framework made to jumpstart projects by solving common web application challenges. The default goo package includes state management, layout and routing functionality. Each of these parts are separate stand-alone modules that are combined together into the final goo function. All modules have a common .use function that accepts goo's configuration objects called blobs.

Top level api

// creates the app
goo(target[, window]);
  // target: DOM node
  // window: window object can be specified if needed

// defines the layout of your app
app(init)
  // init: function that returns an element builder function [() => (state) => element]
app(route, init);
  // route: string pattern to match paths (same as in express)
  // init: function that returns an element builder function [(routeParams) => (state) => element]

// sets the state from which layout is created
app.setState(state);
  // state: an object to replace the current state
app.setState(updater);
  // updater: function that returns the new state [(currentState) => ... newState]

// returns a copy of the current state
app.getState();

// changes the path and renders layout from the new route
app.redirect(path[, params]);
  // path: string of the new pathname
  // params: object to be passed to the route handler

// renders layout from the new route
app.show(path[, params]);
  // path: string of the requested route's path
  // params: object to be passed to the route handler

// executes an action on the state
app.act(type[, params]);
  // type: string of the action type
  // params: arguments given to the action handler
app.act(action);
  // action: function that returns the new state [(currentState) => ... newState]

// adds a blob to the app and sends it to all modules
app.use(blob);
  // blob: the blob to be added

// triggers a rerender from current state (for use when changes are not represented in the state)
app.update();

// undoes the last action (or setState call)
app.undo();

// redoes the previous action
app.redo();

Syntax

text element

let elem = textContent;
  // textContent: content of the textNode
let elem = 'Hello World!';
  // Hello World!

tag element

let elem = [tagName, attributes, children];
  // tagName: string representing the dom tag's name
  // attributes: object containing all attributes of the element
  // children: array of child elements
let elem = ['div'];
  // <div></div>

let elem = ['div', {id: 'banner'}];
  // <div id="banner"></div>

let elem = ['div', {}, ['Hello World!']];
  // <div>Hello World!</div>

let elem = ['div#banner'];
  // <div id="banner"></div>

let elem = ['div.nav'];
  // <div class="nav"></div>

let elem = ['div | height: 10px;'];
  // <div style="height: 10px;"></div>

let elem = ['div#banner.nav.hidden | font-size: 20px;'];
  // <div id="banner" class="nav hidden" style="font-size: 20px;"></div>"

component element

let elem = [component, props, children];
  // component: function which returns an element [(props) => element]
  // props: object containing all props for the component
  // children: array of child elements
let component = ({children, color}) => (
    ['div', {style: `color: ${color}`}, children]
);

let elem = [component, {color: 'red'}, ['content']]
  // <div style="color: red;">content</div>

Example

let app = goo(document.body);

app.setState(['orange', 'apple', 'pear']);

let FruitItem = ({type}) => (
    ['li.fruit', {}, [
        type,
    ]]
);

app(() => (fruits) => (
    ['ul.fruit-list', {},
        fruits.map((type) => (
            [FruitItem, {type}]
        )),
    ]
));
<ul class="fruit-list">
    <li class="fruit">orange</li>
    <li class="fruit">apple</li>
    <li class="fruit">pear</li>
</ul>

Blobs

Goo and its modules all have a use function which takes a single object as argument called a blob. Blobs are very powerful since they are understood by all modules and allow access to deeper layers than the top level api.

A blob can have as many unique keys as necessary. Each key will be tested on each module to determine if that module supports it. The value assigned to each key can be a single object, or an array of objects (ex. adding multiple actions at once). This library is purposefully built to handle the addition of blobs in any order and at any time in an application's lifecycle.

Here is an example of a blob that adds two watchers and adds a route.

app.use({
    watcher: [myFirstWatcher, mySecondWatcher],
    route: myRoute
});

Named blobs are an extension of regular blobs that ensure that the blob is only ever used once in a single app instance. This can be done by simply adding a "name" key to a blob and using it as before.

let myPlugin = {
    name: 'myPluginName',
    middleware: myMiddlware,
}

app.use(myPlugin);
app.use(myPlugin); // will not add the middleware again

Here is the list of the recognized blob keys and the modules that consume them.

module recognized keys
goo-state name, action, watcher, middleware
goo-router name, route, base
goo-dom name, target, builder, state, draw, update, build, prebuild, postbuild

action

let action = {type, target, handler}
  // type: string which names the action
  // target: array to restrict the scope of an action to a specific "address" in the state
  //   OR    function which returns the action's target [(state, params) => ... target]
  // handler: function that makes the change on the target [(target, params) => modifiedTarget]
app.setState({
    name: 'John',
    hobbies: ['tennis', 'gardening'],
});

let action = {
    type: 'REMOVE_HOBBY',
    target: ['hobbies'],
    handler: (hobbies, index) => {
        hobbies.splice(index, 1);
        return hobbies;
    },
};

app.use({action});

app.act('REMOVE_HOBBY', 0);

app.getState(); // {name: 'John', hobbies: ['gardening']}

watcher

let watcher = watcher
  // watcher: function that gets called after each state change [(state, actionType, params) => ...]

Watchers cannot modify the state since they are given a copy, but they can issue more actions.

let watcher = (state, actionType, params) => {
    console.log(`action of type ${actionType} was performed`);
};

app.use({watcher});

middleware

let middleware = middleware
  // middleware: function that is given control of an action before it is executed
  //    [(next, state, actionType, params) => ... next(state[, actionType[, params]])]

This syntax allows middlware to be asynchronous.

If next is called with parameters, they will override the ones issued by the act call.

let middlware = (next, state, actionType, params) => {
    if (params.test) {
        console.log('action changed to TEST');
        next(state, 'TEST');
    } else {
        next();
    }
};

app.use({middleware});

route

let route = {path, callback}
  // path: string pattern to match paths (using express' syntax)
  // callback: function that is called with the route params as argument [(routeParams) => ...]
let route = {
    path: '/user/:uid/profile',
    callback: ({uid}) => {
        // ...
    },
};

app.redirect('/user/123/profile');

base

let base = base
  // base: string specifying the base url of the page(s)
let base = '/subdir/myapp';

app.use({base});

app.redirect('/users');
  // navigates to '/subdir/myapp/users'
  // matches routes for '/users'

target

let target = target
  // target: dom node in which to draw the app
let target = document.querySelector('.app-wrapper');

app.use({target});

builder

let builder = builder
  // builder: function which creates an element out of state [(state) => element]
let builder = (state) => (
    ['div | background-color: red;', {} [
       ['span.title', {} [
           state.title,
       ]]
    ]]
);

app.use(builder);

state

let state = state
  // state: object to update ONLY the layout's state

It is not recommended to use this key. It will ONLY change the state of the DOM module.

let app = goo(document.body);

app.setState('originalState');

app(() => (state) => (
    ['span', {}, [
        state,
    ]]
));
// <span>originalState</span>

app.use({state: 'newState'});
// <span>newState</span>

app.getState();
// 'originalState'

app.update();
// <span>originalState</span>

draw

let draw = draw
  // draw: function that handles the initial drawing to a new target [(target, vdom) => ... vdom]

Using this blob key will override the default draw function.

let draw = (target, vdom) => {
    target.innerHTML = magicallyStringify(vdom);
    return vdom;
};

app.use({draw})

update

let update = update
  // update: function that updates the target with new vdom [(target, vdom, currentVdom) => ... vdom]

Using this blob key will override the default update function.

By overriding both the draw and the update functions it is possible to "render" to any target in any way.

Here is an example of an app that renders to a string instead of a DOM node.

let realTarget = '';

let renderToTarget = (target, vdom) => {
    realTarget = magicallyStringify(vdom);
    return vdom;
};

app.use({
    draw: renderToTarget,
    update: renderToTarget,
})

build

let build = build
  // build: function that creates VDOM out of the return value of an element [(element) => vdom]
let element = (               =>        let vdom = {
    ['div#title', {}, [       =>            tagName: 'div',
        'Hello World!',       =>            attributes: {
    ]]                        =>                id: 'title',
);                            =>            },
                              =>            children: [
                              =>                {
                              =>                    text: 'Hello World!'
                              =>                }
                              =>            ],
                              =>        };

prebuild

let prebuild = prebuild
  // prebuild: function to manipulate elements before they are passed to build [(element) => ... element]

Here is an example which wraps the app in a div.

let prebuild = (original) => (
    ['div | width: 100vw;', {},
        original,
    ]
);

app.use({prebuild});

postbuild

let postbuild = postbuild
  // postbuild: function to manipulate the vdom created by the build function [(vdom) => ... vdom]

Readme

Keywords

none

Package Sidebar

Install

npm i goo-state

Weekly Downloads

2

Version

2.3.5

License

MIT

Last publish

Collaborators

  • g-harel