forked from expo/troggle
384 lines
13 KiB
JavaScript
384 lines
13 KiB
JavaScript
|
/* jQuery treeTable Plugin 2.2 - http://ludo.cubicphuse.nl/jquery-plugins/treeTable/ */
|
||
|
(function($) {
|
||
|
// Helps to make options available to all functions
|
||
|
// TODO: This gives problems when there are both expandable and non-expandable
|
||
|
// trees on a page. The options shouldn't be global to all these instances!
|
||
|
var options;
|
||
|
|
||
|
$.fn.treeTable = function(opts) {
|
||
|
options = $.extend({}, $.fn.treeTable.defaults, opts);
|
||
|
|
||
|
return this.each(function() {
|
||
|
$(this).addClass("treeTable").find("tbody tr").each(function() {
|
||
|
// Initialize root nodes only whenever possible
|
||
|
if(!options.expandable || $(this)[0].className.search("child-of-") == -1) {
|
||
|
initialize($(this));
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
|
||
|
$.fn.treeTable.defaults = {
|
||
|
childPrefix: "child-of-",
|
||
|
expandable: true,
|
||
|
indent: 19,
|
||
|
initialState: "collapsed",
|
||
|
treeColumn: 0
|
||
|
};
|
||
|
|
||
|
// Recursively hide all node's children in a tree
|
||
|
$.fn.collapse = function() {
|
||
|
$(this).addClass("collapsed");
|
||
|
|
||
|
childrenOf($(this)).each(function() {
|
||
|
initialize($(this));
|
||
|
|
||
|
if(!$(this).hasClass("collapsed")) {
|
||
|
$(this).collapse();
|
||
|
}
|
||
|
|
||
|
$(this).hide();
|
||
|
});
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
// Recursively show all node's children in a tree
|
||
|
$.fn.expand = function() {
|
||
|
$(this).removeClass("collapsed").addClass("expanded");
|
||
|
|
||
|
childrenOf($(this)).each(function() {
|
||
|
initialize($(this));
|
||
|
|
||
|
if($(this).is(".expanded.parent")) {
|
||
|
$(this).expand();
|
||
|
}
|
||
|
|
||
|
$(this).show();
|
||
|
});
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
// Add an entire branch to +destination+
|
||
|
$.fn.appendBranchTo = function(destination) {
|
||
|
var node = $(this);
|
||
|
var parent = parentOf(node);
|
||
|
|
||
|
var ancestorNames = $.map(ancestorsOf(destination), function(a) { return a.id; });
|
||
|
|
||
|
// Conditions:
|
||
|
// 1: +node+ should not be inserted in a location in a branch if this would
|
||
|
// result in +node+ being an ancestor of itself.
|
||
|
// 2: +node+ should not have a parent OR the destination should not be the
|
||
|
// same as +node+'s current parent (this last condition prevents +node+
|
||
|
// from being moved to the same location where it already is).
|
||
|
// 3: +node+ should not be inserted as a child of +node+ itself.
|
||
|
if($.inArray(node[0].id, ancestorNames) == -1 && (!parent || (destination.attr("id") != parent[0].id)) && destination.attr("id") != node[0].id) {
|
||
|
indent(node, ancestorsOf(node).length * options.indent * -1); // Remove indentation
|
||
|
|
||
|
if(parent) { node.removeClass(options.childPrefix + parent[0].id); }
|
||
|
|
||
|
var dest_id = $(destination).attr("id");
|
||
|
while ($(".child-of-"+dest_id).length > 0) {
|
||
|
var move_to = $(".child-of-"+dest_id+":last");
|
||
|
dest_id = move_to.attr("id");
|
||
|
}
|
||
|
|
||
|
node.addClass(options.childPrefix + destination.attr("id"));
|
||
|
if (move_to)
|
||
|
moveChild(node, move_to); // Recursively move nodes to new location
|
||
|
else
|
||
|
moveChild(node, destination);
|
||
|
indent(node, ancestorsOf(node).length * options.indent);
|
||
|
}
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
$.fn.insertBranchBefore = function(destination) {
|
||
|
var node = $(this);
|
||
|
var parent = parentOf_jQuery(node);
|
||
|
var dest_parent = parentOf_jQuery(destination);
|
||
|
|
||
|
if ($(this).attr("id")==destination.attr("id"))
|
||
|
return false;
|
||
|
|
||
|
var ancestorNames = $.map(ancestorsOf_jQuery(destination), function(a) { return a.id; });
|
||
|
|
||
|
indent(node, ancestorsOf_jQuery(node).length * options.indent * -1); // Remove indentation
|
||
|
|
||
|
if(parent) { node.removeClass(options.childPrefix + parent[0].id); }
|
||
|
|
||
|
if (dest_parent)
|
||
|
node.addClass(options.childPrefix + dest_parent.attr("id"));
|
||
|
|
||
|
moveBefore(node, destination); // Recursively move nodes to new location
|
||
|
indent(node, (ancestorsOf_jQuery(node).length * options.indent));
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
// Add reverse() function from JS Arrays
|
||
|
$.fn.reverse = function() {
|
||
|
return this.pushStack(this.get().reverse(), arguments);
|
||
|
};
|
||
|
|
||
|
// Toggle an entire branch
|
||
|
$.fn.toggleBranch = function() {
|
||
|
if($(this).hasClass("collapsed")) {
|
||
|
$(this).expand();
|
||
|
} else {
|
||
|
$(this).removeClass("expanded").collapse();
|
||
|
}
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
// === Private functions
|
||
|
|
||
|
function ancestorsOf(node) {
|
||
|
var ancestors = [];
|
||
|
while(node = parentOf(node)) {
|
||
|
ancestors[ancestors.length] = node[0];
|
||
|
}
|
||
|
return ancestors;
|
||
|
};
|
||
|
|
||
|
function childrenOf(node) {
|
||
|
return $("table.treeTable tbody tr." + options.childPrefix + node[0].id);
|
||
|
};
|
||
|
|
||
|
function indent(node, value) {
|
||
|
var cell = $(node.children("td")[options.treeColumn]);
|
||
|
var padding = parseInt(cell.css("padding-left"), 10) + value;
|
||
|
|
||
|
cell.css("padding-left", + padding + "px");
|
||
|
|
||
|
childrenOf(node).each(function() {
|
||
|
indent($(this), value);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
function initialize(node) {
|
||
|
if(!node.hasClass("initialized")) {
|
||
|
node.addClass("initialized");
|
||
|
|
||
|
var childNodes = childrenOf(node);
|
||
|
|
||
|
if(!node.hasClass("parent") && childNodes.length > 0) {
|
||
|
node.addClass("parent");
|
||
|
}
|
||
|
|
||
|
if(node.hasClass("parent")) {
|
||
|
var cell = $(node.children("td")[options.treeColumn]);
|
||
|
var padding = parseInt(cell.css("padding-left"), 10) + options.indent;
|
||
|
|
||
|
childNodes.each(function() {
|
||
|
$($(this).children("td")[options.treeColumn]).css("padding-left", padding + "px");
|
||
|
});
|
||
|
|
||
|
if(options.expandable) {
|
||
|
cell.children(":first").children("span").prepend('<span style="margin-left: -' + (options.indent-15) + 'px; padding-left: ' + (options.indent-5) + 'px;" class="expander"></span>');
|
||
|
//$(cell[0].firstChild).click(function() { node.toggleBranch(); });
|
||
|
|
||
|
// Check for a class set explicitly by the user, otherwise set the default class
|
||
|
if(!(node.hasClass("expanded") || node.hasClass("collapsed"))) {
|
||
|
node.addClass(options.initialState);
|
||
|
}
|
||
|
|
||
|
if(node.hasClass("collapsed")) {
|
||
|
node.collapse();
|
||
|
} else if (node.hasClass("expanded")) {
|
||
|
node.expand();
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
var cell = $(node.children("td")[options.treeColumn]);
|
||
|
cell.children(":first").children("span").prepend('<span style="margin-left: -' + (options.indent-15) + 'px; padding-left: ' + (options.indent-5) + 'px;"></span>');
|
||
|
}
|
||
|
node.children(":first").addClass("padded");
|
||
|
}
|
||
|
};
|
||
|
|
||
|
function move(node, destination) {
|
||
|
node.insertAfter(destination);
|
||
|
childrenOf(node).reverse().each(function() { move($(this), node[0]); });
|
||
|
};
|
||
|
|
||
|
function moveChild(node, destination) {
|
||
|
node.insertAfter(destination)
|
||
|
childrenOf(node).reverse().each(function() { move($(this), node[0]); });
|
||
|
|
||
|
};
|
||
|
|
||
|
function moveBefore(node, destination) {
|
||
|
node.insertBefore(destination)
|
||
|
childrenOf(node).reverse().each(function() { move($(this), node[0]); });
|
||
|
};
|
||
|
|
||
|
function parentOf(node) {
|
||
|
|
||
|
var classNames = node[0].className.split(' ');
|
||
|
|
||
|
for(key in classNames) {
|
||
|
if(classNames[key].match("child-of-")) {
|
||
|
return $("#" + classNames[key].substring(9));
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
})(jQuery);
|
||
|
|
||
|
// public functions
|
||
|
function handle_drop_event(source, dest, method){
|
||
|
var ancestorNames = $.map(ancestorsOf_jQuery(dest), function(a) { return a.attr("id"); });
|
||
|
if (method=="child")
|
||
|
dest.find(".wrap").removeClass("hover-as-child").addClass("nohover");
|
||
|
else // method == "sibling"
|
||
|
dest.find("div:first").remove();
|
||
|
// do not drop on itself or its own children, if method == "child"
|
||
|
if ( (method == "sibling") || (source.attr("id") != dest.attr("id") && $.inArray(source.attr("id"), ancestorNames) == -1) ) {
|
||
|
var source_child_of = null;
|
||
|
if (source.attr("class").match(/child-of-node-(\d+)/))
|
||
|
source_child_of = source.attr("class").match(/child-of-node-(\d+)/)[0];
|
||
|
var dest_child_of = "child-of-" + dest.attr("id");
|
||
|
if (source_child_of && $("."+source_child_of).length - 1 == 0) {
|
||
|
var parent_id = "#" + source_child_of.substring(9);
|
||
|
$(parent_id).removeClass("parent");
|
||
|
if ($(parent_id).hasClass("expanded"))
|
||
|
$(parent_id).removeClass("expanded").addClass("collapsed");
|
||
|
$(parent_id+" .title-col span").removeClass("expander");
|
||
|
}
|
||
|
if (method=="child") {
|
||
|
if ($("."+dest_child_of).length == 0) {
|
||
|
var parent_id = "#" + dest_child_of.substring(9);
|
||
|
$(parent_id).addClass("parent").find(".title-col span").addClass("expander");
|
||
|
}
|
||
|
if (!dest.hasClass("expanded"))
|
||
|
dest.expand();
|
||
|
// *** INSERT ***
|
||
|
source.appendBranchTo(dest);
|
||
|
} else // method == "sibling"
|
||
|
source.insertBranchBefore(dest);
|
||
|
}
|
||
|
source.find(".wrap").switchClass("nohover","flash",0).switchClass("flash","nohover",500);
|
||
|
}
|
||
|
|
||
|
function handle_page_delete(node) {
|
||
|
var page_id = node.attr("class").match(/page-id-(\d+)/)[1];
|
||
|
var parent_id = null;
|
||
|
if (node.attr("class").match(/child-of-node-(\d+)/))
|
||
|
parent_id = node.attr("class").match(/child-of-node-(\d+)/)[1];
|
||
|
var popup_bg = '<div class="popup_bg"></div>';
|
||
|
$("body").append(popup_bg);
|
||
|
if (node.hasClass("parent")){
|
||
|
jAlert('Cannot delete item, because it is parent of at least one other item.',
|
||
|
'Cannot delete item', function(){
|
||
|
$(".popup_bg").remove();
|
||
|
});
|
||
|
} else {
|
||
|
jConfirm('Really delete item?', 'Confirm to delete item', function(r) {
|
||
|
if (r==true) {
|
||
|
$.post('.', {'__cmd': 'delete_item', 'item_id': page_id}, function(data){
|
||
|
if (data == "OK") {
|
||
|
if (parent_id && $(".child-of-node-"+parent_id).length == 1) {
|
||
|
$("#node-"+parent_id).removeClass("parent")
|
||
|
.removeClass("expanded").addClass("collapsed")
|
||
|
.find(".expander").removeClass("expander");
|
||
|
}
|
||
|
node.remove();
|
||
|
$("body").append(popup_bg);
|
||
|
jAlert('Item deleted successfully.',
|
||
|
'Item deleted', function(){
|
||
|
$(".popup_bg").remove();
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
$(".popup_bg").remove();
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function parentOf_jQuery(node) {
|
||
|
if (node.attr("class").match(/child-of-node-(\d+)/)) {
|
||
|
var parent_id = node.attr("class").match(/child-of-node-(\d+)/)[1];
|
||
|
return $("#node-"+parent_id);
|
||
|
}
|
||
|
return null;
|
||
|
};
|
||
|
|
||
|
function ancestorsOf_jQuery(node) {
|
||
|
var ancestors = [];
|
||
|
while(node = parentOf_jQuery(node)) {
|
||
|
ancestors[ancestors.length] = node;
|
||
|
}
|
||
|
return ancestors;
|
||
|
};
|
||
|
|
||
|
function save_page_tree() {
|
||
|
var send_tree = new Array();
|
||
|
|
||
|
// prepare tree
|
||
|
var i = 0;
|
||
|
var ancestor_tree_ids = [];
|
||
|
var ancestor_indeces = [];
|
||
|
var tree_id = 0;
|
||
|
$("#sitetree tbody tr").each(function(){
|
||
|
var tobj = new Array();
|
||
|
// 0 = tree_id, 1 = parent_id, 2 = left, 3 = right, 4 = level, 5 = page_id
|
||
|
var classNames = $(this).attr("class").split(' ');
|
||
|
var is_child = false; var is_parent = false;
|
||
|
var parent_id = "";
|
||
|
tobj[1] = null;
|
||
|
// gather information
|
||
|
for (key in classNames) {
|
||
|
if(classNames[key].match("page-id-"))
|
||
|
tobj[5] = parseInt(classNames[key].substring(8));
|
||
|
if(classNames[key].match("parent"))
|
||
|
is_parent = true;
|
||
|
if(classNames[key].match("child-of-")) {
|
||
|
is_child = true;
|
||
|
var node_parent_id = classNames[key].substring(9);
|
||
|
var parent_id = parseInt($("#"+node_parent_id).attr("class").match(/page-id-(\d+)/)[1])
|
||
|
tobj[1] = parent_id;
|
||
|
}
|
||
|
}
|
||
|
// save info
|
||
|
var inArray = ancestor_tree_ids.indexOf(parent_id);
|
||
|
while ( ( is_child && inArray < ancestor_tree_ids.length - 1 && inArray >= 0) || ( !is_child && ancestor_tree_ids.length > 0 ) ) {
|
||
|
send_tree[ancestor_indeces.pop()][3] = i++;
|
||
|
ancestor_tree_ids.pop();
|
||
|
}
|
||
|
if (!is_child) {
|
||
|
tree_id++;
|
||
|
i = 0;
|
||
|
}
|
||
|
tobj[0] = tree_id;
|
||
|
tobj[4] = ancestor_tree_ids.length;
|
||
|
tobj[2] = i++;
|
||
|
if (is_parent) {
|
||
|
ancestor_tree_ids.push(tobj[5]);
|
||
|
ancestor_indeces.push(send_tree.length);
|
||
|
} else {
|
||
|
tobj[3] = i++;
|
||
|
}
|
||
|
send_tree.push(tobj);
|
||
|
});
|
||
|
while (ancestor_tree_ids.length>0) {
|
||
|
send_tree[ancestor_indeces.pop()][3] = i++;
|
||
|
ancestor_tree_ids.pop();
|
||
|
}
|
||
|
|
||
|
// send tree to url
|
||
|
$.post('.', {'__cmd': 'save_tree', 'tree': $.toJSON(send_tree)}, function(data){
|
||
|
if (data == "OK") {
|
||
|
var popup_bg = '<div class="popup_bg"></div>';
|
||
|
$("body").append(popup_bg);
|
||
|
jAlert("Tree saved successfully.", "Tree saved", function(){
|
||
|
$(".popup_bg").remove();
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
}
|