Skip to content

Commit 05c769b

Browse files
committed
fix .once with other modifiers that prevent execution of a handler (fix vuejs#4846)
1 parent f59aef0 commit 05c769b

File tree

5 files changed

+42
-21
lines changed

5 files changed

+42
-21
lines changed

src/compiler/codegen/events.js

+10-7
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@ const keyCodes: { [key: string]: number | Array<number> } = {
1919
const modifierCode: { [key: string]: string } = {
2020
stop: '$event.stopPropagation();',
2121
prevent: '$event.preventDefault();',
22-
self: 'if($event.target !== $event.currentTarget)return;',
23-
ctrl: 'if(!$event.ctrlKey)return;',
24-
shift: 'if(!$event.shiftKey)return;',
25-
alt: 'if(!$event.altKey)return;',
26-
meta: 'if(!$event.metaKey)return;'
22+
// #4868: modifiers that prevent the execution of the listener
23+
// need to explicitly return null so that we can determine whether to remove
24+
// the listener for .once
25+
self: 'if($event.target !== $event.currentTarget)return null;',
26+
ctrl: 'if(!$event.ctrlKey)return null;',
27+
shift: 'if(!$event.shiftKey)return null;',
28+
alt: 'if(!$event.altKey)return null;',
29+
meta: 'if(!$event.metaKey)return null;'
2730
}
2831

2932
export function genHandlers (events: ASTElementHandlers, native?: boolean): string {
@@ -62,12 +65,12 @@ function genHandler (
6265
const handlerCode = simplePathRE.test(handler.value)
6366
? handler.value + '($event)'
6467
: handler.value
65-
return 'function($event){' + code + handlerCode + '}'
68+
return `function($event){${code}${handlerCode}}`
6669
}
6770
}
6871

6972
function genKeyFilter (keys: Array<string>): string {
70-
return `if(${keys.map(genFilterCode).join('&&')})return;`
73+
return `if(${keys.map(genFilterCode).join('&&')})return null;`
7174
}
7275

7376
function genFilterCode (key: string): string {

src/core/vdom/helpers/update-listeners.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ function createEventHandle (fn: Function | Array<Function>): {
3232
fn[i].apply(null, arguments)
3333
}
3434
} else {
35-
fn.apply(null, arguments)
35+
// return handler return value for single handlers
36+
return fn.apply(null, arguments)
3637
}
3738
}
3839
}

src/platforms/web/runtime/modules/events.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ function add (
1414
const oldHandler = handler
1515
const _target = target // save current target element in closure
1616
handler = function (ev) {
17-
remove(event, handler, capture, _target)
18-
arguments.length === 1
17+
const res = arguments.length === 1
1918
? oldHandler(ev)
2019
: oldHandler.apply(null, arguments)
20+
if (res !== null) {
21+
remove(event, handler, capture, _target)
22+
}
2123
}
2224
}
2325
target.addEventListener(event, handler, capture)

test/unit/features/directives/on.spec.js

+15
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,21 @@ describe('Directive v-on', () => {
173173
expect(callOrder.toString()).toBe('1,2,2')
174174
})
175175

