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 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 `.tmpl` extension. Place them anywhere in the Sprockets load path.
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.tmpl -->
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
- For sure NOT!
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
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "jsrender-rails"
5
- s.version = "1.2"
5
+ s.version = "2.0"
6
6
  s.authors = ["Sebastian Pape"]
7
7
  s.email = ["email@sebastianpape.com"]
8
8
  s.homepage = "https://github.com/spape/jsrender-rails"
@@ -7,7 +7,7 @@ module JsrenderRails
7
7
 
8
8
  initializer "sprockets.jsrender", :after => "sprockets.environment", :group => :all do |app|
9
9
  next unless app.assets
10
- app.assets.register_engine(".tmpl", Jsrender)
10
+ app.assets.register_engine(".jsr", Jsrender)
11
11
  end
12
12
  end
13
13
  end
@@ -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,6 +1,6 @@
1
1
  module Jsrender
2
2
  module Rails
3
- VERSION = "1.2"
4
- JS_RENDER_VERSION = "1.0b30"
3
+ VERSION = "2.0"
4
+ JS_RENDER_VERSION = "1.0.0-beta"
5
5
  end
6
6
  end
@@ -1,2 +1,3 @@
1
1
  //= require jsrender
2
- //= require_tree ./views
2
+ //= require_self
3
+ //= require_tree ./views
@@ -1,3 +1,5 @@
1
1
  <script>
2
- document.body.innerHTML = $.render["views/user"]({name:"Sebastian Pape"});
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
- document.body.innerHTML = $.render["user"]({name:"Sebastian Pape"});
2
+ (function($) {
3
+ document.body.innerHTML = $.views.render["user"]({name:"Sebastian Pape"});
4
+ })((typeof jQuery !== "undefined" && jQuery !== null) ? jQuery : {views: jsviews});
3
5
  </script>
@@ -2,7 +2,7 @@ require "spec_helper"
2
2
 
3
3
  feature "Using jsrender", js: true do
4
4
  after do
5
- Rails.application.config.jsrender.prefix = ''
5
+ Rails.application.config.jsrender.prefix = 'views'
6
6
  end
7
7
 
8
8
  scenario "Loading a page that uses jsrender" do
@@ -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 .tmpl extension" do
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.0pre: http://github.com/BorisMoore/jsrender */
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://github.com/BorisMoore/jsviews)
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.0pre",
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*((\))(?=\s*\.|\s*\^)|\)|\])([([]?))|(\s+)/g,
29
- // lftPrn lftPrn2 path operator err eq path2 prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space
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\s*/g,
32
+ rNewLine = /\s*\n/g,
33
33
  rUnescapeQuotes = /\\(['"])/g,
34
- // escape quotes and \ character
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
- rPrevElem = /<(\w+)[^>\/]*>[^>]*$/,
40
- rAttrEncode = /[<"'&]/g,
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
  ">": "&gt;",
48
46
  "\x00": "&#0;",
49
47
  "'": "&#39;",
50
- '"': "&#34;"
48
+ '"': "&#34;",
49
+ "`": "&#96;"
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(storeName, item) {
295
+ function getResource(resourceType, itemName) {
308
296
  var res,
309
297
  view = this,
310
- store = $views[storeName];
298
+ store = $views[resourceType];
311
299
 
312
- res = store && store[item];
300
+ res = store && store[itemName];
313
301
  while ((res === undefined) && view) {
314
- store = view.tmpl[storeName];
315
- res = store && store[item];
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, isElse, parentTag, i, l, itemRet, tagCtx, tagCtxCtx, content, boundTagFn, tagDef,
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
- tag = tagName._is === "tag" && tagName;
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 || !i && tagDef.template || content;
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.init) {
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.init(tagCtx, linkCtx, ctx);
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 ret = boundTagKey && parentView._.onRender
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, item, parentTmpl) {
529
+ function compileTag(name, tagDef, parentTmpl) {
532
530
  var init, tmpl;
533
- if (typeof item === "function") {
531
+ if (typeof tagDef === "function") {
534
532
  // Simple tag declared as function. No presenter instantation.
535
- item = {
536
- depends: item.depends,
537
- render: item
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 = item.template) {
542
- item.template = "" + tmpl === tmpl ? ($templates[tmpl] || $templates(tmpl)) : tmpl;
539
+ if (tmpl = tagDef.template) {
540
+ tagDef.template = "" + tmpl === tmpl ? ($templates[tmpl] || $templates(tmpl)) : tmpl;
543
541
  }
544
- if (item.init !== false) {
545
- init = item.init = item.init || function(tagCtx) {};
546
- init.prototype = item;
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
- item._parentTmpl = parentTmpl;
548
+ tagDef._parentTmpl = parentTmpl;
552
549
  }
553
- //TODO item.onError = function(e) {
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 item;
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 ("" + name === name) { // name must be a string
724
- 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;
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
- if ($viewsSettings.debugMode) {
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, "\\$1");
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, tmpl || markup, isLinkExpr);
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, elseStartIndex, elseIndex,
1001
- nestedTmpls, tmplName, nestedTmpl, tagAndElses, allowCode, content, markup, notElse, nextIsElse, oldCode, isElse, isGetVal, prm, tagCtxFn,
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 (allowCode = tmpl.allowCode) {
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
- hasCnvt = true;
1091
+ useCnvt = 1;
1102
1092
  }
1103
1093
 
1104
1094
  code += (isGetVal
1105
- ? "\n" + (pathBindings ? "" : noError) + (isLinkExpr ? "return " : "ret+=") + (hasCnvt // Call _cnvt if there is a converter: {{cnvt: ... }} or {^{cnvt: ... }}
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$^.]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*!:?\/]|(=))\s*|([#~]?[\w$^.]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*([)\]])([([]?))|(\s+)
1168
- // lftPrn0-flwed by (- lftPrn path operator err eq path2 prn comma lftPrn2 apos quot rtPrn prn2 space
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 && !name && bindings.push(path); // Add path binding for paths on props and args, but not within ~foo=expr (passing in template property aliases).
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
- var tokens = (aposed
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, lftPrn)
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, objectCall,
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
- if (!$) { $ = global.$ = {}};
1312
- $.render = $render;
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 truey
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 truey, 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)
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 i, arg,
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: '1.2'
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-03-08 00:00:00.000000000 Z
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: -3378559534712469260
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: -3378559534712469260
144
+ hash: -3782156225501647778
145
145
  requirements: []
146
146
  rubyforge_project:
147
147
  rubygems_version: 1.8.24