Source: index.js

/** @module index */



/* Type Definititions */

/**
 * A Redux store.
 * @typedef {Object} Store
 * @property {Function} dispatch Dispatch a provided action with type to invoke a matching reducer.
 * @property {Function} getState Get the current global state.
 * @property {Function} subscribe Provide callback to be invoked every time an action is fired.
 * @property {Function} replaceReducer Replace an existing reducer with a new one.
 */

import h from 'virtual-dom/h';
import diff from 'virtual-dom/diff';
import patch from 'virtual-dom/patch';
import createElement from 'virtual-dom/create-element';
import delegator from 'dom-delegator';
import thunkMiddleware from 'redux-thunk';
import createLogger from 'redux-logger';
import {createStore, compose, applyMiddleware} from 'redux';
import utils from './utils';
import {renderUI} from './ui';
import {reducer} from './reducer';
import {fromJS, Map, Iterable} from 'immutable';
import Rlite from 'rlite-router';
import reduceReducers from 'reduce-reducers';

var nux = window.nux = module.exports = {
  init: init,
  options: {
    localStorage: false,
    logActions: false,
    actionCreators: {}
  },
  utils: utils
};


/**
 * Used to initialize a new Nux application. Any reducer that is provided will be combined with Nux's built
 * in reducers. The initial UI is the base template for the application and is what will be rendered immediately
 * upon initialization. A number of options can be provided as documented, and any element can be specified into
 * which Nux will render the application, allowing for the running of multiple, disparate instances of Nux on
 * a given page. A Redux store is returned, although direct interaction with the store should be unnecessary.
 * Once initialized, any changes to the global state for a given Nux instance will be immediately reflected in
 * via an efficient patching mechanism via virtual-dom.
 *
 * @author Mark Nutter <marknutter@gmail.com>
 * @summary Initialize a Nux application.
 *
 * @param  {Function} appReducer The provided reducer function.
 * @param  {Object}   [options]                            Options to configure the nux application.
 * @param  {Object}   [options.initialUI={ui: {}}]         The initial UI vDOM object which will become the first state to be rendered.
 * @param  {Boolean}  [options.localStorage=false]         Enable caching of global state to localStorage.
 * @param  {Boolean}  [options.logActions=false]           Enable advanced logging of all actions fired.
 * @param  {Element}  [options.targetElem=HTMLBodyElement] The element into which the nux application will be rendered.
 * @param  {Object}   [options.actionCreators={}]          Custom action creators with thunks enabled
 * @return {Store}    Redux store where app state is maintained.
 */
function init(appReducer, options = nux.options) {

  return (initialUI = {ui: {}}) => {

    let initialState = initialUI;
    if (options.localStorage && localStorage.getItem('nux')) {
      initialState = JSON.parse(localStorage.getItem('nux'));
    };

    options.targetElem = options.targetElem || document.body;

    delegator();

    let router = Rlite();
    let middleWare = [thunkMiddleware];
    if (options.logActions) {
      middleWare.push(createLogger({
        stateTransformer: (state) => {
          return state.toJS();
        }
      }));
    }

    let finalAppReducer = typeof appReducer === 'function' ? appReducer : combineReducers(appReducer);

    let finalReducer = reduceReducers(reducer(initialState, options), finalAppReducer);

    const createStoreWithMiddleware = applyMiddleware.apply(this, middleWare)(createStore);
    let store = createStoreWithMiddleware(finalReducer);
    let currentUI = store.getState().get('ui').toVNode(store);
    let rootNode = createElement(currentUI);
    options.targetElem.appendChild(rootNode);

    store.getActionCreator = function(actionName) {
      if (options.actionCreators[actionName] === undefined) {
        throw new Error(`AtionCreatorNotFoundError: no action creator has been specified for the key ${actionName}`);
      }
      return options.actionCreators[actionName];
    }

    if (store.getState().get('routes')) {
      store.getState().get('routes').forEach((action, route) => {
        router.add(route, (r) => {
          store.dispatch(action.merge(Map(r)).toJS());
        });
      });

      function processHash() {
        var hash = location.hash || '#';
        router.run(hash.slice(1));
      }

      window.addEventListener('hashchange', processHash);
      processHash();
    }

    store.subscribe(() => {
      const ui = store.getState().get('ui');
      if (options.localStorage) {
        localStorage.setItem('nux', JSON.stringify(ui ? ui.toJS() : {}));
      }
      var newUI = store.getState().get('ui').toVNode(store);
      var patches = diff(currentUI, newUI);
      rootNode = patch(rootNode, patches);
      currentUI = newUI;
    });

    return store;

  }
}