/*______________ | ______ | U I Z E J A V A S C R I P T F R A M E W O R K | / / | --------------------------------------------------- | / O / | MODULE : Uize.Widget.Drag.Move.Drop Class | / / / | | / / / /| | ONLINE : http://uize.com | /____/ /__/_| | COPYRIGHT : (c)2005-2016 UIZE | /___ | LICENSE : Available under MIT License or GNU General Public License |_______________| http://uize.com/license.html */ /* Module Meta Data type: Class importance: 2 codeCompleteness: 100 docCompleteness: 2 */ /*? Introduction The =Uize.Widget.Drag.Move.Drop= class implements support for adding/removing/wiring widgets as drop targets. *DEVELOPERS:* `Tim Carter`, original code contributed by `Zazzle Inc.` */ Uize.module ({ name:'Uize.Widget.Drag.Move.Drop', required:'Uize.Dom.Pos', builder:function (_superclass) { 'use strict'; var /*** Variables for Scruncher Optimization ***/ _Uize_Dom_Pos = Uize.Dom.Pos, /*** General Variables ***/ _dropTargets = [] // widgets onto which draggable widgets can be dropped ; return _superclass.subclass ({ staticProperties:{ dropTargets:_dropTargets }, staticMethods:{ addDropTarget:function (_widget, _node) { _dropTargets.push ({_widget:_widget, _node:_node}); /** Registers the passed-in widget as a potential drop target for any Uize.Widget.Drag.Move.Drop instance. When a drag instance is dragged, the position of the drag instance is checked against the positions of the drop targets and certain events are fired: 'Drag Enter': fired when the drag instance and drop target's physical positions first intersect. 'Drag Over': fired when the drag instance is over the drop target. 'Drag Leave': fired when the drag instance and the drop target's physical positions no longer intersect. 'Drop': fired when a Drag action terminates while the drag instance and drop target's physical positions intersect. 'Drag Cancel': fired when a Drag is cancelled. Each of these events contains a =dragObject= parameter which points to the drag instance. It is up to each individual drop target to determine whether or not it should respond to the event or dragObject. If the _node parameter is specified, that will be the node used to calculate to drop target's current coordinates. */ }, removeDropTarget:function (_widget) { for (var _dropTargetIndex = _dropTargets.length; --_dropTargetIndex >= 0;) { if (_dropTargets [_dropTargetIndex]._widget == _widget) { _dropTargets.splice (_dropTargetIndex, 1); return; } } /** Removes the passed-in widget from the list of potential drop targets for any Uize.Widget.Drag.Move.Drop instance. */ } }, omegastructor:function() { var m = this, _dropTargetsEntered = m._dropTargetsEntered = {}, _dropTargetIndex = 0, _restTimeout, _updateTimeout, _processDropTargets = function () { /** * foreach drop target: * if the drop target and drag instance intersect: * if they've intersected before: fire 'Drag Over' * else: fire 'Drag Enter' * elseif they've intersected before: * fire: 'Drag Leave' * else: do nothing */ var _dropTargetsLength = _dropTargets.length, _coords = _Uize_Dom_Pos.getCoords (m.getNode()) ; for (_dropTargetIndex = _dropTargetsLength; --_dropTargetIndex >= 0;) { var _currDropTarget = _dropTargets [_dropTargetIndex], _currDropTargetWidget = _currDropTarget._widget, _instanceId = _currDropTargetWidget.instanceId, _currDropTargetNodeCoords = _Uize_Dom_Pos.getCoords((_currDropTarget._node || (_currDropTarget._node = _currDropTargetWidget.getNode ()))), // these always have to be re-calculated (and not pre-calculated) because the drop target could also be moving _hadEntered = _dropTargetsEntered [_instanceId] ; if (_Uize_Dom_Pos.doRectanglesOverlap (_coords.left, _coords.top, _coords.width, _coords.height, _currDropTargetNodeCoords.left, _currDropTargetNodeCoords.top, _currDropTargetNodeCoords.width, _currDropTargetNodeCoords.height)) { _currDropTargetWidget.fire ({ name:_hadEntered ? 'Drag Over' : 'Drag Enter', dragObject:m }); !_hadEntered && (_dropTargetsEntered [_instanceId] = true); } else if (_hadEntered) { _currDropTargetWidget.fire ({ name:'Drag Leave', dragObject:m }); _dropTargetsEntered [_instanceId] = false; } } } ; m.wire ({ 'Drag Cancel':function (_event) { for (_dropTargetIndex = _dropTargets.length; --_dropTargetIndex >= 0;) _dropTargets[_dropTargetIndex].fire ({ name:'Drag Cancel', dragObject:m, domEvent:_event.domEvent }) ; }, 'Drag Rest':function () { // even if the drag instance is at rest, the drop targets might be moving. // so it's necessary to process them even on Drag Rest. // NOTE: if the item is no longer being dragged, but the drop targets are still moving, // no interaction will occur. This could be rationalized as intended, or considered // a bug that requires significant refactoring of the Uize drag-drop model. function _checkOnRest () { _processDropTargets (); _restTimeout = setTimeout ( _checkOnRest, 200 ); } _restTimeout = setTimeout ( _checkOnRest, 200 ); }, 'Drag Update':function () { _restTimeout && clearTimeout (_restTimeout); _updateTimeout && clearTimeout (_updateTimeout); _updateTimeout = setTimeout ( _processDropTargets, 0 ); }, 'Drag Done':function (_event) { _restTimeout && clearTimeout(_restTimeout); _updateTimeout && clearTimeout (_updateTimeout); var _dropTargetsLength = _dropTargets.length, _coords = _Uize_Dom_Pos.getCoords (m.getNode()), _droppedOn = [] ; for (_dropTargetIndex = _dropTargetsLength; --_dropTargetIndex >= 0;) { var _currDropTarget = _dropTargets [_dropTargetIndex], _currDropTargetWidget = _currDropTarget._widget, _instanceId = _currDropTargetWidget.instanceId, _currDropTargetNodeCoords = _Uize_Dom_Pos.getCoords((_currDropTarget._node || (_currDropTarget._node = _currDropTargetWidget.getNode ()))) ; if (_Uize_Dom_Pos.doRectanglesOverlap (_coords.left, _coords.top, _coords.width, _coords.height, _currDropTargetNodeCoords.left, _currDropTargetNodeCoords.top, _currDropTargetNodeCoords.width, _currDropTargetNodeCoords.height)) { _currDropTargetWidget.fire ({ name:'Drop', dragObject:m, domEvent:_event.domEvent }); _droppedOn.push (_currDropTargetWidget); } else if (_dropTargetsEntered [_instanceId]) _currDropTargetWidget.fire ({ name:'Drag Leave', dragObject:m, domEvent:_event.domEvent }) ; } // it might be preferable to fire the 'Dropped' event for each dropTarget, // but that's probably not necessary right now. _droppedOn.length && m.fire ({ name:'Dropped', dropTargets:_droppedOn, domEvent:_event.domEvent }) ; m._dropTargetsEntered = _dropTargetsEntered = {}; } }); } }); } });