sprockets-jsrender 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,847 @@
1
+ /*! JsRender v1.0pre: http://github.com/BorisMoore/jsrender */
2
+ /*
3
+ * Optimized version of jQuery Templates, for rendering to string.
4
+ * Does not require jQuery, or HTML DOM
5
+ * Integrates with JsViews (http://github.com/BorisMoore/jsviews)
6
+ * Copyright 2012, Boris Moore
7
+ * Released under the MIT License.
8
+ */
9
+ // informal pre beta commit counter: 3
10
+
11
+ this.jsviews || this.jQuery && jQuery.views || (function( window, undefined ) {
12
+
13
+ //========================== Top-level vars ==========================
14
+
15
+ var versionNumber = "v1.0pre",
16
+
17
+ $, rTag, rTmplString, extend,
18
+ sub = {},
19
+ FALSE = false, TRUE = true,
20
+ jQuery = window.jQuery,
21
+
22
+ rPath = /^(?:null|true|false|\d[\d.]*|([\w$]+|~([\w$]+)|#(view|([\w$]+))?)([\w$.]*?)(?:[.[]([\w$]+)\]?)?|(['"]).*\8)$/g,
23
+ // nil object helper view viewProperty pathTokens leafToken string
24
+
25
+ rParams = /(\()(?=|\s*\()|(?:([([])\s*)?(?:([#~]?[\w$.]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*!:?\/]|(=))\s*|([#~]?[\w$.]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*([)\]])([([]?))|(\s+)/g,
26
+ // lftPrn lftPrn2 path operator err eq path2 prn comma lftPrn2 apos quot rtPrn prn2 space
27
+ // (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space
28
+
29
+ rNewLine = /\r?\n/g,
30
+ rUnescapeQuotes = /\\(['"])/g,
31
+ rEscapeQuotes = /\\?(['"])/g,
32
+ rBuildHash = /\x08(~)?([^\x08]+)\x08/g,
33
+
34
+ autoViewKey = 0,
35
+ autoTmplName = 0,
36
+ escapeMapForHtml = {
37
+ "&": "&amp;",
38
+ "<": "&lt;",
39
+ ">": "&gt;"
40
+ },
41
+ tmplAttr = "data-jsv-tmpl",
42
+ fnDeclStr = "var j=j||" + (jQuery ? "jQuery." : "js") + "views,",
43
+ htmlSpecialChar = /[\x00"&'<>]/g,
44
+ slice = Array.prototype.slice,
45
+
46
+ render = {},
47
+
48
+ // jsviews object ($.views if jQuery is loaded)
49
+ jsv = {
50
+ jsviews: versionNumber,
51
+ sub: sub, // subscription, e.g. JsViews integration
52
+ debugMode: TRUE,
53
+ err: function( e ) {
54
+ return jsv.debugMode ? ("<br/><b>Error:</b> <em> " + (e.message || e) + ". </em>") : '""';
55
+ },
56
+ tmplFn: tmplFn,
57
+ render: render,
58
+ templates: templates,
59
+ tags: tags,
60
+ helpers: helpers,
61
+ converters: converters,
62
+ View: View,
63
+ convert: convert,
64
+ delimiters: setDelimiters,
65
+ tag: renderTag
66
+ };
67
+
68
+ //========================== Top-level functions ==========================
69
+
70
+ //===================
71
+ // jsviews.delimiters
72
+ //===================
73
+
74
+ function setDelimiters( openChars, closeChars ) {
75
+ // Set the tag opening and closing delimiters. Default is "{{" and "}}"
76
+ // openChar, closeChars: opening and closing strings, each with two characters
77
+ var firstOpenChar = "\\" + openChars.charAt( 0 ), // Escape the characters - since they could be regex special characters
78
+ secondOpenChar = "\\" + openChars.charAt( 1 ),
79
+ firstCloseChar = "\\" + closeChars.charAt( 0 ),
80
+ secondCloseChar = "\\" + closeChars.charAt( 1 );
81
+ // Build regex with new delimiters
82
+ jsv.rTag = rTag // make rTag available to JsViews (or other components) for parsing binding expressions
83
+ = secondOpenChar
84
+ // tag (followed by / space or }) or colon or html or code
85
+ + "(?:(?:(\\w+(?=[\\/\\s" + firstCloseChar + "]))|(?:(\\w+)?(:)|(>)|(\\*)))"
86
+ // params
87
+ + "\\s*((?:[^" + firstCloseChar + "]|" + firstCloseChar + "(?!" + secondCloseChar + "))*?)"
88
+ // slash or closeBlock
89
+ + "(\\/)?|(?:\\/(\\w+)))"
90
+ // }}
91
+ + firstCloseChar;
92
+
93
+ // Default rTag: tag converter colon html code params slash closeBlock
94
+ // /{{(?:(?:(\w+(?=[\/\s\}]))|(?:(\w+)?(:)|(>)|(\*)))\s*((?:[^}]|}(?!\}))*?)(\/)?|(?:\/(\w+)))}}
95
+
96
+ // /{{(?:(?:(\w+(?=[\/!\s\}!]))|(?:(\w+)?(:)|(>)|(\*)))((?:[^\}]|}(?!}))*?)(\/)?|(?:\/(\w+)))}}/g;
97
+ rTag = new RegExp( firstOpenChar + rTag + secondCloseChar, "g" );
98
+ rTmplString = new RegExp( "<.*>|" + openChars + ".*" + closeChars );
99
+ return this;
100
+ }
101
+
102
+ //=================
103
+ // View.hlp
104
+ //=================
105
+
106
+ function getHelper( helper ) {
107
+ // Helper method called as view.hlp() from compiled template, for helper functions or template parameters ~foo
108
+ var view = this,
109
+ tmplHelpers = view.tmpl.helpers || {};
110
+ helper = (view.ctx[ helper ] !== undefined ? view.ctx : tmplHelpers[ helper ] !== undefined ? tmplHelpers : helpers[ helper ] !== undefined ? helpers : {})[ helper ];
111
+ return typeof helper !== "function" ? helper : function() {
112
+ return helper.apply(view, arguments);
113
+ };
114
+ }
115
+
116
+ //=================
117
+ // jsviews.convert
118
+ //=================
119
+
120
+ function convert( converter, view, text ) {
121
+ var tmplConverters = view.tmpl.converters;
122
+ converter = tmplConverters && tmplConverters[ converter ] || converters[ converter ];
123
+ return converter ? converter.call( view, text ) : text;
124
+ }
125
+
126
+ //=================
127
+ // jsviews.tag
128
+ //=================
129
+
130
+ function renderTag( tag, parentView, converter, content, tagObject ) {
131
+ // Called from within compiled template function, to render a nested tag
132
+ // Returns the rendered tag
133
+ tagObject.props = tagObject.props || {};
134
+ var ret,
135
+ tmpl = tagObject.props.tmpl,
136
+ tmplTags = parentView.tmpl.tags,
137
+ nestedTemplates = parentView.tmpl.templates,
138
+ args = arguments,
139
+ tagFn = tmplTags && tmplTags[ tag ] || tags[ tag ];
140
+
141
+ if ( !tagFn ) {
142
+ return "";
143
+ }
144
+ // Set the tmpl property to the content of the block tag, unless set as an override property on the tag
145
+ content = content && parentView.tmpl.tmpls[ content - 1 ];
146
+ tmpl = tmpl || content || undefined;
147
+ tagObject.tmpl =
148
+ "" + tmpl === tmpl // if a string
149
+ ? nestedTemplates && nestedTemplates[ tmpl ] || templates[ tmpl ] || templates( tmpl )
150
+ : tmpl;
151
+
152
+ tagObject.isTag = TRUE;
153
+ tagObject.converter = converter;
154
+ tagObject.view = parentView;
155
+ tagObject.renderContent = renderContent;
156
+ if ( parentView.ctx ) {
157
+ extend( tagObject.ctx, parentView.ctx);
158
+ }
159
+
160
+ ret = tagFn.apply( tagObject, args.length > 5 ? slice.call( args, 5 ) : [] );
161
+ return ret || ( ret == undefined ? "" : ret.toString()); // (If ret is the value 0 or false, will render to string)
162
+ }
163
+
164
+ //=================
165
+ // View constructor
166
+ //=================
167
+
168
+ function View( context, path, parentView, data, template, index ) {
169
+ // Constructor for view object in view hierarchy. (Augmented by JsViews if JsViews is loaded)
170
+ var views = parentView.views,
171
+ // TODO: add this, as part of smart re-linking on existing content ($.link method), or remove completely
172
+ // self = parentView.views[ index ];
173
+ // if ( !self ) { ... }
174
+ self = {
175
+ tmpl: template,
176
+ path: path,
177
+ parent: parentView,
178
+ data: data,
179
+ ctx: context,
180
+ views: $.isArray( data ) ? [] : {},
181
+ hlp: getHelper
182
+ };
183
+
184
+ if ( $.isArray( views )) {
185
+ views.splice(
186
+ self.index = index !== undefined
187
+ ? index
188
+ : views.length, 0, self
189
+ );
190
+ } else {
191
+ views[ self.index = "_" + autoViewKey++ ] = self;
192
+ }
193
+ return self;
194
+ }
195
+
196
+ //=================
197
+ // Registration
198
+ //=================
199
+
200
+ function addToStore( self, store, name, item, process ) {
201
+ // Add item to named store such as templates, helpers, converters...
202
+ var key, onStore;
203
+ if ( name && typeof name === "object" && !name.nodeType ) {
204
+ // If name is a map, iterate over map and call store for key
205
+ for ( key in name ) {
206
+ store( key, name[ key ]);
207
+ }
208
+ return self;
209
+ }
210
+ if ( !name || item === undefined ) {
211
+ if ( process ) {
212
+ item = process( undefined, item || name );
213
+ }
214
+ } else if ( "" + name === name ) { // name must be a string
215
+ if ( item === null ) {
216
+ // If item is null, delete this entry
217
+ delete store[name];
218
+ } else if ( item = process ? process( name, item ) : item ) {
219
+ store[ name ] = item;
220
+ }
221
+ }
222
+ if ( onStore = sub.onStoreItem ) {
223
+ // e.g. JsViews integration
224
+ onStore( store, name, item, process );
225
+ }
226
+ return item;
227
+ }
228
+
229
+ function templates( name, tmpl ) {
230
+ // Register templates
231
+ // Setter: Use $.view.tags( name, tagFn ) or $.view.tags({ name: tagFn, ... }) to add additional tags to the registered tags collection.
232
+ // Getter: Use var tagFn = $.views.tags( name ) or $.views.tags[name] or $.views.tags.name to return the function for the registered tag.
233
+ // Remove: Use $.view.tags( name, null ) to remove a registered tag from $.view.tags.
234
+
235
+ // When registering for {{foo a b c==d e=f}}, tagFn should be a function with the signature:
236
+ // function(a,b). The 'this' pointer will be a hash with properties c and e.
237
+ return addToStore( this, templates, name, tmpl, compile );
238
+ }
239
+
240
+ function tags( name, tagFn ) {
241
+ // Register template tags
242
+ // Setter: Use $.view.tags( name, tagFn ) or $.view.tags({ name: tagFn, ... }) to add additional tags to the registered tags collection.
243
+ // Getter: Use var tagFn = $.views.tags( name ) or $.views.tags[name] or $.views.tags.name to return the function for the registered tag.
244
+ // Remove: Use $.view.tags( name, null ) to remove a registered tag from $.view.tags.
245
+
246
+ // When registering for {{foo a b c==d e=f}}, tagFn should be a function with the signature:
247
+ // function(a,b). The 'this' pointer will be a hash with properties c and e.
248
+ return addToStore( this, tags, name, tagFn );
249
+ }
250
+
251
+ function helpers( name, helperFn ) {
252
+ // Register helper functions for use in templates (or in data-link expressions if JsViews is loaded)
253
+ // Setter: Use $.view.helpers( name, helperFn ) or $.view.helpers({ name: helperFn, ... }) to add additional helpers to the registered helpers collection.
254
+ // Getter: Use var helperFn = $.views.helpers( name ) or $.views.helpers[name] or $.views.helpers.name to return the function.
255
+ // Remove: Use $.view.helpers( name, null ) to remove a registered helper function from $.view.helpers.
256
+ // Within a template, access the helper using the syntax: {{... ~myHelper(...) ...}}.
257
+ return addToStore( this, helpers, name, helperFn );
258
+ }
259
+
260
+ function converters( name, converterFn ) {
261
+ // Register converter functions for use in templates (or in data-link expressions if JsViews is loaded)
262
+ // Setter: Use $.view.converters( name, converterFn ) or $.view.converters({ name: converterFn, ... }) to add additional converters to the registered converters collection.
263
+ // Getter: Use var converterFn = $.views.converters( name ) or $.views.converters[name] or $.views.converters.name to return the converter function.
264
+ // Remove: Use $.view.converters( name, null ) to remove a registered converter from $.view.converters.
265
+ // Within a template, access the converter using the syntax: {{myConverter:...}}.
266
+ return addToStore( this, converters, name, converterFn );
267
+ }
268
+
269
+ //=================
270
+ // renderContent
271
+ //=================
272
+
273
+ function renderContent( data, context, parentView, path, index ) {
274
+ // Render template against data as a tree of subviews (nested template), or as a string (top-level template).
275
+ // tagName parameter for internal use only. Used for rendering templates registered as tags (which may have associated presenter objects)
276
+ var i, l, dataItem, newView, itemWrap, itemsWrap, itemResult, parentContext, tmpl, layout,
277
+ props = {},
278
+ swapContent = index === TRUE,
279
+ self = this,
280
+ result = "";
281
+
282
+ if ( self.isTag ) {
283
+ // This is a call from renderTag
284
+ tmpl = self.tmpl;
285
+ context = context || self.ctx;
286
+ parentView = parentView || self.view;
287
+ path = path || self.path;
288
+ index = index || self.index;
289
+ props = self.props;
290
+ } else {
291
+ tmpl = self.jquery && self[0] // This is a call $.fn.render
292
+ || self; // This is a call from tmpl.render
293
+ }
294
+ parentView = parentView || jsv.topView;
295
+ parentContext = parentView.ctx;
296
+ layout = tmpl.layout;
297
+ if ( data === parentView ) {
298
+ // Inherit the data from the parent view.
299
+ // This may be the contents of an {{if}} block
300
+ data = parentView.data;
301
+ layout = TRUE;
302
+ }
303
+
304
+ // Set additional context on views created here, (as modified context inherited from the parent, and be inherited by child views)
305
+ // Note: If no jQuery, extend does not support chained copies - so limit extend() to two parameters
306
+ context = (context && context === parentContext)
307
+ ? parentContext
308
+ : (parentContext
309
+ // if parentContext, make copy
310
+ ? ((parentContext = extend( {}, parentContext ), context)
311
+ // If context, merge context with copied parentContext
312
+ ? extend( parentContext, context )
313
+ : parentContext)
314
+ // if no parentContext, use context, or default to {}
315
+ : context || {});
316
+
317
+ if ( props.link === FALSE ) {
318
+ // Override inherited value of link by an explicit setting in props: link=false
319
+ // The child views of an unlinked view are also unlinked. So setting child back to true will not have any effect.
320
+ context.link = FALSE;
321
+ }
322
+ if ( !tmpl.fn ) {
323
+ tmpl = templates[ tmpl ] || templates( tmpl );
324
+ }
325
+ itemWrap = context.link && sub.onRenderItem;
326
+ itemsWrap = context.link && sub.onRenderItems;
327
+
328
+ if ( tmpl ) {
329
+ if ( $.isArray( data ) && !layout ) {
330
+ // Create a view for the array, whose child views correspond to each data item.
331
+ // (Note: if index and parentView are passed in along with parent view, treat as
332
+ // insert -e.g. from view.addViews - so parentView is already the view item for array)
333
+ newView = swapContent ? parentView : (index !== undefined && parentView) || View( context, path, parentView, data, tmpl, index );
334
+
335
+ for ( i = 0, l = data.length; i < l; i++ ) {
336
+ // Create a view for each data item.
337
+ dataItem = data[ i ];
338
+ itemResult = tmpl.fn( dataItem, View( context, path, newView, dataItem, tmpl, (index||0) + i ), jsv );
339
+ result += itemWrap ? itemWrap( itemResult, props ) : itemResult;
340
+ }
341
+ } else {
342
+ // Create a view for singleton data object.
343
+ newView = swapContent ? parentView : View( context, path, parentView, data, tmpl, index );
344
+ result += (data || layout) ? tmpl.fn( data, newView, jsv ) : "";
345
+ }
346
+ parentView.topKey = newView.index;
347
+ return itemsWrap ? itemsWrap( result, path, newView.index, tmpl, props ) : result;
348
+ }
349
+ return ""; // No tmpl. Could throw...
350
+ }
351
+
352
+ //===========================
353
+ // Build and compile template
354
+ //===========================
355
+
356
+ // Generate a reusable function that will serve to render a template against data
357
+ // (Compile AST then build template function)
358
+
359
+ function syntaxError() {
360
+ throw "Syntax error";
361
+ }
362
+
363
+ function tmplFn( markup, tmpl, bind ) {
364
+ // Compile markup to AST (abtract syntax tree) then build the template function code from the AST nodes
365
+ // Used for compiling templates, and also by JsViews to build functions for data link expressions
366
+ var newNode, node, i, l, code, hasTag, hasEncoder, getsValue, hasConverter, hasViewPath, tag, converter, params, hash, nestedTmpl, allowCode,
367
+ tmplOptions = tmpl ? {
368
+ allowCode: allowCode = tmpl.allowCode,
369
+ debug: tmpl.debug
370
+ } : {},
371
+ nested = tmpl && tmpl.tmpls,
372
+ astTop = [],
373
+ loc = 0,
374
+ stack = [],
375
+ content = astTop,
376
+ current = [,,,astTop],
377
+ nestedIndex = 0;
378
+
379
+ //==== nested functions ====
380
+ function pushPreceedingContent( shift ) {
381
+ shift -= loc;
382
+ if ( shift ) {
383
+ content.push( markup.substr( loc, shift ).replace( rNewLine, "\\n" ));
384
+ }
385
+ }
386
+
387
+ function parseTag( all, tagName, converter, colon, html, code, params, slash, closeBlock, index ) {
388
+ // tag converter colon html code params slash closeBlock
389
+ // /{{(?:(?:(\w+(?=[\/!\s\}!]))|(?:(\w+)?(:)|(?:(>)|(\*)))((?:[^\}]|}(?!}))*?)(\/)?|(?:\/(\w+)))}}/g;
390
+ // Build abstract syntax tree (AST): [ tagName, converter, params, content, hash, contentMarkup ]
391
+ if ( html ) {
392
+ colon = ":";
393
+ converter = "html";
394
+ }
395
+ var hash = "",
396
+ passedCtx = "",
397
+ block = !slash && !colon; // Block tag if not self-closing and not {{:}} or {{>}} (special case)
398
+
399
+ //==== nested helper function ====
400
+
401
+ tagName = tagName || colon;
402
+ pushPreceedingContent( index );
403
+ loc = index + all.length; // location marker - parsed up to here
404
+ if ( code ) {
405
+ if ( allowCode ) {
406
+ content.push([ "*", params.replace( rUnescapeQuotes, "$1" ) ]);
407
+ }
408
+ } else if ( tagName ) {
409
+ if ( tagName === "else" ) {
410
+ current[ 5 ] = markup.substring( current[ 5 ], index ); // contentMarkup for block tag
411
+ current = stack.pop();
412
+ content = current[ 3 ];
413
+ block = TRUE;
414
+ }
415
+ params = (params
416
+ ? parseParams( params, bind )
417
+ .replace( rBuildHash, function( all, isCtx, keyValue ) {
418
+ if ( isCtx ) {
419
+ passedCtx += keyValue + ",";
420
+ } else {
421
+ hash += keyValue + ",";
422
+ }
423
+ return "";
424
+ })
425
+ : "");
426
+ hash = hash.slice( 0, -1 );
427
+ params = params.slice( 0, -1 );
428
+ newNode = [
429
+ tagName,
430
+ converter || "",
431
+ params,
432
+ block && [],
433
+ "{" + (hash ? ("props:{" + hash + "},"): "") + "path:'" + params + "'" + (passedCtx ? ",ctx:{" + passedCtx.slice( 0, -1 ) + "}" : "") + "}"
434
+ ];
435
+ if ( block ) {
436
+ stack.push( current );
437
+ current = newNode;
438
+ current[ 5 ] = loc; // Store current location of open tag, to be able to add contentMarkup when we reach closing tag
439
+ }
440
+ content.push( newNode );
441
+ } else if ( closeBlock ) {
442
+ //if ( closeBlock !== current[ 0 ]) {
443
+ // throw "unmatched close tag: /" + closeBlock + ". Expected /" + current[ 0 ];
444
+ //}
445
+ current[ 5 ] = markup.substring( current[ 5 ], index ); // contentMarkup for block tag
446
+ current = stack.pop();
447
+ }
448
+ if ( !current ) {
449
+ throw "Expected block tag";
450
+ }
451
+ content = current[ 3 ];
452
+ }
453
+ //==== /end of nested functions ====
454
+
455
+ markup = markup.replace( rEscapeQuotes, "\\$1" );
456
+
457
+ // Build the AST (abstract syntax tree) under astTop
458
+ markup.replace( rTag, parseTag );
459
+
460
+ pushPreceedingContent( markup.length );
461
+
462
+ // Use the AST (astTop) to build the template function
463
+ l = astTop.length;
464
+ code = (l ? "" : '"";');
465
+
466
+ for ( i = 0; i < l; i++ ) {
467
+ // AST nodes: [ tagName, converter, params, content, hash, contentMarkup ]
468
+ node = astTop[ i ];
469
+ if ( node[ 0 ] === "*" ) {
470
+ code = code.slice( 0, i ? -1 : -3 ) + ";" + node[ 1 ] + (i + 1 < l ? "ret+=" : "");
471
+ } else if ( "" + node === node ) { // type string
472
+ code += '"' + node + '"+';
473
+ } else {
474
+ tag = node[ 0 ];
475
+ converter = node[ 1 ];
476
+ params = node[ 2 ];
477
+ content = node[ 3 ];
478
+ hash = node[ 4 ];
479
+ markup = node[ 5 ];
480
+ if ( content ) {
481
+ // Create template object for nested template
482
+ nestedTmpl = TmplObject( markup, tmplOptions, tmpl, nestedIndex++ );
483
+ // Compile to AST and then to compiled function
484
+ tmplFn( markup, nestedTmpl);
485
+ nested.push( nestedTmpl );
486
+ }
487
+ hasViewPath = hasViewPath || hash.indexOf( "view" ) > -1;
488
+ code += (tag === ":"
489
+ ? (converter === "html"
490
+ ? (hasEncoder = TRUE, "e(" + params)
491
+ : converter
492
+ ? (hasConverter = TRUE, 'c("' + converter + '",view,' + params)
493
+ : (getsValue = TRUE, "((v=" + params + ')!=u?v:""')
494
+ )
495
+ : (hasTag = TRUE, 't("' + tag + '",view,"' + (converter || "") + '",'
496
+ + (content ? nested.length : '""') // For block tags, pass in the key (nested.length) to the nested content template
497
+ + "," + hash + (params ? "," : "") + params))
498
+ + ")+";
499
+ }
500
+ }
501
+ code = new Function( "data, view, j, b, u", fnDeclStr
502
+ + (getsValue ? "v," : "")
503
+ + (hasTag ? "t=j.tag," : "")
504
+ + (hasConverter ? "c=j.convert," : "")
505
+ + (hasEncoder ? "e=j.converters.html," : "")
506
+ + "ret; try{\n\n"
507
+ + (tmplOptions.debug ? "debugger;" : "")
508
+ + (allowCode ? 'ret=' : 'return ')
509
+ + code.slice( 0, -1 ) + ";\n\n"
510
+ + (allowCode ? "return ret;" : "")
511
+ + "}catch(e){return j.err(e);}"
512
+ );
513
+
514
+ // Include only the var references that are needed in the code
515
+ if ( tmpl ) {
516
+ tmpl.fn = code;
517
+ tmpl.useVw = hasConverter || hasViewPath || hasTag;
518
+ }
519
+ return code;
520
+ }
521
+
522
+ function parseParams( params, bind ) {
523
+ var named,
524
+ fnCall = {},
525
+ parenDepth = 0,
526
+ quoted = FALSE, // boolean for string content in double quotes
527
+ aposed = FALSE; // or in single quotes
528
+
529
+ function parseTokens( all, lftPrn0, lftPrn, path, operator, err, eq, path2, prn, comma, lftPrn2, apos, quot, rtPrn, prn2, space ) {
530
+ // rParams = /(?:([([])\s*)?(?:([#~]?[\w$.]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*!:?\/]|(=))\s*|([#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*([)\]])([([]?))|(\s+)/g,
531
+ // lftPrn path operator err eq path2 prn comma lftPrn3 apos quot rtPrn prn2 space
532
+ // (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space
533
+ operator = operator || "";
534
+ lftPrn = lftPrn || lftPrn0 || lftPrn2;
535
+ path = path || path2;
536
+ prn = prn || prn2 || "";
537
+ operator = operator || "";
538
+
539
+ function parsePath( all, object, helper, view, viewProperty, pathTokens, leafToken ) {
540
+ // rPath = /^(?:null|true|false|\d[\d.]*|([\w$]+|~([\w$]+)|#(view|([\w$]+))?)([\w$.]*?)(?:[.[]([\w$]+)\]?)?|(['"]).*\8)$/g,
541
+ // object helper view viewProperty pathTokens leafToken string
542
+ if ( object ) {
543
+ var ret = (helper
544
+ ? 'view.hlp("' + helper + '")'
545
+ : view
546
+ ? "view"
547
+ : "data")
548
+ + (leafToken
549
+ ? (viewProperty
550
+ ? "." + viewProperty
551
+ : helper
552
+ ? ""
553
+ : (view ? "" : "." + object)
554
+ ) + (pathTokens || "")
555
+ : (leafToken = helper ? "" : view ? viewProperty || "" : object, ""));
556
+
557
+ if ( bind && prn !== "(" ) {
558
+ ret = "b(" + ret + ',"' + leafToken + '")';
559
+ }
560
+ return ret + (leafToken ? "." + leafToken : "");
561
+ }
562
+ return all;
563
+ }
564
+
565
+ if ( err ) {
566
+ syntaxError();
567
+ } else {
568
+ return (aposed
569
+ // within single-quoted string
570
+ ? (aposed = !apos, (aposed ? all : '"'))
571
+ : quoted
572
+ // within double-quoted string
573
+ ? (quoted = !quot, (quoted ? all : '"'))
574
+ :
575
+ (
576
+ (lftPrn
577
+ ? (parenDepth++, lftPrn)
578
+ : "")
579
+ + (space
580
+ ? (parenDepth
581
+ ? ""
582
+ : named
583
+ ? (named = FALSE, "\b")
584
+ : ","
585
+ )
586
+ : eq
587
+ // named param
588
+ ? (parenDepth && syntaxError(), named = TRUE, '\b' + path + ':')
589
+ : path
590
+ // path
591
+ ? (path.replace( rPath, parsePath )
592
+ + (prn
593
+ ? (fnCall[ ++parenDepth ] = TRUE, prn)
594
+ : operator)
595
+ )
596
+ : operator
597
+ ? all
598
+ : rtPrn
599
+ // function
600
+ ? ((fnCall[ parenDepth-- ] = FALSE, rtPrn)
601
+ + (prn
602
+ ? (fnCall[ ++parenDepth ] = TRUE, prn)
603
+ : "")
604
+ )
605
+ : comma
606
+ ? (fnCall[ parenDepth ] || syntaxError(), ",") // We don't allow top-level literal arrays or objects
607
+ : lftPrn0
608
+ ? ""
609
+ : (aposed = apos, quoted = quot, '"')
610
+ ))
611
+ );
612
+ }
613
+ }
614
+ params = (params + " " ).replace( rParams, parseTokens );
615
+ return params;
616
+ }
617
+
618
+ function compile( name, tmpl, parent, options ) {
619
+ // tmpl is either a template object, a selector for a template script block, the name of a compiled template, or a template object
620
+ // options is the set of template properties, c
621
+ var tmplOrMarkup, elem, key, nested, nestedItem;
622
+
623
+ //==== nested functions ====
624
+ function tmplOrMarkupFromStr( value ) {
625
+ // If value is of type string - treat as selector, or name of compiled template
626
+ // Return the template object, if already compiled, or the markup string
627
+
628
+ if ( ("" + value === value) || value.nodeType > 0 ) {
629
+ // If selector is valid and returns at least one element, get first element
630
+ elem = value.nodeType > 0 ? value : !rTmplString.test( value ) && jQuery && jQuery( value )[0];
631
+ if ( elem && elem.type ) {
632
+ // It is a script element
633
+ // Create a name for data linking if none provided
634
+ value = templates[ elem.getAttribute( tmplAttr )];
635
+ if ( !value ) {
636
+ // Not already compiled and cached, so compile and cache the name
637
+ name = name || "_" + autoTmplName++;
638
+ elem.setAttribute( tmplAttr, name );
639
+ value = compile( name, elem.innerHTML, parent, options ); // Use tmpl as options
640
+ templates[ name ] = value;
641
+ }
642
+ }
643
+ return value;
644
+ }
645
+ // If value is not a string or dom element, return undefined
646
+ }
647
+
648
+ //==== Compile the template ====
649
+ tmplOrMarkup = tmplOrMarkupFromStr( tmpl );
650
+
651
+ // If tmpl is a template object, use it for options
652
+ options = options || (tmpl.markup ? tmpl : {});
653
+ options.name = name;
654
+ nested = options.templates;
655
+
656
+ // If tmpl is not a markup string or a selector string, then it must be a template object
657
+ // In that case, get it from the markup property of the object
658
+ if ( !tmplOrMarkup && tmpl.markup && (tmplOrMarkup = tmplOrMarkupFromStr( tmpl.markup ))) {
659
+ if ( tmplOrMarkup.fn && (tmplOrMarkup.debug !== tmpl.debug || tmplOrMarkup.allowCode !== tmpl.allowCode )) {
660
+ // if the string references a compiled template object, but the debug or allowCode props are different, need to recompile
661
+ tmplOrMarkup = tmplOrMarkup.markup;
662
+ }
663
+ }
664
+ if ( tmplOrMarkup !== undefined ) {
665
+ if ( name && !parent ) {
666
+ render[ name ] = function() {
667
+ return tmpl.render.apply( tmpl, arguments );
668
+ };
669
+ }
670
+ if ( tmplOrMarkup.fn || tmpl.fn ) {
671
+ // tmpl is already compiled, so use it, or if different name is provided, clone it
672
+ if ( tmplOrMarkup.fn ) {
673
+ if ( name && name !== tmplOrMarkup.name ) {
674
+ tmpl = extend( extend( {}, tmplOrMarkup ), options);
675
+ } else {
676
+ tmpl = tmplOrMarkup;
677
+ }
678
+ }
679
+ } else {
680
+ // tmplOrMarkup is a markup string, not a compiled template
681
+ // Create template object
682
+ tmpl = TmplObject( tmplOrMarkup, options, parent, 0 );
683
+ // Compile to AST and then to compiled function
684
+ tmplFn( tmplOrMarkup, tmpl );
685
+ }
686
+ for ( key in nested ) {
687
+ // compile nested template declarations
688
+ nestedItem = nested[ key ];
689
+ if ( nestedItem.name !== key ) {
690
+ nested[ key ] = compile( key, nestedItem, tmpl );
691
+ }
692
+ }
693
+ return tmpl;
694
+ }
695
+ }
696
+ //==== /end of function compile ====
697
+
698
+ function TmplObject( markup, options, parent, index ) {
699
+ // Template object constructor
700
+
701
+ // nested helper function
702
+ function extendStore( storeName ) {
703
+ if ( parent[ storeName ]) {
704
+ // Include parent items except if overridden by item of same name in options
705
+ tmpl[ storeName ] = extend( extend( {}, parent[ storeName ] ), options[ storeName ] );
706
+ }
707
+ }
708
+
709
+ options = options || {};
710
+ var tmpl = {
711
+ markup: markup,
712
+ tmpls: [],
713
+ links: [],
714
+ render: renderContent
715
+ };
716
+ if ( parent ) {
717
+ if ( parent.templates ) {
718
+ tmpl.templates = extend( extend( {}, parent.templates ), options.templates );
719
+ }
720
+ tmpl.parent = parent;
721
+ tmpl.name = parent.name + "[" + index + "]";
722
+ tmpl.index = index;
723
+ }
724
+
725
+ extend( tmpl, options );
726
+ if ( parent ) {
727
+ extendStore( "templates" );
728
+ extendStore( "tags" );
729
+ extendStore( "helpers" );
730
+ extendStore( "converters" );
731
+ }
732
+ return tmpl;
733
+ }
734
+
735
+ //========================== Initialize ==========================
736
+
737
+ if ( jQuery ) {
738
+ ////////////////////////////////////////////////////////////////////////////////////////////////
739
+ // jQuery is loaded, so make $ the jQuery object
740
+ $ = jQuery;
741
+ $.templates = templates;
742
+ $.render = render;
743
+ $.views = jsv;
744
+ $.fn.render = renderContent;
745
+
746
+ } else {
747
+ ////////////////////////////////////////////////////////////////////////////////////////////////
748
+ // jQuery is not loaded.
749
+
750
+ $ = window.jsviews = jsv;
751
+ $.extend = function( target, source ) {
752
+ var name;
753
+ target = target || {};
754
+ for ( name in source ) {
755
+ target[ name ] = source[ name ];
756
+ }
757
+ return target;
758
+ };
759
+
760
+ $.isArray = Array && Array.isArray || function( obj ) {
761
+ return Object.prototype.toString.call( obj ) === "[object Array]";
762
+ };
763
+ }
764
+
765
+ extend = $.extend;
766
+
767
+ jsv.topView = { views: {}, tmpl: {}, hlp: getHelper, ctx: jsv.helpers };
768
+
769
+ function replacerForHtml( ch ) {
770
+ // Original code from Mike Samuel <msamuel@google.com>
771
+ return escapeMapForHtml[ ch ]
772
+ // Intentional assignment that caches the result of encoding ch.
773
+ || (escapeMapForHtml[ ch ] = "&#" + ch.charCodeAt( 0 ) + ";");
774
+ }
775
+
776
+ //========================== Register tags ==========================
777
+
778
+ tags({
779
+ "if": function() {
780
+ var ifTag = this,
781
+ view = ifTag.view;
782
+
783
+ view.onElse = function( tagObject, args ) {
784
+ var i = 0,
785
+ l = args.length;
786
+
787
+ while ( l && !args[ i++ ]) {
788
+ // Only render content if args.length === 0 (i.e. this is an else with no condition) or if a condition argument is truey
789
+ if ( i === l ) {
790
+ return "";
791
+ }
792
+ }
793
+ view.onElse = undefined; // If condition satisfied, so won't run 'else'.
794
+ tagObject.path = "";
795
+ return tagObject.renderContent( view );
796
+ // Test is satisfied, so render content, while remaining in current data context
797
+ // By passing the view, we inherit data context from the parent view, and the content is treated as a layout template
798
+ // (so if the data is an array, it will not iterate over the data
799
+ };
800
+ return view.onElse( this, arguments );
801
+ },
802
+ "else": function() {
803
+ var view = this.view;
804
+ return view.onElse ? view.onElse( this, arguments ) : "";
805
+ },
806
+ "for": function() {
807
+ var i,
808
+ self = this,
809
+ result = "",
810
+ args = arguments,
811
+ l = args.length;
812
+ if ( self.props.layout ) {
813
+ self.tmpl.layout = TRUE;
814
+ }
815
+ for ( i = 0; i < l; i++ ) {
816
+ result += self.renderContent( args[ i ]);
817
+ }
818
+ return result;
819
+ },
820
+ "=": function( value ) {
821
+ return value;
822
+ },
823
+ "*": function( value ) {
824
+ return value;
825
+ }
826
+ });
827
+
828
+ //========================== Register global helpers ==========================
829
+
830
+ // helpers({ // Global helper functions
831
+ // // TODO add any useful built-in helper functions
832
+ // });
833
+
834
+ //========================== Register converters ==========================
835
+
836
+ converters({
837
+ html: function( text ) {
838
+ // HTML encoding helper: Replace < > & and ' and " by corresponding entities.
839
+ // inspired by Mike Samuel <msamuel@google.com>
840
+ return text != undefined ? String( text ).replace( htmlSpecialChar, replacerForHtml ) : "";
841
+ }
842
+ });
843
+
844
+ //========================== Define default delimiters ==========================
845
+ setDelimiters( "{{", "}}" );
846
+
847
+ })( this );