<!doctype html>
<html>
  <head>
    <title>Polymer Redux Demo</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes">
    <script src="//cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/0.7.22/webcomponents-lite.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/redux/3.5.2/redux.min.js"></script>

    <link rel="import" href="//cdn.rawgit.com/Download/polymer-cdn/master/lib/polymer/polymer.html">
    <link rel="import" href="//cdn.rawgit.com/download/polymer-cdn/master/lib/iron-demo-helpers/demo-pages-shared-styles.html">
    <link rel="import" href="//cdn.rawgit.com/download/polymer-cdn/master/lib/iron-demo-helpers/demo-snippet.html">

    <link rel="import" href="polymer-redux.html">

    <style is="custom-style" include="demo-pages-shared-styles">
    </style>
  </head>
  <body>

    <div class="vertical-section-container">
      <h3>Polymer Redux Demo</h3>
      <demo-snippet>
        <template>
          <!-- redux setup -->
          <script>
            const reducer = (state, action) => {
              if (!state) return { friends: [] };

              // copy friends list
              friends = state.friends.slice(0);

              switch (action.type) {
                case 'ADD_FRIEND':
                  friends.push(action.friend);
                  break;
                case 'REMOVE_FRIEND':
                  const idx = friends.indexOf(action.friend)
                  if (idx !== -1) {
                      friends.splice(idx, 1)
                  }
                  break;
                case 'SORT_FRIENDS':
                  friends.sort()
                  break;
              }

              state.friends = friends;

              return state;
            }
            const store = Redux.createStore(reducer);
            const ReduxBehavior = PolymerRedux(store);
          </script>

          <!-- friends list module -->
          <dom-module id="friends-list">
            <template>
              <p>
                <span>You have [[friends.length]] friend(s).</span>
                <template is="dom-if" if="[[canSortFriends(friends.length)]]">
                  <button on-click="sortFriends">Sort Friends</button>
                </template>
              </p>
              <ul>
                <template is="dom-repeat" items="[[friends]]">
                  <li>
                    <span>[[item]]</span>
                    <button on-click="removeFriend">Remove</button>
                  </li>
                </template>
              </ul>
              <input id="friend-name" placeholder="Name" on-keypress="handleKeypress">
              <button on-click="addFriend">Add Friend</button>
            </template>
          </dom-module>
          <script>
            Polymer({
              is: 'friends-list',
              behaviors: [ ReduxBehavior ],
              properties: {
                friends: {
                  type: Array,
                  statePath: 'friends'
                }
              },
              actions: {
                add: function(name) {
                  return {
                    type: 'ADD_FRIEND',
                    friend: name
                  };
                },
                remove: function(name) {
                  return {
                    type: 'REMOVE_FRIEND',
                    friend: name
                  };
                },
                sort: function() {
                  return {
                    type: 'SORT_FRIENDS'
                  };
                },
              },
              addFriend: function() {
                const input = this.$['friend-name'];
                if (input.value) {
                  this.dispatch('add', input.value);
                  input.value = '';
                  input.focus();
                }
              },
              removeFriend: function(event) {
                this.dispatch('remove', event.model.item);
              },
              sortFriends: function() {
                this.dispatch('sort');
              },
              canSortFriends: function(length) {
                return length > 1;
              },
              handleKeypress: function(event) {
                if (event.charCode === 13) {
                  this.addFriend();
                }
              }
            });
          </script>

          <!-- demo -->
          <friends-list></friends-list>
        </template>
      </demo-snippet>
    </div>
  </body>
