Permalink
Browse files

Start to break out svg independent dom event handling logic

- better separation of concerns
- better reuse of logic for other DOM-based renderers
  • Loading branch information...
1 parent c5850ec commit 70d71333324d98d90530c4f1f04323b6d5f32009 @davidaurelio davidaurelio committed Feb 28, 2013
Showing with 207 additions and 0 deletions.
  1. +172 −0 src/renderer/dom/event_handler.js
  2. +34 −0 test/renderer/dom/event_handler-spec.js
  3. +1 −0 test/runner.html
@@ -0,0 +1,172 @@
+define([
+ '../../tools'
+],
+function(tools) {
+ var max = Math.max;
+ var forEach = tools.forEach;
+
+ var eventTypesForRoot = [
+ 'click',
+ 'dblclick',
+ 'mousedown',
+ 'mouseenter',
+ 'mouseleave',
+ 'mousemove',
+ 'mouseout',
+ 'mouseover',
+ 'mouseup',
+ 'touchcancel',
+ 'touchend',
+ 'touchmove',
+ 'touchstart'
+ ];
+
+ var eventTypesForDocument = ['keydown', 'keypress', 'keyup'];
+
+ /**
+ * Determines whether an event type is a keyboard event type.
+ *
+ * @param {string} type The event type
+ * @return {boolean}
+ */
+ function isKeyboardEventType(type) {
+ return type === 'keydown' || type === 'keypress' || type === 'keyup';
+ }
+
+ /**
+ * Determines whether an event type is a mouse event type.
+ *
+ * @param {string} type The event type
+ * @return {boolean}
+ */
+ function isMouseEventType(type) {
+ return type === 'click' || type === 'dblclick' ||
+ type === 'mousedown' || type === 'mousemove' || type === 'mouseout' ||
+ type === 'mouseover' || type === 'mouseup' || type === 'mousewheel';
+ }
+
+ /**
+ * Determines whether an event type is a touch event type.
+ *
+ * @param {string} type The event type
+ * @return {boolean}
+ */
+ function isTouchEventType(type) {
+ return type === 'touchstart' || type === 'touchend' ||
+ type === 'touchmove' || type === 'touchcancel';
+ }
+
+ function EventHandler(renderer) {
+ this.hadTouchCancel = false;
+ this.hadTouchMove = false;
+ this.isMultiTouch = false;
+ this.lastClickFromTouchTime = 0;
+ this.mouseDragId = undefined;
+ this.mouseDragStartX = undefined;
+ this.mouseDragStartY = undefined;
+ this.mouseMoveLastX = undefined;
+ this.mouseMoveLastY = undefined;
+ this.renderer = renderer;
+ this.touchStates = {};
+ }
+
+ EventHandler.prototype = {
+ connect: function(rootNode) {
+ forEach(eventTypesForRoot, function(eventType) {
+ root.addEventListener(eventType, this, false);
+ }, this);
+ var document = rootNode.ownerDocument;
+ forEach(eventTypesForDocument, function(eventType) {
+ document.addEventListener(eventType, this, false);
+ });
+ },
+
+ disconnect: function(rootNode) {
+ forEach(eventTypesForRoot, function(eventType) {
+ root.removeEventListener(eventType, this, false);
+ }, this);
+ var document = rootNode.ownerDocument;
+ forEach(eventTypesForDocument, function(eventType) {
+ document.removeEventListener(eventType, this, false);
+ });
+ }
+
+ handleEvent: function(domEvent) {
+ var renderer = this.renderer;
+ var domEventTarget = domEvent.target, domEventType = domEvent.type;
+
+ var targetId = renderer.getBonsaiIdFor(domEventTarget);
+ if (targetId < 0) { return; }
+
+ var isMouseEvent = isMouseEventType(domEventType);
+ var isTouchEvent = isTouchEventType(domEventType);
+
+ if (isMouseEvent || isTouchEvent) {
+ var stageOffset = renderer.getOffset();
+ var stageX = stageOffset.left, stageY = stageOffset.top;
+ var pointerEvent;
+
+ if (isMouseEvent) {
+ var relatedTargetId, relatedTarget = domEvent.relatedTarget;
+ if (relatedTarget) {
+ relatedTargetId = renderer.getBonsaiIdFor(relatedTarget);
+ }
+ pointerEvent = PointerEvent
+ .fromDomMouseEvent(domEvent, stageX, stageY);
+
+ this.handleMouseEvent(pointerEvent, targetId, relatedTargetId);
+ } else { // touch event
+ var domTouch, touchTargetId;
+ var changedTouches = domEvent.changedTouches;
+ var numTouches = changedTouches.length;
+
+ if (domEventType === 'touchstart') {
+ this.isMultiTouch =
+ this.isMultiTouch || numTouches > 1 || domEvent.touches.length;
+ } else if (domEventType === 'touchmove') {
+ this.hadTouchMove = true;
+ // event killing is needed to prevent native scrolling etc.
+ // within bonsai movies
+ if (!renderer.allowEventDefaults) {
+ domEvent.preventDefault();
+ }
+ }
+
+ for (var i = 0; i < numTouches; i += 1) {
+ domTouch = changedTouches[i];
+ pointerEvent = PointerEvent
+ .fromDomTouch(domTouch, domEvent, stageX, stageY);
+ touchTargetId = renderer.getBonsaiIdFor(domTouch.target, domEventTarget, targetId);
+ this.handleTouchEvent(pointerEvent, touchTargetId);
+ }
+
+ if (domEventType === 'touchend' && domEvent.touches.length === 0) { // last finger raised
+ if (!(this.isMultiTouch || this.hadTouchMove || this.hadTouchCancel)) {
+ this.triggerClickFromTouch(domEvent, pointerEvent);
+ domEvent.preventDefault(); // prevent default click
+ }
+ this.isMultiTouch = this.hadTouchMove = this.hadTouchCancel = false;
+ }
+ } else if (isKeyboardEventType(domEventType)) {
+//TODO: check this bailout
+// if (!target || target._isBSDOMElement || ownerDocument.activeElement === ownerDocument.body) {} else {
+// // There is another currently focused element (outside of the stage), exit:
+// return;
+// }
+ var keyboardEvent = KeyboardEvent.fromDomKeyboardEvent(domEvent);
+ this.emit('userevent', keyboardEvent, targetId);
+ }
+ }
+ },
+
+ triggerClickFromTouch: function(domEvent, pointerEvent) {
+ var clickType, domTimeStamp = domEvent.timeStamp;
+ var isDoubleClick = domTimeStamp - this.lastClickFromTouchTime < 300;
+ var clickType = isDoubleClick ? 'dblclick' : 'click';
+ this.lastClickFromTouchTime = isDoubleClick ? 0 : domTimeStamp;
+ emitMouseEvent(this.renderer, pointerEvent.clone(clickType), touchTargetId);
+ }
+ };
+
+ return EventHandler;
+});
@@ -0,0 +1,34 @@
+define(['bonsai/renderer/dom/event_handler'], function(EventHandler) {
+ 'use strict';
+
+ describe('renderer/dom/EventHandler', function() {
+ it('should use the first argument as "renderer" property', function() {
+ var renderer = {};
+ expect(new EventHandler(renderer)).toHaveOwnProperties({
+ renderer: renderer
+ });
+ });
+
+ it('should have a "touchStates" property that is an object', function() {
+ expect(new EventHandler().touchStates).toBeOfType('object');
+ });
+
+ it('should initialize all other used properties', function() {
+ expect(new EventHandler()).toHaveOwnProperties({
+ hadTouchCancel: false,
+ hadTouchMove: false,
+ isMultiTouch: false,
+ lastClickFromTouchTime: 0,
+ mouseDragId: undefined,
+ mouseDragStartX: undefined,
+ mouseDragStartY: undefined,
+ mouseMoveLastX: undefined,
+ mouseMoveLastY: undefined
+ });
+ });
+
+ it('should have a "handleEvent" event', function() {
+ expect(new EventHandler().handleEvent).toBeOfType('function');
+ });
+ });
+});
View
@@ -84,6 +84,7 @@
// renderer
'./renderer/event-spec',
+ './renderer/dom/event_handler-spec',
// svg renderer
'./renderer/svg-spec',

0 comments on commit 70d7133

Please sign in to comment.