This repository has been archived by the owner on Oct 30, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 214
/
output-adapter.common.js
359 lines (301 loc) · 11.5 KB
/
output-adapter.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
/*
* Copyright (c) 2011-2012, 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-output-adapter-addon', function(Y, NAME) {
var CHARSET = 'charset=utf-8',
// the functions this core addon is going to attach to the
// ActionContext
flush,
done,
error,
// serializer container
serializer,
// private functions
serialize_xml,
serialize_json,
sanitizeConfigCopy,
sanitizeChildren,
attachChildViewIdsToMetaChildren;
/* see action-context.common.js for docs */
flush = function(data, meta) {
// NOTE: 'this' is the ActionContext instance
return this.done(data, meta, true);
};
/* see action-context.common.js for docs */
done = function(data, meta, more) {
// NOTE: 'this' is the ActionContext instance
var callbackFunc = more ? 'flush' : 'done',
instance = this.command.instance,
adapter = this._adapter,
action = this.command.action,
mojitView,
renderer = null,
contentType,
contentPath,
viewEngineOptions = (instance.appConfig && instance.appConfig.viewEngine) || {};
if (Y.Lang.isString(meta)) {
// If the meta string is a serializer set it
if (serializer[meta]) {
meta = {
serialize: meta
};
} else {// Otherwise we think it is a template name
meta = {
view: {name: meta}
};
}
}
meta = meta || {};
meta.assets = meta.assets || {};
meta.assets.bottom = meta.assets.bottom || {};
meta.assets.bottom.js = meta.assets.bottom.js || [];
meta.http = meta.http || {};
meta.http.code = meta.http.code || 200;
meta.http.headers = meta.http.headers || {};
meta.view = meta.view || {};
// Cache all tempates by default
meta.view.cacheTemplates = true;
if (this.app && this.app.config && this.app.config.cacheViewTemplates) {
meta.view.cacheTemplates = this.app.config.cacheViewTemplates ||
false;
}
// Check to see we need to serialize the data
if (meta.serialize && serializer[meta.serialize]) {
// Warning: this metod can change the "meta" object
data = serializer[meta.serialize].apply(this, [data, meta]);
// Once we are done remove the "serialize" option so others don't
// use it by mistake
delete meta.serialize;
}
// We want to know the view name, id, and binder used later so make sure
// "meta" is up-to-date
meta.view.name = meta.view.name || action;
// TODO: Use a different binder
meta.view.binder = meta.view.binder || meta.view.name;
mojitView = instance.views[meta.view.name];
if (!meta.view.id) {
meta.view.id = Y.guid();
//DEBUGGING: meta.view.id += '-viewId-' +
// this.command.instance.type + '-' + this.command.action;
}
// If we are given "meta.view['content-path']" use it over what we got
// from "instance.views"
if (mojitView && meta.view['content-path']) {
mojitView['content-path'] = meta.view['content-path'];
}
// If we are given "meta.view['engine']" use it over what we got from
// "instance.views"
if (mojitView && meta.view.engine) {
mojitView.engine = meta.view.engine;
}
// Here we ask each "thing" attached to the AC if it wants to add view
// "meta"
Y.Object.each(this, function(item) {
if (item && Y.Lang.isFunction(item.mergeMetaInto)) {
item.mergeMetaInto(meta);
}
});
contentType = meta.http.headers['content-type'];
attachChildViewIdsToMetaChildren(meta.children, meta.binders);
if (!meta.binders) {
meta.binders = {};
}
// Don't clobber an existing meta.binders[meta.view.id] entry
if (!meta.binders[meta.view.id]) {
meta.binders[meta.view.id] = {
base: instance.base,
action: action,
config: sanitizeConfigCopy(instance.config),
type: instance.type,
viewId: meta.view.id,
guid: instance.instanceId, // DEPRECATED, use instanceId
instanceId: instance.instanceId,
// We don't use the actual config's children object, because
// that might not have been what was actually dispatched. We get
// the actual children config that was dispatched through the
// meta object.
children: sanitizeChildren(meta.children)
};
}
/*
* Here we provide an easy way to return a string
* data == 'a string of chars'
*/
if (Y.Lang.isString(data)) {
// if the user didn't provided a content type, we'll make it plain
// text
if (!contentType) {
meta.http.headers['content-type'] = ['text/plain; ' + CHARSET];
}
//Y.log('pushing to native adapter', 'info', NAME);
adapter[callbackFunc](data, meta);
Y.log('dispatch complete for ' + instance.instanceId, 'mojito',
'qeperf');
return;
}
// there may not be a view if this is running on the client
if (mojitView) {
data = data || {}; // default null data to empty view template
// Get the YUI Module name of the Binder if we can.
if (meta.binders[meta.view.id]) {
meta.binders[meta.view.id].name = mojitView['binder-module'];
meta.binders[meta.view.id].needs =
mojitView['binder-yui-sorted'];
}
if (!contentType) {
meta.http.headers['content-type'] = ['text/html; ' + CHARSET];
}
data.mojit_guid = instance.instanceId;
data.mojit_view_id = meta.view.id;
data.mojit_assets = this.command.instance.assetsRoot;
// Use engine to compile template view
Y.log('Rendering "' + meta.view.name + '" view for "' +
(instance.id || '@' + instance.type) + '"', 'info', NAME);
contentPath = mojitView['content-path'];
// this is mainly used by html5app
if (this.app.config.pathToRoot) {
contentPath = this.app.config.pathToRoot + contentPath;
}
renderer = new Y.mojito.ViewRenderer(
mojitView.engine,
meta.view.id,
viewEngineOptions
);
renderer.render(data, instance.type, contentPath, adapter,
meta, more);
} else {
if (Y.Lang.isObject(data)) {
throw new Error("Missing view template: '" + meta.view.name +
"'");
}
adapter[callbackFunc](data, meta);
}
// Time marker
Y.mojito.perf.mark('mojito', 'core_action_end[' + instance.type +
':' + action + ']', 'ac.done() completed for Mojit "' +
instance.type + '" with action "' + action + '"');
};
/* see action-context.common.js for docs */
error = function(err) {
// NOTE: 'this' is the ActionContext instance
this._adapter.error(err);
};
sanitizeConfigCopy = function(cfg) {
var copy;
if (!Y.Lang.isObject(cfg)) {
return cfg;
}
copy = Y.mojito.util.copy(cfg);
copy.children = sanitizeChildren(copy.children);
return copy;
};
sanitizeChildren = function(children) {
if (!Y.Lang.isObject(children)) {
return children;
}
Y.Object.each(children, function(v, k) {
// We don't want child params to be included within a mojit's
// configuration, because it can leak implemenation details out to
// other execution environments. For example, the client runtime
// does not need to have the parameters of the mojits that were used
// to construct the initial client DOM.
delete children[k].params;
});
return children;
};
attachChildViewIdsToMetaChildren = function(children, binders) {
if (!children) {
return;
}
Y.Object.each(binders, function(binderData, viewId) {
Y.Object.each(children, function(childData) {
if (binderData.instanceId === childData.instanceId) {
childData.viewId = viewId;
}
});
});
};
/*
* @method serialize_json
* @private
* @param {object} data
* @param {object} meta
* @return {string}
*/
serialize_json = function(data, meta) {
meta.http.headers['content-type'] = ['application/json; ' + CHARSET];
try {
return Y.JSON.stringify(data);
} catch (err) {
throw new Error('Expected JSON data, but there was a parse error' +
' on the string: \"' + data);
}
};
/*
* @method serialize_xml
* @private
* @param {object} data
* @param {object} meta
* @return {string}
*/
serialize_xml = function(data, meta) {
// A dirty XML function I found on the interwebs
function simpleXml(js, wraptag) {
if (js instanceof Object) {
return simpleXml(Y.Object.keys(js).map(function(key) {
return simpleXml(js[key], key);
}).join('\n'), wraptag);
}
return ((wraptag) ? '<' + wraptag + '>' : '') + js +
((wraptag) ? '</' + wraptag + '>' : ''
);
}
meta.http.headers['content-type'] = ['application/xml; ' + CHARSET];
if (Y.Lang.isObject) {
try {
return simpleXml(data, 'xml');
} catch (err) {
throw new Error('Expected XML data, but there was a parse' +
' error on the string: \"' + err.message);
}
}
return '';
};
serializer = {
json: serialize_json,
xml: serialize_xml
};
/**
* <strong>Access point:</strong> <em>ac.*</em>
* The main API point for developers in a Controller. This addon provides
* the core functions
* of the ActionContext: <em>flush</em>, <em>done</em>, and <em>error</em>.
* @class OutputAdapter.common
* @private
*/
function Addon(command, adapter, ac) {
/*
* This plugin doesn't act the same way as the others. It attaches its
* functions directly onto the ActionContext. Each functions is assumed
* that 'this' will be the actual instance of ActionContext, not the
* object this constructor is creating.
*/
ac.flush = flush;
ac.done = done;
ac.error = error;
}
Y.namespace('mojito.addons.ac').core = Addon;
}, '0.1.0', {requires: [
'json-stringify',
'event-custom-base',
'mojito-view-renderer',
'mojito-util',
'mojito-perf'
]});