Skip to content

Commit c5e29ab

Browse files
committed
make class interpolation and v-class work together (fix #1065)
1 parent 3c04cde commit c5e29ab

File tree

4 files changed

+93
-26
lines changed

4 files changed

+93
-26
lines changed

src/compiler/compile.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,10 @@ function makeNodeLinkFn (directives) {
596596
* Check an attribute for potential dynamic bindings,
597597
* and return a directive object.
598598
*
599+
* Special case: class interpolations are translated into
600+
* v-class instead v-attr, so that it can work with user
601+
* provided v-class bindings.
602+
*
599603
* @param {String} name
600604
* @param {String} value
601605
* @param {Object} options
@@ -604,8 +608,10 @@ function makeNodeLinkFn (directives) {
604608

605609
function collectAttrDirective (name, value, options) {
606610
var tokens = textParser.parse(value)
611+
var isClass = name === 'class'
607612
if (tokens) {
608-
var def = options.directives.attr
613+
var dirName = isClass ? 'class' : 'attr'
614+
var def = options.directives[dirName]
609615
var i = tokens.length
610616
var allOneTime = true
611617
while (i--) {
@@ -621,9 +627,14 @@ function collectAttrDirective (name, value, options) {
621627
el.setAttribute(name, vm.$interpolate(value))
622628
}
623629
: function (vm, el) {
624-
var value = textParser.tokensToExp(tokens, vm)
625-
var desc = dirParser.parse(name + ':' + value)[0]
626-
vm._bindDir('attr', el, desc, def)
630+
var exp = textParser.tokensToExp(tokens, vm)
631+
var desc = isClass
632+
? dirParser.parse(exp)[0]
633+
: dirParser.parse(name + ':' + exp)[0]
634+
if (isClass) {
635+
desc._rawClass = value
636+
}
637+
vm._bindDir(dirName, el, desc, def)
627638
}
628639
}
629640
}

src/directives/class.js

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,67 @@ var removeClass = _.removeClass
44

55
module.exports = {
66

7+
bind: function () {
8+
// interpolations like class="{{abc}}" are converted
9+
// to v-class, and we need to remove the raw,
10+
// uninterpolated className at binding time.
11+
var raw = this._descriptor._rawClass
12+
if (raw) {
13+
this.prevKeys = raw.trim().split(/\s+/)
14+
}
15+
},
16+
717
update: function (value) {
818
if (this.arg) {
919
// single toggle
10-
var method = value ? addClass : removeClass
11-
method(this.el, this.arg)
20+
if (value) {
21+
addClass(this.el, this.arg)
22+
} else {
23+
removeClass(this.el, this.arg)
24+
}
1225
} else {
13-
this.cleanup()
1426
if (value && typeof value === 'string') {
15-
// raw class text
16-
addClass(this.el, value)
17-
this.lastVal = value
27+
this.handleObject(stringToObject(value))
1828
} else if (_.isPlainObject(value)) {
19-
// object toggle
20-
for (var key in value) {
21-
if (value[key]) {
22-
addClass(this.el, key)
23-
} else {
24-
removeClass(this.el, key)
25-
}
26-
}
27-
this.prevKeys = Object.keys(value)
29+
this.handleObject(value)
30+
} else {
31+
this.cleanup()
2832
}
2933
}
3034
},
3135

32-
cleanup: function (value) {
33-
if (this.lastVal) {
34-
removeClass(this.el, this.lastVal)
36+
handleObject: function (value) {
37+
this.cleanup(value)
38+
var keys = this.prevKeys = Object.keys(value)
39+
for (var i = 0, l = keys.length; i < l; i++) {
40+
var key = keys[i]
41+
if (value[key]) {
42+
addClass(this.el, key)
43+
} else {
44+
removeClass(this.el, key)
45+
}
3546
}
47+
},
48+
49+
cleanup: function (value) {
3650
if (this.prevKeys) {
3751
var i = this.prevKeys.length
3852
while (i--) {
39-
if (!value || !value[this.prevKeys[i]]) {
40-
removeClass(this.el, this.prevKeys[i])
53+
var key = this.prevKeys[i]
54+
if (!value || !value.hasOwnProperty(key)) {
55+
removeClass(this.el, key)
4156
}
4257
}
4358
}
4459
}
4560
}
61+
62+
function stringToObject (value) {
63+
var res = {}
64+
var keys = value.trim().split(/\s+/)
65+
var i = keys.length
66+
while (i--) {
67+
res[keys[i]] = true
68+
}
69+
return res
70+
}

test/unit/specs/directives/class_spec.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ if (_.inBrowser) {
2626
var dir = _.extend({ el: el }, def)
2727
dir.update('test')
2828
expect(el.className).toBe('haha test')
29-
dir.update('what')
30-
expect(el.className).toBe('haha what')
29+
dir.update('what now test')
30+
expect(el.className).toBe('haha test now what')
31+
dir.update('ok cool')
32+
expect(el.className).toBe('haha cool ok')
3133
dir.update()
3234
expect(el.className).toBe('haha')
3335
})

test/unit/specs/misc_spec.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,4 +244,33 @@ describe('Misc', function () {
244244
Vue.config.strict = false
245245
})
246246

247+
it('class interpolation and v-class should work together', function (done) {
248+
var el = document.createElement('div')
249+
el.setAttribute('class', 'a {{classB}}')
250+
el.setAttribute('v-class', 'c: showC')
251+
var vm = new Vue({
252+
el: el,
253+
data: {
254+
classB: 'b',
255+
showC: true
256+
}
257+
})
258+
assertClasses(['a', 'b', 'c'])
259+
vm.classB = 'bb'
260+
vm.showC = false
261+
Vue.nextTick(function () {
262+
assertClasses(['a', 'bb'])
263+
done()
264+
})
265+
266+
function assertClasses (expectedClasses) {
267+
var classes = el.className.trim().split(/\s+/)
268+
expect(classes.length).toBe(expectedClasses.length)
269+
var has = expectedClasses.every(function (cls) {
270+
return classes.indexOf(cls) > -1
271+
})
272+
expect(has).toBe(true)
273+
}
274+
})
275+
247276
})

0 commit comments

Comments
 (0)