From 39112bd4e9cecb2b32cf9864afc33c7a6bf2dc78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AF=B8=E5=B2=B3?= Date: Thu, 31 Oct 2019 16:48:27 +0800 Subject: [PATCH] feat(g-base): support * for event delegation, add name and delegateObject for event, close #249 --- packages/g-base/src/abstract/base.ts | 6 - packages/g-base/src/event/event-contoller.ts | 34 +++- packages/g-base/src/event/graph-event.ts | 20 ++ packages/g-base/tests/unit/event-spec.js | 4 +- .../g-canvas/tests/bugs/issue-249-spec.js | 182 ++++++++++++++++++ 5 files changed, 229 insertions(+), 17 deletions(-) create mode 100644 packages/g-canvas/tests/bugs/issue-249-spec.js diff --git a/packages/g-base/src/abstract/base.ts b/packages/g-base/src/abstract/base.ts index 26736a5ba..dee65c297 100644 --- a/packages/g-base/src/abstract/base.ts +++ b/packages/g-base/src/abstract/base.ts @@ -8,12 +8,6 @@ abstract class Base extends EE implements IBase { * @type {object} */ cfg: object; - /** - * @private - * 事件集合 - * @type {object} - */ - events: object = {}; /** * 是否被销毁 diff --git a/packages/g-base/src/event/event-contoller.ts b/packages/g-base/src/event/event-contoller.ts index e3eafcb85..939c3fb1c 100644 --- a/packages/g-base/src/event/event-contoller.ts +++ b/packages/g-base/src/event/event-contoller.ts @@ -8,6 +8,7 @@ import { each, isArray } from '../util/util'; const TIME_INTERVAL = 120; // 判断拖拽和点击 const CLICK_OFFSET = 40; const DELEGATION_SPLIT = ':'; +const WILDCARD = '*'; const EVENTS = [ 'mousedown', @@ -81,20 +82,34 @@ function hasDelegation(events, type) { return false; } +// 触发目标事件,目标只能是 shape 或 canvas +function emitTargetEvent(target, type, eventObj) { + eventObj.name = type; + eventObj.target = target; + eventObj.currentTarget = target; + eventObj.delegateTarget = target; + target.emit(type, eventObj); +} + // 触发委托事件 function emitDelegation(container, type, eventObj) { const paths = eventObj.propagationPath; const events = container.getEvents(); - // 至少有一个对象 + // 至少有一个对象,且第一个对象为 shape for (let i = 0; i < paths.length; i++) { const element = paths[i]; // 暂定跟 name 绑定 const name = element.get('name'); if (name) { + // 事件委托的形式 name:type const eventName = name + DELEGATION_SPLIT + type; - if (events[eventName]) { - eventObj.delegateTarget = container; + if (events[eventName] || events[WILDCARD]) { + // 对于通配符 *,事件名称 = 委托事件名称 + eventObj.name = eventName; eventObj.currentTarget = element; + eventObj.delegateTarget = container; + // 将委托事件的监听对象 delegateObject 挂载到事件对象上 + eventObj.delegateObject = element.get('delegateObject'); container.emit(eventName, eventObj); } } @@ -123,8 +138,10 @@ function bubbleEvent(container, type, eventObj) { eventObj.bubbles = false; return; } - // 绑定事件的对象 + // 事件名称可能在委托过程中被修改,因此事件冒泡时需要重新设置事件名称 + eventObj.name = type; eventObj.currentTarget = container; + eventObj.delegateTarget = container; container.emit(type, eventObj); } } @@ -158,7 +175,6 @@ class EventController { _getEventObj(type, event, point, target, fromShape, toShape) { const eventObj = new GraphEvent(type, event); - // eventObj.target = target; eventObj.fromShape = fromShape; eventObj.toShape = toShape; eventObj.x = point.x; @@ -366,9 +382,9 @@ class EventController { const eventObj = this._getEventObj(type, event, pointInfo, shape, fromShape, toShape); // 存在 shape 触发,则进行冒泡处理 if (shape) { - eventObj.target = shape; eventObj.shape = shape; - shape.emit(type, eventObj); + // 触发 shape 上的事件 + emitTargetEvent(shape, type, eventObj); let parent = shape.getParent(); // 执行冒泡 while (parent) { @@ -384,8 +400,8 @@ class EventController { } else { // 如果没有 shape 直接在 canvas 上触发 const canvas = this.canvas; - eventObj.target = canvas; - canvas.emit(type, eventObj); + // 直接触发 canavas 上的事件 + emitTargetEvent(canvas, type, eventObj); } } diff --git a/packages/g-base/src/event/graph-event.ts b/packages/g-base/src/event/graph-event.ts index 43aa2b927..7ab140aca 100644 --- a/packages/g-base/src/event/graph-event.ts +++ b/packages/g-base/src/event/graph-event.ts @@ -6,6 +6,11 @@ class GraphEvent { * @type {string} */ type: string; + /** + * 事件名称 + * @type {string} + */ + name: string; /** * 画布上的位置 x * @type {number} @@ -41,6 +46,16 @@ class GraphEvent { * @type {object} */ currentTarget: object = null; + /** + * 委托对象 + * @type {object} + */ + delegateTarget: object = null; + /** + * 委托事件监听对象的代理对象,即 ev.delegateObject = ev.currentTarget.get('delegateObject') + * @type {object} + */ + delegateObject: object = null; /** * 是否阻止了原生事件 * @type {boolean} @@ -84,6 +99,7 @@ class GraphEvent { constructor(type, event) { this.type = type; + this.name = type; this.domEvent = event; this.timeStamp = event.timeStamp; } @@ -107,6 +123,10 @@ class GraphEvent { const type = this.type; return `[Event (type=${type})]`; } + + save() {} + + restore() {} } export default GraphEvent; diff --git a/packages/g-base/tests/unit/event-spec.js b/packages/g-base/tests/unit/event-spec.js index e828e7721..e47d08f2f 100644 --- a/packages/g-base/tests/unit/event-spec.js +++ b/packages/g-base/tests/unit/event-spec.js @@ -85,7 +85,7 @@ describe('test event object', () => { expect(event.defaultPrevented).eql(true); }); - it('stopProgation', () => { + it('stopPropagation', () => { event.stopPropagation(); expect(event.propagationStopped).eql(true); }); @@ -829,7 +829,7 @@ describe('test graphic events', () => { group1.set('name', null); }); - it('stopProgation', () => { + it('stopPropagation', () => { let group1Called = false; let group11Called = false; let canvasCalled = false; diff --git a/packages/g-canvas/tests/bugs/issue-249-spec.js b/packages/g-canvas/tests/bugs/issue-249-spec.js new file mode 100644 index 000000000..ff9bc2c9c --- /dev/null +++ b/packages/g-canvas/tests/bugs/issue-249-spec.js @@ -0,0 +1,182 @@ +const expect = require('chai').expect; +import Canvas from '../../src/canvas'; +import { simulateMouseEvent, getClientPoint } from '../util'; + +const dom = document.createElement('div'); +document.body.appendChild(dom); +dom.id = 'c1'; + +describe('#249', () => { + it('event attrs should be correct when emit event on shape', (done) => { + const canvas = new Canvas({ + container: dom, + width: 600, + height: 600, + }); + const el = canvas.get('el'); + const groupDelegateObject = { a: 1, b: 2 }; + const circleDelegateObject = { x: 1, y: 2 }; + const group = canvas.addGroup({ + name: 'group', + delegateObject: groupDelegateObject, + }); + const circle = group.addShape('circle', { + name: 'circle', + delegateObject: circleDelegateObject, + attrs: { + x: 50, + y: 50, + r: 50, + fill: 'red', + }, + }); + + // canvas * 事件触发的次数 + let canvasEventCount = 0; + // group * 事件触发的次数 + let groupEventCount = 0; + + // 共触发 3 次 + canvas.on('*', (e) => { + canvasEventCount++; + // 事件会触发多次,且每次的 e.name 都不同 + if (canvasEventCount === 1) { + expect(e.name).eqls('circle:mousedown'); + expect(e.currentTarget).eqls(circle); + expect(e.delegateObject).eqls(circleDelegateObject); + } else if (canvasEventCount === 2) { + expect(e.name).eqls('group:mousedown'); + expect(e.currentTarget).eqls(group); + expect(e.delegateObject).eqls(groupDelegateObject); + } else if (canvasEventCount === 3) { + expect(e.name).eqls('mousedown'); + expect(e.currentTarget).eqls(canvas); + } + expect(e.type).eqls('mousedown'); + expect(e.target).eqls(circle); + expect(e.delegateTarget).eqls(canvas); + }); + + canvas.on('mousedown', (e) => { + expect(e.name).eqls('mousedown'); + expect(e.type).eqls('mousedown'); + expect(e.target).eqls(circle); + expect(e.currentTarget).eqls(canvas); + expect(e.delegateTarget).eqls(canvas); + }); + + canvas.on('group:mousedown', (e) => { + expect(e.name).eqls('group:mousedown'); + expect(e.type).eqls('mousedown'); + expect(e.target).eqls(circle); + expect(e.currentTarget).eqls(group); + expect(e.delegateTarget).eqls(canvas); + expect(e.delegateObject).eqls(groupDelegateObject); + }); + + canvas.on('circle:mousedown', (e) => { + expect(e.name).eqls('circle:mousedown'); + expect(e.type).eqls('mousedown'); + expect(e.target).eqls(circle); + expect(e.currentTarget).eqls(circle); + expect(e.delegateTarget).eqls(canvas); + expect(e.delegateObject).eqls(circleDelegateObject); + }); + + // 共触发 2 次 + group.on('*', (e) => { + groupEventCount++; + if (groupEventCount === 1) { + expect(e.name).eqls('circle:mousedown'); + expect(e.currentTarget).eqls(circle); + expect(e.delegateObject).eqls(circleDelegateObject); + } else if (groupEventCount === 2) { + expect(e.name).eqls('mousedown'); + expect(e.currentTarget).eqls(group); + } + expect(e.type).eqls('mousedown'); + expect(e.target).eqls(circle); + expect(e.delegateTarget).eqls(group); + }); + + group.on('mousedown', (e) => { + expect(e.name).eqls('mousedown'); + expect(e.type).eqls('mousedown'); + expect(e.target).eqls(circle); + expect(e.currentTarget).eqls(group); + expect(e.delegateTarget).eqls(group); + }); + + group.on('circle:mousedown', (e) => { + expect(e.name).eqls('circle:mousedown'); + expect(e.type).eqls('mousedown'); + expect(e.target).eqls(circle); + expect(e.currentTarget).eqls(circle); + expect(e.delegateTarget).eqls(group); + expect(e.delegateObject).eqls(circleDelegateObject); + }); + + circle.on('mousedown', (e) => { + expect(e.name).eqls('mousedown'); + expect(e.type).eqls('mousedown'); + expect(e.target).eqls(circle); + expect(e.currentTarget).eqls(circle); + expect(e.delegateTarget).eqls(circle); + }); + + // emit event on shape + const { clientX, clientY } = getClientPoint(canvas, 50, 50); + simulateMouseEvent(el, 'mousedown', { + clientX, + clientY, + }); + setTimeout(() => { + done(); + }, 500); + }); + + it('event attrs should be correct when emit event on canvas', () => { + const canvas = new Canvas({ + container: dom, + width: 600, + height: 600, + }); + + const el = canvas.get('el'); + const group = canvas.addGroup({ + name: 'group', + }); + group.addShape('circle', { + name: 'circle', + attrs: { + x: 50, + y: 50, + r: 50, + fill: 'red', + }, + }); + + canvas.on('*', (e) => { + expect(e.name).eqls('mousedown'); + expect(e.type).eqls('mousedown'); + expect(e.target).eqls(canvas); + expect(e.currentTarget).eqls(canvas); + expect(e.delegateTarget).eqls(canvas); + }); + + canvas.on('mousedown', (e) => { + expect(e.name).eqls('mousedown'); + expect(e.type).eqls('mousedown'); + expect(e.target).eqls(canvas); + expect(e.currentTarget).eqls(canvas); + expect(e.delegateTarget).eqls(canvas); + }); + + // emit event on canvas + const { clientX, clientY } = getClientPoint(canvas, 100, 100); + simulateMouseEvent(el, 'mousedown', { + clientX, + clientY, + }); + }); +});