Răsfoiți Sursa

handle one-time interpolations inside attributes properly (fix #2097)

Evan You 10 ani în urmă
părinte
comite
fdbea4933f

+ 26 - 7
src/compiler/compile.js

@@ -660,7 +660,7 @@ function compileDirectives (attrs, options) {
     if (tokens) {
       value = tokensToExp(tokens)
       arg = name
-      pushDir('bind', publicDirectives.bind, true)
+      pushDir('bind', publicDirectives.bind, tokens)
       // warn against mixing mustaches with v-bind
       if (process.env.NODE_ENV !== 'production') {
         if (name === 'class' && Array.prototype.some.call(attrs, function (attr) {
@@ -729,11 +729,12 @@ function compileDirectives (attrs, options) {
    *
    * @param {String} dirName
    * @param {Object|Function} def
-   * @param {Boolean} [interp]
+   * @param {Array} [interpTokens]
    */
 
-  function pushDir (dirName, def, interp) {
-    var parsed = parseDirective(value)
+  function pushDir (dirName, def, interpTokens) {
+    var hasOneTimeToken = interpTokens && hasOneTime(interpTokens)
+    var parsed = !hasOneTimeToken && parseDirective(value)
     dirs.push({
       name: dirName,
       attr: rawName,
@@ -741,9 +742,13 @@ function compileDirectives (attrs, options) {
       def: def,
       arg: arg,
       modifiers: modifiers,
-      expression: parsed.expression,
-      filters: parsed.filters,
-      interp: interp
+      // conversion from interpolation strings with one-time token
+      // to expression is differed until directive bind time so that we
+      // have access to the actual vm context for one-time bindings.
+      expression: parsed && parsed.expression,
+      filters: parsed && parsed.filters,
+      interp: interpTokens,
+      hasOneTime: hasOneTimeToken
     })
   }
 
@@ -787,3 +792,17 @@ function makeNodeLinkFn (directives) {
     }
   }
 }
+
+/**
+ * Check if an interpolation string contains one-time tokens.
+ *
+ * @param {Array} tokens
+ * @return {Boolean}
+ */
+
+function hasOneTime (tokens) {
+  var i = tokens.length
+  while (i--) {
+    if (tokens[i].oneTime) return true
+  }
+}

+ 11 - 3
src/directives/public/bind.js

@@ -1,6 +1,7 @@
 import { warn, setClass } from '../../util/index'
 import { BIND } from '../priorities'
 import vStyle from '../internal/style'
+import { tokensToExp } from '../../parsers/text'
 
 // xlink
 const xlinkNS = 'http://www.w3.org/1999/xlink'
@@ -33,14 +34,21 @@ export default {
       this.deep = true
     }
     // handle interpolation bindings
-    if (this.descriptor.interp) {
+    const descriptor = this.descriptor
+    const tokens = descriptor.interp
+    if (tokens) {
+      // handle interpolations with one-time tokens
+      if (descriptor.hasOneTime) {
+        this.expression = tokensToExp(tokens, this._scope || this.vm)
+      }
+
       // only allow binding on native attributes
       if (
         disallowedInterpAttrRE.test(attr) ||
         (attr === 'name' && (tag === 'PARTIAL' || tag === 'SLOT'))
       ) {
         process.env.NODE_ENV !== 'production' && warn(
-          attr + '="' + this.descriptor.raw + '": ' +
+          attr + '="' + descriptor.raw + '": ' +
           'attribute interpolation is not allowed in Vue.js ' +
           'directives and special attributes.'
         )
@@ -50,7 +58,7 @@ export default {
 
       /* istanbul ignore if */
       if (process.env.NODE_ENV !== 'production') {
-        var raw = attr + '="' + this.descriptor.raw + '": '
+        var raw = attr + '="' + descriptor.raw + '": '
         // warn src
         if (attr === 'src') {
           warn(

+ 10 - 6
src/parsers/text.js

@@ -100,16 +100,17 @@ export function parseText (text) {
  * into one single expression as '"a " + b + " c"'.
  *
  * @param {Array} tokens
+ * @param {Vue} [vm]
  * @return {String}
  */
 
-export function tokensToExp (tokens) {
+export function tokensToExp (tokens, vm) {
   if (tokens.length > 1) {
     return tokens.map(function (token) {
-      return formatToken(token)
+      return formatToken(token, vm)
     }).join('+')
   } else {
-    return formatToken(tokens[0], true)
+    return formatToken(tokens[0], vm, true)
   }
 }
 
@@ -117,13 +118,16 @@ export function tokensToExp (tokens) {
  * Format a single token.
  *
  * @param {Object} token
- * @param {Boolean} single
+ * @param {Vue} [vm]
+ * @param {Boolean} [single]
  * @return {String}
  */
 
-function formatToken (token, single) {
+function formatToken (token, vm, single) {
   return token.tag
-    ? inlineFilters(token.value, single)
+    ? token.oneTime && vm
+      ? '"' + vm.$eval(token.value) + '"'
+      : inlineFilters(token.value, single)
     : '"' + token.value + '"'
 }
 

+ 18 - 0
test/unit/specs/compiler/compile_spec.js

@@ -526,6 +526,24 @@ describe('Compile', function () {
     })
   })
 
+  it('attribute interpolation: one-time', function (done) {
+    var vm = new Vue({
+      el: el,
+      template: '<div id="{{a}} b {{*c}}"></div>',
+      data: {
+        a: 'aaa',
+        c: 'ccc'
+      }
+    })
+    expect(el.firstChild.id).toBe('aaa b ccc')
+    vm.a = 'aa'
+    vm.c = 'cc'
+    _.nextTick(function () {
+      expect(el.firstChild.id).toBe('aa b ccc')
+      done()
+    })
+  })
+
   it('attribute interpolation: special cases', function () {
     new Vue({
       el: el,