From 05c769bf4442d57f4593016d562bb702174ae295 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 14 Feb 2017 17:09:37 -0500 Subject: [PATCH] fix .once with other modifiers that prevent execution of a handler (fix #4846) --- src/compiler/codegen/events.js | 17 +++++++++------- src/core/vdom/helpers/update-listeners.js | 3 ++- src/platforms/web/runtime/modules/events.js | 6 ++++-- test/unit/features/directives/on.spec.js | 15 ++++++++++++++ test/unit/modules/compiler/codegen.spec.js | 22 ++++++++++----------- 5 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/compiler/codegen/events.js b/src/compiler/codegen/events.js index f4a2dffca38..28e454241aa 100644 --- a/src/compiler/codegen/events.js +++ b/src/compiler/codegen/events.js @@ -19,11 +19,14 @@ const keyCodes: { [key: string]: number | Array } = { const modifierCode: { [key: string]: string } = { stop: '$event.stopPropagation();', prevent: '$event.preventDefault();', - self: 'if($event.target !== $event.currentTarget)return;', - ctrl: 'if(!$event.ctrlKey)return;', - shift: 'if(!$event.shiftKey)return;', - alt: 'if(!$event.altKey)return;', - meta: 'if(!$event.metaKey)return;' + // #4868: modifiers that prevent the execution of the listener + // need to explicitly return null so that we can determine whether to remove + // the listener for .once + self: 'if($event.target !== $event.currentTarget)return null;', + ctrl: 'if(!$event.ctrlKey)return null;', + shift: 'if(!$event.shiftKey)return null;', + alt: 'if(!$event.altKey)return null;', + meta: 'if(!$event.metaKey)return null;' } export function genHandlers (events: ASTElementHandlers, native?: boolean): string { @@ -62,12 +65,12 @@ function genHandler ( const handlerCode = simplePathRE.test(handler.value) ? handler.value + '($event)' : handler.value - return 'function($event){' + code + handlerCode + '}' + return `function($event){${code}${handlerCode}}` } } function genKeyFilter (keys: Array): string { - return `if(${keys.map(genFilterCode).join('&&')})return;` + return `if(${keys.map(genFilterCode).join('&&')})return null;` } function genFilterCode (key: string): string { diff --git a/src/core/vdom/helpers/update-listeners.js b/src/core/vdom/helpers/update-listeners.js index 2af4a0c7fc6..3bf17aa0210 100644 --- a/src/core/vdom/helpers/update-listeners.js +++ b/src/core/vdom/helpers/update-listeners.js @@ -32,7 +32,8 @@ function createEventHandle (fn: Function | Array): { fn[i].apply(null, arguments) } } else { - fn.apply(null, arguments) + // return handler return value for single handlers + return fn.apply(null, arguments) } } } diff --git a/src/platforms/web/runtime/modules/events.js b/src/platforms/web/runtime/modules/events.js index 23ba7c7b6f4..b3036f6eba9 100644 --- a/src/platforms/web/runtime/modules/events.js +++ b/src/platforms/web/runtime/modules/events.js @@ -14,10 +14,12 @@ function add ( const oldHandler = handler const _target = target // save current target element in closure handler = function (ev) { - remove(event, handler, capture, _target) - arguments.length === 1 + const res = arguments.length === 1 ? oldHandler(ev) : oldHandler.apply(null, arguments) + if (res !== null) { + remove(event, handler, capture, _target) + } } } target.addEventListener(event, handler, capture) diff --git a/test/unit/features/directives/on.spec.js b/test/unit/features/directives/on.spec.js index 67d9a7801c0..cadccfada2d 100644 --- a/test/unit/features/directives/on.spec.js +++ b/test/unit/features/directives/on.spec.js @@ -173,6 +173,21 @@ describe('Directive v-on', () => { expect(callOrder.toString()).toBe('1,2,2') }) + // #4846 + it('should support once and other modifiers', () => { + vm = new Vue({ + el, + template: `
`, + methods: { foo: spy } + }) + triggerEvent(vm.$el.firstChild, 'click') + expect(spy).not.toHaveBeenCalled() + triggerEvent(vm.$el, 'click') + expect(spy).toHaveBeenCalled() + triggerEvent(vm.$el, 'click') + expect(spy.calls.count()).toBe(1) + }) + it('should support keyCode', () => { vm = new Vue({ el, diff --git a/test/unit/modules/compiler/codegen.spec.js b/test/unit/modules/compiler/codegen.spec.js index 52e196a8b78..b8b51b1d81e 100644 --- a/test/unit/modules/compiler/codegen.spec.js +++ b/test/unit/modules/compiler/codegen.spec.js @@ -229,27 +229,27 @@ describe('codegen', () => { it('generate events with keycode', () => { assertCodegen( '', - `with(this){return _c('input',{on:{"input":function($event){if(_k($event.keyCode,"enter",13))return;onInput($event)}}})}` + `with(this){return _c('input',{on:{"input":function($event){if(_k($event.keyCode,"enter",13))return null;onInput($event)}}})}` ) // multiple keycodes (delete) assertCodegen( '', - `with(this){return _c('input',{on:{"input":function($event){if(_k($event.keyCode,"delete",[8,46]))return;onInput($event)}}})}` + `with(this){return _c('input',{on:{"input":function($event){if(_k($event.keyCode,"delete",[8,46]))return null;onInput($event)}}})}` ) // multiple keycodes (chained) assertCodegen( '', - `with(this){return _c('input',{on:{"keydown":function($event){if(_k($event.keyCode,"enter",13)&&_k($event.keyCode,"delete",[8,46]))return;onInput($event)}}})}` + `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)}}})}` ) // number keycode assertCodegen( '', - `with(this){return _c('input',{on:{"input":function($event){if($event.keyCode!==13)return;onInput($event)}}})}` + `with(this){return _c('input',{on:{"input":function($event){if($event.keyCode!==13)return null;onInput($event)}}})}` ) // custom keycode assertCodegen( '', - `with(this){return _c('input',{on:{"input":function($event){if(_k($event.keyCode,"custom"))return;onInput($event)}}})}` + `with(this){return _c('input',{on:{"input":function($event){if(_k($event.keyCode,"custom"))return null;onInput($event)}}})}` ) }) @@ -264,33 +264,33 @@ describe('codegen', () => { ) assertCodegen( '', - `with(this){return _c('input',{on:{"input":function($event){if($event.target !== $event.currentTarget)return;onInput($event)}}})}` + `with(this){return _c('input',{on:{"input":function($event){if($event.target !== $event.currentTarget)return null;onInput($event)}}})}` ) }) it('generate events with mouse event modifiers', () => { assertCodegen( '', - `with(this){return _c('input',{on:{"click":function($event){if(!$event.ctrlKey)return;onClick($event)}}})}` + `with(this){return _c('input',{on:{"click":function($event){if(!$event.ctrlKey)return null;onClick($event)}}})}` ) assertCodegen( '', - `with(this){return _c('input',{on:{"click":function($event){if(!$event.shiftKey)return;onClick($event)}}})}` + `with(this){return _c('input',{on:{"click":function($event){if(!$event.shiftKey)return null;onClick($event)}}})}` ) assertCodegen( '', - `with(this){return _c('input',{on:{"click":function($event){if(!$event.altKey)return;onClick($event)}}})}` + `with(this){return _c('input',{on:{"click":function($event){if(!$event.altKey)return null;onClick($event)}}})}` ) assertCodegen( '', - `with(this){return _c('input',{on:{"click":function($event){if(!$event.metaKey)return;onClick($event)}}})}` + `with(this){return _c('input',{on:{"click":function($event){if(!$event.metaKey)return null;onClick($event)}}})}` ) }) it('generate events with multiple modifers', () => { assertCodegen( '', - `with(this){return _c('input',{on:{"input":function($event){$event.stopPropagation();$event.preventDefault();if($event.target !== $event.currentTarget)return;onInput($event)}}})}` + `with(this){return _c('input',{on:{"input":function($event){$event.stopPropagation();$event.preventDefault();if($event.target !== $event.currentTarget)return null;onInput($event)}}})}` ) })