rspectacles 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,666 @@
1
+ var Plates = (typeof module !== 'undefined' && 'id' in module && typeof exports !== 'undefined') ? exports : {};
2
+
3
+ !function(exports, env, undefined) {
4
+ "use strict";
5
+
6
+ //
7
+ // Cache variables to increase lookup speed.
8
+ //
9
+ var _toString = Object.prototype.toString;
10
+
11
+ //
12
+ // Polyfill the Array#indexOf method for cross browser compatibility.
13
+ //
14
+ [].indexOf || (Array.prototype.indexOf = function indexOf(a, b ,c){
15
+ for (
16
+ c = this.length , b = (c+ ~~b) % c;
17
+ b < c && (!(b in this) || this[b] !==a );
18
+ b++
19
+ );
20
+
21
+ return b^c ? b : -1;
22
+ });
23
+
24
+ //
25
+ // Polyfill Array.isArray for cross browser compatibility.
26
+ //
27
+ Array.isArray || (Array.isArray = function isArray(a) {
28
+ return _toString.call(a) === '[object Array]';
29
+ });
30
+
31
+ //
32
+ // ### function fetch(data, mapping, value, key)
33
+ // #### @data {Object} the data that we need to fetch a value from
34
+ // #### @mapping {Object} The iterated mapping step
35
+ // #### @tagbody {String} the tagbody we operated against
36
+ // #### @key {String} optional key if the mapping doesn't have a dataKey
37
+ // Fetches the correct piece of data
38
+ //
39
+ function fetch(data, mapping, value, tagbody, key) {
40
+ key = mapping.dataKey || key;
41
+
42
+ //
43
+ // Check if we have data manipulation or filtering function.
44
+ //
45
+ if (mapping.dataKey && typeof mapping.dataKey === 'function') {
46
+ return mapping.dataKey(data, value || '', tagbody || '', key);
47
+ }
48
+
49
+ //
50
+ // See if we are using dot notation style
51
+ //
52
+ if (!~key.indexOf('.')) return data[key];
53
+
54
+ var result = key
55
+ , structure = data;
56
+
57
+ for (var paths = key.split('.'), i = 0, length = paths.length; i < length && structure; i++) {
58
+ result = structure[+paths[i] || paths[i]];
59
+ structure = result;
60
+ }
61
+
62
+ return result !== undefined ? result : data[key];
63
+ }
64
+
65
+ //
66
+ // compileMappings
67
+ //
68
+ // sort the mappings so that mappings for the same attribute and value go consecutive
69
+ // and inside those, those that change attributes appear first.
70
+ //
71
+ function compileMappings(oldMappings) {
72
+ var mappings = oldMappings.slice(0);
73
+
74
+ mappings.sort(function(map1, map2) {
75
+ if (!map1.attribute) return 1;
76
+ if (!map2.attribute) return -1;
77
+
78
+ if (map1.attribute !== map2.attribute) {
79
+ return map1.attribute < map2.attribute ? -1 : 1;
80
+ }
81
+ if (map1.value !== map2.value) {
82
+ return map1.value < map2.value ? -1 : 1;
83
+ }
84
+ if (! ('replace' in map1) && ! ('replace' in map2)) {
85
+ throw new Error('Conflicting mappings for attribute ' + map1.attribute + ' and value ' + map1.value);
86
+ }
87
+ if (map1.replace) {
88
+ return 1;
89
+ }
90
+ return -1;
91
+ });
92
+
93
+ return mappings;
94
+ }
95
+
96
+ //
97
+ // Matches a closing tag to a open tag
98
+ //
99
+ function matchClosing(input, tagname, html) {
100
+ var closeTag = '</' + tagname + '>',
101
+ openTag = new RegExp('< *' + tagname + '( *|>)', 'g'),
102
+ closeCount = 0,
103
+ openCount = -1,
104
+ from, to, chunk
105
+ ;
106
+
107
+ from = html.search(input);
108
+ to = from;
109
+
110
+ while(to > -1 && closeCount !== openCount) {
111
+ to = html.indexOf(closeTag, to);
112
+ if (to > -1) {
113
+ to += tagname.length + 3;
114
+ closeCount ++;
115
+ chunk = html.slice(from, to);
116
+ openCount = chunk.match(openTag).length;
117
+ }
118
+ }
119
+ if (to === -1) {
120
+ throw new Error('Unmatched tag ' + tagname + ' in ' + html)
121
+ }
122
+
123
+ return chunk;
124
+ }
125
+
126
+ var Merge = function Merge() {};
127
+ Merge.prototype = {
128
+ nest: [],
129
+
130
+ tag: new RegExp([
131
+ '<',
132
+ '(/?)', // 2 - is closing
133
+ '([-:\\w]+)', // 3 - name
134
+ '((?:[-\\w]+(?:', '=',
135
+ '(?:\\w+|["|\'](?:.*)["|\']))?)*)', // 4 - attributes
136
+ '(/?)', // 5 - is self-closing
137
+ '>'
138
+ ].join('\\s*')),
139
+
140
+ //
141
+ // HTML attribute parser.
142
+ //
143
+ attr: /([\-\w]*)\s*=\s*(?:["\']([\-\.\w\s\/:;&#]*)["\'])/gi,
144
+
145
+ //
146
+ // In HTML5 it's allowed to have to use self closing tags without closing
147
+ // separators. So we need to detect these elements based on the tag name.
148
+ //
149
+ selfClosing: /^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/,
150
+
151
+ //
152
+ // ### function hasClass(str, className)
153
+ // #### @str {String} the class attribute
154
+ // #### @className {String} the className that the classAttribute should contain
155
+ //
156
+ // Helper function for detecting if a class attribute contains the className
157
+ //
158
+ hasClass: function hasClass(str, className) {
159
+ return ~str.split(' ').indexOf(className);
160
+ },
161
+
162
+ //
163
+ // ### function iterate(html, value, components, tagname, key)
164
+ // #### @html {String} peice of HTML
165
+ // #### @value {Mixed} iterateable object with data
166
+ // #### @components {Array} result of the this.tag regexp execution
167
+ // #### @tagname {String} the name of the tag that we iterate on
168
+ // #### @key {String} the key of the data that we need to extract from the value
169
+ // #### @map {Object} attribute mappings
170
+ //
171
+ // Iterate over over the supplied HTML.
172
+ //
173
+ iterate: function iterate(html, value, components, tagname, key, map) {
174
+ var output = '',
175
+ segment = matchClosing(components.input, tagname, html),
176
+ data = {};
177
+
178
+ // Is it an array?
179
+ if (Array.isArray(value)) {
180
+ // Yes: set the output to the result of iterating through the array
181
+ for (var i = 0, l = value.length; i < l; i++) {
182
+ // If there is a key, then we have a simple object and
183
+ // must construct a simple object to use as the data
184
+ if (key) {
185
+ data[key] = value[i];
186
+ } else {
187
+ data = value[i];
188
+ }
189
+
190
+ output += this.bind(segment, data, map);
191
+ }
192
+
193
+ return output;
194
+ } else if (typeof value === 'object') {
195
+ // We need to refine the selection now that we know we're dealing with a
196
+ // nested object
197
+ segment = segment.slice(components.input.length, -(tagname.length + 3));
198
+ return output += this.bind(segment, value, map);
199
+ }
200
+
201
+ return value;
202
+ },
203
+
204
+ //
205
+ // ### function bind(html, data, map)
206
+ // #### @html {String} the template that we need to modify
207
+ // #### @data {Object} data for the template
208
+ // #### @map {Mapper} instructions for the data placement in the template
209
+ // Process the actual template
210
+ //
211
+ bind: function bind(html, data, map) {
212
+ if (Array.isArray(data)) {
213
+ var output = '';
214
+
215
+ for (var i = 0, l = data.length; i<l; i++) {
216
+ output += this.bind(html, data[i], map);
217
+ }
218
+
219
+ return output;
220
+ }
221
+
222
+ html = (html || '').toString();
223
+ data = data || {};
224
+
225
+ var that = this;
226
+
227
+ var openers = 0,
228
+ remove = 0,
229
+ components,
230
+ attributes,
231
+ mappings = map && compileMappings(map.mappings),
232
+ intag = false,
233
+ tagname = '',
234
+ isClosing = false,
235
+ isSelfClosing = false,
236
+ selfClosing = false,
237
+ matchmode = false,
238
+ createAttribute = map && map.conf && map.conf.create,
239
+ closing,
240
+ tagbody;
241
+
242
+ var c,
243
+ buffer = '',
244
+ left;
245
+
246
+ for (var i = 0, l = html.length; i < l; i++) {
247
+ c = html.charAt(i);
248
+
249
+ //
250
+ // Figure out which part of the HTML we are currently processing. And if
251
+ // we have queued up enough HTML to process it's data.
252
+ //
253
+ if (c === '!' && intag && !matchmode) {
254
+ intag = false;
255
+ buffer += html.slice(left, i + 1);
256
+ } else if (c === '<' && !intag) {
257
+ closing = true;
258
+ intag = true;
259
+ left = i;
260
+ } else if (c === '>' && intag) {
261
+ intag = false;
262
+ tagbody = html.slice(left, i + 1);
263
+ components = this.tag.exec(tagbody);
264
+
265
+ if(!components) {
266
+ intag = true;
267
+ continue;
268
+ }
269
+
270
+ isClosing = components[1];
271
+ tagname = components[2];
272
+ attributes = components[3];
273
+ selfClosing = components[4];
274
+ isSelfClosing = this.selfClosing.test(tagname);
275
+
276
+ if (matchmode) {
277
+ //
278
+ // and its a closing.
279
+ //
280
+ if (!!isClosing) {
281
+ if (openers <= 0) {
282
+ matchmode = false;
283
+ } else {
284
+ --openers;
285
+ }
286
+ } else if (!isSelfClosing) {
287
+ //
288
+ // and its not a self-closing tag
289
+ //
290
+ ++openers;
291
+ }
292
+ }
293
+
294
+ if (!isClosing && !matchmode) {
295
+ //
296
+ // if there is a match in progress and
297
+ //
298
+ if (mappings && mappings.length > 0) {
299
+ for (var ii = mappings.length - 1; ii >= 0; ii--) {
300
+ var setAttribute = false
301
+ , mapping = mappings[ii]
302
+ , shouldSetAttribute = mapping.re && attributes.match(mapping.re);
303
+
304
+ //
305
+ // check if we are targetting a element only or attributes
306
+ //
307
+ if ('tag' in mapping && !this.attr.test(tagbody) && mapping.tag === tagname) {
308
+ tagbody = tagbody + fetch(data, mapping, '', tagbody);
309
+ continue;
310
+ }
311
+
312
+ tagbody = tagbody.replace(this.attr, function(str, key, value, a) {
313
+ var newdata;
314
+
315
+ if (shouldSetAttribute && mapping.replace !== key || remove) {
316
+ return str;
317
+ } else if (shouldSetAttribute || typeof mapping.replacePartial1 !== 'undefined') {
318
+ setAttribute = true;
319
+
320
+ //
321
+ // determine if we should use the replace argument or some value from the data object.
322
+ //
323
+ if (typeof mapping.replacePartial2 !== 'undefined') {
324
+ newdata = value.replace(mapping.replacePartial1, mapping.replacePartial2);
325
+ } else if (typeof mapping.replacePartial1 !== 'undefined' && mapping.dataKey) {
326
+ newdata = value.replace(mapping.replacePartial1, fetch(data, mapping, value, tagbody, key));
327
+ } else {
328
+ newdata = fetch(data, mapping, value, tagbody, key);
329
+ }
330
+
331
+ return key + '="' + (newdata || '') + '"';
332
+ } else if (!mapping.replace && mapping.attribute === key) {
333
+ if (
334
+ mapping.value === value ||
335
+ that.hasClass(value, mapping.value ||
336
+ mappings.conf.where === key) ||
337
+ (_toString.call(mapping.value) === '[object RegExp]' &&
338
+ mapping.value.exec(value) !== null)
339
+ ) {
340
+ if (mapping.remove) {
341
+ //
342
+ // only increase the remove counter if it's not a self
343
+ // closing element. As matchmode is suffectient to
344
+ // remove tose
345
+ //
346
+ if (!isSelfClosing) remove++;
347
+ matchmode = true;
348
+ } else if (mapping.plates) {
349
+ var partial = that.bind(
350
+ mapping.plates
351
+ , typeof mapping.data === 'string' ? fetch(data, { dataKey: mapping.data }) : mapping.data || data
352
+ , mapping.mapper
353
+ );
354
+
355
+ buffer += tagbody + that.iterate(html, partial, components, tagname, undefined, map);
356
+ matchmode = true;
357
+ } else {
358
+ var v = newdata = fetch(data, mapping, value, tagbody, key);
359
+ newdata = tagbody + newdata;
360
+
361
+ if (Array.isArray(v)) {
362
+ newdata = that.iterate(html, v, components, tagname, value, map);
363
+ // If the item is an array, then we need to tell
364
+ // Plates that we're dealing with nests
365
+ that.nest.push(tagname);
366
+ } else if (typeof v === 'object') {
367
+ newdata = tagbody + that.iterate(html, v, components, tagname, value, map);
368
+ }
369
+
370
+ buffer += newdata || '';
371
+ matchmode = true;
372
+ }
373
+ }
374
+ }
375
+
376
+ return str;
377
+ });
378
+
379
+ //
380
+ // Do we need to create the attributes if they don't exist.
381
+ //
382
+ if (createAttribute && shouldSetAttribute && !setAttribute) {
383
+ var spliced = selfClosing ? 2 : 1
384
+ , close = selfClosing ? '/>': '>'
385
+ , left = tagbody.substr(0, tagbody.length - spliced);
386
+
387
+ if (left[left.length - 1] === ' ') {
388
+ left = left.substr(0, left.length - 1);
389
+
390
+ if (selfClosing) {
391
+ close = ' ' + close;
392
+ }
393
+ }
394
+
395
+ tagbody = [
396
+ left,
397
+ ' ',
398
+ mapping.replace,
399
+ '="',
400
+ fetch(data, mapping),
401
+ '"',
402
+ close
403
+ ].join('');
404
+ }
405
+ }
406
+ } else {
407
+ //
408
+ // if there is no map, we are just looking to match
409
+ // the specified id to a data key in the data object.
410
+ //
411
+ tagbody.replace(this.attr, function (attr, key, value, idx) {
412
+ if (key === map && map.conf.where || 'id' && data[value]) {
413
+ var v = data[value],
414
+ nest = Array.isArray(v),
415
+ output = nest || typeof v === 'object'
416
+ ? that.iterate(html.substr(left), v, components, tagname, value, map)
417
+ : v;
418
+
419
+ // If the item is an array, then we need to tell
420
+ // Plates that we're dealing with nests
421
+ if (nest) { that.nest.push(tagname); }
422
+
423
+ buffer += nest ? output : tagbody + output;
424
+ matchmode = true;
425
+ }
426
+ });
427
+ }
428
+ }
429
+
430
+ //
431
+ // if there is currently no match in progress
432
+ // just write the tagbody to the buffer.
433
+ //
434
+ if (!matchmode && that.nest.length === 0) {
435
+ if (!remove) buffer += tagbody;
436
+
437
+ if (remove && !!isClosing) --remove;
438
+ } else if (!matchmode && that.nest.length) {
439
+ this.nest.pop();
440
+ }
441
+ } else if (!intag && !matchmode) {
442
+ //
443
+ // currently not inside a tag and there is no
444
+ // match in progress, we can write the char to
445
+ // the buffer.
446
+ //
447
+ if (!remove) buffer += c;
448
+ }
449
+ }
450
+ return buffer;
451
+ }
452
+ };
453
+
454
+ //
455
+ // ### function Mapper(conf)
456
+ // #### @conf {Object} configuration object
457
+ // Constructor function for the Mapper instance that is responsible for
458
+ // providing the mapping for the data structure
459
+ //
460
+ function Mapper(conf) {
461
+ if (!(this instanceof Mapper)) { return new Mapper(conf); }
462
+
463
+ this.mappings = [];
464
+ this.conf = conf || {};
465
+ }
466
+
467
+ //
468
+ // ### function last(newitem)
469
+ // #### @newitem {Boolean} do we need to add a new item to the mapping
470
+ // Helper function for adding new attribute maps to a Mapper instance
471
+ //
472
+ function last(newitem) {
473
+ if (newitem) {
474
+ this.mappings.push({});
475
+ }
476
+
477
+ var m = this.mappings[this.mappings.length - 1];
478
+
479
+ if (m && m.attribute && m.value && m.dataKey && m.replace) {
480
+ m.re = new RegExp(m.attribute + '=([\'"]?)' + m.value + '\\1');
481
+ } else if (m) {
482
+ delete m.re;
483
+ }
484
+
485
+ return m;
486
+ }
487
+
488
+ //
489
+ // Create the actual chainable methods: where('class').is('foo').insert('bla')
490
+ //
491
+ Mapper.prototype = {
492
+ //
493
+ // ### function replace(val1, val2)
494
+ // #### @val1 {String|RegExp} The part of the attribute that needs to be replaced
495
+ // #### @val2 {String} The value it should be replaced with
496
+ //
497
+ replace: function replace(val1, val2) {
498
+ var l = last.call(this);
499
+ l.replacePartial1 = val1;
500
+ l.replacePartial2 = val2;
501
+ return this;
502
+ },
503
+
504
+ //
505
+ // ### function use(val)
506
+ // #### @val {String} A string that represents a key.
507
+ // Data will be inserted into the attribute that was specified in the
508
+ // `where` clause.
509
+ //
510
+ use: function use(val) {
511
+ last.call(this).dataKey = val;
512
+ return last.call(this) && this;
513
+ },
514
+
515
+ //
516
+ // ### function where(val)
517
+ // #### @val {String} an attribute that may be found in a tag
518
+ // This method will initiate a clause. Once a clause has been established
519
+ // other member methods will be chained to each other in any order.
520
+ //
521
+ where: function where(val) {
522
+ last.call(this, true).attribute = val;
523
+ return last.call(this) && this;
524
+ },
525
+
526
+ //
527
+ // ### function class(val)
528
+ // #### @val {String} a value that may be found in the `class` attribute of a tag
529
+ // the method name should be wrapped in quotes or it will throw errors in IE.
530
+ //
531
+ 'class': function className(val) {
532
+ return this.where('class').is(val);
533
+ },
534
+
535
+ //
536
+ // ### function tag(val)
537
+ // #### @val {String} the name of the tag should be found
538
+ //
539
+ tag: function tag(val) {
540
+ last.call(this, true).tag = val;
541
+ return this;
542
+ },
543
+
544
+ //
545
+ // ### function is(val)
546
+ // #### @val {string} The value of the attribute that was specified in the
547
+ // `where` clause.
548
+ //
549
+ is: function is(val) {
550
+ last.call(this).value = val;
551
+ return last.call(this) && this;
552
+ },
553
+
554
+ //
555
+ // ### function has(val)
556
+ // #### @val {String|RegExp} The value of the attribute that was specified
557
+ // in the `where` clause.
558
+ //
559
+ has: function has(val) {
560
+ last.call(this).value = val;
561
+ this.replace(val);
562
+ return last.call(this) && this;
563
+ },
564
+
565
+ //
566
+ // ### function insert(val)
567
+ // #### @val {String} A string that represents a key. Data will be inserted
568
+ // in to the attribute that was specified in the `where` clause.
569
+ //
570
+ insert: function insert(val) {
571
+ var l = last.call(this);
572
+ l.replace = l.attribute;
573
+ l.dataKey = val;
574
+ return last.call(this) && this;
575
+ },
576
+
577
+ //
578
+ // ### function as(val)
579
+ // #### @val {String} A string that represents an attribute in the tag.
580
+ // If there is no attribute by that name name found, one may be created
581
+ // depending on the options that where passed in the `Plates.Map`
582
+ // constructor.
583
+ //
584
+ as: function as(val) {
585
+ last.call(this).replace = val;
586
+ return last.call(this) && this;
587
+ },
588
+
589
+ //
590
+ // ### function remove()
591
+ // This will remove the element that was specified in the `where` clause
592
+ // from the template.
593
+ //
594
+ remove: function remove() {
595
+ last.call(this).remove = true;
596
+ return last.call(this, true);
597
+ },
598
+
599
+ //
600
+ // ### function append(plates, data, map)
601
+ // #### @plates {String} Template or path/id of the template
602
+ // #### @data {Object|String} data for the appended template
603
+ // #### @map {Plates.Map} mapping for the data
604
+ //
605
+ append: function append(plates, data, map) {
606
+ var l = last.call(this);
607
+
608
+ if (data instanceof Mapper) {
609
+ map = data;
610
+ data = undefined;
611
+ }
612
+
613
+ // If the supplied plates template doesn't contain any HTML it's most
614
+ // likely that we need to import it. To improve performance we will cache
615
+ // the result of the file system.
616
+ if (!/<[^<]+?>/.test(plates) && !exports.cache[plates]) {
617
+ // figure out if we are running in Node.js or a browser
618
+ if ('document' in env && 'getElementById' in env.document) {
619
+ exports.cache[plates] = document.getElementById(plates).innerHTML;
620
+ } else {
621
+ exports.cache[plates] = require('fs').readFileSync(
622
+ require('path').join(process.cwd(), plates),
623
+ 'utf8'
624
+ );
625
+ }
626
+ }
627
+
628
+ l.plates = exports.cache[plates] || plates;
629
+ l.data = data;
630
+ l.mapper = map;
631
+
632
+ return last.call(this, true);
633
+ }
634
+ };
635
+
636
+ //
637
+ // Provide helpful aliases that well help with increased compatibility as not
638
+ // all browsers allow the Mapper#class prototype (IE).
639
+ //
640
+ Mapper.prototype.className = Mapper.prototype['class'];
641
+
642
+ //
643
+ // Aliases of different methods.
644
+ //
645
+ Mapper.prototype.partial = Mapper.prototype.append;
646
+ Mapper.prototype.to = Mapper.prototype.use;
647
+
648
+ //
649
+ // Expose a simple cache object so people can clear the cached partials if
650
+ // they want to.
651
+ //
652
+ exports.cache = {};
653
+
654
+ //
655
+ // Expose the Plates#bind interface.
656
+ //
657
+ exports.bind = function bind(html, data, map) {
658
+ var merge = new Merge();
659
+ return merge.bind(html, data, map);
660
+ };
661
+
662
+ //
663
+ // Expose the Mapper.
664
+ //
665
+ exports.Map = Mapper;
666
+ }(Plates, this);