Forráskód Böngészése

fix two-way filters for v-model

Evan You 11 éve
szülő
commit
5bb6dc631c

+ 9 - 5
src/directive.js

@@ -27,7 +27,10 @@ function Directive (name, el, vm, descriptor, def, linker) {
   this.el = el
   this.vm = vm
   // copy descriptor props
-  _.extend(this, descriptor)
+  this.raw = descriptor.raw
+  this.expression = descriptor.expression
+  this.arg = descriptor.arg
+  this.filters = _.resolveFilters(vm, descriptor.filters)
   // private
   this._linker = linker
   this._locked = false
@@ -62,8 +65,9 @@ p._bind = function (def) {
     (!this.isLiteral || this._isDynamicLiteral) &&
     !this._checkExpFn()
   ) {
-    var exp = this._watcherExp
-    var watcher = this.vm._watchers[exp]
+    // use raw expression as identifier because filters
+    // make them different watchers
+    var watcher = this.vm._watchers[this.raw]
     // wrapped updater for context
     var dir = this
     var update = this._update = function (val, oldVal) {
@@ -72,9 +76,9 @@ p._bind = function (def) {
       }
     }
     if (!watcher) {
-      watcher = this.vm._watchers[exp] = new Watcher(
+      watcher = this.vm._watchers[this.raw] = new Watcher(
         this.vm,
-        exp,
+        this._watcherExp,
         update, // callback
         this.filters,
         this.twoWay // need setter

+ 15 - 0
src/directives/model/select.js

@@ -0,0 +1,15 @@
+module.exports = {
+
+  bind: function () {
+    
+  },
+
+  update: function () {
+    
+  },
+
+  unbind: function () {
+    
+  }
+
+}

+ 37 - 12
src/directives/model/text.js

@@ -5,6 +5,7 @@ module.exports = {
   bind: function () {
     var self = this
     var el = this.el
+
     // check params
     // - lazy: update model on "change" instead of "input"
     var lazy = el.hasAttribute('lazy')
@@ -18,7 +19,7 @@ module.exports = {
     if (number) {
       el.removeAttribute('number')
     }
-    this.event = lazy ? 'change' : 'input'
+
     // handle composition events.
     // http://blog.evanyou.me/2014/01/03/composition-event/
     var cpLocked = false
@@ -30,25 +31,47 @@ module.exports = {
     }
     _.on(el,'compositionstart', this.cpLock)
     _.on(el,'compositionend', this.cpUnlock)
-    // if the directive has read filters, we need to
-    // record cursor position and restore it after updating
-    // the input with the filtered value.
-    this.listener = function textInputListener () {
-      if (cpLocked) return
-      var cursorPos
-      try { cursorPos = el.selectionStart } catch (e) {}
+
+    // shared setter
+    function set () {
       self.set(
-        number
-          ? _.toNumber(el.value)
-          : el.value
+        number ? _.toNumber(el.value) : el.value,
+        true
       )
-      if (cursorPos) {
+    }
+
+    // if the directive has filters, we need to
+    // record cursor position and restore it after updating
+    // the input with the filtered value.
+    if (this.filters) {
+      this.listener = function textInputListener () {
+        if (cpLocked) return
+        var cursorPos
+        // some HTML5 input types throw error here
+        try { cursorPos = el.selectionStart } catch (e) {}
+        set()
+        // force a value update, because in
+        // certain cases the write filters output the same
+        // result for different input values, and the Observer
+        // set events won't be triggered.
         _.nextTick(function () {
+          var newVal = self._watcher.value
+          self.update(newVal)
+          if (cursorPos == null) {
+            cursorPos = newVal.toString().length
+          }
           el.setSelectionRange(cursorPos, cursorPos)
         })
       }
+    } else {
+      this.listener = function textInputListener () {
+        if (cpLocked) return
+        set()
+      }
     }
+    this.event = lazy ? 'change' : 'input'
     _.on(el, this.event, this.listener)
+
     // IE9 doesn't fire input event on backspace/del/cut
     if (!lazy && _.isIE9) {
       this.onCut = function () {
@@ -62,6 +85,7 @@ module.exports = {
       _.on(el, 'cut', this.onCut)
       _.on(el, 'keyup', this.onDel)
     }
+
     // set initial value if present
     if (el.value) {
       // watcher is not set up yet
@@ -70,6 +94,7 @@ module.exports = {
   },
 
   update: function (value) {
+    console.log(value)
     this.el.value = value
   },
 

+ 0 - 1
src/directives/style.js

@@ -16,7 +16,6 @@ module.exports = {
 
   update: function (value) {
     var prop = this.prop
-    /* jshint eqeqeq: true */
     // cast possible numbers/booleans into strings
     if (value != null) {
       value += ''

+ 0 - 1
src/filters/array-filters.js

@@ -111,7 +111,6 @@ exports.orderBy = function (arr, sortKey, reverseKey) {
  */
 
 function contains (val, search) {
-  /* jshint eqeqeq: false */
   if (_.isObject(val)) {
     for (var key in val) {
       if (contains(val[key], search)) {

+ 3 - 3
src/instance/init.js

@@ -54,6 +54,9 @@ exports._init = function (options) {
   // set data after merge.
   this._data = options.data || {}
 
+  // setup event system and option events.
+  this._initEvents()
+
   // the `created` hook is called after basic properties
   // have been set up & before data observation happens.
   this._callHook('created')
@@ -64,9 +67,6 @@ exports._init = function (options) {
   // setup bindings to react to data change.
   this._initBindings()
 
-  // setup event system and option events.
-  this._initEvents()
-
   // if `el` option is passed, start compilation.
   if (options.el) {
     this.$mount(options.el)

+ 11 - 17
src/util/filter.js

@@ -9,7 +9,7 @@ var _ = require('./debug')
  *
  * @param {Vue} vm
  * @param {Array<Object>} filters
- * @param {Watcher} [target]
+ * @param {Object} [target]
  * @return {Object}
  */
 
@@ -22,9 +22,7 @@ exports.resolveFilters = function (vm, filters, target) {
   filters.forEach(function (f) {
     var def = vm.$options.filters[f.name]
     _.assertAsset(def, 'filter', f.name)
-    if (!def) {
-      return
-    }
+    if (!def) return
     var args = f.args
     var reader, writer
     if (typeof def === 'function') {
@@ -34,24 +32,19 @@ exports.resolveFilters = function (vm, filters, target) {
       writer = def.write
     }
     if (reader) {
-      if (!res.read) {
-        res.read = []
-      }
+      if (!res.read) res.read = []
       res.read.push(function (value) {
         return args
           ? reader.apply(vm, [value].concat(args))
           : reader.call(vm, value)
       })
     }
-    // only watchers needs write filters
-    if (target && writer) {
-      if (!res.write) {
-        res.write = []
-      }
-      res.write.push(function (value) {
+    if (writer) {
+      if (!res.write) res.write = []
+      res.write.push(function (value, oldVal) {
         return args
-          ? writer.apply(vm, [value, res.value].concat(args))
-          : writer.call(vm, value, res.value)
+          ? writer.apply(vm, [value, oldVal].concat(args))
+          : writer.call(vm, value, oldVal)
       })
     }
   })
@@ -64,15 +57,16 @@ exports.resolveFilters = function (vm, filters, target) {
  * @param {*} value
  * @param {Array} filters
  * @param {Vue} vm
+ * @param {*} oldVal
  * @return {*}
  */
 
-exports.applyFilters = function (value, filters, vm) {
+exports.applyFilters = function (value, filters, vm, oldVal) {
   if (!filters) {
     return value
   }
   for (var i = 0, l = filters.length; i < l; i++) {
-    value = filters[i].call(vm, value)
+    value = filters[i].call(vm, value, oldVal)
   }
   return value
 }

+ 0 - 1
src/util/lang.js

@@ -19,7 +19,6 @@ exports.isReserved = function (str) {
  */
 
 exports.toString = function (value) {
-  /* jshint eqeqeq:false */
   return value == null
     ? ''
     : value.toString()

+ 7 - 5
src/watcher.js

@@ -45,17 +45,17 @@ function Watcher (vm, expression, cb, filters, needSet) {
   this.id = ++uid // uid for batching
   this.value = undefined
   this.active = true
+  this.force = false
   this.deps = Object.create(null)
   this.newDeps = Object.create(null)
   // setup filters if any.
   // We delegate directive filters here to the watcher
   // because they need to be included in the dependency
   // collection process.
-  var res = _.resolveFilters(vm, filters, this)
-  this.readFilters = res && res.read
-  this.writeFilters = res && res.write
+  this.readFilters = filters && filters.read
+  this.writeFilters = filters && filters.write
   // parse expression for getter/setter
-  res = expParser.parse(expression, needSet)
+  var res = expParser.parse(expression, needSet)
   this.getter = res.get
   this.setter = res.set
   this.initDeps(res)
@@ -141,7 +141,9 @@ p.get = function () {
 
 p.set = function (value) {
   var vm = this.vm
-  value = _.applyFilters(value, this.writeFilters, vm)
+  value = _.applyFilters(
+    value, this.writeFilters, vm, this.value
+  )
   this.setter.call(vm, vm, value)
 }
 

+ 6 - 3
test/unit/specs/watcher_spec.js

@@ -1,6 +1,7 @@
 var Vue = require('../../../src/vue')
 var nextTick = Vue.nextTick
 var Watcher = require('../../../src/watcher')
+var _ = Vue.util
 
 describe('Watcher', function () {
 
@@ -202,10 +203,11 @@ describe('Watcher', function () {
     vm.$options.filters.test2 = function (val, str) {
       return val + str
     }
-    var watcher = new Watcher(vm, 'b.c', spy, [
+    var filters = _.resolveFilters(vm, [
       { name: 'test', args: [3] },
       { name: 'test2', args: ['yo']}
     ])
+    var watcher = new Watcher(vm, 'b.c', spy, filters)
     expect(watcher.value).toBe('6yo')
     vm.b.c = 3
     nextTick(function () {
@@ -221,9 +223,10 @@ describe('Watcher', function () {
         return val > arg ? val : oldVal
       }
     }
-    var watcher = new Watcher(vm, 'b["c"]', spy, [
+    var filters = _.resolveFilters(vm, [
       { name: 'test', args: [5] }
-    ], true)
+    ])
+    var watcher = new Watcher(vm, 'b["c"]', spy, filters, true)
     expect(watcher.value).toBe(2)
     watcher.set(4) // shoud not change the value
     nextTick(function () {