Skip to content

Commit

Permalink
Merge pull request #37 from tur-nr/async-dispatcher
Browse files Browse the repository at this point in the history
Async dispatcher w/ inherited behaviors
  • Loading branch information
tur-nr committed Oct 17, 2016
2 parents 95f0735 + f20fa12 commit adde6da
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 7 deletions.
123 changes: 123 additions & 0 deletions demo/async.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<!doctype html>
<html>
<head>
<title>Polymer Redux, Async 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="../../webcomponentsjs/webcomponents-lite.js"></script>
<script src="../../../node_modules/redux/dist/redux.js"></script>
<script src="../../../node_modules/redux-thunk/dist/redux-thunk.js"></script>

<link rel="import" href="../../iron-demo-helpers/demo-pages-shared-styles.html">
<link rel="import" href="../../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, Async Demo</h3>
<demo-snippet>
<template>
<!-- redux setup -->
<script>
var reducer = function(state, action) {
state = state || { counter: 0 };
var counter = state.counter;
if (action.type === 'INCREMENT') {
++counter;
} else if (action.type === 'DOUBLE') {
counter += counter;
}
return {
counter: counter,
};
};
var store = Redux.createStore(
reducer,
Redux.applyMiddleware(ReduxThunk.default)
);
var ReduxBehavior = PolymerRedux(store);
var AsyncBehavior = {
actions: {
double: function() {
return function(dispatch) {
setTimeout(function() {
dispatch({ type: 'DOUBLE' });
}, 1000);
};
}
},
};
</script>

<!-- friends list module -->
<dom-module id="x-counter">
<template>
<p>
Counter value:
<span>[[counter]]</span>
</p>
<button on-click="incrementClick">Increment</button>
<button on-click="increment200Click">Increment (0.2s)</button>
<button on-click="increment500Click">Increment (0.5s)</button>
<button on-click="increment1000Click">Increment (1.0s)</button>
<button on-click="double1000Click">Double (1.0s)</button>
</template>
<script>
Polymer({
is: 'x-counter',
behaviors: [ ReduxBehavior, AsyncBehavior ],
actions: {
increment: function(ms) {
if (ms) {
return function(dispatch) {
setTimeout(function() {
return dispatch('increment');
}, ms);
};
}
return {
type: 'INCREMENT',
};
}
},
properties: {
counter: {
type: Number,
statePath: 'counter',
}
},
incrementClick: function() {
return this.dispatch('increment');
},
increment200Click: function() {
return this.dispatch('increment', 200);
},
increment500Click: function() {
return this.dispatch('increment', 500);
},
increment1000Click: function() {
return this.dispatch(function(dispatch) {
setTimeout(function() {
dispatch('increment');
}, 1000);
});
},
double1000Click: function() {
this.dispatch('double');
}
});
</script>
</dom-module>

<!-- demo -->
<x-counter></x-counter>
</template>
</demo-snippet>
</div>
</body>
</html>
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"polylint": "^2.10.1",
"polymer-cli": "^0.11.1",
"redux": "^3.0.5",
"redux-thunk": "^2.1.0",
"reselect": "^2.5.1",
"web-component-tester": "~4.2.2",
"web-component-tester-istanbul": "https://github.com/t2ym/web-component-tester-istanbul/tarball/0.10.1"
Expand Down
70 changes: 65 additions & 5 deletions polymer-redux.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
/**
* Returns property bindings found on a given Element/Behavior.
*
* @private
* @param {HTMLElement|Object} obj Element or Behavior.
* @param {HTMLElement} element Polymer element.
* @param {Object} store Redux store.
Expand Down Expand Up @@ -47,6 +48,7 @@
* Factory function for creating a listener for a give Polymer element. The
* returning listener should be passed to `store.subscribe`.
*
* @private
* @param {HTMLElement} element Polymer element.
* @return {Function} Redux subcribe listener.
*/
Expand Down Expand Up @@ -127,6 +129,7 @@
/**
* Binds an given Polymer element to a Redux store.
*
* @private
* @param {HTMLElement} element Polymer element.
* @param {Object} store Redux store.
*/
Expand All @@ -144,6 +147,7 @@
/**
* Unbinds a Polymer element from a Redux store.
*
* @private
* @param {HTMLElement} element
*/
function unbindReduxListener(element) {
Expand All @@ -153,37 +157,92 @@
}
}

