Config
Table of Contents

Class

O.DragController

This singleton manages drag and drop events, normalising native drag and drop with mouse(down|move|up) simulated drag and drop. It creates instances of O.Drag and dispatches events to them as necessary.

It is unlikely that an application need interface with this object directly. The O.Draggable, O.DragDataSource and O.DropTarget mixins are used to add the required properties to views, and an instance of O.Drag gives all the information about the drag.

/*global document */

"use strict";

( function ( NS ) {

var isControl = {
 BUTTON: 1,
 INPUT: 1,
 OPTION: 1,
 SELECT: 1,
 TEXTAREA: 1
};
var effectToString = NS.DragEffect.effectToString;
var DEFAULT = NS.DragEffect.DEFAULT;

function TouchDragEvent ( touch ) {
 var clientX = touch.clientX,
   clientY = touch.clientY,
   target = document.elementFromPoint( clientX, clientY ) || touch.target;
 this.touch = touch;
 this.clientX = clientX;
 this.clientY = clientY;
 this.target = target;
 this.targetView = NS.ViewEventsController.getViewFromNode( target );
}

var getTouch = function ( event, touchId ) {
 var touches = event.changedTouches,
   l = touches.length,
   touch;
 while ( l-- ) {
   touch = touches[l];
   if ( touch.identifier === touchId ) {
     return touch;
   }
 }
 return null;
};

var DragController = new NS.Object({

Private Property

O.DragController._x

  • Number
  • private

The (screen-based) x coordinate of the mouse when the last mousedown event fired. Used to detect if the mouse has moved sufficiently whilst down to initiate a drag.

_x: 0,

Private Property

O.DragController._y

  • Number
  • private

The (screen-based) y coordinate of the mouse when the last mousedown event fired. Used to detect if the mouse has moved sufficiently whilst down to initiate a drag.

_y: 0,

Private Property

O.DragController._targetView

  • O.View|null
  • private

The O.View instance on which the mousedown event occurred. Used to determine the target view to trigger a simulated drag event.

_targetView: null,

Private Property

O.DragController._ignore

  • Boolean
  • private

If true, drag events will not be initiated on mousemove. Is set to true whilst the mouse is down (unless it was inside a control), until the mouse is up again or a drag is initiated.

_ignore: true,

Private Property

O.DragController._touchId

  • String|null
  • private

If a touch-inited drag is in progress, this holds the identifier of the touch being tracked

_touchId: null,

Private Property

O.DragController._drag

  • O.Drag|null
  • private

If a drag is in progress, this holds the current O.Drag instance.

_drag: null,

Method

O.DragController.register( drag )

Called by a new O.Drag instance when it is created to set it as the handler for all future drag events. Ends any previous drag if still active.

Parameters

dragO.Drag The new drag instance.
register: function ( drag ) {
   if ( this._drag ) {
     this._drag.endDrag();
   }
   this._drag = drag;
 },

Method

O.DragController.deregister( drag )

Called by a new O.Drag instance when it is finished to deregister from future drag events.

Parameters

dragO.Drag The finished drag instance.
deregister: function ( drag ) {
   if ( this._drag === drag ) {
     this._drag = null;
     this._touchId = null;
   }
 },

Method

O.DragController.getNearestDragView( view )

Parameters

viewO.View

Returns

O.View|null The view passed in or the nearest parent view of that (going up the tree) which is draggable. A view is draggable if it includes the O.Draggable mixin.

getNearestDragView: function ( view ) {
   while ( view ) {
     if ( view.get( 'isDraggable' ) ) {
       break;
     }
     view = view.get( 'parentView' ) || null;
   }
   return view;
 },

Method

O.DragController.handleEvent( event )

Handler for native events. Fires an equivalent O.EventTarget event.

Parameters

eventEvent
handleEvent: function ( event ) {
   this.fire( event.type, event );
 }.invokeInRunLoop(),

 // === Non-native mouse API version ===

Private Method

O.DragController._onMousedown( event )

Tracks mousedown events so that simulated drag events can be dispatched when a drag gesture is detected.

Parameters

eventEvent The mousedown event.
_onMousedown: function ( event ) {
   if ( event.button || event.metaKey || event.ctrlKey ) { return; }
   if ( isControl[ event.target.nodeName ] ) {
     this._ignore = true;
   } else {
     this._x = event.clientX;
     this._y = event.clientY;
     this._targetView = event.targetView;
     this._ignore = false;
   }
 }.on( 'mousedown' ),

Private Method

O.DragController._onMousemove( event )

Tracks mousemove events and creates a new O.Drag instance if a drag gesture is detected, or passes the move event to an existing drag.

Parameters

eventEvent The mousemove event.
_onMousemove: function ( event ) {
   var drag = this._drag;
   if ( drag && !this._touchId ) {
     // Mousemove should only be fired if not native DnD, but sometimes
     // is fired even when there's a native drag
     if ( !drag.get( 'isNative' ) ) {
       drag.move( event );
     }
     // If mousemove during drag, don't propagate to views (for
     // consistency with native DnD).
     event.stopPropagation();
   } else if ( !this._ignore ) {
     var x = event.clientX - this._x,
       y = event.clientY - this._y,
       view;

     if ( ( x*x + y*y ) > 25 ) {
       view = this.getNearestDragView( this._targetView );
       if ( view ) {
         new NS.Drag({
           dragSource: view,
           event: event,
           startPosition: {
             x: this._x,
             y: this._y
           }
         });
       }
       this._ignore = true;
     }
   }
 }.on( 'mousemove' ),

Private Method

O.DragController._onMouseup( event )

Tracks mouseup events to end simulated drags.

Parameters

eventEvent The mouseup event.
_onMouseup: function ( event ) {
   this._ignore = true;
   this._targetView = null;
   // Mouseup will not fire if native DnD
   var drag = this._drag;
   if ( drag && !this._touchId ) {
     drag.drop( event ).endDrag();
   }
 }.on( 'mouseup' ),

 // === Non-native touch API version ===

Private Method

O.DragController._onHold( event )

Parameters

eventEvent The hold event.
_onHold: function ( event ) {
   var touch = event.touch,
     touchEvent = new TouchDragEvent( touch ),
     view = this.getNearestDragView( touchEvent.targetView );
   if ( view && !isControl[ touchEvent.target.nodeName ] ) {
     this._drag = new NS.Drag({
       dragSource: view,
       event: touchEvent
     });
     this._touchId = touch.identifier;
   }
 }.on( 'hold' ),

Private Method

O.DragController._onTouchemove( event )

Parameters

eventEvent The touchmove event.
_onTouchmove: function ( event ) {
   var touchId = this._touchId,
     touch;
   if ( touchId ) {
     touch = getTouch( event, touchId );
     if ( touch ) {
       this._drag.move( new TouchDragEvent( touch ) );
       // Don't propagate to views and don't trigger scroll.
       event.preventDefault();
       event.stopPropagation();
     }
   }
 }.on( 'touchmove' ),

Private Method

O.DragController._onTouchend( event )

Parameters

eventEvent The touchend event.
_onTouchend: function ( event ) {
   var touchId = this._touchId,
     touch;
   if ( touchId ) {
     touch = getTouch( event, touchId );
     if ( touch ) {
       this._drag.drop( new TouchDragEvent( touch ) ).endDrag();
     }
   }
 }.on( 'touchend' ),

Private Method

O.DragController._onTouchcancel( event )

Parameters

eventEvent The touchcancel event.
_onTouchcancel: function ( event ) {
   var touchId = this._touchId,
     touch;
   if ( touchId ) {
     touch = getTouch( event, touchId );
     if ( touch ) {
       this._drag.endDrag();
     }
   }
 }.on( 'touchcancel' ),

 // === Native API version ===

Private Method

O.DragController._onDragstart( event )

Tracks dragstart events to create a new O.Drag instance.

Parameters

eventEvent The dragstart event.
_onDragstart: function ( event ) {
   // Ignore any implicit drags; only use native API when draggable="true"
   // is explicitly set
   var target = event.target,
     explicit = false;
   while ( target && target.getAttribute ) {
     if ( target.getAttribute( 'draggable' ) === 'true' ) {
       explicit = true;
       break;
     }
     target = target.parentNode;
   }
   if ( !explicit ) {
     event.preventDefault();
   } else {
     new NS.Drag({
       dragSource: this.getNearestDragView( event.targetView ),
       event: event,
       isNative: true
     });
   }
 }.on( 'dragstart' ),

Private Method

O.DragController._onDragover( event )

Tracks dragover events to pass mouse movement to the O.Drag instance.

Parameters

eventEvent The dragover event.
_onDragover: function ( event ) {
   var drag = this._drag,
     dataTransfer = event.dataTransfer,
     notify = true,
     dropEffect;
   // Probably hasn't come via root view controller, so doesn't have target
   // view property
   if ( !event.targetView ) {
     event.targetView =
       NS.ViewEventsController.getViewFromNode( event.target );
   }
   if ( !drag ) {
     // Drag from external source:
     drag = new NS.Drag({
       event: event,
       isNative: true,
       allowedEffects:
         effectToString.indexOf( dataTransfer.effectAllowed )
     });
   } else {
     var x = event.clientX,
       y = event.clientY;
     if ( this._x === x && this._y === y ) {
       notify = false;
     } else {
       this._x = x;
       this._y = y;
     }
   }
   if ( notify ) {
     drag.move( event );
   }
   dropEffect = drag.get( 'dropEffect' );
   if ( dropEffect !== DEFAULT ) {
     dataTransfer.dropEffect =
       effectToString[ dropEffect & drag.get( 'allowedEffects' ) ];
     event.preventDefault();
   }
 }.on( 'dragover' ),

Private Property

O.DragController._nativeRefCount

  • Number
  • private

A reference count, incremented each time we see a dragenter event and decremented each time we see a dragleave event.

If a native drag starts outside the window, we never get a dragend event. Instead we need to keep track of the dragenter/dragleave calls. The drag enter event is fired before the drag leave event (see http://dev.w3.org/html5/spec/dnd.html#drag-and-drop-processing-model), so when the count gets down to zero it means the mouse has left the actual window and so we can end the drag.

_nativeRefCount: 0,

Private Method

O.DragController._onDragenter( event )

Tracks dragenter events to increment the O.DragController._nativeRefCount refcount.

Parameters

eventEvent The dragenter event.
_onDragenter: function (/* event */) {
   this._nativeRefCount += 1;
 }.on( 'dragenter' ),

Private Method

O.DragController._onDragleave( event )

Tracks dragleave events to decrement the O.DragController._nativeRefCount refcount, and end the drag if it gets down to 0 (as this means the drag has left the browser window).

Parameters

eventEvent The dragleave event.
_onDragleave: function (/* event */) {
   var drag = this._drag;
   if ( !( this._nativeRefCount -= 1 ) && drag ) {
     drag.endDrag();
   }
 }.on( 'dragleave' ),

Private Method

O.DragController._onDrop( event )

Tracks drop events to pass them to the active O.Drag instance.

Parameters

eventEvent The drop event.
_onDrop: function ( event ) {
   var drag = this._drag;
   if ( drag ) {
     if ( drag.get( 'dropEffect' ) !== DEFAULT ) {
       event.preventDefault();
     }
     // Dragend doesn't fire if the drag didn't start
     // inside the window, so we also call drag end on drop.
     drag.drop( event ).endDrag();
   }
 }.on( 'drop' ),

Private Method

O.DragController._onDragend( event )

Tracks dragend events to pass them to the active O.Drag instance.

Parameters

eventEvent The dragend event.
_onDragend: function (/* event */) {
   var drag = this._drag;
   if ( drag ) {
     drag.endDrag();
   }
 }.on( 'dragend' ),

 // === Cancel on escape ===

Private Method

O.DragController._escCancel( event )

Cancels the drag if the escape key is hit whilst the drag is in progress.

Parameters

eventEvent The keydown event.
_escCancel: function ( event ) {
   var drag = this._drag;
   if ( drag && NS.DOMEvent.lookupKey( event ) === 'esc' ) {
     drag.endDrag();
   }
 }.on( 'keydown' )
});

[ 'dragover', 'dragenter', 'dragleave', 'drop', 'dragend' ]
 .forEach( function ( type ) {
   document.addEventListener( type, DragController, false );
 });

NS.ViewEventsController.addEventTarget( DragController, 20 );

NS.DragController = DragController;

}( O ) );
Animation
Application
Core
DataStore
DOM
DragDrop
Foundation
IO
Localisation
Selection
Parser
TimeZones
Storage
Touch
CollectionViews
UA
ContainerViews
ControlViews
PanelViews
View