UNPKG

doevisualizations

Version:

Data Visualization Library based on RequireJS and D3.js (v4+)

467 lines (444 loc) 13.5 kB
steal('jquery', 'jquerypp/event/drag/core', 'jquerypp/dom/within', 'jquerypp/dom/compare', function($){ var event = $.event; /** * @add jQuery.event.special */ var eventNames = [ /** * @function jQuery.event.special.dropover dropover * @parent jQuery.event.drop * * @body * * `dropover` is triggered when a [jQuery.event.drag drag] is first moved onto this * drop element. * The event handler gets an instance of [jQuery.Drag] passed as the second and a * [jQuery.Drop] as the third parameter. * This event can be used to highlight the element when a drag is moved over it: * * $('.droparea').on('dropover', function(ev, drop, drag) { * $(this).addClass('highlight'); * }); */ "dropover", /** * @function jQuery.event.special.dropon dropon * @parent jQuery.event.drop * * @body * * `dropon` is triggered when a drag is dropped on a drop element. * The event handler gets an instance of [jQuery.Drag] passed as the second and a * [jQuery.Drop] as the third parameter. * * $('.droparea').on('dropon', function(ev, drop, drag) { * $(this).html('Dropped: ' + drag.element.text()); * }); */ "dropon", /** * @function jQuery.event.special.dropout dropout * @parent jQuery.event.drop * * @body * * `dropout` is called when a drag is moved out of this drop element. * The event handler gets an instance of [jQuery.Drag] passed as the second and a * [jQuery.Drop] as the third parameter. * * $('.droparea').on('dropover', function(ev, drop, drag) { * // Remove the drop element highlight * $(this).removeClass('highlight'); * }); */ "dropout", /** * @function jQuery.event.special.dropinit dropinit * @parent jQuery.event.drop * * @body * * `dropinit` is called when a drag motion starts and the drop elements are initialized. * The event handler gets an instance of [jQuery.Drag] passed as the second and a * [jQuery.Drop] as the third parameter. * Calling [jQuery.Drop.prototype.cancel drop.cancel()] prevents the element from * being dropped on: * * $('.droparea').on('dropover', function(ev, drop, drag) { * if(drag.element.hasClass('not-me')) { * drop.cancel(); * } * }); */ "dropinit", /** * @function jQuery.event.special.dropmove dropmove * @parent jQuery.event.drop * * @body * * `dropmove` is triggered repeatedly when a drag is moved over a drop * (similar to a mousemove). * The event handler gets an instance of [jQuery.Drag] passed as the second and a * [jQuery.Drop] as the third parameter. * * $('.droparea').on('dropmove', function(ev, drop, drag) { * $(this).html(drag.location.x() + '/' + drag.location.y()); * }); */ "dropmove", /** * @function jQuery.event.special.dropend dropend * @parent jQuery.event.drop * * @body * * `dropend` is called when the drag motion is done for this drop element. * The event handler gets an instance of [jQuery.Drag] passed as the second and a * [jQuery.Drop] as the third parameter. * * * $('.droparea').on('dropend', function(ev, drop, drag) { * // Remove the drop element highlight * $(this).removeClass('highlight'); * }); */ "dropend"]; /** * @constructor jQuery.Drop * @parent jQuery.event.drop * @plugin jquerypp/event/drop * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquerypp/event/drop/drop.js * @test jquerypp/event/drag/qunit.html * * The `jQuery.Drop` constructor is never called directly but an instance is passed to the * to the `dropinit`, `dropover`, `dropmove`, `dropon`, and `dropend` event handlers as the * third argument (the second will be the [jQuery.Drag]): * * $('#dropper').on('dropover', function(el, drop, drag) { * // drop -> $.Drop * // drag -> $.Drag * }); */ $.Drop = function(callbacks, element){ $.extend(this,callbacks); this.element = $(element); } // add the elements ... $.each(eventNames, function(){ event.special[this] = { add: function( handleObj ) { //add this element to the compiles list var el = $(this), current = (el.data("dropEventCount") || 0); el.data("dropEventCount", current+1 ) if(current==0){ $.Drop.addElement(this); } }, remove: function() { var el = $(this), current = (el.data("dropEventCount") || 0); el.data("dropEventCount", current-1 ) if(current<=1){ $.Drop.removeElement(this); } } } }); $.extend($.Drop,{ /** * @static */ lowerName: "drop", _rootElements: [], //elements that are listening for drops _elements: $(), //elements that can be dropped on last_active: [], endName: "dropon", // adds an element as a 'root' element // this element might have events that we need to respond to addElement: function( el ) { // check other elements for(var i =0; i < this._rootElements.length ; i++ ){ if(el ==this._rootElements[i]) return; } this._rootElements.push(el); }, removeElement: function( el ) { for(var i =0; i < this._rootElements.length ; i++ ){ if(el == this._rootElements[i]){ this._rootElements.splice(i,1) return; } } }, /** * @hide * For a list of affected drops, sorts them by which is deepest in the DOM first. */ sortByDeepestChild: function( a, b ) { // Use jQuery.compare to compare two elements var compare = a.element.compare(b.element); if(compare & 16 || compare & 4) return 1; if(compare & 8 || compare & 2) return -1; return 0; }, /** * @hide * Tests if a drop is within the point. */ isAffected: function( point, moveable, responder ) { return ((responder.element != moveable.element) && (responder.element.within(point[0], point[1], responder._cache).length == 1)); }, /** * @hide * Calls dropout and sets last active to null * @param {Object} drop * @param {Object} drag * @param {Object} event */ deactivate: function( responder, mover, event ) { mover.out(event, responder) responder.callHandlers(this.lowerName+'out',responder.element[0], event, mover) }, /** * @hide * Calls dropover * @param {Object} drop * @param {Object} drag * @param {Object} event */ activate: function( responder, mover, event ) { //this is where we should call over mover.over(event, responder) responder.callHandlers(this.lowerName+'over',responder.element[0], event, mover); }, move: function( responder, mover, event ) { responder.callHandlers(this.lowerName+'move',responder.element[0], event, mover) }, /** * `jQuery.Drop.compile()` gets all elements that are droppable and adds them to a list. * * This should be called if and when new drops are added to the page * during the motion of a single drag. * * This is called by default when a drag motion starts. * * ## Use * * After adding an element or drop, call compile. * * $("#midpoint").bind("dropover",function(){ * // when a drop hovers over midpoint, * // make drop a drop. * $("#drop").bind("dropover", function(){ * * }); * $.Drop.compile(); * }); */ compile: function( event, drag ) { // if we called compile w/o a current drag if(!this.dragging && !drag){ return; }else if(!this.dragging){ this.dragging = drag; this.last_active = []; } var el, drops, selector, dropResponders, newEls = [], dragging = this.dragging; // go to each root element and look for drop elements for(var i=0; i < this._rootElements.length; i++){ //for each element el = this._rootElements[i] // gets something like {"": ["dropinit"], ".foo" : ["dropover","dropmove"] } var drops = $.event.findBySelector(el, eventNames) // get drop elements by selector for(selector in drops){ dropResponders = selector ? jQuery(selector, el) : [el]; // for each drop element for(var e= 0; e < dropResponders.length; e++){ // add the callbacks to the element's Data // there already might be data, so we merge it if( this.addCallbacks(dropResponders[e], drops[selector], dragging) ){ newEls.push(dropResponders[e]) }; } } } // once all callbacks are added, call init on everything ... this.add(newEls, event, dragging) }, // adds the drag callbacks object to the element or merges other callbacks ... // returns true or false if the element is new ... // onlyNew lets only new elements add themselves addCallbacks : function(el, callbacks, onlyNew){ var origData = $.data(el,"_dropData"); if(!origData){ $.data(el,"_dropData", new $.Drop(callbacks, el)); return true; }else if(!onlyNew){ var origCbs = origData; // merge data for(var eventName in callbacks){ origCbs[eventName] = origCbs[eventName] ? origCbs[eventName].concat(callbacks[eventName]) : callbacks[eventName]; } return false; } }, // calls init on each element's drags. // if its cancelled it's removed // adds to the current elements ... add: function( newEls, event, drag , dragging) { var i = 0, drop; while(i < newEls.length){ drop = $.data(newEls[i],"_dropData"); drop.callHandlers(this.lowerName+'init', newEls[i], event, drag) if(drop._canceled){ newEls.splice(i,1) }else{ i++; } } this._elements.push.apply(this._elements, newEls) }, show: function( point, moveable, event ) { var element = moveable.element; if(!this._elements.length) return; var respondable, affected = [], propagate = true, i = 0, j, la, toBeActivated, aff, oldLastActive = this.last_active, responders = [], self = this, drag; // what's still affected ... we can also move element out here while( i < this._elements.length){ drag = $.data(this._elements[i],"_dropData"); if (!drag) { this._elements.splice(i, 1) } else { i++; if (self.isAffected(point, moveable, drag)) { affected.push(drag); } } } // we should only trigger on lowest children affected.sort(this.sortByDeepestChild); event.stopRespondPropagate = function(){ propagate = false; } toBeActivated = affected.slice(); // all these will be active this.last_active = affected; // deactivate everything in last_active that isn't active for (j = 0; j < oldLastActive.length; j++) { la = oldLastActive[j]; i = 0; while((aff = toBeActivated[i])){ if(la == aff){ toBeActivated.splice(i,1);break; }else{ i++; } } if(!aff){ this.deactivate(la, moveable, event); } if(!propagate) return; } for(var i =0; i < toBeActivated.length; i++){ this.activate(toBeActivated[i], moveable, event); if(!propagate) return; } // activate everything in affected that isn't in last_active for (i = 0; i < affected.length; i++) { this.move(affected[i], moveable, event); if(!propagate) return; } }, end: function( event, moveable ) { var la, endName = this.lowerName+'end', onEvent = $.Event(this.endName, event), dropData; // call dropon // go through the actives ... if you are over one, call dropped on it for(var i = 0; i < this.last_active.length; i++){ la = this.last_active[i] if( this.isAffected(event.vector(), moveable, la) && la[this.endName]){ la.callHandlers(this.endName, null, onEvent, moveable); } if (onEvent.isPropagationStopped()) { break; } } // call dropend for(var r =0; r<this._elements.length; r++){ dropData = $.data(this._elements[r],"_dropData"); dropData && dropData.callHandlers(endName, null, event, moveable); } this.clear(); }, /** * Called after dragging has stopped. * @hide */ clear: function() { this._elements.each(function(){ // remove temporary drop data $.removeData(this,"_dropData") }) this._elements = $(); delete this.dragging; } }) $.Drag.responder = $.Drop; $.extend($.Drop.prototype,{ /** * @prototype */ callHandlers: function( method, el, ev, drag ) { var length = this[method] ? this[method].length : 0 for(var i =0; i < length; i++){ this[method][i].call(el || this.element[0], ev, this, drag) } }, /** * `drop.cache(value)` sets the drop to cache positions of draggable elements. * This should be called on `dropinit`. For example: * * $('#dropable').on('dropinit', function( el, ev, drop ) { * drop.cache(); * }); * * @param {Boolean} [value=true] Whether to cache drop elements or not. */ cache: function( value ) { this._cache = value != null ? value : true; }, /** * `drop.cancel()` prevents this drop from being dropped on. * * $('.droparea').on('dropover', function(ev, drop, drag) { * if(drag.element.hasClass('not-me')) { * drop.cancel(); * } * }); */ cancel: function() { this._canceled = true; } }); return $; });