UNPKG

ganglion-impulse

Version:

A JavaScript library that transmits impulses from a central ganglion, triggering chain of actions.

300 lines (212 loc) 8.48 kB
Ganglion Impulse ================ > In anatomy, a [ganglion](https://en.wikipedia.org/wiki/Ganglion) (/ˈɡæŋɡliən/ > gang-glee-ən; plural ganglia) is a nerve cell cluster. A JavaScript library for registering fibers that transmit impulses from a central ganglion, triggering a chain of actions. Ganglion is heavily inspired by [Cerebral](https://github.com/christianalfoni/cerebral) but does not include a data store or any [React](http://facebook.github.io/react/) component integration as Cerebral does. Ganglion is small (~1k minimised and gzipped) and it does not have any dependencies. Why Use Ganglion ---------------- Ganglion can be used as the central nervous system of an application. It facilitates a clean and understandable definition of one way data flows through an application. It can utilise reusable action functions and manage asynchronous actions in series or parallel. If your async server operation is taking longer than expected it can let your app know it needs to give feedback to the user via a spinner or asking the user if they wish to cancel. Ganglion is best when paired with an [immutable data store](https://github.com/rtfeldman/seamless-immutable), which it can pass to all actions via context provided to its constructor. For user interface rendering [React](http://facebook.github.io/react/) works well, but other libraries could also be used. Hooks on the ganglion allow operations to be executed before or after every impulse. These can be used to setup automatic re-rendering after mutative impulses have completed. Installation ------------ ``` npm install ganglion-impulse ``` Include ganglion in your app with: ```JavaScript var Ganglion = require('ganglion-impulse'); ``` For a client-side project you can use [Browserify](http://browserify.org/) or [webpack](http://webpack.github.io/). Usage ----- ### Example ```JavaScript // es6 example, but will also work with es5. import Ganglion from 'ganglion-impulse'; import { sendCredentialsToServer, setUserInfo } from './custom-application-actions'; // create a new ganglion let ganglion = new Ganglion(); // define some fibers ganglion.fiber('loginClicked', sendCredentialsToServer, setUserInfo); // send impulses ganglion.impulse.loginClicked({ userName, password }).then(function () { console.log('impulse completed'); }); ``` If actions return a Promise then ganglion will wait for them to resolve before calling the next action in the chain. ### Parallel actions Actions can run in parallel if passed as an array: ```JavaScript ganglion.fiber('buttonClicked', prepareAction, [parallelAction1, parallelAction2], finalAction); ``` ### Sending Data The first action receives the parameter passed when the impulse is initiated, subsequent actions receive the response from the previous action. The response from the final action is returned via a promise to the caller. When parallel actions are used, the response of both will be passed as an array to the next in the same order as the actions are defined on the fiber. ### Context Data Context information can be passed to the ganglion constructor which will then be made available to all actions called when an impulse is initiated: ```JavaScript // create a new ganglion with some context data let ganglion = new Ganglion({ context: { dataStore: {} } }); ganglion.fiber('userChangedName', function updateUserName(userName) { this.dataStore.userName = userName; }); ``` The name of the current fiber is also available via the impulse's context: ```JavaScript ganglion.fiber('eventReceived', function logTheFiberName() { console.log(this.fiberName); }); ``` Context is initialised per impulse. Any mutations will be available to subsequent actions but will be discarded after the final action. The properties defined with original context passed to the ganglion constructor are retained across all impulses. To make changes to the context after the ganglion is initialised you can use the `addToContext` method: ```JavaScript // create a new ganglion with some context data let ganglion = new Ganglion({ context: { dataStore: {} } }); // add log method to the ganglion ganglion.addToContext({ log: logMethod }); ganglion.fiber('userChangedName', function updateUserName(userName) { this.log(`The user changed their name to ${userName}`); }); ``` Any existing values with the same key will be overwritten. ### Events #### beforeImpulse and afterImpulse `beforeImpulse` and `afterImpulse` events are fired by the ganglion before and after every impulse emitted. Handlers have the same signature as actions, but the return value is discarded. ```JavaScript let ganglion = new Ganglion(); // add beforeImpulse and afterImpulse event handlers ganglion.on('beforeImpulse', function (data) { // data will be also be passed to the first action console.log(`${this.fiberName} impulse started`); }); ganglion.on('afterImpulse', function (data) { // data is the value that was returned by the last action console.log(`${this.fiberName} impulse ended`); }); ``` #### slowAsyncActionStart and slowAsyncActionEnd `slowAsyncActionStart` and `slowAsyncActionEnd` events are fired by the ganglion when async actions take a while to complete. ```JavaScript let slowActionHandler = function (isStart) { let status = isStart ? 'is running slow' : 'completed'; console.log(`${this.fiberName} async action ${status}`); }; let ganglion = new Ganglion({ callSlowAsyncActionAfter: 500, // ms after which the // onSlowAsyncActionStart will be called }); // add slowAsyncActionStart and slowAsyncActionEnd event handlers ganglion.on('slowAsyncActionStart', slowActionHandler); ganglion.on('slowAsyncActionEnd', slowActionHandler); ``` To ensure that the slow async action events are not triggered unnecessarily, `callSlowAsyncActionAfter` can be used to define after how many milliseconds should be hooks be called. By default this is set to 500ms. #### error `error` events are fired when async actions reject their promise. ```JavaScript let ganglion = new Ganglion(); // add error event handlers ganglion.on('error', function (error) { console.error(`${this.fiberName} returned an error`, error); }); ``` ### Canceling an Impulse Any action or event handler can cancel an impulse by setting `cancelImpulse = true` on the context: ```JavaScript ganglion.fiber('buttonClicked', firstAction, function secondAction(data) { this.cancelImpulse = true; }, finalActionWontBeCalled); ``` Contributing ------------ Checkout the git repository and install the build and test dependencies ``` npm install ``` ### Testing The automated tests can be run via ``` npm test ``` ### Building Ganglion uses es6 and should be transpiled prior to publishing ``` npm run build ``` Change Log ---------- 0.4.2 * Added addToContext method 0.4.1 * Emit error events * Removed debug code which can now be acheived with events * Added more tests 0.4.0 * Allow alternative spelling of fiber/fibre * [Breaking Change] Changed hooks to events 0.3.1 * Improved the documentation * Added the ability for any action or hook to cancel an impulse 0.3.0 * Added onSlowAsyncActionStart and onSlowAsyncActionEnd hooks 0.2.0 * Added onBeforeImpulse and onAfterImpulse hooks 0.1.1 * Fixed dist build 0.1.0 * Initial release License ------- The MIT License (MIT) Copyright (c) 2015 Garth Williams Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.