import $ from 'jquery' import reduce from 'lodash.reduce' import isObject from 'lodash.isobject' import forIn from 'lodash.forin' import { createStore as reduxCreateStore } from 'redux' /** * Create a redux store given the reducer. It also enables the Redux dev tools. */ export function createStore (reducer) { return reduxCreateStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()) } /** * Connect elements with the redux store. It must receive an object with the following attributes: * * elements: It is an object with elements that are going to react to the redux state or add something * to the initial state. * * ```javascript * const elements = { * // The JQuery selector for finding elements in the page. * '[data-counter]': { * // Useful to put things from the page to the redux state. * load ($element) {...}, * // Check for state changes and manipulates the DOM accordingly. * render ($el, state, oldState) {...} * } * } * ``` * * The load and render functions are optional, you can have both or just one of them. It depends * on if you want to load something to the state in the first render and/or that the element should * react to the redux state. Notice that you can include more elements if you want to since elements * also is an object. * * store: It is the redux store that the elements should be connected with. * ```javascript * const store = createStore(reducer) * ``` * * action: The first action that the store is going to dispatch. Optional, by default 'ELEMENTS_LOAD' * is going to be dispatched. * * ### Examples * * Given the markup: * ```HTML *
* 1 *
* ``` * * The reducer: * ```javascript * function reducer (state = { number: null }, action) { * switch (action.type) { * case 'ELEMENTS_LOAD': { * return Object.assign({}, state, { number: action.number }) * } * case 'INCREMENT': { * return Object.assign({}, state, { number: state.number + 1 }) * } * default: * return state * } * } * ``` * * The elements: * ```javascript * const elements = { * // '[data-counter]' is the element that will be connected to the redux store. * '[data-counter]': { * // Find the number within data-counter and add to the state. * load ($el) { * return { number: $el.find('.number').val() } * }, * // React to redux state. Case the number in the state changes, it is going to render the * // new number. * render ($el, state, oldState) { * if (state.number === oldState.number) return * * $el.html(state.number) * } * } * } * * All we need to do is connecting the store and the elements using this function. * ```javascript * connectElements({store, elements}) * ``` * * Now, if we dispatch the `INCREMENT` action, the state is going to change and the [data-counter] * element is going to re-render since they are connected. * ```javascript * store.dispatch({type: 'INCREMENT'}) * ``` */ export function connectElements ({ elements, store, action = 'ELEMENTS_LOAD' }) { function loadElements () { return reduce(elements, (pageLoadParams, { load }, selector) => { if (!load) return pageLoadParams const $el = $(selector) if (!$el.length) return pageLoadParams const morePageLoadParams = load($el, store) return isObject(morePageLoadParams) ? Object.assign(pageLoadParams, morePageLoadParams) : pageLoadParams }, {}) } function renderElements (state, oldState) { forIn(elements, ({ render }, selector) => { if (!render) return const $el = $(selector) if (!$el.length) return render($el, state, oldState) }) } let oldState = store.getState() store.subscribe(() => { const state = store.getState() renderElements(state, oldState) oldState = state }) store.dispatch(Object.assign(loadElements(), { type: action })) }