-
Notifications
You must be signed in to change notification settings - Fork 227
/
FluxibleContext.js
238 lines (209 loc) · 7.38 KB
/
FluxibleContext.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/**
* Copyright 2014, Yahoo! Inc.
* Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
'use strict';
var debug = require('debug')('Fluxible:Context');
var objectAssign = require('object-assign');
require('setimmediate');
/**
* A request or browser-session context
* @class FluxibleContext
* @param {Object} options
* @param {Fluxible} options.app The Fluxible instance used to create the context
* @constructor
*/
function FluxContext(options) {
options = options || {};
// Options
this._app = options.app;
// To be created on demand
this._dispatcher = null;
// Plugins
this._plugins = [];
// Set up contexts
this._actionContext = null;
this._componentContext = null;
this._storeContext = null;
}
/**
* Creates an instance of the app level component with given props and a proper component
* context.
* @param {Object} props
* @return {ReactElement}
*/
FluxContext.prototype.createElement = function createElement(props) {
var Component = this._app.getAppComponent();
if (!Component) {
throw new Error('An appComponent was not specified.');
}
return Component(objectAssign({}, props, {
context: this.getComponentContext()
}));
};
/**
* Provides plugin mechanism for adding application level settings that are persisted
* between server/client and also modification of the FluxibleContext
* @method plug
* @param {Object} plugin
* @param {String} plugin.name Name of the plugin
* @param {Function} [plugin.plugActionContext] Method called after action context is created to allow
* dynamically modifying the action context
* @param {Function} [plugin.plugComponentContext] Method called after component context is created to
* allow dynamically modifying the component context
* @param {Function} [plugin.plugStoreContext] Method called after store context is created to allow
* dynamically modifying the store context
* @param {Object} [plugin.dehydrate] Method called to serialize the plugin settings to be persisted
* to the client
* @param {Object} [plugin.rehydrate] Method called to rehydrate the plugin settings from the server
*/
FluxContext.prototype.plug = function (plugin) {
if (!plugin.name) {
throw new Error('Context plugin must have a name');
}
this._plugins.push(plugin);
};
/**
* Executes an action passing an action interface to as the first parameter
* @param {Function} action An action creator function that receives actionContext, payload,
* and done as parameters
* @param {Object} payload The action payload
* @param {Function} done Method to be called once action execution has completed
*/
FluxContext.prototype.executeAction = function executeAction(action, payload, done) {
var self = this;
payload = payload || {};
setImmediate(function executeActionImmediate() {
debug('Executing action ' + (action.name) + ' with payload', payload);
action(self.getActionContext(), payload, done);
});
};
/**
* Sets up the dispatcher with access to the store context
* @method _initializeDispatcher
* @private
*/
FluxContext.prototype._initializeDispatcher = function initializeDispatcher() {
this._dispatcher = this._app.createDispatcherInstance(this.getStoreContext());
};
/**
* Returns the context for action controllers
* @method getActionContext
* @return {Object} Action context information
*/
FluxContext.prototype.getActionContext = function getActionContext() {
if (this._actionContext) {
return this._actionContext;
}
var self = this;
if (!self._dispatcher) {
self._initializeDispatcher();
}
var actionContext = {
dispatch: this._dispatcher.dispatch.bind(this._dispatcher),
executeAction: this.executeAction.bind(this),
getStore: this._dispatcher.getStore.bind(this._dispatcher)
};
self._plugins.forEach(function pluginsEach(plugin) {
var actionContextPlugin = plugin.plugActionContext;
if (actionContextPlugin) {
actionContextPlugin(actionContext, self, self._app);
}
});
self._actionContext = actionContext;
return self._actionContext;
};
/**
* Returns the context for action controllers
* @method getComponentContext
* @return {Object} Component context information
*/
FluxContext.prototype.getComponentContext = function getComponentContext() {
if (this._componentContext) {
return this._componentContext;
}
var self = this;
if (!self._dispatcher) {
self._initializeDispatcher();
}
var componentContext = {
getStore: this._dispatcher.getStore.bind(this._dispatcher),
// Disallows components from handling the callback for an action
executeAction: function componentExecuteAction(action, payload) {
self.executeAction(action, payload, function executeActionCallback(err) {
// @TODO provide a way to configure component action error handler
if (err) {
debug('Action returned error', err);
throw err;
}
});
}
};
self._plugins.forEach(function pluginsEach(plugin) {
var componentPlugin = plugin.plugComponentContext;
if (componentPlugin) {
componentPlugin(componentContext, self, self._app);
}
});
self._componentContext = componentContext;
return self._componentContext;
};
/**
* Returns the context for stores
* @method getStoreContext
* @return {Object} Store context information
*/
FluxContext.prototype.getStoreContext = function getStoreContext() {
if (this._storeContext) {
return this._storeContext;
}
var self = this;
var storeContext = {};
self._plugins.forEach(function pluginsEach(plugin) {
var storeContextPlugin = plugin.plugStoreContext;
if (storeContextPlugin) {
storeContextPlugin(storeContext, self, self._app);
}
});
self._storeContext = storeContext;
return self._storeContext;
};
/**
* Returns a serializable context state
* @method dehydrate
* @return {Object} See rehydrate method for properties
*/
FluxContext.prototype.dehydrate = function dehydrate() {
var self = this;
var state = {
dispatcher: (this._dispatcher && this._dispatcher.dehydrate()) || {},
plugins: {}
};
self._plugins.forEach(function pluginsEach(plugin) {
if ('function' === typeof plugin.dehydrate) {
// Use a namespace for storing plugin state and provide access to the application
state.plugins[plugin.name] = plugin.dehydrate(self);
}
});
return state;
};
/**
* Rehydrates the context state
* @method rehydrate
* @param {Object} obj Configuration
* @param {Object} obj.plugins Dehydrated context plugin state
* @param {Object} obj.dispatcher Dehydrated dispatcher state
*/
FluxContext.prototype.rehydrate = function rehydrate(obj) {
var self = this;
obj.plugins = obj.plugins || {};
self._plugins.forEach(function pluginsEach(plugin) {
if ('function' === typeof plugin.rehydrate) {
// Send in the plugin namespace state and provide access to the application instance
plugin.rehydrate(obj.plugins[plugin.name], self);
}
});
self._dispatcher = this._app.createDispatcherInstance(self.getStoreContext());
self._dispatcher.rehydrate(obj.dispatcher || {});
};
module.exports = FluxContext;