/* * Drag and drop for Raphael elements. * * Original Author: Gabe Hollombe. Rewritten several times over by Clifford Heath. * (c) Copyright. Subject to MIT License. * * You need to include jquery and Raphael first. * * When you call draggable() on any Raphael object, it becomes a handle that can start a drag. * A mousedown on the handle followed by enough motion causes a drag to start. * * The dragged object may be the handle itself, another object defined in the options to * draggable(), or an object returned from the handle's dragStart method (if defined). * * The drag proceeds by calling drag_obj.dragUpdate for each mouse motion, see below. * If no dragUpdate is defined, a Raphael translate() is used to provide simple motion. * * If the Escape key is pressed during the drag, the motion is reverted and the drag stops. * In this case, dragCancel will be called, see below. * * When a drag finished normally, dragFinish is called as defined below. * * Options: * drag_obj * A click on the handle will start a drag on this object. * Otherwise handle will be dragged, unless handle.dragStart() * returns a different draggable object. * right_button * unset means drag using either button, otherwise false/true means left/right only. * reluctance * Number of pixels of motion before a drag starts (default 3). * * Optional method on handle. The events are passed so you can see the modifier keys. * dragStart(x, y, mousedownevent, mousemoveevent) * called (after reluctance) with the mousedown event and canvas location. * Must return the object to drag. Good place to call toFront so the drag_obj doesn't hide. * * Optional methods on drag_obj. The event is passed so you can see the modifier keys. * dragUpdate(dragging_over, dx, dy, event) * dragging_over: the object under the cursor (after hiding drag_obj) * dx,dy: the number of units of motion * event: the mousemotion event * dragFinish(dropped_on, x, y, event) * dropped_on: the object under the cursor (after hiding drag_obj) * x,y: the page location * event: the mouseup event * dragCancel() * called if the drag is cancelled */ Raphael.el.draggable = function(options) { var handle = this; // The object you click on if (!options) options = {}; // If you define drag_obj to be null, you must provide a dragStart that returns one: var drag_obj = typeof options.drag_obj !== 'undefined' ? options.drag_obj : handle; // Set right_button to true for right-click dragging only, to false for left-click only. Otherwise you get both. var right_button = options.right_button; // Check that this is an ok thing to even think about doing if (!(handle.node && handle.node.raphael)) { alert(handle+' is not a Raphael object so you can\'t make it draggable'); return; } if (drag_obj && !(drag_obj.node && drag_obj.node.raphael)) { alert(drag_obj+' is not a Raphael object so you can\'t make it draggable'); return; } // options.reluctance is the number of pixels of motion before a drag will start: var reluctance = options.reluctance; if (typeof reluctance == 'undefined') reluctance = 3; var skip_click; var mousedown = function(event) { if (typeof right_button != 'undefined' && (right_button === false) === (event.button > 1)) return true; skip_click = false; // Used to skip a click after dragging var started = false; // Has the drag started? var start_event = event; // The starting mousedown var last_x = event.pageX, last_y = event.pageY; // Where did we move from last? // Figure out what object (other than drag_obj) is under the pointer var over = function(event) { var paper = handle.paper || drag_obj.paper; if (!paper) return null; // Something was deleted if (drag_obj) drag_obj.hide(); // Unfortunately Opera's elementFromPoint seems to be hopelessly broken in SVG var dragging_over = document.elementFromPoint(event.pageX, event.pageY); if (drag_obj) drag_obj.show(); if (!dragging_over) return null; if (dragging_over.nodeType == 3) return dragging_over.parentNode; // Safari/Opera if (dragging_over.tagName != 'svg' && dragging_over == paper.canvas.parentNode) return paper.canvas; // Safari if (dragging_over == paper.canvas) return dragging_over; if (!dragging_over.raphael) return dragging_over.parentNode; // A tspan inside a Raphael text object perhaps? return dragging_over; }; var mousemove = function(event) { var delta_x = event.pageX-last_x; var delta_y = event.pageY-last_y; if (!started && (delta_x>reluctance || delta_x<-reluctance || delta_y>reluctance || delta_y<-reluctance)) { if (handle.dragStart) { var position = jQuery.browser.opera ? jQuery(handle.paper.canvas.parentNode).offset() : jQuery(handle.paper.canvas).offset(); var o = handle.dragStart(event.pageX-delta_x-position.left, event.pageY-delta_y-position.top, start_event, event); if (!o) return false; // Don't start the drag yet if told not to drag_obj = o; } started = true; skip_click = true; } if (!started || !drag_obj) return false; var dragging_over = over(event); // console.log("Move "+drag_obj.node.id+" over "+dragging_over.id+" to X="+event.pageX+", Y="+event.pageY); var update = drag_obj.dragUpdate ? drag_obj.dragUpdate : function(o, dx, dy, e) { drag_obj.translate(dx, dy); }; update(dragging_over, delta_x, delta_y, event); last_x = event.pageX; last_y = event.pageY; return false; }; if (reluctance == 0 && handle.dragStart) { var o = handle.dragStart(0, 0, event, event); if (!o) return false; drag_obj = o; started = true; skip_click = true; } var revert; var cancel; // Process keyboard input so we can cancel var keydown = function(event) { if (event.keyCode == 27) { // Escape revert(event); if (drag_obj.dragCancel) drag_obj.dragCancel(); cancel(); return false; } return true; }; // Revert to starting location revert = function(event) { if (!started) return; if (drag_obj && drag_obj.dragUpdate) drag_obj.dragUpdate(null, start_event.pageX-last_x, start_event.pageY-last_y, event); started = false; // Sometimes get the same event twice. }; // The drag has ended, deal with it. var mouseup = function(event) { if (started) { var dropped_on = over(event); if (drag_obj && drag_obj.dragFinish) { var position = jQuery.browser.opera ? jQuery(handle.paper.canvas.parentNode).offset() : jQuery(handle.paper.canvas).offset(); drag_obj.dragFinish(dropped_on, event.pageX-position.left, event.pageY-position.top, event); } cancel(); return true; // Don't let it bubble } cancel(); return true; }; // Undo event bindings after the drag handle.originalDraggableNode = handle.node; cancel = function() { jQuery(handle.node).unbind('mouseup', mouseup); jQuery(handle.node).unbind('mousemove', mousemove); jQuery(handle.node).unbind('keydown', keydown); if (jQuery.browser.msie) { // Rebind the mousedown if it got lost when the node was recreated: if (handle.originalDraggableNode != handle.node) jQuery(handle.node).bind('mousedown', mousedown); handle.originalDraggableNode = handle.node; } started = false; }; // Bind the appropriate events for the duration of the drag: jQuery(handle.node).bind('keydown', keydown); jQuery(handle.node).bind('mousemove', mousemove); jQuery(handle.node).bind('mouseup', mouseup); event.stopImmediatePropagation(); // Ensure that whatever drag we start, there's only one! return false; }; var click = function(event) { if (skip_click) event.stopImmediatePropagation(); skip_click = false; // Used to skip a click after dragging return true; }; jQuery(handle.node).bind('mousedown', mousedown); jQuery(handle.node).bind('click', click); // Bind click now so that we can stop other click handlers };