documon
Version:
A documentation system for mortals. Use with any language.
465 lines (371 loc) • 11.1 kB
JavaScript
/*
Part of Documon.
Copyright (c) Michael Gieson.
www.documon.net
*/
this.documon = this.documon || {};
(function() {
"use strict";
/**
* Makes a DOM Element draggable. NOTE: This is a stand-alone implementation. The jbeeb.utils.Draggable class is used for jbeeb objects. Whereas this class can be used on any DOM element (outside of jbeeb).
*
* @class Draggable
* @param {Object} params - Configuration settings.
* @param {string | DOM Element} params.target - The element to move. String = the ID of the element, or send in the DOM element directly.
* @param {function} params.callback - A function to ping for each kind of operation. See (callback)[#callback] for details.
* @param {string} params.constrain - Constrain movement along the "x" or "y" axis. Both constrain and constrainRect can be used together or independantly.
* @param {Object} params.constrainRect - Constrain movement within a rectangle. The rectangle can be any object (including a DisplayObject) that contains the following properties `x, y, width, height`
* @param {number} params.threshold=5 - The pixel threshold for issuing the "didMove" flag on "end"
* @param {any} params.arg - A custom argument to be delivered to the (callback)[#callback].
*
* @example
*
var myDrag = new documon.Draggable({
target : obj
, callback : fn
, constrain : "x"
, constrainRect : obj
, threshold : 5
});
*/
var Draggable = function(params) {
this.init(params);
}
/**
* Shortcut to set the style position.
* @private
* @method setX
* @param {DOM Element} evt - The element to move.
* @param {number} val - The x position in pixels.
*/
function setX(elem, val){
elem.style.left = val + "px";
}
/**
* Shortcut to set the style position.
* @private
* @method setY
* @param {DOM Element} evt - The element to move.
* @param {number} val - The y position in pixels.
*/
function setY(elem, val){
elem.style.top = val + "px";
}
/**
* Shortcut to set the style position.
* @private
* @method setXY
* @param {DOM Element} evt - The element to move.
* @param {number} x - The x position in pixels.
* @param {number} x - The y position in pixels.
*/
function setXY(elem, x, y){
var s = elem.style;
s.left = x + "px";
s.top = y + "px";
}
/**
* Gets the CSS position (releative) of a DOM Element.
* @private
* @method parsePos
* @param {DOM Element} elem - The element
*/
function parsePos(elem){
/*
var ret = {};
// getComputedStyle getBoundingClientRect
var pos = window.getComputedStyle(elem);
var childPos = obj.offset();
var parentPos = obj.parent().offset();
var childOffset = {
top: childPos.top - parentPos.top,
left: childPos.left - parentPos.left
}
for(var prop in pos){
ret[prop] = parseFloat(pos[prop]);
}
*/
/*
return {
left : elem.offsetLeft,
top : elem.offsetTop
};
*/
var pos = window.getComputedStyle(elem);
return {
left : parseFloat(pos.left),
top : parseFloat(pos.top)
};
}
var p = Draggable.prototype;
/**
* @private
* @property {DOM Element} _target - The object we're going to drag.
*/
p._target = null;
/**
* @private
* @property {function} _callback - Configured callback
*/
p._callback = null;
/**
* @private
* @property {function} _callbackArg - Configured argument to send to the (callback)[#callback].
*/
p._callbackArg = null;
/**
* @private
* @property {object} _constrain
*/
p._constrain = null;
/**
* @private
* @property {object} _constrainRect
*/
p._constrainRect = null;
/**
* @private
* @property {object} _startX
*/
p._startX = null;
/**
* @private
* @property {object} _startY
*/
p._startY = null;
/**
* @private
* @property {object} _startMouseX
*/
p._startMouseX = null;
/**
* @private
* @property {object} _startMouseY
*/
p._startMouseY = null;
/**
* @private
* @property {object} _didMove
*/
p._didMove = null;
/**
* @private
* @property {object} _didMoveFudge
*/
p._didMoveFudge = null;
/**
* @private
* @property {object} _didInitMove
*/
p._didInitMove = false;
/**
* @private
* @property {function} _didMoveThreshold=5 - The pixel threshold for issuing the "didMove" flag on "end"
*/
p._didMoveThreshold = 5;
/**
* @private
* @property {object} _down_bound
*/
p._down_bound;
/**
* @private
* @property {object} _up_bound
*/
p._up_bound;
/**
* @private
* @property {object} _move_bound
*/
p._move_bound;
/**
* @private
* @property {DOM Element} _mask - The overlay masking object used to reject mouse events on underlaying DOM elements.
*/
p._mask;
/**
* Initialize
* @protected
* @method init
* @param {object} params - See main class description for details.
*/
p.init = function(params){
this._target = params.target;
this._down_bound = this._down.bind(this);
this._up_bound = this._up.bind(this);
this._move_bound = this._move.bind(this);
this._target.addEventListener("mousedown", this._down_bound, false);
if(params.callback){
this._callback = params.callback;
this._callbackArg = params.arg;
}
this._constrain = params.constrain;
this._constrainRect = params.constrainRect;
this._didMoveThreshold = typeof params.threshold != "undefined" ? params.threshold : 5;
}
/**
* The function to ping when an operation occurs.
* @method callback
* @protected
* @param {object} obj - The internal object we use to manage the
* @param {object} pos - An object with the following properties: left, top
* @param {string} kind - The kind of operation that occured. Can be "start" | "end" | "move"
* @param {boolean} didmove - Indicates if the item was simply clicked, or if it was dragged. Only issued when event == "end"
* @param {any} arg - The argument that was defined during initialization.
*/
/**
* The mouse "down" listener function
* @private
* @method _down
* @param {MouseEvent} evt
*/
p._down = function(evt){
evt.preventDefault();
this._didMove = false;
this._didInitMove = false;
document.addEventListener("mouseup", this._up_bound, false);
document.addEventListener("mousemove", this._move_bound, false);
}
/**
* A one-time setup to extablish the starting position and create the (mask)[#maks].
* @private
* @method _initMove
* @param {MouseEvent} evt - Transfered from teh "move" listener.
*/
p._initMove = function(evt){
var elem = this._target;
var pos = parsePos(elem);
elem.style.zIndex = 1000;
//elem.style.left = pos.left + "px";
//elem.style.top = pos.top + "px";
this._startX = pos.left;
this._startY = pos.top;
this._startMouseX = evt.pageX;
this._startMouseY = evt.pageY;
var mask = document.createElement("div");
var s = mask.style;
s.position = "absolute";
s.left = 0;
s.right = 0;
s.top = 0;
s.bottom = 0;
s.zIndex = 10000;
//s.backgroundColor = "rgba(255,0,0,0.1)";
document.body.appendChild(mask);
this._mask = mask;
if(this._callback){
this._callback(elem, pos, "start", false, this._callbackArg);
}
}
/**
* The mouse "move" listener function
* @private
* @method _move
* @param {MouseEvent} evt
*/
p._move = function(evt){
evt.preventDefault();
if( ! this._didInitMove ){
this._didInitMove = true;
this._initMove(evt);
}
var elem = this._target; //evt.target;
var diffX = evt.pageX - this._startMouseX;
var diffY = evt.pageY - this._startMouseY;
var newX = this._startX + diffX;
var newY = this._startY + diffY;
if( ! this._didMove ){
this._didMove = Math.abs(diffX) > this._didMoveThreshold || Math.abs(diffY) > this._didMoveThreshold;
}
var rect = this._constrainRect;
if(rect){
var rX = rect.x;
var rY = rect.y;
var rMax;
if(newX < rX){
newX = rX
} else if(newX > (rMax = rX + rect.width) ){
newX = rMax;
}
if(newY < rY){
newY = rY
} else if(newY > (rMax = rY + rect.height) ){
newY = rMax;
}
}
var strain = this._constrain;
if(strain){
if(strain == "x"){
setX(elem, newX);
} else {
setY(elem, newY);
}
} else {
setXY(elem, newX, newY);
}
var pos = {
top : newY,
left : newX,
x : newX,
y : newY
}
if(this._callback){
this._callback(elem, pos, "move", true, this._callbackArg);
}
}
/**
* The mouse "up" listener function
* @private
* @method _up
* @param {MouseEvent} evt
*/
p._up = function(evt){
document.removeEventListener("mousemove", this._move_bound);
document.removeEventListener("mouseup", this._up_bound);
if(this._mask){
var elem = this._target; //e.target;
var pos = parsePos(elem);
// getComputedStyle getBoundingClientRect
document.body.removeChild(this._mask);
this._mask = null;
if(this._callback){
this._callback(elem, pos, "end", this._didMove, this._callbackArg);
}
}
}
/**
* Sets the rectangle used to for the boundaries that the item can move within.
* @method setConstrainRect
* @param {Object} val - An object containing the following properties: `x, y, width, height`. Use "null" or no argument to clear contstraint.
*/
p.setConstrainRect = function(rect){
this._constrainRect = rect;
}
/**
* Causes the movement to be contrained along the X or Y axis.
* @method setConstrain
* @param {string} val - Use "x" or "y". Use "null" or no argument to clear contstraint.
*/
p.setConstrain = function(val){
this._constrain = val;
}
/**
* Destroys and removes listeners.
* @method destroy
*/
p.destroy = function(){
this._target.removeEventListener("mousedown", this._down_bound, false);
document.removeEventListener("mousemove", this._move_bound);
document.removeEventListener("mouseup", this._up_bound);
if(this._mask){
document.body.removeChild(this._mask);
this._mask = null;
}
this._target = null;
this._callback = null;
this._constrain = null;
this._constrainRect = null;
}
p.type = "Draggable";
documon.Draggable = Draggable;
}());