</html>
<link rel="import" href="//cdn.rawgit.com/Download/polymer-cdn/master/lib/polymer/polymer.html">
<script src="//cdnjs.cloudflare.com/ajax/libs/redux/3.5.2/redux.min.js"></script>
<script>
(function(root, factory) {
    /* istanbul ignore next */
    if (typeof exports === 'object' && typeof module === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define(factory);
    } else {
        root['PolymerRedux'] = factory();
    }
})(this, function() {
    var warning = 'Polymer Redux: <%s>.%s has "notify" enabled, two-way bindings goes against Redux\'s paradigm';

    /**
     * Factory function for creating a listener for a give Polymer element. The
     * returning listener should be passed to `store.subscribe`.
     *
     * @param {HTMLElement} element Polymer element.
     * @return {Function} Redux subcribe listener.
     */
    function createListener(element, store) {
        var props = [];

        // property bindings
        if (element.properties != null) {
            Object.keys(element.properties).forEach(function(name) {
                prop = element.properties[name];
                if (prop.hasOwnProperty('statePath')) {
                    // notify flag, warn against two-way bindings
                    if (prop.notify && !prop.readOnly) {
                        console.warn(warning, element.is, name);
                    }
                    props.push({
                        name: name,
                        // Empty statePath return state
                        path: prop.statePath || store.getState,
                        readOnly: prop.readOnly,
                        type: prop.type
                    });
                }
            });
        }

        // redux listener
        return function() {
            var state = store.getState();
            props.forEach(function(property) {
                var propName = property.name;
                var splices = [];
                var value, previous;

                // statePath, a path or function.
                var path = property.path;
                if (typeof path == 'function') {
                    value = path.call(element, state);
                } else {
                    value = Polymer.Base.get(path, state);
                }

                // prevent unnecesary polymer notifications
                previous = element.get(property.name);
                if (value === previous) {
                    return;
                }

                // type of array, work out splices before setting the value
                if (property.type === Array) {
                    value = value || /* istanbul ignore next */ [];
                    previous = previous || /* istanbul ignore next */ [];

                    // check the value type
                    if (!Array.isArray(value)) {
                        throw new TypeError(
                            '<'+ element.is +'>.'+ propName +' type is Array but given: ' + (typeof value)
                        );
                    }

                    splices = Polymer.ArraySplice.calculateSplices(value, previous);
                }

                // set
                if (property.readOnly) {
                    element.notifyPath(propName, value);
                } else {
                    element.set(propName, value);
                }

                // notify element of splices
                if (splices.length) {
                    element.notifySplices(propName, splices);
                }
            });
            element.fire('state-changed', state);
        }
    }

    /**
     * Binds an given Polymer element to a Redux store.
     *
     * @param {HTMLElement} element Polymer element.
     * @param {Object} store Redux store.
     */
    function bindReduxListener(element, store) {
        var listener;

        if (element._reduxUnsubscribe) return;

        listener = createListener(element, store);
        listener(); // start bindings

        element._reduxUnsubscribe = store.subscribe(listener);
    }

    /**
     * Unbinds a Polymer element from a Redux store.
     *
     * @param {HTMLElement} element
     */
    function unbindReduxListener(element) {
        if (typeof element._reduxUnsubscribe === 'function') {
            element._reduxUnsubscribe();
            delete element._reduxUnsubscribe;
        }
    }

    /**
     * Dispatches a Redux action via a Polymer element. This gives the element
     * a polymorphic dispatch function. See the readme for the various ways to
     * dispatch.
     *
     * @param {HTMLElement} element Polymer element.
     * @param {Object} store Redux store.
     * @param {Array} args The arguments passed to `element.dispatch`.
     * @return {Object} The computed Redux action.
     */
    function dispatchReduxAction(element, store, args) {
        var action = args[0];
        var actions = element.actions;

        // action name
        if (actions && typeof action === 'string') {
            if (typeof actions[action] !== 'function') {
                throw new TypeError('Polymer Redux: <' + element.is + '> has no action "' + action + '"');
            }
            return store.dispatch(actions[action].apply(element, args.slice(1)));
        }

        // action creator
        if (typeof action === 'function' && action.length === 0) {
            return store.dispatch(action());
        }

        // action
        return store.dispatch(action);
    }

    /**
     * Creates PolymerRedux behaviors from a given Redux store.
     *
     * @param {Object} store Redux store.
     * @return {PolymerRedux}
     */
    return function(store) {
        var PolymerRedux;

        // check for store
        if (!store) {
            throw new TypeError('missing redux store');
        }

        /**
         * `PolymerRedux` binds a given Redux store's state to implementing Elements.
         *
         * Full documentation available, https://github.com/tur-nr/polymer-redux.
         *
         * @polymerBehavior PolymerRedux
         * @demo demo/index.html
         */
        return PolymerRedux = {
            /**
             * Fired when the Redux store state changes.
             * @event state-changed
             * @param {*} state
             */

            ready: function() {
                bindReduxListener(this, store);
            },

            attached: function() {
                bindReduxListener(this, store);
            },

            detached: function() {
                unbindReduxListener(this);
            },

            /**
             * Dispatches an action to the Redux store.
             *
             * @param {String|Object|Function} action
             * @return {Object} The action that was dispatched.
             */
            dispatch: function(action /*, [...args] */) {
                var args = Array.prototype.slice.call(arguments);
                return dispatchReduxAction(this, store, args);
            },

            /**
             * Gets the current state in the Redux store.
             * @return {*}
             */
            getState: function() {
                return store.getState();
            },
        };
    };
});
</script>