211 lines
7.9 KiB
JavaScript
211 lines
7.9 KiB
JavaScript
/*
|
|
* 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
|
|
};
|