|
|
ブラウザのブックマークのように、画面上でリストをドラッグ&ドロップで並べ替えができると便利だなと思うことがあります。そう思う人は多いので、JavaScriptのフレームワークでもそういった機能を提供しているものも多いようです。
ただ、そのためだけにあの巨大なフレームワークの虜になってしまうのも何なので、なるべくコンパクトにそういう機能が提供できたらとの思いから作ってみました。結果としては、使い勝手を考えると他のライブラリに依存することになり、さほどコンパクトにはならなかった気がします。
[ サンプルの実行]←クリックすると下のリストをドラッグ&ドロップで並べ替えできるようになります var DnD = @INCLUDE(dnd.js);
//下のUL要素
var dnd1 = new DnD("li.draggable", "ul.droppable");
//下のA要素の並び
var dnd2 = new DnD("a.draggable", "div.droppable");
- dnd.js
/*
* dnd.js - 要素をマウスで並べ替える
* 関連ライブラリ
* queryselector.js, event.js geometry.js
* 影響を受けるプロパティ
* document.body.style.cursor
* document.onmousemove
* document.onmouseup
* document.onselectstart
* document.ondragstart
*/
function(draggable, droppable, callback) {
if (typeof callback != 'function') {
callback = function(node, prev, next) {
if (next) next.parentNode.insertBefore(node, next);
else node.parentNode.appendChild(node);
};
}
/**
* initialize draggable
*/
var $ = @INCLUDE(queryselector.js);
var fixEvent = @INCLUDE(event.js);
var geometry = new @INCLUDE(geometry.js);
var bind = function(obj, fn) {
return function() { return fn.apply(obj, arguments); }
};
var self = this;
$(draggable).each(function(index, node) {
node.ondragstart = bind(node, function(evt) {
evt = fixEvent(evt);
//window.status = window.defaultStatus = "dragstart";
if (evt.type != 'mousemove') return;
evt.stop();
this.onmousemove = null;
this.droppables = self.prepareDroppable(droppable);
var position = geometry.getGeometry(this);
var shadow = this.shadow = this.parentNode.appendChild(this.cloneNode(true));
shadow.offX = evt.offsetX;
shadow.offY = evt.offsetY;
shadow.style.left = (position.left + 16) + 'px';
shadow.style.top = (position.top + 16) + 'px';
shadow.style.position = "absolute";
shadow.onmouseup = null;
document.body.style.cursor = 'no-drop';
document.onmousemove = bind(this, function(evt) {
evt = fixEvent(evt);
//window.status = window.defaultStatus = "drag[" + evt.pageX + "," + evt.pageY + "]";
this.shadow.style.left = (evt.pageX + 16) + "px";
this.shadow.style.top = (evt.pageY + 16) + "px";
});
document.onmouseup = this.ondragend;
return false;
});
node.ondragend = bind(node, function(evt) {
evt = fixEvent(evt);
if (evt.type != 'mouseup' || evt.which != 1) return true;
evt.stop();
//window.status = window.defaultStatus = "dragend" + this.shadow.nodeName;
this.shadow.parentNode.removeChild(this.shadow);
for (var i = 0; i < this.droppables.length; i++) {
var d = this.droppables[i];
if (d.selected && typeof callback == 'function') {
try {
callback(this, d.previousSibling, d.nextSibling);
} catch (ex){ alert(ex); }
}
d.parentNode.removeChild(d);
}
document.body.style.cursor = '';
document.onmousemove = null;
document.onmouseup = null;
});
node.onmousedown = bind(node, function(evt) {
evt = fixEvent(evt);
if (evt.type != 'mousedown' || evt.which != 1) return true;
evt.stop();
document.onmousemove = this.ondragstart;
document.onselectstart = function(evt) { evt = fixEvent(evt); evt.stop(); };
document.ondragstart = function(evt) { evt = fixEvent(evt); evt.stop(); };
});
node.onmouseup = bind(node, function(evt) {
document.onmousemove = null;
});
});
/**
*
*/
this.finish = function(){};
/**
* initialize droppable
*/
this.prepareDroppable = function(selector) {
var $ = @INCLUDE(queryselector.js);
var handler = @INCLUDE(event.js);
var result = new Array();
var self = this;
$(selector).each(function(index, droppable) {
var target;
for (var i = droppable.childNodes.length - 1; i >= 0; i--) {
if (droppable.childNodes.item(i).nodeType != 1) continue;
target = self.createDropTarget(droppable, droppable.childNodes.item(i));
result.push(target);
}
target = self.createDropTarget(droppable);
result.push(target);
});
return result;
};
this.createDropTarget = function(droppable, child) {
var target = document.createElement('span');
target.className = target.styleClass = 'droptarget';
target.style.width = '0px';
target.style.height = '0px';
var bar = target.bar = target.appendChild(document.createElement('span'));
bar.innerHTML = ' ';
if (child) droppable.insertBefore(target, child);
else droppable.appendChild(target);
target.selected = false;
if (document.all) { //IE
target.style.backgroundColor = '#FFFFFF';
target.style.filter = 'alpha(opacity=1)';
}
target.onmouseover = bind(target, function(evt) {
document.body.style.cursor = 'default';
this.style.filter = '';
this.bar.style.display = '';
this.selected = true;
});
target.onmouseout = bind(target, function(evt) {
document.body.style.cursor = 'no-drop';
this.style.filter = 'alpha(opacity=1)';
this.bar.style.display = 'none';
this.selected = false;
});
var prev = target.previousSibling;
while (prev && prev.nodeType != 1) prev = prev.previousSibling;
var next = target.nextSibling;
while (next && next.nodeType != 1) next = next.previousSibling;
if ((' ' + (droppable.className || droppable.styleClass) + ' ').match(/ vertical /)) {
target.style.width = droppable.offsetWidth + 'px';
target.style.height = '1em';
target.style.marginTop = '-0.5em';
target.style.position = 'absolute';
bar.style.width = droppable.offsetWidth + 'px';
bar.style.height = '0';
bar.style.marginTop = '0.5em';
bar.style.borderTop = '1px dotted black';
bar.style.display = 'none';
bar.style.cssFloat = bar.style.styleFloat = 'left';
bar.style.lineHeight = 0;
} else {
target.style.width = '1em';
target.style.height = (prev ? prev.offsetHeight : next.offsetHeight) + 'px';
target.style.marginLeft = '-0.5em';
target.style.position = 'absolute';
bar.style.width = '0px';
bar.style.height = '100%';
bar.style.marginLeft = '0.5em';
bar.style.borderLeft = '1px dotted black';
bar.style.display = 'none';
}
return target;
};
}
|
|
|