* [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()
       })
     })

Reply via email to