* [html5] add event manager to delegate events.
Project: http://git-wip-us.apache.org/repos/asf/incubator-weex/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-weex/commit/2504db58 Tree: http://git-wip-us.apache.org/repos/asf/incubator-weex/tree/2504db58 Diff: http://git-wip-us.apache.org/repos/asf/incubator-weex/diff/2504db58 Branch: refs/heads/master Commit: 2504db58d113ae7964d07c934c4fa745b05c2f0c Parents: 52b0497 Author: MrRaindrop <tekk...@gmail.com> Authored: Thu Aug 24 14:52:22 2017 +0800 Committer: MrRaindrop <tekk...@gmail.com> Committed: Thu Aug 24 14:52:22 2017 +0800 ---------------------------------------------------------------------- html5/render/vue/components/a.js | 19 +-- html5/render/vue/components/div.js | 4 +- html5/render/vue/components/input.js | 7 +- html5/render/vue/components/scrollable/cell.js | 3 +- .../render/vue/components/scrollable/header.js | 5 +- html5/render/vue/components/scrollable/list.js | 2 +- .../components/scrollable/mixins/scrollable.js | 6 - .../vue/components/scrollable/scroller.js | 2 +- .../vue/components/scrollable/waterfall.js | 2 +- html5/render/vue/components/slider/index.js | 2 + html5/render/vue/config.js | 3 +- html5/render/vue/core/node.js | 117 ++------------- html5/render/vue/env/event-manager.js | 148 +++++++++++++++++++ html5/render/vue/index.js | 4 + html5/render/vue/lib/gesture.js | 2 +- html5/render/vue/utils/event.js | 16 ++ 16 files changed, 195 insertions(+), 147 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/components/a.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/components/a.js b/html5/render/vue/components/a.js index 1d8946f..ff4a59d 100644 --- a/html5/render/vue/components/a.js +++ b/html5/render/vue/components/a.js @@ -28,8 +28,7 @@ let cnt = 0 function getA (weex) { const { extractComponentStyle, - trimTextVNodes, - createEventMap + trimTextVNodes } = weex return { @@ -37,21 +36,6 @@ function getA (weex) { props: { href: String }, - mounted () { - const $el = this.$el - const id = $el.id - - /** - * if there is a child component already triggered a click handler, then - * this link jumping should be prevented. - */ - $el.addEventListener('click', (e) => { - const el = e._triggered && e._triggered.el - if (el && (el !== $el) && !el.querySelector(`#${id}`)) { - e.preventDefault() - } - }) - }, render (createElement) { /* istanbul ignore next */ // if (process.env.NODE_ENV === 'development') { @@ -64,7 +48,6 @@ function getA (weex) { 'id': `weex-a-${id}`, href: this.href }, - on: createEventMap(this), staticClass: 'weex-a weex-ct', staticStyle: extractComponentStyle(this) }, trimTextVNodes(this.$slots.default)) http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/components/div.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/components/div.js b/html5/render/vue/components/div.js index 8f05163..8b29a70 100644 --- a/html5/render/vue/components/div.js +++ b/html5/render/vue/components/div.js @@ -26,8 +26,7 @@ body > .weex-div { function getDiv (weex) { const { extractComponentStyle, - trimTextVNodes, - createEventMap + trimTextVNodes } = weex return { @@ -35,7 +34,6 @@ function getDiv (weex) { render (createElement) { return createElement('html:div', { attrs: { 'weex-type': 'div' }, - on: createEventMap(this), staticClass: 'weex-div weex-ct', staticStyle: extractComponentStyle(this) }, trimTextVNodes(this.$slots.default)) http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/components/input.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/components/input.js b/html5/render/vue/components/input.js index 0e8d466..b9d403d 100644 --- a/html5/render/vue/components/input.js +++ b/html5/render/vue/components/input.js @@ -21,8 +21,7 @@ * @fileOverview Input component. * Support v-model only if vue version is large than 2.2.0 */ -let extractComponentStyle, createEventMap -let extend, mapFormEvents, appendCss +let extractComponentStyle, mapFormEvents, appendCss const ID_PREFIX_PLACEHOLDER_COLOR = 'wipt_plc_' const ID_PREFIX_INPUT = 'wipt_' @@ -104,7 +103,7 @@ function getInput (weex) { if (!this._id) { this._id = idCount++ } - const events = extend(createEventMap(this), mapFormEvents(this)) + const events = mapFormEvents(this) return createElement('html:input', { attrs: { 'weex-type': 'input', @@ -132,8 +131,6 @@ function getInput (weex) { export default { init (weex) { extractComponentStyle = weex.extractComponentStyle - createEventMap = weex.createEventMap - extend = weex.utils.extend mapFormEvents = weex.utils.mapFormEvents appendCss = weex.utils.appendCss http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/components/scrollable/cell.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/components/scrollable/cell.js b/html5/render/vue/components/scrollable/cell.js index fb6ca1e..e5e8301 100644 --- a/html5/render/vue/components/scrollable/cell.js +++ b/html5/render/vue/components/scrollable/cell.js @@ -18,13 +18,12 @@ */ function getCell (weex) { - const { extractComponentStyle, createEventMap } = weex + const { extractComponentStyle } = weex return { name: 'weex-cell', render (createElement) { return createElement('section', { attrs: { 'weex-type': 'cell' }, - on: createEventMap(this), staticClass: 'weex-cell weex-ct', staticStyle: extractComponentStyle(this) }, this.$slots.default) http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/components/scrollable/header.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/components/scrollable/header.js b/html5/render/vue/components/scrollable/header.js index 35fe72a..5abd78e 100644 --- a/html5/render/vue/components/scrollable/header.js +++ b/html5/render/vue/components/scrollable/header.js @@ -18,7 +18,7 @@ */ function getHeader (weex) { - const { extractComponentStyle, createEventMap } = weex + const { extractComponentStyle } = weex const { supportSticky } = weex.utils return { @@ -33,7 +33,7 @@ function getHeader (weex) { mounted () { this.initTop = this.$el.offsetTop - this.placeholder = window.document.createElement('div') + this.placeholder = window.document.createElement('header') }, updated () { @@ -68,7 +68,6 @@ function getHeader (weex) { // } return createElement('html:header', { attrs: { 'weex-type': 'header' }, - on: createEventMap(this), ref: 'header', staticClass: 'weex-header weex-ct', class: { 'weex-sticky': this.sticky, 'weex-ios-sticky': this.supportSticky }, http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/components/scrollable/list.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/components/scrollable/list.js b/html5/render/vue/components/scrollable/list.js index aa6740b..77f8926 100644 --- a/html5/render/vue/components/scrollable/list.js +++ b/html5/render/vue/components/scrollable/list.js @@ -44,7 +44,7 @@ function getList (weex) { return true }) return [ - h('html:div', { + h('article', { ref: 'inner', staticClass: 'weex-list-inner weex-ct' }, this._cells) http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/components/scrollable/mixins/scrollable.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/components/scrollable/mixins/scrollable.js b/html5/render/vue/components/scrollable/mixins/scrollable.js index 0d78db9..8f39580 100644 --- a/html5/render/vue/components/scrollable/mixins/scrollable.js +++ b/html5/render/vue/components/scrollable/mixins/scrollable.js @@ -211,8 +211,6 @@ export default { }, handleTouchStart (event) { - // event.preventDefault() - // event.stopPropagation() if (this._loading || this._refresh) { const touch = event.changedTouches[0] this._touchParams = { @@ -227,8 +225,6 @@ export default { }, handleTouchMove (event) { - // event.preventDefault() - // event.stopPropagation() if (this._touchParams) { const inner = this.$refs.inner const { startY, reachTop, reachBottom } = this._touchParams @@ -247,8 +243,6 @@ export default { }, handleTouchEnd (event) { - // event.preventDefault() - // event.stopPropagation() if (this._touchParams) { const inner = this.$refs.inner const { reachTop, reachBottom } = this._touchParams http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/components/scrollable/scroller.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/components/scrollable/scroller.js b/html5/render/vue/components/scrollable/scroller.js index 56e83d4..655edcf 100644 --- a/html5/render/vue/components/scrollable/scroller.js +++ b/html5/render/vue/components/scrollable/scroller.js @@ -65,7 +65,7 @@ function getScroller (weex) { return true }) return [ - h('html:div', { + h('article', { ref: 'inner', staticClass: 'weex-scroller-inner weex-ct' }, this._cells) http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/components/scrollable/waterfall.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/components/scrollable/waterfall.js b/html5/render/vue/components/scrollable/waterfall.js index 82b2295..9052994 100644 --- a/html5/render/vue/components/scrollable/waterfall.js +++ b/html5/render/vue/components/scrollable/waterfall.js @@ -124,7 +124,7 @@ function getWaterfall (weex) { }, this._columns)) this._loading && children.push(this._loading) return [ - h('html:div', { + h('article', { ref: 'inner', staticClass: 'weex-waterfall-inner weex-ct' }, children) http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/components/slider/index.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/components/slider/index.js b/html5/render/vue/components/slider/index.js index cd9d4ec..80699d9 100644 --- a/html5/render/vue/components/slider/index.js +++ b/html5/render/vue/components/slider/index.js @@ -18,11 +18,13 @@ */ import slider from './slider' +import neighbor from './slider-neighbor' import indicator from './indicator' export default { init (weex) { weex.install(slider) + weex.install(neighbor) weex.install(indicator) } } http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/config.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/config.js b/html5/render/vue/config.js index bccf1e3..0858b2f 100644 --- a/html5/render/vue/config.js +++ b/html5/render/vue/config.js @@ -23,7 +23,6 @@ export default { 'panmove', 'panend', 'swipe', - 'longpress', - 'tap' + 'longpress' ] } http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/core/node.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/core/node.js b/html5/render/vue/core/node.js index d515ea5..caf4230 100644 --- a/html5/render/vue/core/node.js +++ b/html5/render/vue/core/node.js @@ -17,11 +17,8 @@ * under the License. */ -import { isArray, createEvent, createBubblesEvent } from '../utils' -import config from '../config' +import { isArray } from '../utils' -// The space used to store the tap event queue -const _tapQueue = [] /** * remove text nodes in the nodes array. * @param {Array} nodes @@ -35,20 +32,6 @@ export function trimTextVNodes (vnodes) { } /** - * is a element in a '<a>' tag? - * @param {HTMLElement} el - */ -function isInANode (el) { - let parent = el.parentNode - while (parent && parent !== document.body) { - if (parent.tagName.toLowerCase() === 'a') { - return true - } - parent = parent.parentNode - } -} - -/** * get listeners from on config and v-on binding. * v-on binding has a priority over on config. * @param {vnode} vnode @@ -70,20 +53,13 @@ function getListeners (vnode, evt) { return handlers } -const supportedEvents = [ - 'longpress', 'appear', 'disappear', - 'touchstart', 'touchmove', 'touchend', - 'panstart', 'panmove', 'panend', 'swipe', 'longpress' -] - /** * emit native events to enable v-on. * @param {VComponent} context: which one to emit a event on. - * @param {array | object} extras: extra events. You can pass in multiple arguments here. + * @param {array | object} events: extra events. You can pass in multiple arguments here. */ -export function createEventMap (context, ...extras) { +export function createEventMap (context, ...events) { const eventMap = {} - const gestureEvents = config.gestureEvents /** * Bind some original type event to your specified type event handler. * e.g. bind 'tap' event to 'click' event listener: bindFunc('tap')('click'). @@ -99,17 +75,14 @@ export function createEventMap (context, ...extras) { else if (typeof listenTo === 'string') { handler = function (e) { /** - * 1. use '_triggered' to control actural bubbling (allow original bubbling). - * 2. use '_for' to distinguish from gesture events generated by other - * not-vue-render librarys. + * use '_triggered' to control actural bubbling (allow original bubbling). */ - if (e._triggered - || gestureEvents.indexOf(originalType) > -1 - && e._for !== 'weex') { + if (e._triggered) { return } - // but should trigger the closest parent which has bound the - // event handler. + /** + * trigger the closest parent which has bound event handlers. + */ let vm = context while (vm) { const ons = getListeners(vm.$vnode, listenTo) @@ -121,18 +94,7 @@ export function createEventMap (context, ...extras) { if (on && on.fn) { on = on.fn } - let evt = e - if (originalType && evtName !== listenTo) { - if (listenTo === 'click') { - // dispatch real target click event befor calling listeners - evt = createBubblesEvent(e.target, 'click', { _triggered: { el: e.target }}) - e.target.dispatchEvent(evt) - } - else { - evt = createEvent(e.target, listenTo) - } - } - on && on.call(vm, evt) + on && on.call(vm, e) idx++ } // once a parent node (or self node) has triggered the handler, then @@ -140,15 +102,6 @@ export function createEventMap (context, ...extras) { e._triggered = { el: vm.$el } - // when originalType is tap, push a tapEvent to _tapQueue - // el is uesed to store a real target which dispach this event - if (originalType === 'tap') { - const tapEvent = { - el: e.target, - event: e - } - _tapQueue.push(tapEvent) - } return } vm = vm.$parent @@ -163,20 +116,14 @@ export function createEventMap (context, ...extras) { } /** - * 1. Dispatch default supported events directly to user's event listeners. These - * listeners will be triggered before extras event handlers. - */ - supportedEvents.forEach(bindFunc()) - - /** - * 2. component's extra event bindings. This is mostly for the needs of component's + * component's extra event bindings. This is mostly for the needs of component's * own special behaviours. These handlers will be processed after the user's * corresponding event handlers. */ - if (extras) { - const len = extras.length + if (events) { + const len = events.length for (let i = 0; i < len; i++) { - const extra = extras[i] + const extra = events[i] if (isArray(extra)) { extra.forEach(bindFunc()) } @@ -188,43 +135,5 @@ export function createEventMap (context, ...extras) { } } - /** - * 3. special binding for click event, which should be a fastclick event without - * 300ms latency. - */ - bindFunc('tap')('click') - /** - * Special treatment for click event: - * we already use tap to trigger click event, so the click event should: - * 1. trigger none of any vm's click listeners. - * 2. prevent default behaviour for a `<a>` element. - * 3. stop propagation if triggered already. - * 4. set a _triggered flag to the event object if triggered already. - * This means the click event should always be swallowed in silence. - */ - bindFunc('click')(function (e) { - let vm = context - if (e._triggered) { - return - } - if (_tapQueue.length > 0) { - const _tapEvent = _tapQueue.shift() - if (e.target !== _tapEvent.el) { - e.preventDefault() - } - // prevent click events from bubbling,because event bubbling has been handled in the tap event - e.stopPropagation() - } - // if an element(not <a>) handler click event in <a> element,call stopPropagation and preventDefault on event - while (vm) { - const ons = getListeners(vm.$vnode, 'click') - const len = ons.length - if (len > 0 && vm.$el) { - e._triggered = { el: vm.$el } - return isInANode(vm.$el) && e.preventDefault() - } - vm = vm.$parent - } - }) return eventMap } http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/env/event-manager.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/env/event-manager.js b/html5/render/vue/env/event-manager.js new file mode 100644 index 0000000..1a57f84 --- /dev/null +++ b/html5/render/vue/env/event-manager.js @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import config from '../config' +import { createEvent, supportsPassive } from '../utils' + +const gestureEvents = config.gestureEvents +const touchEvents = ['touchstart', 'touchmove', 'touchend'] +const needPassive = ['touchmove'] + +const events = gestureEvents.concat(touchEvents) + +// /** +// * is a element in a '<a>' tag? +// * @param {HTMLElement} el +// */ +// function isInANode (el) { +// let parent = el.parentNode +// while (parent && parent !== document.body) { +// if (parent.tagName.toLowerCase() === 'a') { +// return true +// } +// parent = parent.parentNode +// } +// } + +/** + * if el is a `<a>` element. + * @param {HTMLElement} el + */ +function isANode (el) { + return el.tagName.toLowerCase() === 'a' +} + +/** + * 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 +} + +let _inited = false +function _init (doc) { + if (_inited) { + return + } + if (!doc) { + return + } + _inited = true + const _sp = supportsPassive() + events.forEach(function (evt) { + const option = needPassive.indexOf(evt) > -1 && _sp + ? { passive: true } + : false + doc.addEventListener(evt, function (e) { + const el = e.target + let vm = el.__vue__ + let disposed = false + + /** + * take full control of redirection of <a> element. + */ + if (evt === 'click') { + e.preventDefault() + } + + while (vm) { + const vnode = vm.$vnode || vm._vnode + const elm = vm.$el + const ons = getListeners(vnode, evt) + const len = ons && ons.length + + if (len > 0) { + for (let i = 0; i < len; i++) { + let handler = ons[i] + if (handler && handler.fn) { + handler = handler.fn + } + // create a no bubble event. + const newEvt = createEvent(el, evt) + handler && handler.call(vm, newEvt) + } + e._triggered = { target: vm.$el } + disposed = true + } + + /** + * if the handler is binding on a <a> element, should trigger + * the handler first and then jump to href. + * NOTE: if target==='_blank' then do no jumping and dispatch the + * click event to document.body for further disposing. + */ + if (evt === 'click' && isANode(elm)) { + const href = elm.getAttribute('href') + const target = elm.getAttribute('target') + disposed = true + if (target !== '_blank') { + location.href = href + } + else { + document.body.dispatchEvent(e) + } + } + + if (disposed) { + return + } + vm = vm.$parent + } + }, option) + }) +} + +export default function init () { + _init(document) +} http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/index.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/index.js b/html5/render/vue/index.js index 8a00a57..038f67a 100644 --- a/html5/render/vue/index.js +++ b/html5/render/vue/index.js @@ -20,6 +20,8 @@ import weex from './env' import { setVue } from './env' import { base, style, sticky } from './mixins' +import initEventMgr from './env/event-manager' + /** * init weex. * @param {Vue$2} Vue: Vue Constructor. @@ -46,6 +48,8 @@ function init (Vue/*, options = {}*/) { Vue.mixin(base) Vue.mixin(style) Vue.mixin(sticky) + + initEventMgr() } // auto init in dist mode. http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/lib/gesture.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/lib/gesture.js b/html5/render/vue/lib/gesture.js index 62ad226..5f72b3c 100644 --- a/html5/render/vue/lib/gesture.js +++ b/html5/render/vue/lib/gesture.js @@ -384,7 +384,7 @@ function touchendHandler(event) { if (gesture.status === 'tapping') { gesture.timestamp = Date.now() // fire click, not tap. - fireEvent(gesture.element, 'tap', { + fireEvent(gesture.element, 'click', /* 'tap', */{ touch: touch, touchEvent: event }) http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/2504db58/html5/render/vue/utils/event.js ---------------------------------------------------------------------- diff --git a/html5/render/vue/utils/event.js b/html5/render/vue/utils/event.js index c5f8c38..ad14ae8 100644 --- a/html5/render/vue/utils/event.js +++ b/html5/render/vue/utils/event.js @@ -28,6 +28,22 @@ function extend (to, ...args) { return to } +// if support passive event listeners. +let _supportsPassive = false +try { + document.createElement('div').addEventListener('test', _ => {}, { + get passive () { + _supportsPassive = true + } + }) +} +catch (e) { + // do nothing. +} +export function supportsPassive () { + return _supportsPassive +} + /** * Create Event. * @param {DOMString} type