瀏覽代碼

Allow rendering properties of primitive types that are not objects (#618)

* prevent value from being returned by Context.prototype.lookup if lookupHit is false

* add test for renderability of Array.length via dot notation

* Remove `typeof obj === 'object'` constraint in prop lookup

Allows rendering properties of primitive types that are not objects, such as a string.

* pop lookup needs to use hasOwnProperty for non-objs

* re-add  constraint in prop lookup, but make property lookups for primitives possible through dot notation

* add test to address #589 specifically

* enhance readability of primitiveHasOwnProperty and add comments to explain why it is used in one case but not the other
tags/v3.0.0
Raymond Lam Phillip Johnsen 7 年之前
父節點
當前提交
a2699e4673
共有 4 個檔案被更改,包括 59 行新增8 行删除
  1. +51
    -7
      mustache.js
  2. +2
    -1
      test/_files/dot_notation.js
  3. +3
    -0
      test/_files/dot_notation.mustache
  4. +3
    -0
      test/_files/dot_notation.txt

+ 51
- 7
mustache.js 查看文件

@@ -45,6 +45,19 @@
return obj != null && typeof obj === 'object' && (propName in obj); return obj != null && typeof obj === 'object' && (propName in obj);
} }


/**
* Safe way of detecting whether or not the given thing is a primitive and
* whether it has the given property
*/
function primitiveHasOwnProperty (primitive, propName) {
return (
primitive != null
&& typeof primitive !== 'object'
&& primitive.hasOwnProperty
&& primitive.hasOwnProperty(propName)
);
}

// Workaround for https://issues.apache.org/jira/browse/COUCHDB-577 // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
// See https://github.com/janl/mustache.js/issues/189 // See https://github.com/janl/mustache.js/issues/189
var regExpTest = RegExp.prototype.test; var regExpTest = RegExp.prototype.test;
@@ -377,11 +390,11 @@
if (cache.hasOwnProperty(name)) { if (cache.hasOwnProperty(name)) {
value = cache[name]; value = cache[name];
} else { } else {
var context = this, names, index, lookupHit = false;
var context = this, intermediateValue, names, index, lookupHit = false;


while (context) { while (context) {
if (name.indexOf('.') > 0) { if (name.indexOf('.') > 0) {
value = context.view;
intermediateValue = context.view;
names = name.split('.'); names = name.split('.');
index = 0; index = 0;


@@ -395,20 +408,51 @@
* *
* This is specially necessary for when the value has been set to * This is specially necessary for when the value has been set to
* `undefined` and we want to avoid looking up parent contexts. * `undefined` and we want to avoid looking up parent contexts.
*
* In the case where dot notation is used, we consider the lookup
* to be successful even if the last "object" in the path is
* not actually an object but a primitive (e.g., a string, or an
* integer), because it is sometimes useful to access a property
* of an autoboxed primitive, such as the length of a string.
**/ **/
while (value != null && index < names.length) {
while (intermediateValue != null && index < names.length) {
if (index === names.length - 1) if (index === names.length - 1)
lookupHit = hasProperty(value, names[index]);
lookupHit = (
hasProperty(intermediateValue, names[index])
|| primitiveHasOwnProperty(intermediateValue, names[index])
);


value = value[names[index++]];
intermediateValue = intermediateValue[names[index++]];
} }
} else { } else {
value = context.view[name];
intermediateValue = context.view[name];

/**
* Only checking against `hasProperty`, which always returns `false` if
* `context.view` is not an object. Deliberately omitting the check
* against `primitiveHasOwnProperty` if dot notation is not used.
*
* Consider this example:
* ```
* Mustache.render("The length of a football field is {{#length}}{{length}}{{/length}}.", {length: "100 yards"})
* ```
*
* If we were to check also against `primitiveHasOwnProperty`, as we do
* in the dot notation case, then render call would return:
*
* "The length of a football field is 9."
*
* rather than the expected:
*
* "The length of a football field is 100 yards."
**/
lookupHit = hasProperty(context.view, name); lookupHit = hasProperty(context.view, name);
} }


if (lookupHit)
if (lookupHit) {
value = intermediateValue;
break; break;
}


context = context.parent; context = context.parent;
} }


+ 2
- 1
test/_files/dot_notation.js 查看文件

@@ -19,5 +19,6 @@
truthy: { truthy: {
zero: 0, zero: 0,
notTrue: false notTrue: false
}
},
singletonList: [{singletonItem: "singleton item"}]
}) })

+ 3
- 0
test/_files/dot_notation.mustache 查看文件

@@ -7,3 +7,6 @@
<h2>Test truthy false values:</h2> <h2>Test truthy false values:</h2>
<p>Zero: {{truthy.zero}}</p> <p>Zero: {{truthy.zero}}</p>
<p>False: {{truthy.notTrue}}</p> <p>False: {{truthy.notTrue}}</p>
<p>length of string should be rendered: {{price.currency.name.length}}</p>
<p>length of string in a list should be rendered: {{#singletonList}}{{singletonItem.length}}{{/singletonList}}</p>
<p>length of an array should be rendered: {{authors.length}}</p>

+ 3
- 0
test/_files/dot_notation.txt 查看文件

@@ -7,3 +7,6 @@
<h2>Test truthy false values:</h2> <h2>Test truthy false values:</h2>
<p>Zero: 0</p> <p>Zero: 0</p>
<p>False: false</p> <p>False: false</p>
<p>length of string should be rendered: 3</p>
<p>length of string in a list should be rendered: 14</p>
<p>length of an array should be rendered: 2</p>

Loading…
取消
儲存