/**
* Builds list of action creators from a given element and it's inherited
* behaviors setting the list onto the element.
*
* @private
* @param {HTMLElement} element Polymer element instance.
*/
function compileActionCreators(element) {
var actions = {};
var behaviors = element.behaviors;

if (element._reduxActions) return;

// add behavior actions first, in reverse order so we keep priority
if (Array.isArray(behaviors)) {
for (var i = behaviors.length - 1; i >= 0; i--) {
Object.assign(actions, behaviors[i].actions);
}
}

// element actions have priority
element._reduxActions = Object.assign(actions, element.actions);
}

/**
* 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.
*
* @private
* @param {HTMLElement} element Polymer element.
* @param {Object} store Redux store.
* @param {Array} args The arguments passed to `element.dispatch`.
* @param {Arguments} 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;
var actions = element._reduxActions;

args = castArgumentsToArray(args);

// 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 = actions[action].apply(element, args.slice(1));
}

// !!! DEPRECIATED !!!
// This will be removed as of 1.0.

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

// ---

// middleware, make sure we pass the polymer-redux dispatch
// so we have access to the elements action creators
if (typeof action === 'function') {
return store.dispatch(function() {
var argv = castArgumentsToArray(arguments);
// replace redux dispatch
argv.splice(0, 1, function() {
return dispatchReduxAction(element, store, arguments);
});
return action.apply(element, argv);
});
}

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

/**
* Turns arguments into an Array.
*
* @param {Arguments} args
* @return {Array}
*/
function castArgumentsToArray(args) {
return Array.prototype.slice.call(args, 0);
}

/**
* Creates PolymerRedux behaviors from a given Redux store.
*
Expand Down Expand Up @@ -215,10 +274,12 @@

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

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

detached: function() {
Expand All @@ -232,8 +293,7 @@
* @return {Object} The action that was dispatched.
*/
dispatch: function(action /*, [...args] */) {
var args = Array.prototype.slice.call(arguments);
return dispatchReduxAction(this, store, args);
return dispatchReduxAction(this, store, arguments);
},

/**
Expand Down
36 changes: 35 additions & 1 deletion test/polymer-redux.unit-spec.html
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
});
this.actionElement = {
is: 'fake-element',
actions: {
_reduxActions: {
test: this.testAction
}
};
Expand Down Expand Up @@ -237,6 +237,40 @@

sinon.assert.notCalled(middleware);
});

it('should call middleware with polymer-redux dispatch', function() {
var middleware = sinon.spy(function(dispatch) {});
var dispatcher = sinon.stub();
var storeDispatch = this.store.dispatch;

this.store.resetStub();
this.store.dispatch = sinon.stub().yields(dispatcher);
this.behavior.dispatch(middleware);

sinon.assert.calledWithMatch(middleware,
sinon.match.func.and(sinon.match(function(fn) {
return fn !== dispatcher;
}))
);

this.store.dispatch = storeDispatch;
});

it.only('should dispatch actions from middleware', function() {
var action = {};
var middleware = sinon.spy(function(dispatch) {
dispatch(action);
});

this.store.resetStub();
this.behavior.dispatch(middleware);

// call middleware
var fn = this.store.dispatch.getCall(0).args[0];
fn();

sinon.assert.calledWith(this.store.dispatch, action);
});
});
});

Expand Down
2 changes: 1 addition & 1 deletion wct.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
"thresholds": {}
}
}
}
}

0 comments on commit adde6da

Please sign in to comment.