Skip to content

Commit bbd13af

Browse files
committed
fix(v-model): update model correctly when input event is bound
Use the "input" event instead of "change" when listening for changes to the selection state since the "input" event will always fire before the "change" event. This avoids an issue where a user binds to the "input" event with "v-bind" and causes the component to update and set its model value back to the value of the select before it has received the new selection value. Fixes vuejs#6690
1 parent 1780b1f commit bbd13af

File tree

3 files changed

+45
-18
lines changed

3 files changed

+45
-18
lines changed

src/platforms/web/compiler/directives/model.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ function genSelect (
126126
const assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]'
127127
let code = `var $$selectedVal = ${selectedVal};`
128128
code = `${code} ${genAssignmentCode(value, assignment)}`
129-
addHandler(el, 'change', code, null, true)
129+
addHandler(el, 'input', code, null, true)
130130
}
131131

132132
function genDefaultModel (

src/platforms/web/runtime/directives/model.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ export default {
5252
const prevOptions = el._vOptions
5353
const curOptions = el._vOptions = [].map.call(el.options, getValue)
5454
if (curOptions.some((o, i) => !looseEqual(o, prevOptions[i]))) {
55-
// trigger change event if
55+
// trigger input event if
5656
// no matching option found for at least one value
5757
const needReset = el.multiple
5858
? binding.value.some(v => hasNoMatchingOption(v, curOptions))
5959
: binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, curOptions)
6060
if (needReset) {
61-
trigger(el, 'change')
61+
trigger(el, 'input')
6262
}
6363
}
6464
}

