380 lines
12 KiB
JavaScript
380 lines
12 KiB
JavaScript
![]() |
/*
|
|||
|
|
|||
![]() |
Простая 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();
|
|||
|
};
|