176+
// #4846
177+
it('should support once and other modifiers', () => {
178+
vm = new Vue({
179+
el,
180+
template: `<div @click.once.self="foo"><span/></div>`,
181+
methods: { foo: spy }
182+
})
183+
triggerEvent(vm.$el.firstChild, 'click')
184+
expect(spy).not.toHaveBeenCalled()
185+
triggerEvent(vm.$el, 'click')
186+
expect(spy).toHaveBeenCalled()
187+
triggerEvent(vm.$el, 'click')
188+
expect(spy.calls.count()).toBe(1)
189+
})
190+
176191
it('should support keyCode', () => {
177192
vm = new Vue({
178193
el,

test/unit/modules/compiler/codegen.spec.js

+11-11
Original file line numberDiff line numberDiff line change
@@ -229,27 +229,27 @@ describe('codegen', () => {
229229
it('generate events with keycode', () => {
230230
assertCodegen(
231231
'<input @input.enter="onInput">',
232-
`with(this){return _c('input',{on:{"input":function($event){if(_k($event.keyCode,"enter",13))return;onInput($event)}}})}`
232+
`with(this){return _c('input',{on:{"input":function($event){if(_k($event.keyCode,"enter",13))return null;onInput($event)}}})}`
233233
)
234234
// multiple keycodes (delete)
235235
assertCodegen(
236236
'<input @input.delete="onInput">',
237-
`with(this){return _c('input',{on:{"input":function($event){if(_k($event.keyCode,"delete",[8,46]))return;onInput($event)}}})}`
237+
`with(this){return _c('input',{on:{"input":function($event){if(_k($event.keyCode,"delete",[8,46]))return null;onInput($event)}}})}`
238238
)
239239
// multiple keycodes (chained)
240240
assertCodegen(
241241
'<input @keydown.enter.delete="onInput">',
242-
`with(this){return _c('input',{on:{"keydown":function($event){if(_k($event.keyCode,"enter",13)&&_k($event.keyCode,"delete",[8,46]))return;onInput($event)}}})}`
242+
`with(this){return _c('input',{on:{"keydown":function($event){if(_k($event.keyCode,"enter",13)&&_k($event.keyCode,"delete",[8,46]))return null;onInput($event)}}})}`
243243
)
244244
// number keycode
245245
assertCodegen(
246246
'<input @input.13="onInput">',
247-
`with(this){return _c('input',{on:{"input":function($event){if($event.keyCode!==13)return;onInput($event)}}})}`
247+
`with(this){return _c('input',{on:{"input":function($event){if($event.keyCode!==13)return null;onInput($event)}}})}`
248248
)
249249
// custom keycode
250250
assertCodegen(
251251
'<input @input.custom="onInput">',
252-
`with(this){return _c('input',{on:{"input":function($event){if(_k($event.keyCode,"custom"))return;onInput($event)}}})}`
252+
`with(this){return _c('input',{on:{"input":function($event){if(_k($event.keyCode,"custom"))return null;onInput($event)}}})}`
253253
)
254254
})
255255

@@ -264,33 +264,33 @@ describe('codegen', () => {
264264
)
265265
assertCodegen(
266266
'<input @input.self="onInput">',
267-
`with(this){return _c('input',{on:{"input":function($event){if($event.target !== $event.currentTarget)return;onInput($event)}}})}`
267+
`with(this){return _c('input',{on:{"input":function($event){if($event.target !== $event.currentTarget)return null;onInput($event)}}})}`
268268
)
269269
})
270270

271271
it('generate events with mouse event modifiers', () => {
272272
assertCodegen(
273273
'<input @click.ctrl="onClick">',
274-
`with(this){return _c('input',{on:{"click":function($event){if(!$event.ctrlKey)return;onClick($event)}}})}`
274+
`with(this){return _c('input',{on:{"click":function($event){if(!$event.ctrlKey)return null;onClick($event)}}})}`
275275
)
276276
assertCodegen(
277277
'<input @click.shift="onClick">',
278-
`with(this){return _c('input',{on:{"click":function($event){if(!$event.shiftKey)return;onClick($event)}}})}`
278+
`with(this){return _c('input',{on:{"click":function($event){if(!$event.shiftKey)return null;onClick($event)}}})}`
279279
)
280280
assertCodegen(
281281
'<input @click.alt="onClick">',
282-
`with(this){return _c('input',{on:{"click":function($event){if(!$event.altKey)return;onClick($event)}}})}`
282+
`with(this){return _c('input',{on:{"click":function($event){if(!$event.altKey)return null;onClick($event)}}})}`
283283
)
284284
assertCodegen(
285285
'<input @click.meta="onClick">',
286-
`with(this){return _c('input',{on:{"click":function($event){if(!$event.metaKey)return;onClick($event)}}})}`
286+
`with(this){return _c('input',{on:{"click":function($event){if(!$event.metaKey)return null;onClick($event)}}})}`
287287
)
288288
})
289289

290290
it('generate events with multiple modifers', () => {
291291
assertCodegen(
292292
'<input @input.stop.prevent.self="onInput">',
293-
`with(this){return _c('input',{on:{"input":function($event){$event.stopPropagation();$event.preventDefault();if($event.target !== $event.currentTarget)return;onInput($event)}}})}`
293+
`with(this){return _c('input',{on:{"input":function($event){$event.stopPropagation();$event.preventDefault();if($event.target !== $event.currentTarget)return null;onInput($event)}}})}`
294294
)
295295
})
296296

0 commit comments

Comments
 (0)