jsrender-rails 1.2 → 2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +3 -3
- data/jsrender-rails.gemspec +1 -1
- data/lib/jsrender-rails/engine.rb +1 -1
- data/lib/jsrender-rails/jsrender.rb +1 -1
- data/lib/jsrender-rails/version.rb +2 -2
- data/spec/dummy/app/assets/javascripts/application.js +2 -1
- data/spec/dummy/app/views/main/index.html.erb +3 -1
- data/spec/dummy/app/views/main/prefix.html.erb +3 -1
- data/spec/integration_spec.rb +1 -1
- data/spec/jsrender-rails/jsrender_spec.rb +2 -2
- data/vendor/assets/javascripts/jsrender.js +144 -133
- metadata +4 -4
data/README.md
CHANGED
@@ -8,10 +8,10 @@ Add it to your Gemfile and run `bundle` or run `gem install jsrender-rails`.
|
|
8
8
|
|
9
9
|
## Usage
|
10
10
|
|
11
|
-
jsrender tempaltes will be recognized by Sprockets with the `.
|
11
|
+
jsrender tempaltes will be recognized by Sprockets with the `.jsr` extension. Place them anywhere in the Sprockets load path.
|
12
12
|
|
13
13
|
```html
|
14
|
-
<!-- app/assets/javascripts/views/user.
|
14
|
+
<!-- app/assets/javascripts/views/user.jsr -->
|
15
15
|
<div class="user">{{>name}}</div>
|
16
16
|
```
|
17
17
|
|
@@ -47,7 +47,7 @@ config.jsrender.prefix = %r{([^/]*/)*}
|
|
47
47
|
|
48
48
|
## Compatible with jQuery Templates?
|
49
49
|
|
50
|
-
|
50
|
+
Yes, due to the fact that you use the .jsr extension instead of the prior .tmpl one it should work in parallel.
|
51
51
|
|
52
52
|
## HAML
|
53
53
|
|
data/jsrender-rails.gemspec
CHANGED
@@ -16,7 +16,7 @@ module JsrenderRails
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def evaluate(scope, locals, &block)
|
19
|
-
%{$.templates("#{template_name(scope)}", "#{escape_javascript(data)}");}
|
19
|
+
%{(function($) {$.views.templates("#{template_name(scope)}", "#{escape_javascript(data)}");})((typeof jQuery !== "undefined" && jQuery !== null) ? jQuery : {views: jsviews});}
|
20
20
|
end
|
21
21
|
|
22
22
|
private
|
@@ -1,3 +1,5 @@
|
|
1
1
|
<script>
|
2
|
-
|
2
|
+
(function($) {
|
3
|
+
document.body.innerHTML = $.views.render["views/user"]({name:"Sebastian Pape"});
|
4
|
+
})((typeof jQuery !== "undefined" && jQuery !== null) ? jQuery : {views: jsviews});
|
3
5
|
</script>
|
@@ -1,3 +1,5 @@
|
|
1
1
|
<script>
|
2
|
-
|
2
|
+
(function($) {
|
3
|
+
document.body.innerHTML = $.views.render["user"]({name:"Sebastian Pape"});
|
4
|
+
})((typeof jQuery !== "undefined" && jQuery !== null) ? jQuery : {views: jsviews});
|
3
5
|
</script>
|
data/spec/integration_spec.rb
CHANGED
@@ -7,9 +7,9 @@ describe JsrenderRails::Jsrender do
|
|
7
7
|
Rails.application.assets["jsrender"].should_not be_nil
|
8
8
|
end
|
9
9
|
|
10
|
-
it "compiles templates with the .
|
10
|
+
it "compiles templates with the .jsr extension" do
|
11
11
|
template = Rails.application.assets["views/user"]
|
12
|
-
template.to_s.should == %{$.templates("views/user", "<div class=\\\"user\\\">{{>name}}<\\/div>\\n");}
|
12
|
+
template.to_s.should == %{(function($) {$.templates(\"views/user\", \"<div class=\\\"user\\\">{{>name}}<\\/div>\\n\");})((typeof jQuery !== \"undefined\" && jQuery !== null) ? jQuery : {views: jsviews});}
|
13
13
|
end
|
14
14
|
|
15
15
|
context "when prefix is set" do
|
@@ -1,12 +1,11 @@
|
|
1
|
-
/*! JsRender v1.
|
1
|
+
/*! JsRender v1.0.0-beta: http://github.com/BorisMoore/jsrender and http://jsviews.com/jsviews */
|
2
2
|
/*
|
3
3
|
* Optimized version of jQuery Templates, for rendering to string.
|
4
4
|
* Does not require jQuery, or HTML DOM
|
5
|
-
* Integrates with JsViews (http://
|
5
|
+
* Integrates with JsViews (http://jsviews.com/jsviews)
|
6
6
|
* Copyright 2013, Boris Moore
|
7
7
|
* Released under the MIT License.
|
8
8
|
*/
|
9
|
-
// informal pre beta commit counter: 30 (Beta Candidate)
|
10
9
|
|
11
10
|
(function(global, jQuery, undefined) {
|
12
11
|
// global is the this object, which is window when running in the usual browser environment.
|
@@ -16,29 +15,28 @@
|
|
16
15
|
|
17
16
|
//========================== Top-level vars ==========================
|
18
17
|
|
19
|
-
var versionNumber = "v1.
|
18
|
+
var versionNumber = "v1.0.0-beta",
|
19
|
+
|
20
|
+
$, jsvStoreName, rTag, rTmplString,// nodeJsModule,
|
20
21
|
|
21
|
-
$, jsvStoreName, rTag, rTmplString,
|
22
22
|
//TODO tmplFnsCache = {},
|
23
23
|
delimOpenChar0 = "{", delimOpenChar1 = "{", delimCloseChar0 = "}", delimCloseChar1 = "}", linkChar = "^",
|
24
24
|
|
25
25
|
rPath = /^(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
|
26
26
|
// object helper view viewProperty pathTokens leafToken
|
27
27
|
|
28
|
-
rParams = /(\()(?=\s*\()|(?:([([])\s*)?(?:([#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*!:?\/]|(=))\s*|([#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*((\)
|
29
|
-
// lftPrn
|
28
|
+
rParams = /(\()(?=\s*\()|(?:([([])\s*)?(?:([#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*!:?\/]|(=))\s*|([#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*\.|\s*\^)|[)\]])([([]?))|(\s+)/g,
|
29
|
+
// lftPrn0 lftPrn path operator err eq path2 prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space
|
30
30
|
// (left paren? followed by (path? followed by operator) or (path followed by left paren?)) or comma or apos or quot or right paren or space
|
31
31
|
|
32
|
-
rNewLine = /\s*\n
|
32
|
+
rNewLine = /\s*\n/g,
|
33
33
|
rUnescapeQuotes = /\\(['"])/g,
|
34
|
-
//
|
35
|
-
rEscapeQuotes = /([\\'"])/g,
|
34
|
+
rEscapeQuotes = /['"\\]/g, // Escape quotes and \ character
|
36
35
|
rBuildHash = /\x08(~)?([^\x08]+)\x08/g,
|
37
36
|
rTestElseIf = /^if\s/,
|
38
37
|
rFirstElem = /<(\w+)[>\s]/,
|
39
|
-
|
40
|
-
|
41
|
-
rHtmlEncode = /[><"'&]/g,
|
38
|
+
rAttrEncode = /[\x00`><"'&]/g, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings
|
39
|
+
rHtmlEncode = rAttrEncode,
|
42
40
|
autoTmplName = 0,
|
43
41
|
viewId = 0,
|
44
42
|
charEntities = {
|
@@ -47,10 +45,10 @@
|
|
47
45
|
">": ">",
|
48
46
|
"\x00": "�",
|
49
47
|
"'": "'",
|
50
|
-
'"': """
|
48
|
+
'"': """,
|
49
|
+
"`": "`"
|
51
50
|
},
|
52
51
|
tmplAttr = "data-jsv-tmpl",
|
53
|
-
slice = [].slice,
|
54
52
|
|
55
53
|
$render = {},
|
56
54
|
jsvStores = {
|
@@ -68,7 +66,6 @@
|
|
68
66
|
$views = {
|
69
67
|
jsviews: versionNumber,
|
70
68
|
render: $render,
|
71
|
-
View: View,
|
72
69
|
settings: {
|
73
70
|
delimiters: $viewsDelimiters,
|
74
71
|
debugMode: true,
|
@@ -76,18 +73,17 @@
|
|
76
73
|
},
|
77
74
|
sub: {
|
78
75
|
// subscription, e.g. JsViews integration
|
76
|
+
View: View,
|
79
77
|
Error: JsViewsError,
|
80
78
|
tmplFn: tmplFn,
|
81
79
|
parse: parseParams,
|
82
80
|
extend: $extend,
|
83
81
|
error: error,
|
84
82
|
syntaxError: syntaxError
|
85
|
-
//TODO invoke: $invoke
|
86
83
|
},
|
87
84
|
_cnvt: convertVal,
|
88
85
|
_tag: renderTag,
|
89
86
|
|
90
|
-
// TODO provide better debug experience - e.g. support $.views.onError callback
|
91
87
|
_err: function(e) {
|
92
88
|
// Place a breakpoint here to intercept template rendering errors
|
93
89
|
return $viewsSettings.debugMode ? ("Error: " + (e.message || e)) + ". " : '';
|
@@ -115,15 +111,6 @@
|
|
115
111
|
return target;
|
116
112
|
}
|
117
113
|
|
118
|
-
//TODO function $invoke() {
|
119
|
-
// try {
|
120
|
-
// return arguments[1].apply(arguments[0], arguments[2]);
|
121
|
-
// }
|
122
|
-
// catch(e) {
|
123
|
-
// throw new $views.sub.Error(e, arguments[0]);
|
124
|
-
// }
|
125
|
-
// }
|
126
|
-
|
127
114
|
(JsViewsError.prototype = new Error()).constructor = JsViewsError;
|
128
115
|
|
129
116
|
//========================== Top-level functions ==========================
|
@@ -296,6 +283,7 @@
|
|
296
283
|
value = boundTagCtx && view._.onRender
|
297
284
|
? view._.onRender(value, view, boundTagCtx)
|
298
285
|
: value;
|
286
|
+
view._.tag = undefined;
|
299
287
|
}
|
300
288
|
return value;
|
301
289
|
}
|
@@ -304,15 +292,15 @@
|
|
304
292
|
// jsviews._tag
|
305
293
|
//=============
|
306
294
|
|
307
|
-
function getResource(
|
295
|
+
function getResource(resourceType, itemName) {
|
308
296
|
var res,
|
309
297
|
view = this,
|
310
|
-
store = $views[
|
298
|
+
store = $views[resourceType];
|
311
299
|
|
312
|
-
res = store && store[
|
300
|
+
res = store && store[itemName];
|
313
301
|
while ((res === undefined) && view) {
|
314
|
-
store = view.tmpl[
|
315
|
-
res = store && store[
|
302
|
+
store = view.tmpl[resourceType];
|
303
|
+
res = store && store[itemName];
|
316
304
|
view = view.parent;
|
317
305
|
}
|
318
306
|
return res;
|
@@ -322,14 +310,18 @@
|
|
322
310
|
// Called from within compiled template function, to render a template tag
|
323
311
|
// Returns the rendered tag
|
324
312
|
|
325
|
-
var render, tag, tags, attr,
|
313
|
+
var render, tag, tags, attr, parentTag, i, l, itemRet, tagCtx, tagCtxCtx, content, boundTagFn, tagDef, callInit,
|
326
314
|
ret = "",
|
327
315
|
boundTagKey = +tagCtxs === tagCtxs && tagCtxs, // if tagCtxs is an integer, then it is the boundTagKey
|
328
316
|
linkCtx = parentView.linkCtx || 0,
|
329
317
|
ctx = parentView.ctx,
|
330
318
|
parentTmpl = tmpl || parentView.tmpl,
|
331
|
-
parentView_ = parentView._
|
332
|
-
|
319
|
+
parentView_ = parentView._;
|
320
|
+
|
321
|
+
if (tagName._is === "tag") {
|
322
|
+
tag = tagName;
|
323
|
+
tagName = tag.tagName;
|
324
|
+
}
|
333
325
|
|
334
326
|
// Provide tagCtx, linkCtx and ctx access from tag
|
335
327
|
if (boundTagKey) {
|
@@ -350,7 +342,7 @@
|
|
350
342
|
if (!i && (!tmpl || !tag)) {
|
351
343
|
tagDef = parentView.getRsc("tags", tagName) || error("Unknown tag: {{"+ tagName + "}}");
|
352
344
|
}
|
353
|
-
tmpl = tmpl ||
|
345
|
+
tmpl = tmpl || (tag ? tag._def : tagDef).template || content;
|
354
346
|
tmpl = "" + tmpl === tmpl // if a string
|
355
347
|
? parentView.getRsc("templates", tmpl) || $templates(tmpl)
|
356
348
|
: tmpl;
|
@@ -366,13 +358,14 @@
|
|
366
358
|
if (!tag) {
|
367
359
|
// This will only be hit for initial tagCtx (not for {{else}}) - if the tag instance does not exist yet
|
368
360
|
// Instantiate tag if it does not yet exist
|
369
|
-
if (tagDef.
|
361
|
+
if (tagDef._ctr) {
|
370
362
|
// If the tag has not already been instantiated, we will create a new instance.
|
371
363
|
// ~tag will access the tag, even within the rendering of the template content of this tag.
|
372
364
|
// From child/descendant tags, can access using ~tag.parent, or ~parentTags.tagName
|
373
365
|
// TODO provide error handling owned by the tag - using tag.onError
|
374
366
|
// try {
|
375
|
-
tag = new tagDef.
|
367
|
+
tag = new tagDef._ctr();
|
368
|
+
callInit = !!tag.init;
|
376
369
|
// }
|
377
370
|
// catch(e) {
|
378
371
|
// tagDef.onError(e);
|
@@ -381,7 +374,7 @@
|
|
381
374
|
tag.attr = tag.attr || tagDef.attr || undefined;
|
382
375
|
// Setting either linkCtx.attr or this.attr in the init() allows per-instance choice of target attrib.
|
383
376
|
} else {
|
384
|
-
// This is a simple tag declared as a function. We won't instantiate a specific tag constructor - just a standard instance object.
|
377
|
+
// This is a simple tag declared as a function, or with init set to false. We won't instantiate a specific tag constructor - just a standard instance object.
|
385
378
|
tag = {
|
386
379
|
// tag instance object if no init constructor
|
387
380
|
render: tagDef.render
|
@@ -401,30 +394,35 @@
|
|
401
394
|
tag._.arrVws = {};
|
402
395
|
}
|
403
396
|
tag.tagName = tagName;
|
404
|
-
tag.parent = parentTag = ctx && ctx.tag
|
397
|
+
tag.parent = parentTag = ctx && ctx.tag;
|
405
398
|
tag._is = "tag";
|
399
|
+
tag._def = tagDef;
|
406
400
|
// Provide this tag on view, for addBindingMarkers on bound tags to add the tag to view._.bnds, associated with the tag id,
|
407
401
|
}
|
408
402
|
parentView_.tag = tag;
|
409
403
|
tagCtx.tag = tag;
|
410
404
|
tag.tagCtxs = tagCtxs;
|
411
|
-
tag.rendering = {}; // Provide object for state during render calls to tag and elses. (Used by {{if}} and {{for}}...)
|
412
|
-
|
413
405
|
if (!tag.flow) {
|
414
406
|
tagCtxCtx = tagCtx.ctx = tagCtx.ctx || {};
|
415
407
|
|
416
408
|
// tags hash: tag.ctx.tags, merged with parentView.ctx.tags,
|
417
|
-
tags = tagCtxCtx.parentTags = ctx && extendCtx(tagCtxCtx.parentTags, ctx.parentTags) || {};
|
409
|
+
tags = tag.parents = tagCtxCtx.parentTags = ctx && extendCtx(tagCtxCtx.parentTags, ctx.parentTags) || {};
|
418
410
|
if (parentTag) {
|
419
411
|
tags[parentTag.tagName] = parentTag;
|
420
412
|
}
|
421
413
|
tagCtxCtx.tag = tag;
|
422
414
|
}
|
423
415
|
}
|
416
|
+
tag.rendering = {}; // Provide object for state during render calls to tag and elses. (Used by {{if}} and {{for}}...)
|
424
417
|
for (i = 0; i < l; i++) {
|
425
418
|
tagCtx = tag.tagCtx = tagCtxs[i];
|
426
419
|
tag.ctx = tagCtx.ctx;
|
427
420
|
|
421
|
+
if (!i && callInit) {
|
422
|
+
tag.init(tagCtx, linkCtx, tag.ctx);
|
423
|
+
callInit = undefined;
|
424
|
+
}
|
425
|
+
|
428
426
|
if (render = tag.render) {
|
429
427
|
itemRet = render.apply(tag, tagCtx.args);
|
430
428
|
}
|
@@ -445,7 +443,7 @@
|
|
445
443
|
? $converters.html(ret)
|
446
444
|
: "";
|
447
445
|
}
|
448
|
-
return
|
446
|
+
return boundTagKey && parentView._.onRender
|
449
447
|
// Call onRender (used by JsViews if present, to add binding annotations around rendered content)
|
450
448
|
? parentView._.onRender(ret, parentView, boundTagKey)
|
451
449
|
: ret;
|
@@ -528,29 +526,28 @@
|
|
528
526
|
}
|
529
527
|
}
|
530
528
|
|
531
|
-
function compileTag(name,
|
529
|
+
function compileTag(name, tagDef, parentTmpl) {
|
532
530
|
var init, tmpl;
|
533
|
-
if (typeof
|
531
|
+
if (typeof tagDef === "function") {
|
534
532
|
// Simple tag declared as function. No presenter instantation.
|
535
|
-
|
536
|
-
depends:
|
537
|
-
render:
|
533
|
+
tagDef = {
|
534
|
+
depends: tagDef.depends,
|
535
|
+
render: tagDef
|
538
536
|
};
|
539
537
|
} else {
|
540
538
|
// Tag declared as object, used as the prototype for tag instantiation (control/presenter)
|
541
|
-
if (tmpl =
|
542
|
-
|
539
|
+
if (tmpl = tagDef.template) {
|
540
|
+
tagDef.template = "" + tmpl === tmpl ? ($templates[tmpl] || $templates(tmpl)) : tmpl;
|
543
541
|
}
|
544
|
-
if (
|
545
|
-
init =
|
546
|
-
init.prototype =
|
547
|
-
(init.prototype = item).constructor = init;
|
542
|
+
if (tagDef.init !== false) {
|
543
|
+
init = tagDef._ctr = function(tagCtx) {};
|
544
|
+
(init.prototype = tagDef).constructor = init;
|
548
545
|
}
|
549
546
|
}
|
550
547
|
if (parentTmpl) {
|
551
|
-
|
548
|
+
tagDef._parentTmpl = parentTmpl;
|
552
549
|
}
|
553
|
-
//TODO
|
550
|
+
//TODO tagDef.onError = function(e) {
|
554
551
|
// var error;
|
555
552
|
// if (error = this.prototype.onError) {
|
556
553
|
// error.call(this, e);
|
@@ -558,7 +555,7 @@
|
|
558
555
|
// throw e;
|
559
556
|
// }
|
560
557
|
// }
|
561
|
-
return
|
558
|
+
return tagDef;
|
562
559
|
}
|
563
560
|
|
564
561
|
function compileTmpl(name, tmpl, parentTmpl, storeName, storeSettings, options) {
|
@@ -706,13 +703,17 @@
|
|
706
703
|
}
|
707
704
|
return $views;
|
708
705
|
}
|
709
|
-
thisStore = parentTmpl ? parentTmpl[storeNames] = parentTmpl[storeNames] || {} : theStore;
|
710
|
-
|
711
706
|
// Adding a single unnamed item to the store
|
712
707
|
if (item === undefined) {
|
713
708
|
item = name;
|
714
709
|
name = undefined;
|
715
710
|
}
|
711
|
+
if (name && "" + name !== name) { // name must be a string
|
712
|
+
parentTmpl = item;
|
713
|
+
item = name;
|
714
|
+
name = undefined;
|
715
|
+
}
|
716
|
+
thisStore = parentTmpl ? parentTmpl[storeNames] = parentTmpl[storeNames] || {} : theStore;
|
716
717
|
compile = storeSettings.compile;
|
717
718
|
if (onStore = $viewsSub.onBeforeStoreItem) {
|
718
719
|
// e.g. provide an external compiler or preprocess the item.
|
@@ -720,13 +721,11 @@
|
|
720
721
|
}
|
721
722
|
if (!name) {
|
722
723
|
item = compile(undefined, item);
|
723
|
-
} else if (
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
thisStore[name] = compile ? (item = compile(name, item, parentTmpl, storeName, storeSettings)) : item;
|
729
|
-
}
|
724
|
+
} else if (item === null) {
|
725
|
+
// If item is null, delete this entry
|
726
|
+
delete thisStore[name];
|
727
|
+
} else {
|
728
|
+
thisStore[name] = compile ? (item = compile(name, item, parentTmpl, storeName, storeSettings)) : item;
|
730
729
|
}
|
731
730
|
if (item) {
|
732
731
|
item._is = storeName;
|
@@ -854,9 +853,7 @@
|
|
854
853
|
// (Compile AST then build template function)
|
855
854
|
|
856
855
|
function error(message) {
|
857
|
-
|
858
|
-
throw new $views.sub.Error(message);
|
859
|
-
}
|
856
|
+
throw new $views.sub.Error(message);
|
860
857
|
}
|
861
858
|
|
862
859
|
function syntaxError(message) {
|
@@ -867,7 +864,6 @@
|
|
867
864
|
// Compile markup to AST (abtract syntax tree) then build the template function code from the AST nodes
|
868
865
|
// Used for compiling templates, and also by JsViews to build functions for data link expressions
|
869
866
|
|
870
|
-
|
871
867
|
//==== nested functions ====
|
872
868
|
function pushprecedingContent(shift) {
|
873
869
|
shift -= loc;
|
@@ -920,7 +916,7 @@
|
|
920
916
|
if (params) {
|
921
917
|
// remove newlines from the params string, to avoid compiled code errors for unterminated strings
|
922
918
|
params = params.replace(rNewLine, " ");
|
923
|
-
code = parseParams(params, pathBindings)
|
919
|
+
code = parseParams(params, pathBindings, tmpl)
|
924
920
|
.replace(rBuildHash, function(all, isCtx, keyValue) {
|
925
921
|
if (isCtx) {
|
926
922
|
passedCtx += keyValue + ",";
|
@@ -969,7 +965,7 @@
|
|
969
965
|
content = astTop,
|
970
966
|
current = [, , , astTop];
|
971
967
|
|
972
|
-
markup = markup.replace(rEscapeQuotes, "
|
968
|
+
markup = markup.replace(rEscapeQuotes, "\\$&");
|
973
969
|
|
974
970
|
//TODO result = tmplFnsCache[markup]; // Only cache if template is not named and markup length < ...,
|
975
971
|
//and there are no bindings or subtemplates?? Consider standard optimization for data-link="a.b.c"
|
@@ -991,14 +987,14 @@
|
|
991
987
|
}
|
992
988
|
// result = tmplFnsCache[markup] = buildCode(astTop, tmpl);
|
993
989
|
// }
|
994
|
-
return buildCode(astTop,
|
990
|
+
return buildCode(astTop, isLinkExpr ? markup : tmpl, isLinkExpr);
|
995
991
|
}
|
996
992
|
|
997
993
|
function buildCode(ast, tmpl, isLinkExpr) {
|
998
994
|
// Build the template function code from the AST nodes, and set as property on the passed-in template object
|
999
995
|
// Used for compiling templates, and also by JsViews to build functions for data link expressions
|
1000
|
-
var i, node, tagName, converter, params, hash, hasTag, hasEncoder, getsVal, hasCnvt, tmplBindings, pathBindings,
|
1001
|
-
nestedTmpls, tmplName, nestedTmpl, tagAndElses,
|
996
|
+
var i, node, tagName, converter, params, hash, hasTag, hasEncoder, getsVal, hasCnvt, useCnvt, tmplBindings, pathBindings,
|
997
|
+
nestedTmpls, tmplName, nestedTmpl, tagAndElses, content, markup, nextIsElse, oldCode, isElse, isGetVal, prm, tagCtxFn,
|
1002
998
|
tmplBindingKey = 0,
|
1003
999
|
code = "",
|
1004
1000
|
noError = "",
|
@@ -1010,7 +1006,7 @@
|
|
1010
1006
|
tmpl = 0;
|
1011
1007
|
} else {
|
1012
1008
|
tmplName = tmpl.tmplName || "unnamed";
|
1013
|
-
if (
|
1009
|
+
if (tmpl.allowCode) {
|
1014
1010
|
tmplOptions.allowCode = true;
|
1015
1011
|
}
|
1016
1012
|
if (tmpl.debug) {
|
@@ -1043,7 +1039,7 @@
|
|
1043
1039
|
|
1044
1040
|
if (!(isElse = tagName === "else")) {
|
1045
1041
|
tmplBindingKey = 0;
|
1046
|
-
if (pathBindings = node[6]) { // Array of paths, or false if not data-bound
|
1042
|
+
if (tmplBindings && (pathBindings = node[6])) { // Array of paths, or false if not data-bound
|
1047
1043
|
tmplBindingKey = tmplBindings.push(pathBindings);
|
1048
1044
|
}
|
1049
1045
|
}
|
@@ -1076,17 +1072,11 @@
|
|
1076
1072
|
// Switch to a new code string for this bound tag (and its elses, if it has any) - for returning the tagCtxs array
|
1077
1073
|
oldCode = code;
|
1078
1074
|
code = "";
|
1079
|
-
elseStartIndex = i;
|
1080
1075
|
}
|
1081
1076
|
nextIsElse = ast[i + 1];
|
1082
1077
|
nextIsElse = nextIsElse && nextIsElse[0] === "else";
|
1083
1078
|
}
|
1084
1079
|
|
1085
|
-
//TODO consider passing in ret to c() and t() so they can look at the previous ret, and detect whether this is a jsrender tag _within_an_HTML_element_tag_
|
1086
|
-
// and if so, don't insert marker nodes, add data-link attributes to the HTML element markup... No need for people to set link=false.
|
1087
|
-
|
1088
|
-
//TODO consider the following approach rather than noerror=true: params.replace(/data.try\([^]*\)/)
|
1089
|
-
|
1090
1080
|
hash += ",args:[" + params + "]}";
|
1091
1081
|
|
1092
1082
|
if (isGetVal && pathBindings || converter && tagName !== ">") {
|
@@ -1098,12 +1088,12 @@
|
|
1098
1088
|
if (isLinkExpr) {
|
1099
1089
|
return tagCtxFn;
|
1100
1090
|
}
|
1101
|
-
|
1091
|
+
useCnvt = 1;
|
1102
1092
|
}
|
1103
1093
|
|
1104
1094
|
code += (isGetVal
|
1105
|
-
? "\n" + (pathBindings ? "" : noError) + (isLinkExpr ? "return " : "ret+=") + (
|
1106
|
-
? (hasCnvt = true, 'c("' + converter + '",view,' + (pathBindings
|
1095
|
+
? "\n" + (pathBindings ? "" : noError) + (isLinkExpr ? "return " : "ret+=") + (useCnvt // Call _cnvt if there is a converter: {{cnvt: ... }} or {^{cnvt: ... }}
|
1096
|
+
? (useCnvt = 0, hasCnvt = true, 'c("' + converter + '",view,' + (pathBindings
|
1107
1097
|
? ((tmplBindings[tmplBindingKey - 1] = tagCtxFn), tmplBindingKey) // Store the compiled tagCtxFn in tmpl.bnds, and pass the key to convertVal()
|
1108
1098
|
: "{" + hash) + ");")
|
1109
1099
|
: tagName === ">"
|
@@ -1161,26 +1151,31 @@
|
|
1161
1151
|
return code;
|
1162
1152
|
}
|
1163
1153
|
|
1164
|
-
function parseParams(params, bindings) {
|
1154
|
+
function parseParams(params, bindings, tmpl) {
|
1155
|
+
|
1156
|
+
//function pushBindings() { // Consider structured path bindings
|
1157
|
+
// if (bindings) {
|
1158
|
+
// named ? bindings[named] = bindings.pop(): bindings.push(list = []);
|
1159
|
+
// }
|
1160
|
+
//}
|
1165
1161
|
|
1166
1162
|
function parseTokens(all, lftPrn0, lftPrn, path, operator, err, eq, path2, prn, comma, lftPrn2, apos, quot, rtPrn, rtPrnDot, prn2, space, index, full) {
|
1167
|
-
// rParams = /(\()(?=\s*\()|(?:([([])\s*)?(?:([#~]?[\w
|
1168
|
-
//
|
1163
|
+
// rParams = /(\()(?=\s*\()|(?:([([])\s*)?(?:([#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*!:?\/]|(=))\s*|([#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*((\))(?=\s*\.|\s*\^)|\)|\])([([]?))|(\s+)/g,
|
1164
|
+
// lftPrn lftPrn2 path operator err eq path2 prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space
|
1169
1165
|
// (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space
|
1166
|
+
var expr;
|
1170
1167
|
operator = operator || "";
|
1171
1168
|
lftPrn = lftPrn || lftPrn0 || lftPrn2;
|
1172
1169
|
path = path || path2;
|
1173
|
-
if (bindings && rtPrnDot) {
|
1174
|
-
// TODO check for nested call ~foo(~bar().x).y
|
1175
|
-
objectCall = bindings.push({_jsvOb: full.slice(pathStart[parenDepth - 1] + 1, index + 1)});
|
1176
|
-
}
|
1177
1170
|
prn = prn || prn2 || "";
|
1178
1171
|
|
1179
1172
|
function parsePath(all, object, helper, view, viewProperty, pathTokens, leafToken) {
|
1180
1173
|
// rPath = /^(?:null|true|false|\d[\d.]*|([\w$]+|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,
|
1181
1174
|
// object helper view viewProperty pathTokens leafToken
|
1182
1175
|
if (object) {
|
1183
|
-
bindings && !
|
1176
|
+
bindings && !isAlias && bindings.push(path); // Add path binding for paths on props and args,
|
1177
|
+
// but not within foo=expr (named parameter) or ~foo=expr (passing in template parameter aliases).
|
1178
|
+
// bindings && !isAlias && list.push(path);
|
1184
1179
|
if (object !== ".") {
|
1185
1180
|
var ret = (helper
|
1186
1181
|
? 'view.hlp("' + helper + '")'
|
@@ -1209,7 +1204,22 @@
|
|
1209
1204
|
if (err) {
|
1210
1205
|
syntaxError(params);
|
1211
1206
|
} else {
|
1212
|
-
|
1207
|
+
if (bindings && rtPrnDot) {
|
1208
|
+
// This is a binding to a path in which an object is returned by a helper/data function/expression, e.g. foo()^x.y or (a?b:c)^x.y
|
1209
|
+
// We create a compiled function to get the object instance (which will be called when the dependent data of the subexpression changes, to return the new object, and trigger re-binding of the subsequent path)
|
1210
|
+
expr = pathStart[parenDepth];
|
1211
|
+
if (full.length - 2 > index - expr) { // We need to compile a subexpression
|
1212
|
+
expr = full.slice(expr, index + 1);
|
1213
|
+
rtPrnDot = delimOpenChar1 + ":" + expr + delimCloseChar0; // The parameter or function subexpression
|
1214
|
+
rtPrnDot = tmplLinks[rtPrnDot] = tmplLinks[rtPrnDot] || tmplFn(delimOpenChar0 + rtPrnDot + delimCloseChar1, tmpl, true); // Compile the expression (or use cached copy already in tmpl.links)
|
1215
|
+
if (!rtPrnDot.paths) {
|
1216
|
+
parseParams(expr, rtPrnDot.paths = [], tmpl);
|
1217
|
+
}
|
1218
|
+
bindings.push({_jsvOb: rtPrnDot}); // Insert special object for in path bindings, to be used for binding the compiled sub expression ()
|
1219
|
+
//list.push({_jsvOb: rtPrnDot});
|
1220
|
+
}
|
1221
|
+
}
|
1222
|
+
return (aposed
|
1213
1223
|
// within single-quoted string
|
1214
1224
|
? (aposed = !apos, (aposed ? all : '"'))
|
1215
1225
|
: quoted
|
@@ -1218,24 +1228,27 @@
|
|
1218
1228
|
:
|
1219
1229
|
(
|
1220
1230
|
(lftPrn
|
1221
|
-
? (parenDepth++, pathStart[parenDepth] = index
|
1231
|
+
? (parenDepth++, pathStart[parenDepth] = index++, lftPrn)
|
1222
1232
|
: "")
|
1223
1233
|
+ (space
|
1224
1234
|
? (parenDepth
|
1225
1235
|
? ""
|
1236
|
+
//: (pushBindings(), named
|
1237
|
+
// ? (named = isAlias = false, "\b")
|
1238
|
+
// : ",")
|
1226
1239
|
: named
|
1227
|
-
? (named = false, "\b")
|
1240
|
+
? (named = isAlias = false, "\b")
|
1228
1241
|
: ","
|
1229
1242
|
)
|
1230
1243
|
: eq
|
1231
1244
|
// named param
|
1232
1245
|
// Insert backspace \b (\x08) as separator for named params, used subsequently by rBuildHash
|
1233
|
-
? (parenDepth && syntaxError(params), named = path, '\b' + path + ':')
|
1246
|
+
? (parenDepth && syntaxError(params), named = path, /*pushBindings(),*/isAlias = path.charAt(0) === "~", '\b' + path + ':')
|
1234
1247
|
: path
|
1235
1248
|
// path
|
1236
1249
|
? (path.split("^").join(".").replace(rPath, parsePath)
|
1237
1250
|
+ (prn
|
1238
|
-
? (fnCall[++parenDepth] = true, prn)
|
1251
|
+
? (fnCall[++parenDepth] = true, path.charAt(0) !== "." && (pathStart[parenDepth] = index), prn)
|
1239
1252
|
: operator)
|
1240
1253
|
)
|
1241
1254
|
: operator
|
@@ -1254,17 +1267,19 @@
|
|
1254
1267
|
: (aposed = apos, quoted = quot, '"')
|
1255
1268
|
))
|
1256
1269
|
);
|
1257
|
-
return tokens;
|
1258
1270
|
}
|
1259
1271
|
}
|
1260
1272
|
|
1261
|
-
var named,
|
1273
|
+
var named, isAlias,// list,
|
1274
|
+
tmplLinks = tmpl.links,
|
1262
1275
|
fnCall = {},
|
1263
1276
|
pathStart = {0:-1},
|
1264
1277
|
parenDepth = 0,
|
1265
1278
|
quoted = false, // boolean for string content in double quotes
|
1266
1279
|
aposed = false; // or in single quotes
|
1267
1280
|
|
1281
|
+
//pushBindings();
|
1282
|
+
|
1268
1283
|
return (params + " ").replace(rParams, parseTokens);
|
1269
1284
|
}
|
1270
1285
|
|
@@ -1283,6 +1298,11 @@
|
|
1283
1298
|
: parentContext && $extend({}, parentContext);
|
1284
1299
|
}
|
1285
1300
|
|
1301
|
+
// Get character entity for HTML and Attribute encoding
|
1302
|
+
function getCharEntity(ch) {
|
1303
|
+
return charEntities[ch] || (charEntities[ch] = "&#" + ch.charCodeAt(0) + ";");
|
1304
|
+
}
|
1305
|
+
|
1286
1306
|
//========================== Initialize ==========================
|
1287
1307
|
|
1288
1308
|
for (jsvStoreName in jsvStores) {
|
@@ -1300,24 +1320,28 @@
|
|
1300
1320
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
1301
1321
|
// jQuery is loaded, so make $ the jQuery object
|
1302
1322
|
$ = jQuery;
|
1303
|
-
$.render = $render;
|
1304
|
-
$.views = $views;
|
1305
|
-
$.templates = $templates = $views.templates;
|
1306
1323
|
$.fn.render = renderContent;
|
1307
1324
|
|
1308
1325
|
} else {
|
1309
1326
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
1310
1327
|
// jQuery is not loaded.
|
1311
|
-
|
1312
|
-
|
1313
|
-
$.views = $views;
|
1314
|
-
$.templates = $templates = $views.templates;
|
1328
|
+
|
1329
|
+
$ = global.jsviews = {};
|
1315
1330
|
|
1316
1331
|
$.isArray = Array && Array.isArray || function(obj) {
|
1317
1332
|
return Object.prototype.toString.call(obj) === "[object Array]";
|
1318
1333
|
};
|
1334
|
+
|
1335
|
+
// //========================== Future Node.js support ==========================
|
1336
|
+
// if ((nodeJsModule = global.module) && nodeJsModule.exports) {
|
1337
|
+
// nodeJsModule.exports = $;
|
1338
|
+
// }
|
1319
1339
|
}
|
1320
1340
|
|
1341
|
+
$.render = $render;
|
1342
|
+
$.views = $views;
|
1343
|
+
$.templates = $templates = $views.templates;
|
1344
|
+
|
1321
1345
|
//========================== Register tags ==========================
|
1322
1346
|
|
1323
1347
|
$tags({
|
@@ -1326,7 +1350,7 @@
|
|
1326
1350
|
render: function(val) {
|
1327
1351
|
// This function is called once for {{if}} and once for each {{else}}.
|
1328
1352
|
// We will use the tag.rendering object for carrying rendering state across the calls.
|
1329
|
-
// If not done (a previous block has not been rendered), look at expression for this block and render the block if expression is
|
1353
|
+
// If not done (a previous block has not been rendered), look at expression for this block and render the block if expression is truthy
|
1330
1354
|
// Otherwise return ""
|
1331
1355
|
var self = this,
|
1332
1356
|
ret = (self.rendering.done || !val && (arguments.length || !self.tagCtx.index))
|
@@ -1344,7 +1368,7 @@
|
|
1344
1368
|
different = !prevArg !== !tagCtxs[tci].args[0];
|
1345
1369
|
if (!!prevArg || different) {
|
1346
1370
|
return different;
|
1347
|
-
// If newArg and prevArg are both
|
1371
|
+
// If newArg and prevArg are both truthy, return false to cancel update. (Even if values on later elses are different, we still don't want to update, since rendered output would be unchanged)
|
1348
1372
|
// If newArg and prevArg are different, return true, to update
|
1349
1373
|
// If newArg and prevArg are both falsey, move to the next {{else ...}}
|
1350
1374
|
}
|
@@ -1358,8 +1382,7 @@
|
|
1358
1382
|
render: function(val) {
|
1359
1383
|
// This function is called once for {{for}} and once for each {{else}}.
|
1360
1384
|
// We will use the tag.rendering object for carrying rendering state across the calls.
|
1361
|
-
var
|
1362
|
-
self = this,
|
1385
|
+
var self = this,
|
1363
1386
|
tagCtx = self.tagCtx,
|
1364
1387
|
noArg = !arguments.length,
|
1365
1388
|
result = "",
|
@@ -1380,7 +1403,7 @@
|
|
1380
1403
|
}
|
1381
1404
|
return result;
|
1382
1405
|
},
|
1383
|
-
onUpdate: function(ev, eventArgs, tagCtxs) {
|
1406
|
+
//onUpdate: function(ev, eventArgs, tagCtxs) {
|
1384
1407
|
//Consider adding filtering for perf optimization. However the below prevents update on some scenarios which _should_ update - namely when there is another array on which for also depends.
|
1385
1408
|
//var i, l, tci, prevArg;
|
1386
1409
|
//for (tci = 0; (prevArg = this.tagCtxs[tci]) && prevArg.args.length; tci++) {
|
@@ -1389,12 +1412,12 @@
|
|
1389
1412
|
// }
|
1390
1413
|
//}
|
1391
1414
|
//return false;
|
1392
|
-
},
|
1415
|
+
//},
|
1393
1416
|
onArrayChange: function(ev, eventArgs) {
|
1394
1417
|
var arrayView,
|
1395
1418
|
self = this,
|
1396
1419
|
change = eventArgs.change;
|
1397
|
-
if (this.tagCtxs[1] && (
|
1420
|
+
if (this.tagCtxs[1] && ( // There is an {{else}}
|
1398
1421
|
change === "insert" && ev.target.length === eventArgs.items.length // inserting, and new length is same as inserted length, so going from 0 to n
|
1399
1422
|
|| change === "remove" && !ev.target.length // removing , and new length 0, so going from n to 0
|
1400
1423
|
|| change === "refresh" && !eventArgs.oldItems.length !== !ev.target.length // refreshing, and length is going from 0 to n or from n to 0
|
@@ -1424,36 +1447,24 @@
|
|
1424
1447
|
}
|
1425
1448
|
});
|
1426
1449
|
|
1427
|
-
//========================== Register global helpers ==========================
|
1428
|
-
|
1429
|
-
// $helpers({ // Global helper functions
|
1430
|
-
// // TODO add any useful built-in helper functions
|
1431
|
-
// });
|
1432
|
-
|
1433
1450
|
//========================== Register converters ==========================
|
1434
1451
|
|
1435
|
-
// Get character entity for HTML and Attribute encoding
|
1436
|
-
function getCharEntity(ch) {
|
1437
|
-
return charEntities[ch];
|
1438
|
-
}
|
1439
|
-
|
1440
1452
|
$converters({
|
1441
1453
|
html: function(text) {
|
1442
1454
|
// HTML encode: Replace < > & and ' and " by corresponding entities.
|
1443
|
-
return text != undefined ? String(text).replace(rHtmlEncode, getCharEntity) : "";
|
1455
|
+
return text != undefined ? String(text).replace(rHtmlEncode, getCharEntity) : ""; // null and undefined return ""
|
1444
1456
|
},
|
1445
1457
|
attr: function(text) {
|
1446
|
-
// Attribute encode: Replace < & ' and " by corresponding entities.
|
1447
|
-
return text != undefined ? String(text).replace(rAttrEncode, getCharEntity) : "";
|
1458
|
+
// Attribute encode: Replace < > & ' and " by corresponding entities.
|
1459
|
+
return text != undefined ? String(text).replace(rAttrEncode, getCharEntity) : text === null ? null : ""; // null returns null, e.g. to remove attribute. undefined returns ""
|
1448
1460
|
},
|
1449
1461
|
url: function(text) {
|
1450
|
-
// TODO - support chaining {{attr|url:....}} to protect against injection attacks from url parameters containing " or '.
|
1451
1462
|
// URL encoding helper.
|
1452
|
-
return text != undefined ? encodeURI(String(text)) : "";
|
1463
|
+
return text != undefined ? encodeURI(String(text)) : text === null ? null : ""; // null returns null, e.g. to remove attribute. undefined returns ""
|
1453
1464
|
}
|
1454
1465
|
});
|
1455
1466
|
|
1456
1467
|
//========================== Define default delimiters ==========================
|
1457
1468
|
$viewsDelimiters();
|
1458
1469
|
|
1459
|
-
})(this, this.jQuery);
|
1470
|
+
})(this, this.jQuery);
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsrender-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '
|
4
|
+
version: '2.0'
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-06-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -132,7 +132,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
132
132
|
version: '0'
|
133
133
|
segments:
|
134
134
|
- 0
|
135
|
-
hash: -
|
135
|
+
hash: -3782156225501647778
|
136
136
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
137
|
none: false
|
138
138
|
requirements:
|
@@ -141,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
141
|
version: '0'
|
142
142
|
segments:
|
143
143
|
- 0
|
144
|
-
hash: -
|
144
|
+
hash: -3782156225501647778
|
145
145
|
requirements: []
|
146
146
|
rubyforge_project:
|
147
147
|
rubygems_version: 1.8.24
|