Kaynağa Gözat

make class interpolation and v-class work together (fix #1065)

Evan You 11 yıl önce
ebeveyn
işleme
c5e29ab98e

+ 15 - 4
src/compiler/compile.js

@@ -596,6 +596,10 @@ function makeNodeLinkFn (directives) {
  * Check an attribute for potential dynamic bindings,
  * and return a directive object.
  *
+ * Special case: class interpolations are translated into
+ * v-class instead v-attr, so that it can work with user
+ * provided v-class bindings.
+ *
  * @param {String} name
  * @param {String} value
  * @param {Object} options
@@ -604,8 +608,10 @@ function makeNodeLinkFn (directives) {
 
 function collectAttrDirective (name, value, options) {
   var tokens = textParser.parse(value)
+  var isClass = name === 'class'
   if (tokens) {
-    var def = options.directives.attr
+    var dirName = isClass ? 'class' : 'attr'
+    var def = options.directives[dirName]
     var i = tokens.length
     var allOneTime = true
     while (i--) {
@@ -621,9 +627,14 @@ function collectAttrDirective (name, value, options) {
             el.setAttribute(name, vm.$interpolate(value))
           }
         : function (vm, el) {
-            var value = textParser.tokensToExp(tokens, vm)
-            var desc = dirParser.parse(name + ':' + value)[0]
-            vm._bindDir('attr', el, desc, def)
+            var exp = textParser.tokensToExp(tokens, vm)
+            var desc = isClass
+              ? dirParser.parse(exp)[0]
+              : dirParser.parse(name + ':' + exp)[0]
+            if (isClass) {
+              desc._rawClass = value
+            }
+            vm._bindDir(dirName, el, desc, def)
           }
     }
   }

+ 45 - 20
src/directives/class.js

@@ -4,42 +4,67 @@ var removeClass = _.removeClass
 
 module.exports = {
 
+  bind: function () {
+    // interpolations like class="{{abc}}" are converted
+    // to v-class, and we need to remove the raw,
+    // uninterpolated className at binding time.
+    var raw = this._descriptor._rawClass
+    if (raw) {
+      this.prevKeys = raw.trim().split(/\s+/)
+    }
+  },
+
   update: function (value) {
     if (this.arg) {
       // single toggle
-      var method = value ? addClass : removeClass
-      method(this.el, this.arg)
+      if (value) {
+        addClass(this.el, this.arg)
+      } else {
+        removeClass(this.el, this.arg)
+      }
     } else {
-      this.cleanup()
       if (value && typeof value === 'string') {
-        // raw class text
-        addClass(this.el, value)
-        this.lastVal = value
+        this.handleObject(stringToObject(value))
       } else if (_.isPlainObject(value)) {
-        // object toggle
-        for (var key in value) {
-          if (value[key]) {
-            addClass(this.el, key)
-          } else {
-            removeClass(this.el, key)
-          }
-        }
-        this.prevKeys = Object.keys(value)
+        this.handleObject(value)
+      } else {
+        this.cleanup()
       }
     }
   },
 
-  cleanup: function (value) {
-    if (this.lastVal) {
-      removeClass(this.el, this.lastVal)
+  handleObject: function (value) {
+    this.cleanup(value)
+    var keys = this.prevKeys = Object.keys(value)
+    for (var i = 0, l = keys.length; i < l; i++) {
+      var key = keys[i]
+      if (value[key]) {
+        addClass(this.el, key)
+      } else {
+        removeClass(this.el, key)
+      }
     }
+  },
+
+  cleanup: function (value) {
     if (this.prevKeys) {
       var i = this.prevKeys.length
       while (i--) {
-        if (!value || !value[this.prevKeys[i]]) {
-          removeClass(this.el, this.prevKeys[i])
+        var key = this.prevKeys[i]
+        if (!value || !value.hasOwnProperty(key)) {
+          removeClass(this.el, key)
         }
       }
     }
   }
 }
+
+function stringToObject (value) {
+  var res = {}
+  var keys = value.trim().split(/\s+/)
+  var i = keys.length
+  while (i--) {
+    res[keys[i]] = true
+  }
+  return res
+}

+ 4 - 2
test/unit/specs/directives/class_spec.js

@@ -26,8 +26,10 @@ if (_.inBrowser) {
       var dir = _.extend({ el: el }, def)
       dir.update('test')
       expect(el.className).toBe('haha test')
-      dir.update('what')
-      expect(el.className).toBe('haha what')
+      dir.update('what now test')
+      expect(el.className).toBe('haha test now what')
+      dir.update('ok cool')
+      expect(el.className).toBe('haha cool ok')
       dir.update()
       expect(el.className).toBe('haha')
     })

+ 29 - 0
test/unit/specs/misc_spec.js

@@ -244,4 +244,33 @@ describe('Misc', function () {
     Vue.config.strict = false
   })
 
+  it('class interpolation and v-class should work together', function (done) {
+    var el = document.createElement('div')
+    el.setAttribute('class', 'a {{classB}}')
+    el.setAttribute('v-class', 'c: showC')
+    var vm = new Vue({
+      el: el,
+      data: {
+        classB: 'b',
+        showC: true
+      }
+    })
+    assertClasses(['a', 'b', 'c'])
+    vm.classB = 'bb'
+    vm.showC = false
+    Vue.nextTick(function () {
+      assertClasses(['a', 'bb'])
+      done()
+    })
+
+    function assertClasses (expectedClasses) {
+      var classes = el.className.trim().split(/\s+/)
+      expect(classes.length).toBe(expectedClasses.length)
+      var has = expectedClasses.every(function (cls) {
+        return classes.indexOf(cls) > -1
+      })
+      expect(has).toBe(true)
+    }
+  })
+
 })