-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
453 lines (411 loc) · 12.5 KB
/
index.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
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
/**
* Module dependencies.
*/
var debug = require('debug')('glint-util');
/**
* requires modules. works on server and client,
* even if the component didn't declare the dependency,
* as long as the dependency was declared in the global component.json.
* @param {String} path Path to the required module.
* @return {Object} The required module.
*/
exports.require = function (path) {
return exports.isBrowser ?
exports.global.require(path) :
require(path);
};
var _globalObj;
/**
* Add Glint Object to the global object.
* In the browser: window.Glint
* In node.js: global.Glint
* @return {[type]} [description]
*/
exports.global = (function () {
if (_globalObj) return _globalObj;
if (typeof window != 'undefined') _globalObj = window;
if (typeof global != 'undefined') _globalObj = global;
if (!_globalObj) _globalObj = this;
return _globalObj;
}());
var isBrowser = exports.isBrowser = (function () {
return (typeof window != 'undefined');
}());
/**
* Let a inherit from b.
* @param {Function} a Constructor Function a.
* @param {Function} b Constructor Function b.
*/
exports.inherit = function (a, b) {
var fn = function () {
};
fn.prototype = b.prototype;
a.prototype = new fn;
a.prototype.constructor = a;
// provide "proto" property that links to b
a.prototype.proto = b.prototype;
};
/**
* Extend object a with all the properties of b.
*
* var a = { foo: 'bar' }
* , b = { bar: 'baz' };
*
* utils.extend(a, b);
* // => a == { foo: 'bar', bar: 'baz' }
*
* @param {Object} a
* @param {Object} b
* @return {Object} a with merged properties from b
*/
exports.merge =
exports.extend = function (a, b) {
a = a || {};
if (b) {
for (var key in b) {
a[key] = b[key];
}
}
return a;
};
/**
* Extend object a with the missing properties of b.
* Existing properties of a will not be overwritten.
*
* var a = { foo: 'bar' }
* , b = { foo: 'BAR', bar: 'baz' };
*
* utils.defaults(a, b);
* // => a == { foo: 'bar', bar: 'baz' }
*
* @param {Object} a
* @param {Object} b
* @return {Object} a with default properties from b
*/
exports.defaults = function (a, b) {
a = a || {};
if (b) {
for (var key in b) {
if (a[key] === void 0) a[key] = b[key];
}
}
return a;
};
/* TODO move to Server, Cache controller*/
/**
* Proxies the data object with the corresponding proxy function provided in the map object.
* The data object is cloned into a new Object that's modified and returned.
*
* Usage: this function can be called to proxy certain property values of a template locals object.
*
* How it works: For every key in the data object, the map object is searched with the same key.
* The map value function is then called with the key as argument.
*
* Example:
*
* var data = {
* title: "Glint No Management Content System, GNMCS",
* text1: "edit me!",
* text2: "textarea editable, cool"
* }
*
*
* var blocks = {
* text1: {
* controller: require('glint-block-text'),
* selector: '[data-id=text1]'
* },
* text2: {
* controller: require('glint-block-template'),
* selector: '[data-id=text2]'
* }
* };
*
* var locals = proxyData(data, blocks);
* res.render('index', locals);
*
* @param {Object} data
* @param {Object} blocks
* @returns {Object} the modified data object
*/
exports.proxyData = function (data, blocks) {
// clone data object
var obj;
try {
obj = JSON.parse(JSON.stringify(data));
} catch (e) {
debug('could not parse data', data, e);
return data;
}
// proxy object values
Object.keys(obj).forEach(function (key) {
var val = obj[key];
var mv = blocks[key];
// check if the controller shall be instantiated
if (!mv || !mv.controller) return;
if (mv.browser) return;
// prepare controller options
mv.controllerOptions = mv.controllerOptions || {};
mv.controllerOptions.id = mv.controllerOptions.id || key;
// instantiate the controller and call the render proxy function with the value from the data object.
obj[key] = mv.controller(mv.controllerOptions).render(val);
});
return obj;
};
exports.renderBlocks = function (blocks, data) {
var obj = {};
if (data) {
// clone data object
try {
obj = JSON.parse(JSON.stringify(data));
} catch (e) {
debug('could not parse data', data, e);
if (typeof obj == 'object') {
obj = data;
}
}
}
// proxy object values
Object.keys(blocks).forEach(function (key) {
var mv = blocks[key];
var val = obj[key];
// check if the controller shall be instantiated
if (!mv || !mv.controller) return;
// fill with empty string if it shall be rendered in the browser
if (mv.browser) {
obj[key] = '';
return;
}
// prepare controller options
mv.controllerOptions = mv.controllerOptions || {};
mv.controllerOptions.id = mv.controllerOptions.id || key;
// instantiate the controller and call the render proxy function with the value from the data object.
obj[key] = mv.controller(mv.controllerOptions).render(val);
});
return obj;
};
exports.instantiateControllers = function (blocks) {
var controllers = {};
Object.keys(blocks).forEach(function (key) {
var mv = blocks[key];
// check if the controller shall be instantiated
if (!mv || !mv.controller) return;
// prepare controller options
mv.controllerOptions = mv.controllerOptions || {};
mv.controllerOptions.id = mv.controllerOptions.id || key;
if (isBrowser) mv.controllerOptions.el = document.querySelector(mv.selector);
// instantiate the controller and call the render proxy function with the value from the data object.
controllers[key] = mv.controller(mv.controllerOptions);
});
return controllers;
};
/**
* Get all nodes that match the selector and return them in an `Array`.
*
* @param {Node} node DOM Node
* @param {String} selector css selector
* @return {Array} NodeList as Array
*/
exports.getNodes = function (node, selector) {
var nodes = node.querySelectorAll(selector);
var nodesArray = Array.prototype.slice.call(nodes);
return nodesArray;
};
/**
* Get the nodes that match the `selector` and are child nodes of the `node`,
* but only the nodes that don't have parents that match the `selector` and are children of `node`.
*
* Test: http://jsfiddle.net/intesso/uAZ3y/
*
* @param {Node} node DOM Node
* @param {String} selector css selector
* @return {Array} NodeList as Array
*/
exports.getFirstLevelNodes = function (node, selector) {
var nodes = exports.getNodes(node, selector);
// test that they don't have parents with the same selector that are as well children of the same starting `node`.
return nodes.filter(function (n) {
var p = n.parentNode;
var parent = false;
// test that none of the parents are part of the nodes
while (p) {
parent = nodes.some(function (s) {
return p.isSameNode(s);
});
if (parent) return false;
p = p.parentNode;
}
return true;
});
};
exports.getId = function (path) {
var id = path || ((exports.isBrowser) ? window.location.pathname : '');
id = id.replace(/^\//g, ''); // remove leading slash
id = id.replace(/\/$/g, ''); // remove ending slash
id = id.replace(/\//g, '-'); // replace slashes with dashes
if (id.length == 0) id = '.' // default id
debug("getId", path, id, exports.isBrowser);
return id;
};
exports.callElementsFunction = function (elements, functionName, args, fn) {
if (args && 'function' == typeof args) fn = args, args = undefined;
for (var property in elements) {
if (elements.hasOwnProperty(property)) {
var element = elements[property];
var arg = (args) ? args[property] : undefined;
var result = element[functionName](arg);
if (fn) fn(element, result);
}
}
return elements;
};
exports.each = function(obj, name, args, fn) {
if (!fn && args && 'function' == typeof args) fn = args, args = undefined;
Object.keys(obj).forEach(function(key) {
var value = obj[key];
var arg = (args) ? args[key] : undefined;
var result = value[name](arg);
if (fn) fn(value, result);
});
return obj;
};
exports.toArray = function (obj) {
var arr = [];
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
arr.push(obj[i]);
}
}
return arr;
};
/**
* http://stackoverflow.com/questions/1068834/object-comparison-in-javascript
* @param {[type]} x [description]
* @param {[type]} y [description]
* @return {Boolean} [description]
*/
exports.isEqual = function (x, y) {
if (x === y) return true;
// if both x and y are null or undefined and exactly the same
if (!(x instanceof Object) || !(y instanceof Object)) return false;
// if they are not strictly equal, they both need to be Objects
if (x.constructor !== y.constructor) return false;
// they must have the exact same prototype chain, the closest we can do is
// test there constructor.
for (var p in x) {
if (!x.hasOwnProperty(p)) continue;
// other properties were tested using x.constructor === y.constructor
if (!y.hasOwnProperty(p)) return false;
// allows to compare x[ p ] and y[ p ] when set to undefined
if (x[p] === y[p]) continue;
// if they have the same strict value or identity then they are equal
if (typeof(x[p]) !== "object") return false;
// Numbers, Strings, Functions, Booleans must be strictly equal
if (!exports.isEqual(x[p], y[p])) return false;
// Objects and Arrays must be tested recursively
}
for (p in y) {
if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) return false;
// allows x[ p ] to be set to undefined
}
return true;
};
/**
* Get the Elements that match the `selector` and are child Elements of the initial Collection `$`,
* but only the Elements that don't have parents that match the `selector` and are children of the initial Collection `$`.
*
* Test: http://jsfiddle.net/intesso/AM4tb/
*
* @param {Object} $ jQuery or cheerio object
* @param {String} selector css selector
* @return {Object} jQuery or cheerio collection
*/
exports.getFirstLevelElements = function ($, $el, selector) {
var $els = $el.find(selector);
debug('$els', $els.length, $els.html());
// test that they don't have parents with the same selector that are as well children of the same starting `node`.
return $els.filter(function (i) {
var _el = $(this);
debug('$el filter', _el);
var found = false;
var $parents = $(this).parents(selector).filter(function (i) {
if (found) return false;
var _p = $(this);
$els.each(function (i) {
if (exports.isEqual($(this), _p)) {
debug("found parent element", _p);
found = true;
return false;
}
});
return (!found);
});
return (!found);
});
};
exports.xor = function (a, b) {
return (!a != !b);
};
exports.xnor = function (a, b) {
return (!xor(a, b));
};
exports.getData = function getData($el) {
var data = $el.data();
if (!data.id) throw new Error("Attribute missing: data-id", data.block);
return data;
};
exports.parseList = function (list, types) {
var obj = {};
for (var name in list) {
var value = list[name];
try {
value = value.replace(/\'/g, '\"');
value = JSON.parse(value);
if (types) {
var test = types.some(function (type) {
return (typeof value == type);
});
if (!test) value = list[name];
}
} catch (e) {
value = list[name];
}
obj[name] = value;
}
return obj;
};
exports.getOptions = function getOptions(node) {
var attributes = node.attributes;
var options = {};
options.$el = node;
for (var i = 0; i < attributes.length; i++) {
var attribute = attributes[i];
var name = attribute.name;
var value = attribute.value;
if (name.indexOf('data-') == 0) {
name = name.substring(5);
options[name] = value;
}
}
if (!options.id) throw new Error("Attribute missing: data-id", options.block);
return options;
};
exports.loadCss = function (attributes) {
// setting default attributes
if (typeof attributes === "string") {
var href = attributes;
attributes = {
href: href
};
}
if (!attributes.rel) {
attributes.rel = "stylesheet"
}
// appending the stylesheet... just plain dom manipulations
var styleSheet = document.createElement("link");
for (var key in attributes) {
styleSheet.attr(key, attributes[key]);
}
var head = document.getElementsByTagName("head")[0];
head.appendChild(styleSheet);
};