Skip to content

Commit

Permalink
fix(v-model): fix input listener with modifier blocking v-model update
Browse files Browse the repository at this point in the history
fix #6552
  • Loading branch information
yyx990803 committed Sep 12, 2017
1 parent a977740 commit 6f312d6
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 2 deletions.
24 changes: 22 additions & 2 deletions src/core/vdom/helpers/update-listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@ import { cached, isUndef } from 'shared/util'

const normalizeEvent = cached((name: string): {
name: string,
plain: boolean,
once: boolean,
capture: boolean,
passive: boolean
passive: boolean,
handler?: Function
} => {
const passive = name.charAt(0) === '&'
name = passive ? name.slice(1) : name
const once = name.charAt(0) === '~' // Prefixed last, checked first
name = once ? name.slice(1) : name
const capture = name.charAt(0) === '!'
name = capture ? name.slice(1) : name
const plain = !(passive || once || capture)
return {
name,
plain,
once,
capture,
passive
Expand All @@ -40,6 +44,11 @@ export function createFnInvoker (fns: Function | Array<Function>): Function {
return invoker
}

// #6552
function prioritizePlainEvents (a, b) {
return a.plain ? -1 : b.plain ? 1 : 0
}

export function updateListeners (
on: Object,
oldOn: Object,
Expand All @@ -48,10 +57,13 @@ export function updateListeners (
vm: Component
) {
let name, cur, old, event
const toAdd = []
let hasModifier = false
for (name in on) {
cur = on[name]
old = oldOn[name]
event = normalizeEvent(name)
if (!event.plain) hasModifier = true
if (isUndef(cur)) {
process.env.NODE_ENV !== 'production' && warn(
`Invalid handler for event "${event.name}": got ` + String(cur),
Expand All @@ -61,12 +73,20 @@ export function updateListeners (
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur)
}
add(event.name, cur, event.once, event.capture, event.passive)
event.handler = cur
toAdd.push(event)
} else if (cur !== old) {
old.fns = cur
on[name] = old
}
}
if (toAdd.length) {
if (hasModifier) toAdd.sort(prioritizePlainEvents)
for (let i = 0; i < toAdd.length; i++) {
const event = toAdd[i]
add(event.name, event.handler, event.once, event.capture, event.passive)
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name)
Expand Down
61 changes: 61 additions & 0 deletions test/unit/features/directives/model-text.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,5 +293,66 @@ describe('Directive v-model text', () => {
triggerEvent(vm.$el, 'compositionend')
expect(spy.calls.count()).toBe(2)
})

// #4392
it('should not update value with modifiers when in focus if post-conversion values are the same', done => {
const vm = new Vue({
data: {
a: 1,
foo: false
},
template: '<div>{{ foo }}<input ref="input" v-model.number="a"></div>'
}).$mount()

document.body.appendChild(vm.$el)
vm.$refs.input.focus()
vm.$refs.input.value = '1.000'
vm.foo = true

waitForUpdate(() => {
expect(vm.$refs.input.value).toBe('1.000')
}).then(done)
})

// #6552
// Root cause: input listeners with modifiers are added as a separate
// DOM listener. If a change is triggered inside this listener, an update
// will happen before the second listener is fired! (obviously microtasks
// can be processed in between DOM events on the same element)
// This causes the domProps module to receive state that has not been
// updated by v-model yet (because v-model's listener has not fired yet)
// Solution: make sure to always fire v-model's listener first
it('should not block input when another input listener with modifier is used', done => {
const vm = new Vue({
data: {
a: 'a',
foo: false
},
template: `
<div>
<input ref="input" v-model="a" @input.capture="onInput">{{ a }}
<div v-if="foo">foo</div>
</div>
`,
methods: {
onInput (e) {
this.foo = true
}
}
}).$mount()

document.body.appendChild(vm.$el)
vm.$refs.input.focus()
vm.$refs.input.value = 'b'
triggerEvent(vm.$refs.input, 'input')

// not using wait for update here because there will be two update cycles
// one caused by onInput in the first listener
setTimeout(() => {
expect(vm.a).toBe('b')
expect(vm.$refs.input.value).toBe('b')
done()
}, 16)
})
}
})

0 comments on commit 6f312d6

Please sign in to comment.