wiki.cuates.net/source/static/mptt/draggable-admin.js

412 lines
14 KiB
JavaScript

/* global django */
// IE<9 lacks Array.prototype.indexOf
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(needle) {
for (var i=0, l=this.length; i<l; ++i) {
if (this[i] === needle) return i;
}
return -1;
};
}
// https://github.com/jquery/jquery-ui/blob/master/ui/disable-selection.js
django.jQuery.fn.extend({
disableSelection: (function() {
var eventType = 'onselectstart' in document.createElement('div') ? 'selectstart' : 'mousedown';
return function() {
return this.on(eventType + '.ui-disableSelection', function(event) {
event.preventDefault();
});
};
})(),
enableSelection: function() {
return this.off('.ui-disableSelection');
}
});
django.jQuery(function($){
// We are not on a changelist it seems.
if (!document.getElementById('result_list')) return;
var DraggableMPTTAdmin = null;
function isExpandedNode(id) {
return DraggableMPTTAdmin.collapsedNodes.indexOf(id) == -1;
}
function markNodeAsExpanded(id) {
// remove itemId from array of collapsed nodes
var idx = DraggableMPTTAdmin.collapsedNodes.indexOf(id);
if(idx >= 0)
DraggableMPTTAdmin.collapsedNodes.splice(idx, 1);
}
function markNodeAsCollapsed(id) {
if(isExpandedNode(id))
DraggableMPTTAdmin.collapsedNodes.push(id);
}
function treeNode(pk) {
return $('.tree-node[data-pk="' + pk + '"]');
}
// toggle children
function doToggle(id, show) {
var children = DraggableMPTTAdmin.treeStructure[id] || [];
for (var i=0; i<children.length; ++i) {
var childId = children[i];
if(show) {
treeNode(childId).closest('tr').show();
// only reveal children if current node is not collapsed
if(isExpandedNode(childId)) {
doToggle(childId, show);
}
} else {
treeNode(childId).closest('tr').hide();
// always recursively hide children
doToggle(childId, show);
}
}
}
function rowLevel($row) {
try {
return $row.find('.tree-node').data('level') || 0;
} catch (e) {
return 0;
}
}
/*
* FeinCMS Drag-n-drop tree reordering.
* Based upon code by bright4 for Radiant CMS, rewritten for
* FeinCMS by Bjorn Post.
*
* September 2010
*/
$.extend($.fn.feinTree = function() {
$.each(DraggableMPTTAdmin.treeStructure, function(key, value) {
treeNode(key).addClass('children');
});
$('div.drag-handle').bind('mousedown', function(event) {
var BEFORE = 'before';
var AFTER = 'after';
var CHILD = 'child';
var CHILD_PAD = DraggableMPTTAdmin.levelIndent;
var originalRow = $(event.target).closest('tr');
var rowHeight = originalRow.height();
var moveTo = new Object();
var resultListWidth = $('#result_list').width();
$('body').addClass('dragging').disableSelection().bind('mousemove', function(event) {
// Remove focus
originalRow.blur();
// attach dragged item to mouse
var cloned = originalRow.html();
if($('#ghost').length == 0) {
$('<div id="ghost"></div>').appendTo('body');
}
$('#ghost').html(cloned).css({
'opacity': .8,
'position': 'absolute',
'top': event.pageY,
'left': event.pageX - 30,
'width': 600
});
// check on edge of screen
if(event.pageY+100 > $(window).height()+$(window).scrollTop()) {
$('html,body').stop().animate({scrollTop: $(window).scrollTop()+250 }, 500);
} else if(event.pageY-50 < $(window).scrollTop()) {
$('html,body').stop().animate({scrollTop: $(window).scrollTop()-250 }, 500);
}
// check if drag-line element already exists, else append
if($('#drag-line').length < 1) {
$('body').append('<div id="drag-line"><span></span></div>');
}
// loop trough all rows
$('tr', originalRow.parent()).each(function(index, el) {
var element = $(el),
top = element.offset().top,
next;
// check if mouse is over a row
if (event.pageY >= top && event.pageY < top + rowHeight) {
var targetRow = null,
targetLoc = null,
elementLevel = rowLevel(element);
if (event.pageY >= top && event.pageY < top + rowHeight / 3) {
targetRow = element;
targetLoc = BEFORE;
} else if (event.pageY >= top + rowHeight / 3 && event.pageY < top + rowHeight * 2 / 3) {
next = element.next();
// there's no point in allowing adding children when there are some already
// better move the items to the correct place right away
if (!next.length || rowLevel(next) <= elementLevel) {
targetRow = element;
targetLoc = CHILD;
}
} else if (event.pageY >= top + rowHeight * 2 / 3 && event.pageY < top + rowHeight) {
next = element.next();
if (!next.length || rowLevel(next) <= elementLevel) {
targetRow = element;
targetLoc = AFTER;
}
}
if(targetRow) {
// Positioning relative to cell containing the link
var offset = targetRow.find('th').offset();
var left = offset.left
+ rowLevel(targetRow) * CHILD_PAD
+ (targetLoc == CHILD ? CHILD_PAD : 0)
+ 5; // Center of the circle aligns with start of link text (cell padding!)
$('#drag-line').css({
'width': resultListWidth - left,
'left': left,
'top': offset.top + (targetLoc == BEFORE ? 0 : rowHeight)
}).find('span').text(DraggableMPTTAdmin.messages[targetLoc] || '');
// Store the found row and options
moveTo.hovering = element;
moveTo.relativeTo = targetRow;
moveTo.side = targetLoc;
return true;
}
}
});
});
$('body').keydown(function(event) {
if (event.which == '27') {
$('#drag-line').remove();
$('#ghost').remove();
$('body').removeClass('dragging').enableSelection().unbind('mousemove').unbind('mouseup');
event.preventDefault();
}
});
$('body').bind('mouseup', function() {
if(moveTo.relativeTo) {
var cutItem = originalRow.find('.tree-node').data('pk');
var pastedOn = moveTo.relativeTo.find('.tree-node').data('pk');
// get out early if items are the same
if(cutItem != pastedOn) {
var isParent = (
rowLevel(moveTo.relativeTo.next()) >
rowLevel(moveTo.relativeTo));
var position = '';
// determine position
if(moveTo.side == CHILD && !isParent) {
position = 'last-child';
} else if (moveTo.side == BEFORE) {
position = 'left';
} else {
position = 'right';
}
$.ajax({
complete: function() {
window.location.reload();
},
data: {
cmd: 'move_node',
position: position,
cut_item: cutItem,
pasted_on: pastedOn
},
headers: {
'X-CSRFToken': $('input[type=hidden][name=csrfmiddlewaretoken]').val()
},
method: 'POST'
});
} else {
$('#drag-line').remove();
$('#ghost').remove();
}
$('body').removeClass('dragging').enableSelection().unbind('mousemove').unbind('mouseup');
}
});
});
return this;
});
/* Every time the user expands or collapses a part of the tree, we remember
the current state of the tree so we can restore it on a reload. */
function storeCollapsedNodes(nodes) {
window.localStorage && window.localStorage.setItem(
DraggableMPTTAdmin.storageName,
JSON.stringify(nodes)
);
}
function retrieveCollapsedNodes() {
try {
return JSON.parse(window.localStorage.getItem(
DraggableMPTTAdmin.storageName
));
} catch(e) {
return null;
}
}
function expandOrCollapseNode(item) {
var show = true;
if (!item.hasClass('children'))
return;
var itemId = item.data('pk');
if (!isExpandedNode(itemId)) {
item.removeClass('closed');
markNodeAsExpanded(itemId);
} else {
item.addClass('closed');
show = false;
markNodeAsCollapsed(itemId);
}
storeCollapsedNodes(DraggableMPTTAdmin.collapsedNodes);
doToggle(itemId, show);
}
function collapseTree() {
var rlist = $("#result_list");
rlist.hide();
$('tbody tr', rlist).each(function(i, el) {
var marker = $('.tree-node', el);
if (marker.hasClass('children')) {
var itemId = marker.data('pk');
doToggle(itemId, false);
marker.addClass('closed');
markNodeAsCollapsed(itemId);
}
});
storeCollapsedNodes(DraggableMPTTAdmin.collapsedNodes);
rlist.show();
return false;
}
function expandTree() {
var rlist = $("#result_list");
rlist.hide();
$('tbody tr', rlist).each(function(i, el) {
var marker = $('.tree-node', el);
if (marker.hasClass('children')) {
var itemId = $('.tree-node', el).data('pk');
doToggle(itemId, true);
marker.removeClass('closed');
markNodeAsExpanded(itemId);
}
});
storeCollapsedNodes([]);
rlist.show();
return false;
}
var changelistTab = function(elem, event, direction) {
event.preventDefault();
elem = $(elem);
var ne = (direction > 0) ? elem.nextAll(':visible:first') : elem.prevAll(':visible:first');
if(ne) {
elem.attr('tabindex', -1);
ne.attr('tabindex', '0');
ne.focus();
}
};
function keyboardNavigationHandler(event) {
// On form element? Ignore.
if (/textarea|select|input/i.test(event.target.nodeName))
return;
// console.log('keydown', this, event.keyCode);
switch (event.keyCode) {
case 40: // down
changelistTab(this, event, 1);
break;
case 38: // up
changelistTab(this, event, -1);
break;
case 37: // left
case 39: // right
expandOrCollapseNode($(this).find('.tree-node'));
break;
case 13: // return
document.location = $('a', this).attr('href');
break;
default:
break;
}
}
function addObjectTool(title, handler) {
var $a = $('<a href/>');
$a.click(handler);
$a.text(title);
$a.prependTo('.object-tools').wrap('<li>');
}
// Some old browsers do not support JSON.parse (the only thing we require)
var jsonParse = JSON.parse || function jsonParse(sJSON) { return eval('(' + sJSON + ')'); };
DraggableMPTTAdmin = jsonParse(
document.getElementById('draggable-admin-context').getAttribute('data-context'));
addObjectTool(DraggableMPTTAdmin.messages.collapseTree, collapseTree);
addObjectTool(DraggableMPTTAdmin.messages.expandTree, expandTree);
// fire!
var rlist = $("#result_list"),
rlist_tbody = rlist.find('tbody');
if ($('tbody tr', rlist).length > 1) {
rlist_tbody.feinTree();
rlist.find('.tree-node').on('click', function(event) {
event.preventDefault();
event.stopPropagation();
expandOrCollapseNode($(this));
});
/* Enable focussing, put focus on first result, add handler for keyboard navigation */
$('tr', rlist).attr('tabindex', -1);
$('tbody tr:first', rlist).attr('tabindex', 0).focus();
$('tr', rlist).keydown(keyboardNavigationHandler);
DraggableMPTTAdmin.collapsedNodes = [];
var storedNodes = retrieveCollapsedNodes();
if (storedNodes) {
for(var i=0; i<storedNodes.length; i++) {
expandOrCollapseNode(treeNode(storedNodes[i]));
}
} else {
if (!DraggableMPTTAdmin.expandTreeByDefault) {
collapseTree();
}
}
}
});