Sfoglia il codice sorgente

rework two-way filters for v-model (close #1141)

Evan You 10 anni fa
parent
commit
199a55fca2
3 ha cambiato i file con 67 aggiunte e 61 eliminazioni
  1. 6 0
      src/directive.js
  2. 45 55
      src/directives/model/text.js
  3. 16 6
      test/unit/specs/directives/model_spec.js

+ 6 - 0
src/directive.js

@@ -198,10 +198,16 @@ p._teardown = function () {
  */
 
 p.set = function (value) {
+  /* istanbul ignore else */
   if (this.twoWay) {
     this._withLock(function () {
       this._watcher.set(value)
     })
+  } else if (process.env.NODE_ENV !== 'production') {
+    _.warn(
+      'Directive.set() can only be used inside twoWay' +
+      'directives.'
+    )
   }
 }
 

+ 45 - 55
src/directives/model/text.js

@@ -5,6 +5,7 @@ module.exports = {
   bind: function () {
     var self = this
     var el = this.el
+    var isRange = el.type === 'range'
 
     // check params
     // - lazy: update model on "change" instead of "input"
@@ -22,7 +23,7 @@ module.exports = {
     // Chinese, but instead triggers them for spelling
     // suggestions... (see Discussion/#162)
     var composing = false
-    if (!_.isAndroid) {
+    if (!_.isAndroid && !isRange) {
       this.onComposeStart = function () {
         composing = true
       }
@@ -37,63 +38,38 @@ module.exports = {
       _.on(el, 'compositionend', this.onComposeEnd)
     }
 
-    function syncToModel () {
-      var val = number
-        ? _.toNumber(el.value)
-        : el.value
-      self.set(val)
-    }
-
-    // if the directive has filters, we need to
-    // record cursor position and restore it after updating
-    // the input with the filtered value.
-    // also force update for type="range" inputs to enable
-    // "lock in range" (see #506)
-    if (this.hasRead || el.type === 'range') {
-      this.listener = function () {
-        if (composing) return
-        var charsOffset
-        // some HTML5 input types throw error here
-        try {
-          // record how many chars from the end of input
-          // the cursor was at
-          charsOffset = el.value.length - el.selectionStart
-        } catch (e) {}
-        // Fix IE10/11 infinite update cycle
-        // https://github.com/yyx990803/vue/issues/592
-        /* istanbul ignore if */
-        if (charsOffset < 0) {
-          return
-        }
-        syncToModel()
-        _.nextTick(function () {
-          // 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.
-          var newVal = self._watcher.value
-          self.update(newVal)
-          if (charsOffset != null) {
-            var cursorPos =
-              _.toString(newVal).length - charsOffset
-            el.setSelectionRange(cursorPos, cursorPos)
-          }
-        })
+    // prevent messing with the input when user is typing,
+    // and force update on blur.
+    this.focused = false
+    if (!isRange) {
+      this.onFocus = function () {
+        self.focused = true
       }
-    } else {
-      this.listener = function () {
-        if (composing) return
-        syncToModel()
+      this.onBlur = function () {
+        self.focused = false
+        self.listener()
       }
+      _.on(el, 'focus', this.onFocus)
+      _.on(el, 'blur', this.onBlur)
     }
 
+    // Now attach the main listener
+    this.listener = function () {
+      if (composing) return
+      var val = number || isRange
+        ? _.toNumber(el.value)
+        : el.value
+      self.set(val)
+      // force update here, because the watcher may not
+      // run when the value is the same.
+      _.nextTick(function () {
+        self.update(self._watcher.value)
+      })
+    }
     if (debounce) {
       this.listener = _.debounce(this.listener, debounce)
     }
 
-    // Now attach the main listener
-
-    this.event = lazy ? 'change' : 'input'
     // Support jQuery events, since jQuery.trigger() doesn't
     // trigger native events in some cases and some plugins
     // rely on $.trigger()
@@ -106,9 +82,15 @@ module.exports = {
     // jQuery variable in tests.
     this.hasjQuery = typeof jQuery === 'function'
     if (this.hasjQuery) {
-      jQuery(el).on(this.event, this.listener)
+      jQuery(el).on('change', this.listener)
+      if (!lazy) {
+        jQuery(el).on('input', this.listener)
+      }
     } else {
-      _.on(el, this.event, this.listener)
+      _.on(el, 'change', this.listener)
+      if (!lazy) {
+        _.on(el, 'input', this.listener)
+      }
     }
 
     // IE9 doesn't fire input event on backspace/del/cut
@@ -137,15 +119,19 @@ module.exports = {
   },
 
   update: function (value) {
-    this.el.value = _.toString(value)
+    if (!this.focused) {
+      this.el.value = _.toString(value)
+    }
   },
 
   unbind: function () {
     var el = this.el
     if (this.hasjQuery) {
-      jQuery(el).off(this.event, this.listener)
+      jQuery(el).off('change', this.listener)
+      jQuery(el).off('input', this.listener)
     } else {
-      _.off(el, this.event, this.listener)
+      _.off(el, 'change', this.listener)
+      _.off(el, 'input', this.listener)
     }
     if (this.onComposeStart) {
       _.off(el, 'compositionstart', this.onComposeStart)
@@ -155,5 +141,9 @@ module.exports = {
       _.off(el, 'cut', this.onCut)
       _.off(el, 'keyup', this.onDel)
     }
+    if (this.onFocus) {
+      _.off(el, 'focus', this.onFocus)
+      _.off(el, 'blur', this.onBlur)
+    }
   }
 }

+ 16 - 6
test/unit/specs/directives/model_spec.js

@@ -480,17 +480,21 @@ if (_.inBrowser) {
         template: '<input v-model="test | uppercase | test">'
       })
       expect(el.firstChild.value).toBe('B')
+      trigger(el.firstChild, 'focus')
       el.firstChild.value = 'cc'
       trigger(el.firstChild, 'input')
       _.nextTick(function () {
-        expect(el.firstChild.value).toBe('CC')
+        expect(el.firstChild.value).toBe('cc')
         expect(vm.test).toBe('cc')
-        done()
+        trigger(el.firstChild, 'blur')
+        _.nextTick(function () {
+          expect(el.firstChild.value).toBe('CC')
+          expect(vm.test).toBe('cc')
+          done()
+        })
       })
     })
 
-    // when there's only write filter, should allow
-    // out of sync between the input field and actual data
     it('text with only write filter', function (done) {
       var vm = new Vue({
         el: el,
@@ -506,12 +510,18 @@ if (_.inBrowser) {
         },
         template: '<input v-model="test | test">'
       })
+      trigger(el.firstChild, 'focus')
       el.firstChild.value = 'cc'
       trigger(el.firstChild, 'input')
       _.nextTick(function () {
         expect(el.firstChild.value).toBe('cc')
         expect(vm.test).toBe('CC')
-        done()
+        trigger(el.firstChild, 'blur')
+        _.nextTick(function () {
+          expect(el.firstChild.value).toBe('CC')
+          expect(vm.test).toBe('CC')
+          done()
+        })
       })
     })
 
@@ -647,7 +657,7 @@ if (_.inBrowser) {
         data: {
           test: 'b'
         },
-        template: '<input v-model="test" lazy>'
+        template: '<input v-model="test">'
       })
       expect(el.firstChild.value).toBe('b')
       vm.test = 'a'