Source: index.js

  1. /** @module index */
  2. /* Type Definititions */
  3. /**
  4. * A Redux store.
  5. * @typedef {Object} Store
  6. * @property {Function} dispatch Dispatch a provided action with type to invoke a matching reducer.
  7. * @property {Function} getState Get the current global state.
  8. * @property {Function} subscribe Provide callback to be invoked every time an action is fired.
  9. * @property {Function} replaceReducer Replace an existing reducer with a new one.
  10. */
  11. import h from 'virtual-dom/h';
  12. import diff from 'virtual-dom/diff';
  13. import patch from 'virtual-dom/patch';
  14. import createElement from 'virtual-dom/create-element';
  15. import delegator from 'dom-delegator';
  16. import thunkMiddleware from 'redux-thunk';
  17. import createLogger from 'redux-logger';
  18. import {createStore, compose, applyMiddleware} from 'redux';
  19. import utils from './utils';
  20. import {renderUI} from './ui';
  21. import {reducer} from './reducer';
  22. import {fromJS, Map, Iterable} from 'immutable';
  23. import Rlite from 'rlite-router';
  24. import reduceReducers from 'reduce-reducers';
  25. var nux = window.nux = module.exports = {
  26. init: init,
  27. options: {
  28. localStorage: false,
  29. logActions: false,
  30. actionCreators: {}
  31. },
  32. utils: utils
  33. };
  34. /**
  35. * Used to initialize a new Nux application. Any reducer that is provided will be combined with Nux's built
  36. * in reducers. The initial UI is the base template for the application and is what will be rendered immediately
  37. * upon initialization. A number of options can be provided as documented, and any element can be specified into
  38. * which Nux will render the application, allowing for the running of multiple, disparate instances of Nux on
  39. * a given page. A Redux store is returned, although direct interaction with the store should be unnecessary.
  40. * Once initialized, any changes to the global state for a given Nux instance will be immediately reflected in
  41. * via an efficient patching mechanism via virtual-dom.
  42. *
  43. * @author Mark Nutter <marknutter@gmail.com>
  44. * @summary Initialize a Nux application.
  45. *
  46. * @param {Function} appReducer The provided reducer function.
  47. * @param {Object} [options] Options to configure the nux application.
  48. * @param {Object} [options.initialUI={ui: {}}] The initial UI vDOM object which will become the first state to be rendered.
  49. * @param {Boolean} [options.localStorage=false] Enable caching of global state to localStorage.
  50. * @param {Boolean} [options.logActions=false] Enable advanced logging of all actions fired.
  51. * @param {Element} [options.targetElem=HTMLBodyElement] The element into which the nux application will be rendered.
  52. * @param {Object} [options.actionCreators={}] Custom action creators with thunks enabled
  53. * @return {Store} Redux store where app state is maintained.
  54. */
  55. function init(appReducer, options = nux.options) {
  56. return (initialUI = {ui: {}}) => {
  57. let initialState = initialUI;
  58. if (options.localStorage && localStorage.getItem('nux')) {
  59. initialState = JSON.parse(localStorage.getItem('nux'));
  60. };
  61. options.targetElem = options.targetElem || document.body;
  62. delegator();
  63. let router = Rlite();
  64. let middleWare = [thunkMiddleware];
  65. if (options.logActions) {
  66. middleWare.push(createLogger({
  67. stateTransformer: (state) => {
  68. return state.toJS();
  69. }
  70. }));
  71. }
  72. let finalAppReducer = typeof appReducer === 'function' ? appReducer : combineReducers(appReducer);
  73. let finalReducer = reduceReducers(reducer(initialState, options), finalAppReducer);
  74. const createStoreWithMiddleware = applyMiddleware.apply(this, middleWare)(createStore);
  75. let store = createStoreWithMiddleware(finalReducer);
  76. let currentUI = store.getState().get('ui').toVNode(store);
  77. let rootNode = createElement(currentUI);
  78. options.targetElem.appendChild(rootNode);
  79. store.getActionCreator = function(actionName) {
  80. if (options.actionCreators[actionName] === undefined) {
  81. throw new Error(`AtionCreatorNotFoundError: no action creator has been specified for the key ${actionName}`);
  82. }
  83. return options.actionCreators[actionName];
  84. }
  85. if (store.getState().get('routes')) {
  86. store.getState().get('routes').forEach((action, route) => {
  87. router.add(route, (r) => {
  88. store.dispatch(action.merge(Map(r)).toJS());
  89. });
  90. });
  91. function processHash() {
  92. var hash = location.hash || '#';
  93. router.run(hash.slice(1));
  94. }
  95. window.addEventListener('hashchange', processHash);
  96. processHash();
  97. }
  98. store.subscribe(() => {
  99. const ui = store.getState().get('ui');
  100. if (options.localStorage) {
  101. localStorage.setItem('nux', JSON.stringify(ui ? ui.toJS() : {}));
  102. }
  103. var newUI = store.getState().get('ui').toVNode(store);
  104. var patches = diff(currentUI, newUI);
  105. rootNode = patch(rootNode, patches);
  106. currentUI = newUI;
  107. });
  108. return store;
  109. }
  110. }