Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 199a55f

Browse files
committed
rework two-way filters for v-model (close vuejs#1141)
1 parent 762077b commit 199a55f

File tree

3 files changed

+67
-61
lines changed

3 files changed

+67
-61
lines changed

src/directive.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,16 @@ p._teardown = function () {
198198
*/
199199

200200
p.set = function (value) {
201+
/* istanbul ignore else */
201202
if (this.twoWay) {
202203
this._withLock(function () {
203204
this._watcher.set(value)
204205
})
206+
} else if (process.env.NODE_ENV !== 'production') {
207+
_.warn(
208+
'Directive.set() can only be used inside twoWay' +
209+
'directives.'
210+
)
205211
}
206212
}
207213

src/directives/model/text.js

Lines changed: 45 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module.exports = {
55
bind: function () {
66
var self = this
77
var el = this.el
8+
var isRange = el.type === 'range'
89

910
// check params
1011
// - lazy: update model on "change" instead of "input"
@@ -22,7 +23,7 @@ module.exports = {
2223
// Chinese, but instead triggers them for spelling
2324
// suggestions... (see Discussion/#162)
2425
var composing = false
25-
if (!_.isAndroid) {
26+
if (!_.isAndroid && !isRange) {
2627
this.onComposeStart = function () {
2728
composing = true
2829
}
@@ -37,63 +38,38 @@ module.exports = {
3738
_.on(el, 'compositionend', this.onComposeEnd)
3839
}
3940

40-
function syncToModel () {
41-
var val = number
42-
? _.toNumber(el.value)
43-
: el.value
44-
self.set(val)
45-
}
46-
47-
// if the directive has filters, we need to
48-
// record cursor position and restore it after updating
49-
// the input with the filtered value.
50-
// also force update for type="range" inputs to enable
51-
// "lock in range" (see #506)
52-
if (this.hasRead || el.type === 'range') {
53-
this.listener = function () {
54-
if (composing) return
55-
var charsOffset
56-
// some HTML5 input types throw error here
57-
try {
58-
// record how many chars from the end of input
59-
// the cursor was at
60-
charsOffset = el.value.length - el.selectionStart
61-
} catch (e) {}
62-
// Fix IE10/11 infinite update cycle
63-
// https://github.com/yyx990803/vue/issues/592
64-
/* istanbul ignore if */
65-
if (charsOffset < 0) {
66-
return
67-
}
68-
syncToModel()
69-
_.nextTick(function () {
70-
// force a value update, because in
71-
// certain cases the write filters output the
72-
// same result for different input values, and
73-
// the Observer set events won't be triggered.
74-
var newVal = self._watcher.value
75-
self.update(newVal)
76-
if (charsOffset != null) {
77-
var cursorPos =
78-
_.toString(newVal).length - charsOffset
79-
el.setSelectionRange(cursorPos, cursorPos)
80-
}
81-
})
41+
// prevent messing with the input when user is typing,
42+
// and force update on blur.
43+
this.focused = false
44+
if (!isRange) {
45+
this.onFocus = function () {
46+
self.focused = true
8247
}
83-
} else {
84-
this.listener = function () {
85-
if (composing) return
86-
syncToModel()
48+
this.onBlur = function () {
49+
self.focused = false
50+
self.listener()
8751
}
52+
_.on(el, 'focus', this.onFocus)
53+
_.on(el, 'blur', this.onBlur)
8854
}
8955

56+
// Now attach the main listener
57+
this.listener = function () {
58+
if (composing) return
59+
var val = number || isRange
60+
? _.toNumber(el.value)
61+
: el.value
62+
self.set(val)
63+
// force update here, because the watcher may not
64+
// run when the value is the same.
65+
_.nextTick(function () {
66+
self.update(self._watcher.value)
67+
})
68+
}
9069
if (debounce) {
9170
this.listener = _.debounce(this.listener, debounce)
9271
}
9372

94-
// Now attach the main listener
95-
96-
this.event = lazy ? 'change' : 'input'
9773
// Support jQuery events, since jQuery.trigger() doesn't
9874
// trigger native events in some cases and some plugins
9975
// rely on $.trigger()
@@ -106,9 +82,15 @@ module.exports = {
10682
// jQuery variable in tests.
10783
this.hasjQuery = typeof jQuery === 'function'
10884
if (this.hasjQuery) {
109-
jQuery(el).on(this.event, this.listener)
85+
jQuery(el).on('change', this.listener)
86+
if (!lazy) {
87+
jQuery(el).on('input', this.listener)
88+
}
11089
} else {
111-
_.on(el, this.event, this.listener)
90+
_.on(el, 'change', this.listener)
91+
if (!lazy) {
92+
_.on(el, 'input', this.listener)
93+
}
11294
}
11395

11496
// IE9 doesn't fire input event on backspace/del/cut
@@ -137,15 +119,19 @@ module.exports = {
137119
},
138120

139121
update: function (value) {
140-
this.el.value = _.toString(value)
122+
if (!this.focused) {
123+
this.el.value = _.toString(value)
124+
}
141125
},
142126

143127
unbind: function () {
144128
var el = this.el
145129
if (this.hasjQuery) {
146-
jQuery(el).off(this.event, this.listener)
130+
jQuery(el).off('change', this.listener)
131+
jQuery(el).off('input', this.listener)
147132
} else {
148-
_.off(el, this.event, this.listener)
133+
_.off(el, 'change', this.listener)
134+
_.off(el, 'input', this.listener)
149135
}
150136
if (this.onComposeStart) {
151137
_.off(el, 'compositionstart', this.onComposeStart)
@@ -155,5 +141,9 @@ module.exports = {
155141
_.off(el, 'cut', this.onCut)
156142
_.off(el, 'keyup', this.onDel)
157143
}
144+
if (this.onFocus) {
145+
_.off(el, 'focus', this.onFocus)
146+
_.off(el, 'blur', this.onBlur)
147+
}
158148
}
159149
}

test/unit/specs/directives/model_spec.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -480,17 +480,21 @@ if (_.inBrowser) {
480480
template: '<input v-model="test | uppercase | test">'
481481
})
482482
expect(el.firstChild.value).toBe('B')
483+
trigger(el.firstChild, 'focus')
483484
el.firstChild.value = 'cc'
484485
trigger(el.firstChild, 'input')
485486
_.nextTick(function () {
486-
expect(el.firstChild.value).toBe('CC')
487+
expect(el.firstChild.value).toBe('cc')
487488
expect(vm.test).toBe('cc')
488-
done()
489+
trigger(el.firstChild, 'blur')
490+
_.nextTick(function () {
491+
expect(el.firstChild.value).toBe('CC')
492+
expect(vm.test).toBe('cc')
493+
done()
494+
})
489495
})
490496
})
491497

492-
// when there's only write filter, should allow
493-
// out of sync between the input field and actual data
494498
it('text with only write filter', function (done) {
495499
var vm = new Vue({
496500
el: el,
@@ -506,12 +510,18 @@ if (_.inBrowser) {
506510
},
507511
template: '<input v-model="test | test">'
508512
})
513+
trigger(el.firstChild, 'focus')
509514
el.firstChild.value = 'cc'
510515
trigger(el.firstChild, 'input')
511516
_.nextTick(function () {
512517
expect(el.firstChild.value).toBe('cc')
513518
expect(vm.test).toBe('CC')
514-
done()
519+
trigger(el.firstChild, 'blur')
520+
_.nextTick(function () {
521+
expect(el.firstChild.value).toBe('CC')
522+
expect(vm.test).toBe('CC')
523+
done()
524+
})
515525
})
516526
})
517527

@@ -647,7 +657,7 @@ if (_.inBrowser) {
647657
data: {
648658
test: 'b'
649659
},
650-
template: '<input v-model="test" lazy>'
660+
template: '<input v-model="test">'
651661
})
652662
expect(el.firstChild.value).toBe('b')
653663
vm.test = 'a'

0 commit comments

Comments
 (0)