forked from YahooArchive/mojito
-
Notifications
You must be signed in to change notification settings - Fork 0
/
composite.common.js
363 lines (313 loc) · 11.6 KB
/
composite.common.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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
/*
* Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved.
* Copyrights licensed under the New BSD License.
* See the accompanying LICENSE file for terms.
*/
/*jslint anon:true, sloppy:true, nomen:true*/
/*global YUI*/
/**
* @module ActionContextAddon
*/
YUI.add('mojito-composite-addon', function(Y, NAME) {
function AdapterBuffer(buffer, id, callback) {
this.buffer = buffer;
this.id = id;
this.callback = callback;
this.__meta = [];
}
AdapterBuffer.prototype = {
done: function(data, meta) {
// HookSystem::StartBlock
Y.mojito.hooks.hook('adapterBuffer', this.hook, 'end', this);
// HookSystem::EndBlock
this.buffer[this.id].meta = meta;
this.buffer[this.id].data += data;
this.buffer.__counter__ -= 1;
if (this.buffer.__counter__ === 0) {
// TODO: Check why this can be called more than once.
this.callback();
}
},
flush: function(data, meta) {
this.buffer[this.id].meta = meta;
this.buffer[this.id].data += data;
},
error: function(err) {
Y.log("Error executing child mojit at '" + this.id + "':", 'error',
NAME);
if (err.message) {
Y.log(err.message, 'error', NAME);
} else {
Y.log(err, 'error', NAME);
}
if (err.stack) {
Y.log(err.stack, 'error', NAME);
}
// Pass back some empty data so we don't fail the composite
this.done('');
}
};
/**
* <strong>Access point:</strong> <em>ac.composite.*</em>
* Provides methods for working with many Mojits.
* @class Composite.common
*/
function Addon(command, adapter, ac) {
this.command = command;
this.dispatch = ac._dispatch;
this.ac = ac;
this.adapter = adapter;
}
Addon.prototype = {
namespace: 'composite',
/**
* Automatically dispatches all the children of this mojit and collects
* their executed values into the view template, keyed by the child's
* name within the mojit's configuration. For example, given the mojit
* spec:
*
*
<pre>
"specs": {
"parent": {
"type": "MyCompositeMojit",
"config": {
"children": {
"foo": {
"type": "FooMojit"
},
"bar": {
"type": "BarMojit"
}
}
}
}
}
</pre>
* And given the view template:
<pre>
<div id="{{mojit_view_id}}">
<h1>{{title}}</h1>
<div class="fooslot">
{{{foo}}}
</div>
<div class="barslot">
{{{bar}}}
</div>
</div>
</pre>
* And the controller:
<pre>
Y.mojito.controller = {
index: function(ac) {
ac.composite.done({
template: { title: 'Hello there' } // for the view only
});
}
};
</pre>
* This will execute the child intances of the "FooMojit" and
* "BarMojit", returning their rendered values into the parent's view
* template, thus rendering the full parent view including the children.
* All the parent parameters are passed along to children.
* @method done
* @param {object} opts The configuration object to be used.
* <em>template<em> can be used to provide additional
* view template values.
*/
done: function(opts) {
var template,
ac = this.ac,
cfg = this.command.instance.config,
children = cfg.children;
opts = opts || {};
template = opts.template || {};
if (!children || Y.Object.size(children) === 0) {
throw new Error('Cannot run composite mojit children because' +
' there are no children defined in the' +
' composite mojit spec.');
}
this.execute(cfg, function(data, meta) {
var merged = Y.merge(template, data);
ac.done(merged, meta);
}, this);
},
/**
* This method requires an explicit config object and returns
* a RMP compliant object via a callback.
*
<pre>
cfg = {
children: {
slot-1: {
type: "default",
action: "index"
},
slot-2: {
type: "default",
action: "index",
params: {
route: {},
url: {},
body: {},
file: {}
}
}
},
assets: {}
}
</pre>
*
* The "callback" is an object containg the child slots with its
* rendered data.
*
<pre>
callback({
slot-1: <string>,
slot-2: <string>
},
{
http: {}
assets: {}
})
</pre>
* @method execute
* @param {object} cfg The configuration object to be used.
* @param {function} cb The callback that will be called.
*/
execute: function(cfg, cb) {
var ac = this.ac,
buffer = {},
content = {},
my = this,
meta = {};
cfg.children = cfg.children || {};
// check to ensure children is an Object, not an array
if (Y.Lang.isArray(cfg.children)) {
throw new Error('Cannot process children in the format of an' +
' array. \'children\' must be an object.');
}
// HookSystem::StartBlock
Y.mojito.hooks.hook('addon', this.hook, 'start', my, cfg);
// HookSystem::EndBlock
meta.children = cfg.children;
buffer.__counter__ = Y.Object.size(cfg.children);
this._dispatchChildren(cfg.children, this.command, buffer,
function() {
var name;
// Reference the data we want from the "buffer" into our
// "content" obj Also merge the meta we collected.
for (name in buffer) {
if (buffer.hasOwnProperty(name) &&
name !== '__counter__') {
content[name] = buffer[name].data || '';
if (buffer[name].meta) {
meta = Y.mojito.util.metaMerge(meta,
buffer[name].meta);
}
}
}
// Mix in the assets given via the config
if (cfg.assets) {
if (!meta.assets) {
meta.assets = {};
}
ac.assets.mixAssets(meta.assets, cfg.assets);
}
// HookSystem::StartBlock
Y.mojito.hooks.hook('addon', my.hook, 'end', my);
// HookSystem::EndBlock
cb(content, meta);
});
},
_dispatchChildren: function(children, command, buffer, callback) {
//Y.log('_dispatchChildren()', 'debug', NAME);
var childName,
child,
childAdapter,
newCommand,
name;
// Process deferred children before dispatching
for (name in children) {
if (children.hasOwnProperty(name)) {
child = children[name];
// check to ensure children doesn't have a null child
// in which case it will be automatically skipped to
// facilitate disabling children based on the context.
if (!child) {
continue;
}
// first off, check to see if this child's execution should be
// deferred
if (child.defer) {
// it doesn't make sense to have a deferred child with a
// proxy, because the defer means to proxy it
// through the LazyLoad mojit
if (Y.Lang.isObject(child.proxy)) {
throw new Error('Cannot specify a child mojit spec' +
' with both \'defer\' and \'proxy\'' +
' configurations, because \'defer\'' +
' assumes a \'proxy\' to the LazyLoad' +
' mojit.');
}
// aha! that means we will give it a proxy to the LazyLoad
// mojit, which will handle lazy execution on the client.
child.proxy = {
type: 'LazyLoad'
};
}
if (Y.Lang.isObject(child.proxy)) {
// found a proxy, replace the child with the proxy and shove
// the child to proxy into it
children[name] = child.proxy;
if (!children[name].config) {
children[name].config = {};
}
// remove any defer or proxy flags so it doesn't reload
// infinitely
child.proxy = undefined;
child.defer = false;
children[name].config.proxied = child;
}
}
}
for (childName in children) {
if (children.hasOwnProperty(childName)) {
child = children[childName];
// check to ensure children doesn't have a null child
// in which case it will be automatically skipped to
// facilitate disabling children based on the context.
if (!child) {
buffer.__counter__ -= 1;
continue;
}
// Create a buffer for the child
buffer[childName] = {name: childName, data: '', meta: {}};
// Make a new "command" that works in the context of this
// composite
newCommand = {
instance: child,
// use action in child spec or default to index
action: child.action || 'index',
context: command.context,
params: child.params || command.params
};
childAdapter = new AdapterBuffer(buffer, childName,
callback);
// HookSystem::StartBlock
Y.mojito.hooks.hook('adapterBuffer', this.adapter.hook, 'start', childAdapter);
// HookSystem::EndBlock
childAdapter = Y.mix(childAdapter, this.adapter);
this.dispatch(newCommand, childAdapter);
}
}
}
};
Y.namespace('mojito.addons.ac').composite = Addon;
}, '0.1.0', {requires: [
'mojito',
'mojito-util',
'mojito-hooks',
'mojito-assets-addon',
'mojito-params-addon'
]});