Skip to content

Commit 2ec18e8

Browse files
author
mmasterson
committed
#8 More robust tests for multiple stores. Updated Readme and deployment options. Checking in mdapi format metadata.
1 parent 3cc85d3 commit 2ec18e8

File tree

14 files changed

+802
-1
lines changed

14 files changed

+802
-1
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
[![Deploy](https://deploy-to-sfdx.com/dist/assets/images/DeployToSFDX.svg)](https://deploy-to-sfdx.com)
2+
<a href="https://githubsfdeploy.herokuapp.com">
3+
<img alt="Deploy to Salesforce"
4+
src="https://raw.githubusercontent.com/afawcett/githubsfdeploy/master/deploy.png">
5+
</a>
6+
17
# Lightning-Redux [![Build Status](https://travis-ci.org/madmax983/lightning-redux.svg?branch=master)](https://travis-ci.org/madmax983/lightning-redux)
28

39
[Redux](http://redux.js.org/) bindings for the [Lightning Component Framework](https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/intro_framework.htm)
410

511
## Usage
612
The new version of Lightning-Redux simplifies the previous iteration down to a single Redux component. It serves as a wrapper around Redux itself, along with a few helper methods specific to Lightning.
13+
It has one attribute to specify, which is the name of the store you would like to create. This name defaults to "redux" if no name is specified.
714

815
### Component Methods
916
####
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<aura:component description="TestReduxContainer">
2+
<aura:attribute name="name" type="string" default="redux"/>
23
<aura:attribute name="message" type="string" />
3-
<c:Redux aura:id="store"/>
4+
<c:Redux aura:id="store" name="{!v.name}"/>
45
</aura:component>

force-app/test/default/staticresources/reduxTests.resource

Lines changed: 362 additions & 0 deletions
Large diffs are not rendered by default.

src/aura/Redux/Redux.auradoc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<aura:documentation>
2+
<aura:description>
3+
<p>Redux bindings for the Lightning Component Framework</p>
4+
<p>Component Methods</p>
5+
<code>createStore(name, reducer, initialState, middleware)</code>
6+
<br/>
7+
Creates the Redux store.
8+
<br/>
9+
name: The name of the slice of state for the reducer creating the store.
10+
<br/>
11+
reducer: Initial root reducer
12+
<br/>
13+
initialState: You can specify an initial shape to your store's shape.
14+
<br/>
15+
middleware: You can include redux middleware like redux-thunk here. Lightning-Redux automatically wraps this with Redux compose.
16+
<br/>
17+
<code>dispatch(action)</code>
18+
<br/>
19+
Dispatches the action to the Redux store. Fun fact: connect also sets a dispatch expando on the connected component for convenience.
20+
<br/>
21+
<code>subscribe(listener)</code>
22+
<br/>
23+
This shouldn't be needed to be implemented, but is provided in case you want to write a custom connect method. The listener callback will be called on store updates.
24+
<br/>
25+
<code>replaceReducer(nextReducer)</code>
26+
<br/>
27+
Wraps Redux's replaceReducer function. Replaces the reducer function in the redux store.
28+
<br/>
29+
<code>registerReducer(name, reducer)</code>
30+
<br/>
31+
Adds the specified reducer function to the redux store with the specificed name as it's slice of state.
32+
<br/>
33+
<code>connect(mapStateToAttributes, target)</code>
34+
<br/>
35+
mapStateToAttributes either a key pair object where the key is the attribute on the component you want set, and the value is either the value in the redux state graph, or a callback function. mapStateToAttributes can also be a function that returns this key pair value. This function receives the state and component to compute the value. You can pass the component you want connected, but if it isn't populated, it will get populated by event.getSource(). It also sets a dispatch expando for convenience in the component's controller methods.
36+
<br/>
37+
<code>getState()</code>
38+
<br/>
39+
Retrieves the store state.
40+
</aura:description>
41+
</aura:documentation>

src/aura/Redux/Redux.cmp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<aura:component description="Redux">
2+
<ltng:require scripts="{!$Resource.redux}"/>
3+
<aura:attribute name="name" type="string" default="redux" />
4+
<aura:method name="createStore" action="{!c.createStore}">
5+
<aura:attribute name="name" type="string" />
6+
<aura:attribute name="rootReducer" type="Map" />
7+
<aura:attribute name="initialState" type="Map" />
8+
<aura:attribute name="middleware" type="Map" />
9+
</aura:method>
10+
<aura:method name="getState" action="{!c.getState}" />
11+
<aura:method name="dispatch" action="{!c.dispatch}" >
12+
<aura:attribute name="action" type="Map" />
13+
</aura:method>
14+
<aura:method name="subscribe" action="{!c.subscribe}" >
15+
<aura:attribute name="listener" type="Map" />
16+
</aura:method>
17+
<aura:method name="replaceReducer" action="{!c.replaceReducer}" >
18+
<aura:attribute name="nextReducer" type="Map" />
19+
</aura:method>
20+
<aura:method name="registerReducer" action="{!c.registerReducer}" >
21+
<aura:attribute name="name" type="string" />
22+
<aura:attribute name="reducer" type="Map" />
23+
</aura:method>
24+
<aura:method name="connect" action="{!c.connect}" >
25+
<aura:attribute name="mapStateToAttributes" type="Map" />
26+
<aura:attribute name="target" type="Aura.Component" />
27+
</aura:method>
28+
</aura:component>

src/aura/Redux/Redux.cmp-meta.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<AuraDefinitionBundle xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<apiVersion>40.0</apiVersion>
4+
<description>Redux</description>
5+
</AuraDefinitionBundle>

src/aura/Redux/ReduxController.js

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
({
2+
createStore: function(component, event) {
3+
var reduxName = component.get("v.name");
4+
var params = event.getParam("arguments");
5+
6+
if(params && Redux) {
7+
8+
var reducerName = params.name;
9+
var rootReducer = params.rootReducer;
10+
var initialState = params.initialState;
11+
var middleware = params.middleware;
12+
var combinedReducer;
13+
14+
var reducerObject = {};
15+
reducerObject[reducerName] = rootReducer;
16+
17+
if(window.reducerQueue && window.reducerQueue[reduxName]) {
18+
combinedReducer = Redux.combineReducers(Object.assign({}, reducerObject, window.reducerQueue[reduxName]));
19+
window.reducerRegistry[reduxName] = Object.assign({}, window.reducerRegistry[reduxName], reducerObject, window.reducerQueue[reduxName]);
20+
} else if(window.reducerRegistry && window.reducerRegistry[reduxName]) {
21+
combinedReducer = Redux.combineReducers(reducerObject);
22+
window.reducerRegistry[reduxName] = Object.assign({}, window.reducerRegistry[reduxName], reducerObject);
23+
} else {
24+
combinedReducer = Redux.combineReducers(reducerObject);
25+
window.reducerRegistry = Object.assign({}, window.reducerRegistry, {
26+
[reduxName]: Object.assign({}, window.reducerRegistry ? window.reducerRegistry[reduxName] : {}, reducerObject)
27+
});
28+
}
29+
30+
31+
window.reduxStore = window.reduxStore || {};
32+
33+
if(!window.reduxStore[reduxName]) {
34+
if (rootReducer && initialState && middleware) {
35+
window.reduxStore[reduxName] = Redux.createStore(combinedReducer, initialState, Redux.compose(Redux.applyMiddleware(middleware)));
36+
} else if (rootReducer && initialState && !middleware) {
37+
window.reduxStore[reduxName] = Redux.createStore(combinedReducer, initialState);
38+
} else if (rootReducer && !initialState && !middleware) {
39+
window.reduxStore[reduxName] = Redux.createStore(combinedReducer);
40+
}
41+
} else {
42+
component.registerReducer(reducerName, rootReducer)
43+
}
44+
45+
if(window.subscriberQueue && window.subscriberQueue[reduxName]) {
46+
window.subscriberQueue[reduxName].map(function(subscriber) {
47+
component.connect(subscriber.mapStateToAttributes, subscriber.target);
48+
});
49+
}
50+
51+
if(window.dispatchQueue && window.dispatchQueue[reduxName]) {
52+
window.dispatchQueue[reduxName].map(function(action) {
53+
component.dispatch(action);
54+
});
55+
}
56+
}
57+
},
58+
59+
getState: function(component) {
60+
var reduxName = component.get("v.name");
61+
if(window.reduxStore && window.reduxStore[reduxName]) {
62+
return window.reduxStore[reduxName].getState();
63+
} else {
64+
return null;
65+
}
66+
},
67+
68+
dispatch: function(component, event) {
69+
var reduxName = component.get("v.name");
70+
var params = event.getParam("arguments");
71+
72+
var action = params ? params.action : null;
73+
if(action) {
74+
if(window.reduxStore && window.reduxStore[reduxName]) {
75+
window.reduxStore[reduxName].dispatch(action);
76+
} else {
77+
if(window.dispatchQueue && window.dispatchQueue[reduxName]) {
78+
window.dispatchQueue[reduxName].push(action);
79+
} else {
80+
window.dispatchQueue = {};
81+
window.dispatchQueue[reduxName] = [action];
82+
}
83+
}
84+
}
85+
},
86+
87+
subscribe: function(component, event) {
88+
var reduxName = component.get("v.name");
89+
var params = event.getParam("arguments");
90+
var listener = params ? params.listener : null;
91+
92+
if(listener && window.reduxStore && window.reduxStore[reduxName]) {
93+
window.reduxStore[reduxName].subscribe(listener);
94+
}
95+
},
96+
97+
replaceReducer: function(component, event) {
98+
var reduxName = component.get("v.name");
99+
var params = event.getParam("arguments");
100+
var nextReducer = params ? params.nextReducer : null;
101+
102+
if(nextReducer && window.reduxStore && window.reduxStore[reduxName]) {
103+
window.reduxStore[reduxName].replaceReducer(nextReducer);
104+
}
105+
},
106+
107+
registerReducer: function(component, event) {
108+
var reduxName = component.get("v.name");
109+
var params = event.getParam("arguments");
110+
var name = params ? params.name : null;
111+
var reducer = params ? params.reducer : null;
112+
113+
if(reducer && Redux) {
114+
var reducerObject = {};
115+
reducerObject[name] = reducer;
116+
117+
if(window.reduxStore && window.reduxStore[reduxName]) {
118+
var newRootReducer = Redux.combineReducers(Object.assign({}, window.reducerRegistry[reduxName], reducerObject));
119+
window.reducerRegistry[reduxName] = Object.assign({}, window.reducerRegistry[reduxName], reducerObject);
120+
window.reduxStore[reduxName].replaceReducer(newRootReducer);
121+
} else {
122+
if(window.reducerQueue && window.reducerQueue[reduxName]){
123+
window.reducerQueue[reduxName] = Object.assign({}, window.reducerQueue[reduxName], reducerObject);
124+
} else {
125+
window.reducerQueue = {};
126+
window.reducerQueue[reduxName] = reducerObject;
127+
}
128+
}
129+
}
130+
},
131+
132+
connect: function(component, event) {
133+
var reduxName = component.get("v.name");
134+
var params = event.getParam("arguments");
135+
var mapStateToAttributes = params ? params.mapStateToAttributes : null;
136+
var target = params && params.target ? params.target : event.getSource();
137+
138+
if(typeof mapStateToAttributes === "function") {
139+
mapStateToAttributes = mapStateToAttributes();
140+
}
141+
142+
if(window.reduxStore && window.reduxStore[reduxName]) {
143+
function handleChanges(){
144+
if(target.isValid()) {
145+
var state = window.reduxStore[reduxName].getState();
146+
147+
for(var key in mapStateToAttributes) {
148+
if(mapStateToAttributes.hasOwnProperty(key)) {
149+
if(typeof mapStateToAttributes[key] === "function") {
150+
try {
151+
var returnedFunction = mapStateToAttributes[key]();
152+
target.set(key, returnedFunction(state, target));
153+
} catch(e) {
154+
target.set(key, mapStateToAttributes[key](state, target))
155+
}
156+
} else {
157+
target.set(key, state[mapStateToAttributes[key]]);
158+
}
159+
}
160+
}
161+
}
162+
}
163+
164+
handleChanges();
165+
166+
window.reduxStore[reduxName].subscribe(handleChanges);
167+
target.dispatch = window.reduxStore[reduxName].dispatch;
168+
} else {
169+
if(window.subscriberQueue && window.subscriberQueue[reduxName]) {
170+
window.subscriberQueue[reduxName].push({
171+
target: target,
172+
mapStateToAttributes: mapStateToAttributes
173+
});
174+
} else {
175+
window.subscriberQueue = {};
176+
window.subscriberQueue[reduxName] = [{
177+
target: target,
178+
mapStateToAttributes: mapStateToAttributes
179+
}];
180+
}
181+
}
182+
}
183+
184+
})

src/package.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<types>
4+
<name>AuraDefinitionBundle</name>
5+
<members>Redux</members>
6+
</types>
7+
<types>
8+
<name>StaticResource</name>
9+
<members>redux</members>
10+
<members>reduxThunk</members>
11+
<members>reselect</members>
12+
</types>
13+
<version>40.0</version>
14+
</Package>

src/staticresources/redux.resource

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.Redux=t.Redux||{})}(this,function(t){"use strict";function e(t){var e=v.call(t,j),n=t[j];try{t[j]=void 0;var r=!0}catch(t){}var o=g.call(t);return r&&(e?t[j]=n:delete t[j]),o}function n(t){return w.call(t)}function r(t){return null==t?void 0===t?O:m:x&&x in Object(t)?e(t):n(t)}function o(t){return null!=t&&"object"==typeof t}function i(t){if(!o(t)||r(t)!=I)return!1;var e=E(t);if(null===e)return!0;var n=A.call(e,"constructor")&&e.constructor;return"function"==typeof n&&n instanceof n&&N.call(n)==R}function u(t,e,n){function r(){p===l&&(p=l.slice())}function o(){return s}function c(t){if("function"!=typeof t)throw Error("Expected listener to be a function.");var e=!0;return r(),p.push(t),function(){if(e){e=!1,r();var n=p.indexOf(t);p.splice(n,1)}}}function a(t){if(!i(t))throw Error("Actions must be plain objects. Use custom middleware for async actions.");if(void 0===t.type)throw Error('Actions may not have an undefined "type" property. Have you misspelled a constant?');if(y)throw Error("Reducers may not dispatch actions.");try{y=!0,s=d(s,t)}finally{y=!1}for(var e=l=p,n=0;e.length>n;n++)(0,e[n])();return t}var f;if("function"==typeof e&&void 0===n&&(n=e,e=void 0),void 0!==n){if("function"!=typeof n)throw Error("Expected the enhancer to be a function.");return n(u)(t,e)}if("function"!=typeof t)throw Error("Expected the reducer to be a function.");var d=t,s=e,l=[],p=l,y=!1;return a({type:k.INIT}),f={dispatch:a,subscribe:c,getState:o,replaceReducer:function(t){if("function"!=typeof t)throw Error("Expected the nextReducer to be a function.");d=t,a({type:k.INIT})}},f[C]=function(){var t,e=c;return t={subscribe:function(t){function n(){t.next&&t.next(o())}if("object"!=typeof t)throw new TypeError("Expected the observer to be an object.");return n(),{unsubscribe:e(n)}}},t[C]=function(){return this},t},f}function c(t){"undefined"!=typeof console&&"function"==typeof console.error&&console.error(t);try{throw Error(t)}catch(t){}}function a(t,e){var n=e&&e.type;return"Given action "+(n&&'"'+n+'"'||"an action")+', reducer "'+t+'" returned undefined. To ignore an action, you must explicitly return the previous state. If you want this reducer to hold no value, you can return null instead of undefined.'}function f(t){Object.keys(t).forEach(function(e){var n=t[e];if(void 0===n(void 0,{type:k.INIT}))throw Error('Reducer "'+e+"\" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined.");if(void 0===n(void 0,{type:"@@redux/PROBE_UNKNOWN_ACTION_"+Math.random().toString(36).substring(7).split("").join(".")}))throw Error('Reducer "'+e+"\" returned undefined when probed with a random type. Don't try to handle "+k.INIT+' or other actions in "redux/*" namespace. They are considered private. Instead, you must return the current state for any unknown actions, unless it is undefined, in which case you must return the initial state, regardless of the action type. The initial state may not be undefined, but can be null.')})}function d(t,e){return function(){return e(t.apply(void 0,arguments))}}function s(){for(var t=arguments.length,e=Array(t),n=0;t>n;n++)e[n]=arguments[n];return 0===e.length?function(t){return t}:1===e.length?e[0]:e.reduce(function(t,e){return function(){return t(e.apply(void 0,arguments))}})}var l,p="object"==typeof global&&global&&global.Object===Object&&global,y="object"==typeof self&&self&&self.Object===Object&&self,b=(p||y||Function("return this")()).Symbol,h=Object.prototype,v=h.hasOwnProperty,g=h.toString,j=b?b.toStringTag:void 0,w=Object.prototype.toString,m="[object Null]",O="[object Undefined]",x=b?b.toStringTag:void 0,E=function(t,e){return function(n){return t(e(n))}}(Object.getPrototypeOf,Object),I="[object Object]",T=Function.prototype,S=Object.prototype,N=T.toString,A=S.hasOwnProperty,R=N.call(Object),C=function(t){var e,n=t.Symbol;return"function"==typeof n?n.observable?e=n.observable:(e=n("observable"),n.observable=e):e="@@observable",e}(l="undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof module?module:Function("return this")()),k={INIT:"@@redux/INIT"},P=Object.assign||function(t){for(var e=1;arguments.length>e;e++){var n=arguments[e];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r])}return t};t.createStore=u,t.combineReducers=function(t){for(var e=Object.keys(t),n={},r=0;e.length>r;r++){var o=e[r];"function"==typeof t[o]&&(n[o]=t[o])}var i=Object.keys(n),u=void 0;try{f(n)}catch(t){u=t}return function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=arguments[1];if(u)throw u;for(var r=!1,o={},c=0;i.length>c;c++){var f=i[c],d=n[f],s=t[f],l=d(s,e);if(void 0===l){var p=a(f,e);throw Error(p)}o[f]=l,r=r||l!==s}return r?o:t}},t.bindActionCreators=function(t,e){if("function"==typeof t)return d(t,e);if("object"!=typeof t||null===t)throw Error("bindActionCreators expected an object or a function, instead received "+(null===t?"null":typeof t)+'. Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?');for(var n=Object.keys(t),r={},o=0;n.length>o;o++){var i=n[o],u=t[i];"function"==typeof u?r[i]=d(u,e):c("bindActionCreators expected a function actionCreator for key '"+i+"', instead received type '"+typeof u+"'.")}return r},t.applyMiddleware=function(){for(var t=arguments.length,e=Array(t),n=0;t>n;n++)e[n]=arguments[n];return function(t){return function(n,r,o){var i=t(n,r,o),u=i.dispatch,c=[],a={getState:i.getState,dispatch:function(t){return u(t)}};return c=e.map(function(t){return t(a)}),u=s.apply(void 0,c)(i.dispatch),P({},i,{dispatch:u})}}},t.compose=s,Object.defineProperty(t,"__esModule",{value:!0})});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<cacheControl>Public</cacheControl>
4+
<contentType>application/javascript</contentType>
5+
<description>Predictable State Container</description>
6+
</StaticResource>

0 commit comments

Comments
 (0)