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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy