Page MenuHomestyx hydra

DraggableList.js
No OneTemporary

DraggableList.js

/**
* @provides phabricator-draggable-list
* @requires javelin-install
* javelin-dom
* javelin-stratcom
* javelin-util
* javelin-vector
* javelin-magical-init
* @javelin
*/
JX.install('DraggableList', {
construct : function(sigil, root) {
this._sigil = sigil;
this._root = root || document.body;
// NOTE: Javelin does not dispatch mousemove by default.
JX.enableDispatch(document.body, 'mousemove');
JX.DOM.listen(this._root, 'mousedown', sigil, JX.bind(this, this._ondrag));
JX.Stratcom.listen('mousemove', null, JX.bind(this, this._onmove));
JX.Stratcom.listen('mouseup', null, JX.bind(this, this._ondrop));
},
events : [
'didLock',
'didUnlock',
'shouldBeginDrag',
'didBeginDrag',
'didCancelDrag',
'didEndDrag',
'didDrop'],
properties : {
findItemsHandler : null
},
members : {
_root : null,
_dragging : null,
_locked : 0,
_origin : null,
_target : null,
_targets : null,
_dimensions : null,
_ghostHandler : null,
_ghostNode : null,
setGhostHandler : function(handler) {
this._ghostHandler = handler;
return this;
},
getGhostHandler : function() {
return this._ghostHandler || JX.bind(this, this._defaultGhostHandler);
},
getGhostNode : function() {
if (!this._ghostNode) {
this._ghostNode = JX.$N('li', {className: 'drag-ghost'});
}
return this._ghostNode;
},
setGhostNode : function(node) {
this._ghostNode = node;
return this;
},
_defaultGhostHandler : function(ghost, target) {
var parent = this._dragging.parentNode;
if (target && target.nextSibling) {
parent.insertBefore(ghost, target.nextSibling);
} else if (!target && parent.firstChild) {
parent.insertBefore(ghost, parent.firstChild);
} else {
parent.appendChild(ghost);
}
},
findItems : function() {
var handler = this.getFindItemsHandler();
if (__DEV__) {
if (!handler) {
JX.$E('JX.Draggable.findItems(): No findItemsHandler set!');
}
}
return handler();
},
_ondrag : function(e) {
if (this._dragging) {
// Don't start dragging if we're already dragging something.
return;
}
if (this._locked) {
// Don't start drag operations while locked.
return;
}
if (!e.isNormalMouseEvent()) {
// Don't start dragging for shift click, right click, etc.
return;
}
if (this.invoke('shouldBeginDrag', e).getPrevented()) {
return;
}
e.kill();
this._dragging = e.getNode(this._sigil);
this._origin = JX.$V(e);
this._dimensions = JX.$V(this._dragging);
var targets = [];
var items = this.findItems();
for (var ii = 0; ii < items.length; ii++) {
targets.push({
item: items[ii],
y: JX.$V(items[ii]).y + (JX.Vector.getDim(items[ii]).y / 2)
});
}
targets.sort(function(u, v) { return v.y - u.y; });
this._targets = targets;
this._target = false;
if (!this.invoke('didBeginDrag', this._dragging).getPrevented()) {
var ghost = this.getGhostNode();
ghost.style.height = JX.Vector.getDim(this._dragging).y + 'px';
JX.DOM.alterClass(this._dragging, 'drag-dragging', true);
}
},
_onmove : function(e) {
if (!this._dragging) {
return;
}
var ghost = this.getGhostNode();
var target = this._target;
var targets = this._targets;
var dragging = this._dragging;
var origin = this._origin;
var p = JX.$V(e);
// Compute the size and position of the drop target indicator, because we
// need to update our static position computations to account for it.
var adjust_h = JX.Vector.getDim(ghost).y;
var adjust_y = JX.$V(ghost).y;
// Find the node we're dragging the object underneath. This is the first
// node in the list that's above the cursor. If that node is the node
// we're dragging or its predecessor, don't select a target, because the
// operation would be a no-op.
// NOTE: When we're dragging into the first position in the list, we
// use the target `null`. When we don't have a valid target, we use
// the target `false`. Spooky! Magic! Anyway, `null` and `false` mean
// completely different things.
var cur_target = null;
var trigger;
for (var ii = 0; ii < targets.length; ii++) {
// If the drop target indicator is above the target, we need to adjust
// the target's trigger height down accordingly. This makes dragging
// items down the list smoother, because the target doesn't jump to the
// next item while the cursor is over it.
trigger = targets[ii].y;
if (adjust_y <= trigger) {
trigger += adjust_h;
}
// If the cursor is above this target, we aren't dropping underneath it.
if (trigger >= p.y) {
continue;
}
// Don't choose the dragged row or its predecessor as targets.
cur_target = targets[ii].item;
if (cur_target == dragging) {
cur_target = false;
}
if (targets[ii - 1] && targets[ii - 1].item == dragging) {
cur_target = false;
}
break;
}
// If we've selected a new target, update the UI to show where we're
// going to drop the row.
if (cur_target != target) {
if (target) {
JX.DOM.remove(ghost);
}
if (cur_target !== false) {
var ok = this.getGhostHandler()(ghost, cur_target);
// If the handler returns explicit `false`, prevent the drag.
if (ok === false) {
cur_target = false;
}
}
target = cur_target;
if (target !== false) {
// If we've changed where the ghost node is, update the adjustments
// so we accurately reflect document state when we tweak things below.
// This avoids a flash of bad state as the mouse is dragged upward
// across the document.
adjust_h = JX.Vector.getDim(ghost).y;
adjust_y = JX.$V(ghost).y;
}
}
// If the drop target indicator is above the cursor in the document,
// adjust the cursor position for the change in node document position.
// Do this before choosing a new target to avoid a flash of nonsense.
if (target !== false) {
if (adjust_y <= origin.y) {
p.y -= adjust_h;
}
}
p.x = 0;
p.y -= origin.y;
p.setPos(dragging);
this._target = target;
e.kill();
},
_ondrop : function(e) {
if (!this._dragging) {
return;
}
var target = this._target;
var dragging = this._dragging;
var ghost = this.getGhostNode();
this._dragging = null;
JX.$V(0, 0).setPos(dragging);
if (target !== false) {
JX.DOM.remove(dragging);
JX.DOM.replace(ghost, dragging);
this.invoke('didDrop', dragging, target);
} else {
this.invoke('didCancelDrag', dragging);
}
if (!this.invoke('didEndDrag', dragging).getPrevented()) {
JX.DOM.alterClass(dragging, 'drag-dragging', false);
}
e.kill();
},
lock : function() {
this._locked++;
if (this._locked === 1) {
this.invoke('didLock');
}
return this;
},
unlock : function() {
if (__DEV__) {
if (!this._locked) {
JX.$E("JX.Draggable.unlock(): Draggable is not locked!");
}
}
this._locked--;
if (!this._locked) {
this.invoke('didUnlock');
}
return this;
}
}
});

File Metadata

Mime Type
text/plain
Expires
Tue, Feb 24, 7:46 PM (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
847674
Default Alt Text
DraggableList.js (7 KB)

Event Timeline