Skip to content

Commit fdbea49

Browse files
committed
handle one-time interpolations inside attributes properly (fix vuejs#2097)
1 parent e840dc6 commit fdbea49

File tree

4 files changed

+65
-16
lines changed

4 files changed

+65
-16
lines changed

src/compiler/compile.js

+26-7
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ function compileDirectives (attrs, options) {
660660
if (tokens) {
661661
value = tokensToExp(tokens)
662662
arg = name
663-
pushDir('bind', publicDirectives.bind, true)
663+
pushDir('bind', publicDirectives.bind, tokens)
664664
// warn against mixing mustaches with v-bind
665665
if (process.env.NODE_ENV !== 'production') {
666666
if (name === 'class' && Array.prototype.some.call(attrs, function (attr) {
@@ -729,21 +729,26 @@ function compileDirectives (attrs, options) {
729729
*
730730
* @param {String} dirName
731731
* @param {Object|Function} def
732-
* @param {Boolean} [interp]
732+
* @param {Array} [interpTokens]
733733
*/
734734

735-
function pushDir (dirName, def, interp) {
736-
var parsed = parseDirective(value)
735+
function pushDir (dirName, def, interpTokens) {
736+
var hasOneTimeToken = interpTokens && hasOneTime(interpTokens)
737+
var parsed = !hasOneTimeToken && parseDirective(value)
737738
dirs.push({
738739
name: dirName,
739740
attr: rawName,
740741
raw: rawValue,
741742
def: def,
742743
arg: arg,
743744
modifiers: modifiers,
744-
expression: parsed.expression,
745-
filters: parsed.filters,
746-
interp: interp
745+
// conversion from interpolation strings with one-time token
746+
// to expression is differed until directive bind time so that we
747+
// have access to the actual vm context for one-time bindings.
748+
expression: parsed && parsed.expression,
749+
filters: parsed && parsed.filters,
750+
interp: interpTokens,
751+
hasOneTime: hasOneTimeToken
747752
})
748753
}
749754

@@ -787,3 +792,17 @@ function makeNodeLinkFn (directives) {
787792
}
788793
}
789794
}
795+
796+
/**
797+
* Check if an interpolation string contains one-time tokens.
798+
*
799+
* @param {Array} tokens
800+
* @return {Boolean}
801+
*/
802+
803+
function hasOneTime (tokens) {
804+
var i = tokens.length
805+
while (i--) {
806+
if (tokens[i].oneTime) return true
807+
}
808+
}

src/directives/public/bind.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { warn, setClass } from '../../util/index'
22
import { BIND } from '../priorities'
33
import vStyle from '../internal/style'
4+
import { tokensToExp } from '../../parsers/text'
45

56
// xlink
67
const xlinkNS = 'http://www.w3.org/1999/xlink'
@@ -33,14 +34,21 @@ export default {
3334
this.deep = true
3435
}
3536
// handle interpolation bindings
36-
if (this.descriptor.interp) {
37+
const descriptor = this.descriptor
38+
const tokens = descriptor.interp
39+
if (tokens) {
40+
// handle interpolations with one-time tokens
41+
if (descriptor.hasOneTime) {
42+
this.expression = tokensToExp(tokens, this._scope || this.vm)
43+
}
44+
3745
// only allow binding on native attributes
3846
if (
3947
disallowedInterpAttrRE.test(attr) ||
4048
(attr === 'name' && (tag === 'PARTIAL' || tag === 'SLOT'))
4149
) {
4250
process.env.NODE_ENV !== 'production' && warn(
43-
attr + '="' + this.descriptor.raw + '": ' +
51+
attr + '="' + descriptor.raw + '": ' +
4452
'attribute interpolation is not allowed in Vue.js ' +
4553
'directives and special attributes.'
4654
)
@@ -50,7 +58,7 @@ export default {
5058

5159
/* istanbul ignore if */
5260
if (process.env.NODE_ENV !== 'production') {
53-
var raw = attr + '="' + this.descriptor.raw + '": '
61+
var raw = attr + '="' + descriptor.raw + '": '
5462
// warn src
5563
if (attr === 'src') {
5664
warn(

src/parsers/text.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -100,30 +100,34 @@ export function parseText (text) {
100100
* into one single expression as '"a " + b + " c"'.
101101
*
102102
* @param {Array} tokens
103+
* @param {Vue} [vm]
103104
* @return {String}
104105
*/
105106

106-
export function tokensToExp (tokens) {
107+
export function tokensToExp (tokens, vm) {
107108
if (tokens.length > 1) {
108109
return tokens.map(function (token) {
109-
return formatToken(token)
110+
return formatToken(token, vm)
110111
}).join('+')
111112
} else {
112-
return formatToken(tokens[0], true)
113+
return formatToken(tokens[0], vm, true)
113114
}
114115
}
115116

116117
/**
117118
* Format a single token.
118119
*
119120
* @param {Object} token
120-
* @param {Boolean} single
121+
* @param {Vue} [vm]
122+
* @param {Boolean} [single]
121123
* @return {String}
122124
*/
123125

124-
function formatToken (token, single) {
126+
function formatToken (token, vm, single) {
125127
return token.tag
126-
? inlineFilters(token.value, single)
128+
? token.oneTime && vm
129+
? '"' + vm.$eval(token.value) + '"'
130+
: inlineFilters(token.value, single)
127131
: '"' + token.value + '"'
128132
}
129133

test/unit/specs/compiler/compile_spec.js

+18
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,24 @@ describe('Compile', function () {
526526
})
527527
})
528528

529+
it('attribute interpolation: one-time', function (done) {
530+
var vm = new Vue({
531+
el: el,
532+
template: '<div id="{{a}} b {{*c}}"></div>',
533+
data: {
534+
a: 'aaa',
535+
c: 'ccc'
536+
}
537+
})
538+
expect(el.firstChild.id).toBe('aaa b ccc')
539+
vm.a = 'aa'
540+
vm.c = 'cc'
541+
_.nextTick(function () {
542+
expect(el.firstChild.id).toBe('aa b ccc')
543+
done()
544+
})
545+
})
546+
529547
it('attribute interpolation: special cases', function () {
530548
new Vue({
531549
el: el,

0 commit comments

Comments
 (0)