test/unit/features/directives/model-select.spec.js

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe('Directive v-model select', () => {
5656
expect(vm.$el.value).toBe('c')
5757
expect(vm.$el.childNodes[2].selected).toBe(true)
5858
updateSelect(vm.$el, 'a')
59-
triggerEvent(vm.$el, 'change')
59+
triggerEvent(vm.$el, 'input')
6060
expect(vm.test).toBe('a')
6161
}).then(done)
6262
})
@@ -82,11 +82,11 @@ describe('Directive v-model select', () => {
8282
expect(vm.$el.childNodes[2].selected).toBe(true)
8383

8484
updateSelect(vm.$el, '1')
85-
triggerEvent(vm.$el, 'change')
85+
triggerEvent(vm.$el, 'input')
8686
expect(vm.test).toBe('1')
8787

8888
updateSelect(vm.$el, '2')
89-
triggerEvent(vm.$el, 'change')
89+
triggerEvent(vm.$el, 'input')
9090
expect(vm.test).toBe(2)
9191
}).then(done)
9292
})
@@ -110,11 +110,11 @@ describe('Directive v-model select', () => {
110110
expect(vm.$el.childNodes[2].selected).toBe(true)
111111

112112
updateSelect(vm.$el, '1')
113-
triggerEvent(vm.$el, 'change')
113+
triggerEvent(vm.$el, 'input')
114114
expect(vm.test).toBe('1')
115115

116116
updateSelect(vm.$el, { a: 2 })
117-
triggerEvent(vm.$el, 'change')
117+
triggerEvent(vm.$el, 'input')
118118
expect(vm.test).toEqual({ a: 2 })
119119
}).then(done)
120120
})
@@ -138,11 +138,11 @@ describe('Directive v-model select', () => {
138138
expect(vm.$el.childNodes[2].selected).toBe(true)
139139

140140
updateSelect(vm.$el, '1')
141-
triggerEvent(vm.$el, 'change')
141+
triggerEvent(vm.$el, 'input')
142142
expect(vm.test).toBe('1')
143143

144144
updateSelect(vm.$el, [{ a: 2 }])
145-
triggerEvent(vm.$el, 'change')
145+
triggerEvent(vm.$el, 'input')
146146
expect(vm.test).toEqual([{ a: 2 }])
147147
}).then(done)
148148
})
@@ -167,7 +167,7 @@ describe('Directive v-model select', () => {
167167
expect(vm.$el.value).toBe('c')
168168
expect(vm.$el.childNodes[2].selected).toBe(true)
169169
updateSelect(vm.$el, 'a')
170-
triggerEvent(vm.$el, 'change')
170+
triggerEvent(vm.$el, 'input')
171171
expect(vm.test).toBe('a')
172172
// update v-for opts
173173
vm.opts = ['d', 'a']
@@ -196,7 +196,7 @@ describe('Directive v-model select', () => {
196196
expect(vm.$el.value).toBe('3')
197197
expect(vm.$el.childNodes[2].selected).toBe(true)
198198
updateSelect(vm.$el, 1)
199-
triggerEvent(vm.$el, 'change')
199+
triggerEvent(vm.$el, 'input')
200200
expect(vm.test).toBe(1)
201201
// update v-for opts
202202
vm.opts = [0, 1]
@@ -256,7 +256,7 @@ describe('Directive v-model select', () => {
256256
expect(opts[2].selected).toBe(true)
257257
opts[0].selected = false
258258
opts[1].selected = true
259-
triggerEvent(vm.$el, 'change')
259+
triggerEvent(vm.$el, 'input')
260260
expect(vm.test).toEqual(['b', 'c'])
261261
}).then(done)
262262
})
@@ -283,14 +283,14 @@ describe('Directive v-model select', () => {
283283
expect(opts[2].selected).toBe(true)
284284
opts[0].selected = false
285285
opts[1].selected = true
286-
triggerEvent(vm.$el, 'change')
286+
triggerEvent(vm.$el, 'input')
287287
expect(vm.test).toEqual(['b', 'c'])
288288
// update v-for opts
289289
vm.opts = ['c', 'd']
290290
}).then(() => {
291291
expect(opts[0].selected).toBe(true)
292292
expect(opts[1].selected).toBe(false)
293-
expect(vm.test).toEqual(['c']) // should remove 'd' which no longer has a matching option
293+
expect(vm.test).toEqual(['c']) // should remove 'b' which no longer has a matching option
294294
}).then(done)
295295
})
296296
}
@@ -313,7 +313,7 @@ describe('Directive v-model select', () => {
313313
}).$mount()
314314
document.body.appendChild(vm.$el)
315315
vm.$el.options[1].selected = true
316-
triggerEvent(vm.$el, 'change')
316+
triggerEvent(vm.$el, 'input')
317317
waitForUpdate(() => {
318318
expect(spy).toHaveBeenCalled()
319319
expect(vm.selections).toEqual(['1', '2'])
@@ -382,7 +382,7 @@ describe('Directive v-model select', () => {
382382
var selects = vm.$el.getElementsByTagName('select')
383383
var select0 = selects[0]
384384
select0.options[0].selected = true
385-
triggerEvent(select0, 'change')
385+
triggerEvent(select0, 'input')
386386
waitForUpdate(() => {
387387
expect(spy).toHaveBeenCalled()
388388
expect(vm.selections).toEqual(['foo', ''])
@@ -403,7 +403,7 @@ describe('Directive v-model select', () => {
403403
}).$mount()
404404
document.body.appendChild(vm.$el)
405405
updateSelect(vm.$el, '1')
406-
triggerEvent(vm.$el, 'change')
406+
triggerEvent(vm.$el, 'input')
407407
expect(vm.test).toBe(1)
408408
})
409409

@@ -546,4 +546,31 @@ describe('Directive v-model select', () => {
546546
expect(spy).not.toHaveBeenCalled()
547547
}).then(done)
548548
})
549+
550+
// #6690
551+
it('should work on an element with an input binding', done => {
552+
const vm = new Vue({
553+
data: {
554+
test1: '',
555+
inputEvaluated: ''
556+
},
557+
template:
558+
`<select v-model="test1" v-on:input="inputEvaluated = 'blarg'" v-bind:name="inputEvaluated">` +
559+
'<option value="">test1 Please Select</option>' +
560+
'<option value="alpha">Alpha</option>' +
561+
'<option value="beta">Beta</option>' +
562+
'</select>'
563+
}).$mount()
564+
document.body.appendChild(vm.$el)
565+
566+
vm.$el.children[1].selected = true
567+
568+
triggerEvent(vm.$el, 'input')
569+
570+
waitForUpdate(() => {
571+
triggerEvent(vm.$el, 'change')
572+
}).then(() => {
573+
expect(vm.$el.value).toBe('alpha')
574+
}).then(done)
575+
})
549576
})

0 commit comments

Comments
 (0)