bugzilla-4intranet/js/DragDrop.js

380 lines
12 KiB
JavaScript
Raw Normal View History

/*
Простая JS-библиотека для организации Drag&Drop (не HTML5)
http://yourcmc.ru/wiki/JSLib#Drag.26Drop
(c) Виталий Филиппов, 2010-2011
Основано на уроке Ильи Кантора http://javascript.ru/ui/draganddrop
Использование:
dragObject = new DragObject(element); // то, что тащим
dropTarget = new DropTarget(element); // то, куда тащим
Обработчики DropTarget:
* boolean dropTarget.canAccept(DragObject)
Вернуть true, если эта цель может принять этот DragObject
* dropTarget.onAccept(DragObject, pos = { x: int, y: int })
Объект перетаскивают на эту цель и отпускают
x, y - относительные цели координаты, в которых объект отпущен
* dropTarget.onEnter()
Объект приносят на этой цель
* dropTarget.onLeave()
Объект уносят с этой цели
* dropTarget.onMove(pos = { x: int, y: int })
Объект таскают по цели
x, y - относительные цели координаты, в которых объект находится
Обработчики DragObject:
* dragObject.onDragStart(offset = { x: int, y: int })
Объект начинают перетаскивать
x, y - относительные координаты, за которые пользователь взял объект мышкой
* dragObject.onDragMove(x, y)
Объект перетаскивают
x, y - абсолютные координаты нахождения объекта
* dragObject.onDragSuccess(DropTarget, pos = { x: int, y: int })
Объект принят целью
x, y - относительные цели координаты, в которых объект отпущен
* dragObject.onDragFail()
Объект не принят ни одной целью
Соответственно, все эти обработчики можно переопределять. Так и работаем.
Можно даже унаследоваться от класса и написать обработчики прототипами:
function MyDropTarget(e) { DropTarget.call(this, e); }
MyDropTarget.prototype = new DropTarget();
MyDropTarget.prototype.onAccept = function(obj, pos) {...};
Ещё можно использовать кое-что в DragMaster'е:
* Event DragMaster.fixEvent(Event e)
Фиксит некоторые не-кроссбраузерности в событии:
* e = e || window.event
* pX, pY = правильным смещениям клика мышкой от начала документа
* which = добавляется для IE
* _target = правильная кроссбраузерная цель события
* DragMaster.noDragElements = { 'nodeName' => true|false }
Отключает перетаскивание при клике на элементе nodeName, даже
если он содержится внутри перетаскиваемого объекта.
По умолчанию это input, textarea, button.
*/
var DragMaster = (function()
{
var dragObject;
var mouseDownAt;
var currentDropTarget;
var self = {};
self.noDragElements = { 'input': 1, 'textarea': 1, 'button': 1 };
self.usePageX = (function()
{
var m = navigator.userAgent.match(/Opera.([\d\.]+)/);
var mv;
if (m && (mv = navigator.userAgent.match(/Version\/([\d\.]+)/)))
m = mv;
var sf = navigator.userAgent.match(/Safari\//) &&
!navigator.userAgent.match(/Chrome/);
if (sf || m && parseFloat(m[1]) < 10.5)
return true;
return false;
})();
self.fixEvent = function(e)
{
// получить объект событие для IE
e = e || window.event;
// добавить pageX/pageY для IE
if (e.pageX == null && e.clientX != null)
{
var html = document.documentElement;
var body = document.body;
e.pageX = e.clientX + (html && html.scrollLeft || body && body.scrollLeft || 0) - (html.clientLeft || 0);
e.pageY = e.clientY + (html && html.scrollTop || body && body.scrollTop || 0) - (html.clientTop || 0);
}
// правильный pageX/pageY
if (self.usePageX && e.pageX != null)
{
e.pX = e.pageX;
e.pY = e.pageY;
}
else if (e.pageY != null)
{
e.pX = e.clientX;
e.pY = e.clientY;
}
// добавить which для IE
if (!e.which && e.button)
e.which = e.button & 1 ? 1 : ( e.button & 2 ? 3 : ( e.button & 4 ? 2 : 0 ) );
// добавить кроссбраузерную цель события
var t = e.target;
if (!t)
t = e.srcElement;
if (t && t.nodeType == 3)
t = t.parentNode; // Safari bug
e._target = t;
return e;
};
function getOffset(elem)
{
if (elem.getBoundingClientRect)
return getOffsetRect(elem);
else
return getOffsetSum(elem);
};
function getOffsetRect(elem)
{
var box = elem.getBoundingClientRect();
var body = document.body;
var docElem = document.documentElement;
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
var clientTop = docElem.clientTop || body.clientTop || 0;
var clientLeft = docElem.clientLeft || body.clientLeft || 0;
var top = box.top + scrollTop - clientTop;
var left = box.left + scrollLeft - clientLeft;
return { top: Math.round(top), left: Math.round(left) };
};
function getOffsetSum(elem)
{
var top = 0, left = 0;
while(elem)
{
top = top + parseInt(elem.offsetTop);
left = left + parseInt(elem.offsetLeft);
elem = elem.offsetParent;
}
return { top: top, left: left };
};
function mouseDown(e)
{
e = self.fixEvent(e);
if (self.noDragElements[e._target.nodeName.toLowerCase()])
return true;
if (e.which != 1)
return false;
mouseDownAt = { x: e.pageX, y: e.pageY, element: this };
addDocumentEventHandlers();
return false;
};
function mouseMove(e)
{
e = self.fixEvent(e);
// (1)
if (mouseDownAt)
{
if (Math.abs(mouseDownAt.x-e.pageX) < 5 &&
Math.abs(mouseDownAt.y-e.pageY) < 5)
return false;
// Начать перенос
var elem = mouseDownAt.element;
// текущий объект для переноса
dragObject = elem.dragObject;
// запомнить, с каких относительных координат начался перенос
var mouseOffset = getMouseOffset(elem, mouseDownAt.x, mouseDownAt.y);
mouseDownAt = null; // запомненное значение больше не нужно, сдвиг уже вычислен
dragObject.dragStart(mouseOffset); // начали
}
// (2)
dragObject.dragMove(e.pageX, e.pageY);
dragObject.element._moved = true;
// (3)
var newTarget = getCurrentTarget(e);
// (4)
if (currentDropTarget != newTarget)
{
if (currentDropTarget && currentDropTarget.onLeave)
currentDropTarget.onLeave();
if (newTarget && newTarget.onEnter)
newTarget.onEnter();
currentDropTarget = newTarget;
}
if (currentDropTarget && currentDropTarget.onMove)
currentDropTarget.onMove(getMouseOffset(currentDropTarget.element, e.pX, e.pY));
// (5)
return false;
};
function mouseUp(e)
{
e = self.fixEvent(e);
if (!dragObject) // (1)
mouseDownAt = null;
else
{
// (2)
if (currentDropTarget)
{
var pos = getMouseOffset(currentDropTarget.element, e.pX, e.pY);
currentDropTarget.accept(dragObject, pos);
dragObject.dragSuccess(currentDropTarget, pos);
}
else
dragObject.dragFail();
dragObject = null;
}
// (3)
removeDocumentEventHandlers();
};
function getMouseOffset(target, x, y)
{
var docPos = getOffset(target);
return { x: x-docPos.left, y: y-docPos.top };
};
function getCurrentTarget(e)
{
// спрятать объект, получить элемент под ним - и тут же показать опять
var x = e.pX, y = e.pY;
// чтобы не было заметно мигание - максимально снизим время от hide до show
dragObject.hide();
var elem = document.elementFromPoint(x, y);
dragObject.show();
// найти самую вложенную dropTarget
while (elem)
{
// которая может принять dragObject
if (elem.dropTarget && (!elem.dropTarget.canAccept || elem.dropTarget.canAccept(dragObject)))
return elem.dropTarget;
elem = elem.parentNode;
}
// dropTarget не нашли
return null;
};
function addDocumentEventHandlers()
{
document.onmousemove = mouseMove;
document.onmouseup = mouseUp;
document.ondragstart = document.body.onselectstart = function() { return false };
};
function removeDocumentEventHandlers()
{
document.onmousemove =
document.onmouseup =
document.ondragstart =
document.body.onselectstart = null;
};
self.makeDraggable = function(element)
{
element.onmousedown = mouseDown;
element.onclick = function()
{
var r = element._moved && true;
element._moved = false;
return !r;
};
}
return self;
}());
/* DragObject */
function DragObject(element)
{
if (!element)
return;
element.dragObject = this;
DragMaster.makeDraggable(element);
this.element = element;
}
DragObject.prototype.dragStart = function(offset)
{
var s = this.element.style;
this.rememberPosition = {
top: s.top,
left: s.left,
position: s.position,
opacity: s.opacity
};
s.position = 'absolute';
this.mouseOffset = offset;
if (this.onDragStart)
this.onDragStart(offset);
};
DragObject.prototype.hide = function()
{
this.element.style.display = 'none';
};
DragObject.prototype.show = function()
{
this.element.style.display = '';
};
DragObject.prototype.dragMove = function(x, y)
{
this.element.style.top = y - this.mouseOffset.y + 'px';
this.element.style.left = x - this.mouseOffset.x + 'px';
if (this.onDragMove)
this.onDragMove(x, y);
};
DragObject.prototype.dragSuccess = function(dropTarget, pos)
{
this.restorePosition();
if (this.onDragSuccess)
this.onDragSuccess(dropTarget, pos);
};
DragObject.prototype.restorePosition = function()
{
var s = this.element.style;
for (var i in this.rememberPosition)
s[i] = this.rememberPosition[i];
};
DragObject.prototype.dragFail = function()
{
this.restorePosition()
if (this.onDragFail)
this.onDragFail();
};
/* DropTarget */
function DropTarget(element)
{
if (!element)
return;
element.dropTarget = this;
this.element = element;
};
DropTarget.prototype.accept = function(dragObject, pos)
{
if (this.onAccept)
this.onAccept(dragObject, pos);
if (this.onLeave)
this.onLeave();
};