* [html5] fix event bubbles.
Project: http://git-wip-us.apache.org/repos/asf/incubator-weex/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-weex/commit/f96ad274 Tree: http://git-wip-us.apache.org/repos/asf/incubator-weex/tree/f96ad274 Diff: http://git-wip-us.apache.org/repos/asf/incubator-weex/diff/f96ad274 Branch: refs/heads/0.12-dev Commit: f96ad2745f3a8048f1e46a70f9ac5b0859756b05 Parents: 06b444b Author: MrRaindrop <tekk...@gmail.com> Authored: Fri Apr 14 17:12:43 2017 +0800 Committer: MrRaindrop <tekk...@gmail.com> Committed: Fri Apr 14 17:12:43 2017 +0800 ---------------------------------------------------------------------- html5/render/vue/core/node.js | 49 +++++++++++++++- html5/test/render/vue/core/node.js | 62 ++++++++++++++++++++ .../render/vue/data/dotvue/event-bubble-bar.vue | 20 +++++++ .../render/vue/data/dotvue/event-bubble.vue | 25 ++++++++ .../vue/data/dotvue/first-screen-appear-foo.vue | 27 +++++++++ .../vue/data/dotvue/first-screen-appear.vue | 2 +- html5/test/render/vue/data/dotvue/foo.vue | 27 --------- html5/test/render/vue/helper/main.js | 5 ++ html5/test/render/vue/utils/component.js | 4 +- 9 files changed, 190 insertions(+), 31 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/f96ad274/html5/render/vue/core/node.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/core/node.js b/html5/render/vue/core/node.js index a780645..69a0269 100644 --- a/html5/render/vue/core/node.js +++ b/html5/render/vue/core/node.js @@ -10,9 +10,31 @@ export function trimTextVNodes (vnodes) { return vnodes } +/** + * get listeners from on config and v-on binding. + * v-on binding has a priority over on config. + * @param {vnode} vnode + * @param {String} evt: event name. + */ +function getListeners (vnode, evt) { + const handlers = [] + while (vnode) { + if (vnode.data && vnode.data.on) { + const handler = vnode.data.on[evt] + handler && handlers.push(handler) + } + if (vnode.componentOptions && vnode.componentOptions.listeners) { + const handler = vnode.componentOptions.listeners[evt] + handler && handlers.push(handler) + } + vnode = vnode.parent + } + return handlers +} + const supportedEvents = [ 'click', 'longpress', 'appear', 'disappear', - 'touchstart', 'touchmove', 'touchend', + // 'touchstart', 'touchmove', 'touchend', 'panstart', 'panmove', 'panend', 'swipe', 'longpress' ] /** @@ -24,8 +46,31 @@ export function createEventMap (context, extras = []) { const eventMap = {} supportedEvents.concat(extras).forEach(name => { eventMap[name] = function (e) { + // no original bubbling. e.stopPropagation() - context.$emit(name, e) + // but should trigger the closest parent which has bound the + // event handler. + let vm = context + while (vm) { + const ons = getListeners(vm.$vnode, name) + const len = ons.length + let idx = 0 + while (idx < len) { + let on = ons[idx] + if (on && on.fn) { + on = on.fn + } + on && on.call(vm, e) + idx++ + } + + // once a parent node (or self node) has triggered the handler, + // then it stops bubble immediately. + if (len > 0) { + return + } + vm = vm.$parent + } } }) return eventMap http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/f96ad274/html5/test/render/vue/core/node.js ---------------------------------------------------------------------- diff --git a/html5/test/render/vue/core/node.js b/html5/test/render/vue/core/node.js new file mode 100644 index 0000000..936dca0 --- /dev/null +++ b/html5/test/render/vue/core/node.js @@ -0,0 +1,62 @@ +import { init } from '../helper/runtime' +import div from '../../../../render/vue/components/div' + +import eventBubbleBundle from '../data/build/dotvue/event-bubble.js' + +init('core node', (Vue, helper) => { + const id = 'test-event-bubble' + + before(() => { + helper.register('div', div) + }) + + describe('stop event bubble', function () { + let infoStr = '' + let i = 0 + function trackerShouldBe (tracker, shouldBe) { + shouldBe = infoStr + shouldBe + expect(tracker).to.equal(shouldBe) + infoStr = shouldBe + } + + it('should trigger the closest parent.', function (done) { + const vm = helper.createVm(eventBubbleBundle, id) + const el = vm.$el.querySelector('.event-bubble-outter') + expect(vm.tracker).to.equal('') + + /** + * click outter div. should trigget event on the outter div. + * and should execute handlers by the priority of: + * child vnode -> parent vnode. + * e.g. div -> foo (whoes root element is the div.) + */ + const evt = new Event('click', { bubbles: true }) + el.dispatchEvent(evt) + + helper.registerDone(id, (tracker) => { + trackerShouldBe(tracker, ' > in-bar-outter-div > component-bar') + helper.unregisterDone(id) + done() + }) + }) + + it ('should not bubble if already triggered.', function (done) { + + const vm = helper.createVm(eventBubbleBundle, id) + const inner = vm.$el.querySelector('.event-bubble-inner') + + /** + * click inner div. should just trigget the inner handler and + * shouldn't bubbe to outter div. + */ + const evt = new Event('click', { bubbles: true }) + inner.dispatchEvent(evt) + + helper.registerDone(id, (tracker) => { + trackerShouldBe(tracker, ' > in-bar-inner-div') + helper.unregisterDone(id) + done() + }) + }) + }) +}) http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/f96ad274/html5/test/render/vue/data/dotvue/event-bubble-bar.vue ---------------------------------------------------------------------- diff --git a/html5/test/render/vue/data/dotvue/event-bubble-bar.vue b/html5/test/render/vue/data/dotvue/event-bubble-bar.vue new file mode 100644 index 0000000..bcf547a --- /dev/null +++ b/html5/test/render/vue/data/dotvue/event-bubble-bar.vue @@ -0,0 +1,20 @@ +<template> + <div class="event-bubble-outter" @click="outterClick"> + <div> + <div class="event-bubble-inner" @click="innerClick"></div> + </div> + </div> +</template> + +<script> + module.exports = { + methods: { + outterClick (evt) { + this.$parent.$parent.tracker += ' > in-bar-outter-div' + }, + innerClick (evt) { + this.$parent.$parent.tracker += ' > in-bar-inner-div' + } + } + } +</script> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/f96ad274/html5/test/render/vue/data/dotvue/event-bubble.vue ---------------------------------------------------------------------- diff --git a/html5/test/render/vue/data/dotvue/event-bubble.vue b/html5/test/render/vue/data/dotvue/event-bubble.vue new file mode 100644 index 0000000..71b77a4 --- /dev/null +++ b/html5/test/render/vue/data/dotvue/event-bubble.vue @@ -0,0 +1,25 @@ +<template> + <div> + <bar @click="click"></bar> + <text>{{tracker}}</text> + </div> +</template> + +<script> + module.exports = { + data: { + tracker: '' + }, + updated () { + this.done('test-event-bubble', this.tracker) + }, + methods: { + click (e) { + this.tracker += ' > component-bar' + } + }, + components: { + bar: require('./event-bubble-bar.vue') + } + } +</script> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/f96ad274/html5/test/render/vue/data/dotvue/first-screen-appear-foo.vue ---------------------------------------------------------------------- diff --git a/html5/test/render/vue/data/dotvue/first-screen-appear-foo.vue b/html5/test/render/vue/data/dotvue/first-screen-appear-foo.vue new file mode 100644 index 0000000..57d8591 --- /dev/null +++ b/html5/test/render/vue/data/dotvue/first-screen-appear-foo.vue @@ -0,0 +1,27 @@ +<template> + <div v-if="show" style="width:200px;height:200px;background-color:red;"></div> +</template> + +<script> +module.exports = { + data () { + return { + show: false + } + }, + mounted () { + setTimeout(() => { + this.show = true + setTimeout(() => { + this.show = false + setTimeout(() => { + this.show = true + setTimeout(() => { + this.done('test-first-screen-appear') + }, 25) + }, 300) + }, 300) + }, 300) + } +} +</script> http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/f96ad274/html5/test/render/vue/data/dotvue/first-screen-appear.vue ---------------------------------------------------------------------- diff --git a/html5/test/render/vue/data/dotvue/first-screen-appear.vue b/html5/test/render/vue/data/dotvue/first-screen-appear.vue index c2fe877..cdb1088 100644 --- a/html5/test/render/vue/data/dotvue/first-screen-appear.vue +++ b/html5/test/render/vue/data/dotvue/first-screen-appear.vue @@ -7,7 +7,7 @@ <script> module.exports = { components: { - foo: require('./foo.vue') + foo: require('./first-screen-appear-foo.vue') }, methods: { appear: function (evt) { http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/f96ad274/html5/test/render/vue/data/dotvue/foo.vue ---------------------------------------------------------------------- diff --git a/html5/test/render/vue/data/dotvue/foo.vue b/html5/test/render/vue/data/dotvue/foo.vue deleted file mode 100644 index 57d8591..0000000 --- a/html5/test/render/vue/data/dotvue/foo.vue +++ /dev/null @@ -1,27 +0,0 @@ -<template> - <div v-if="show" style="width:200px;height:200px;background-color:red;"></div> -</template> - -<script> -module.exports = { - data () { - return { - show: false - } - }, - mounted () { - setTimeout(() => { - this.show = true - setTimeout(() => { - this.show = false - setTimeout(() => { - this.show = true - setTimeout(() => { - this.done('test-first-screen-appear') - }, 25) - }, 300) - }, 300) - }, 300) - } -} -</script> http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/f96ad274/html5/test/render/vue/helper/main.js ---------------------------------------------------------------------- diff --git a/html5/test/render/vue/helper/main.js b/html5/test/render/vue/helper/main.js index 962e078..5b71013 100644 --- a/html5/test/render/vue/helper/main.js +++ b/html5/test/render/vue/helper/main.js @@ -62,6 +62,11 @@ const helper = { this._done[id] = cb }, + unregisterDone(id) { + if (!id) { return } + delete this._done[id] + }, + done (id, ...args) { const done = this._done[id] done && done(...args) http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/f96ad274/html5/test/render/vue/utils/component.js ---------------------------------------------------------------------- diff --git a/html5/test/render/vue/utils/component.js b/html5/test/render/vue/utils/component.js index 06d7c70..fd97d5b 100644 --- a/html5/test/render/vue/utils/component.js +++ b/html5/test/render/vue/utils/component.js @@ -13,13 +13,14 @@ init('utils component', (Vue, helper) => { spy && spy(evt) } + const id = 'test-first-screen-appear' + before(() => { helper.register('div', div) }) describe('watchAppear', function () { it('should work when mounted and updated.', function (done) { - const id = 'test-first-screen-appear' helper.createVm(firstScreenAppearBundle, id) helper.registerDone(id, () => { const { appear: appearSpy, disappear: disappearSpy } = spys @@ -31,6 +32,7 @@ init('utils component', (Vue, helper) => { expect(appearSpy.args[0][0].direction).to.not.exist expect(appearSpy.args[1][0].direction).to.not.exist expect(disappearSpy.args[0][0].direction).to.not.exist + helper.unregisterDone(id) done() }) })