diff --git a/editor/media/editor/jquery.treeTable.css b/editor/media/editor/jquery.treeTable.css new file mode 100644 index 0000000..f9fccd9 --- /dev/null +++ b/editor/media/editor/jquery.treeTable.css @@ -0,0 +1,65 @@ +/* jQuery TreeTable Core 2.0 stylesheet + * + * This file contains styles that are used to display the tree table. Each tree + * table is assigned the +treeTable+ class. + * ========================================================================= */ + +/* jquery.treeTable.collapsible + * ------------------------------------------------------------------------- */ +.treeTable tr td .expander { + background-position: left center; + background-repeat: no-repeat; + cursor: pointer; + padding: 0; + zoom: 1; /* IE7 Hack */ +} + +.treeTable tr.collapsed td .expander { + background-image: url(toggle-expand-dark.png); +} + +.treeTable tr.expanded td .expander { + background-image: url(toggle-collapse-dark.png); +} + +#changelist table.treeTable td.disclosure { + font-size: 12px; + text-align: left; + font-weight: bold; + padding-left: 19px; +} +#changelist table.treeTable td.disclosure input[type="checkbox"] { + margin-right: 10px; +} + +/* jquery.treeTable.sortable + * ------------------------------------------------------------------------- */ +.treeTable tr.selected, .treeTable tr.accept { + background-color: #3875d7; + color: #fff; +} + +.treeTable .ui-draggable-dragging { + border-width: 0; +} + +.treeTable tr.selected, .treeTable tr.ghost_row td { + padding : 5px; + background-color : #ccc; + border : dotted 1px #eee; + font-weight : bold; + text-align : center; +} + +.treeTable tr.collapsed.selected td .expander, .treeTable tr.collapsed.accept td .expander { + background-image: url(toggle-expand-light.png); +} + +.treeTable tr.expanded.selected td .expander, .treeTable tr.expanded.accept td .expander { + background-image: url(toggle-collapse-light.png); +} + +.treeTable .ui-draggable-dragging { + color: #000; + z-index: 1; +} \ No newline at end of file diff --git a/editor/media/editor/toggle-collapse-dark.png b/editor/media/editor/toggle-collapse-dark.png new file mode 100644 index 0000000..76577a5 Binary files /dev/null and b/editor/media/editor/toggle-collapse-dark.png differ diff --git a/editor/media/editor/toggle-collapse-light.png b/editor/media/editor/toggle-collapse-light.png new file mode 100644 index 0000000..ed1612f Binary files /dev/null and b/editor/media/editor/toggle-collapse-light.png differ diff --git a/editor/media/editor/toggle-expand-dark.png b/editor/media/editor/toggle-expand-dark.png new file mode 100644 index 0000000..cfb42a4 Binary files /dev/null and b/editor/media/editor/toggle-expand-dark.png differ diff --git a/editor/media/editor/toggle-expand-light.png b/editor/media/editor/toggle-expand-light.png new file mode 100644 index 0000000..27b5234 Binary files /dev/null and b/editor/media/editor/toggle-expand-light.png differ diff --git a/editor/templates/admin/editor/tree_list_results.html b/editor/templates/admin/editor/tree_list_results.html new file mode 100644 index 0000000..703bf45 --- /dev/null +++ b/editor/templates/admin/editor/tree_list_results.html @@ -0,0 +1,24 @@ +{% if result_hidden_fields %} +
{# DIV for HTML validation #} +{% for item in result_hidden_fields %}{{ item }}{% endfor %} +
+{% endif %} +{% if results %} +
+ + + +{% for header in result_headers %} +{% if header.sortable %}{% endif %} +{{ header.text|capfirst }} +{% if header.sortable %}{% endif %}{% endfor %} + + + +{% for result in results %} +{% for item in result %}{{ item }}{% endfor %} +{% endfor %} + +
+
+{% endif %} diff --git a/example/static/editor/jquery.treeTable.css b/example/static/editor/jquery.treeTable.css new file mode 100644 index 0000000..f9fccd9 --- /dev/null +++ b/example/static/editor/jquery.treeTable.css @@ -0,0 +1,65 @@ +/* jQuery TreeTable Core 2.0 stylesheet + * + * This file contains styles that are used to display the tree table. Each tree + * table is assigned the +treeTable+ class. + * ========================================================================= */ + +/* jquery.treeTable.collapsible + * ------------------------------------------------------------------------- */ +.treeTable tr td .expander { + background-position: left center; + background-repeat: no-repeat; + cursor: pointer; + padding: 0; + zoom: 1; /* IE7 Hack */ +} + +.treeTable tr.collapsed td .expander { + background-image: url(toggle-expand-dark.png); +} + +.treeTable tr.expanded td .expander { + background-image: url(toggle-collapse-dark.png); +} + +#changelist table.treeTable td.disclosure { + font-size: 12px; + text-align: left; + font-weight: bold; + padding-left: 19px; +} +#changelist table.treeTable td.disclosure input[type="checkbox"] { + margin-right: 10px; +} + +/* jquery.treeTable.sortable + * ------------------------------------------------------------------------- */ +.treeTable tr.selected, .treeTable tr.accept { + background-color: #3875d7; + color: #fff; +} + +.treeTable .ui-draggable-dragging { + border-width: 0; +} + +.treeTable tr.selected, .treeTable tr.ghost_row td { + padding : 5px; + background-color : #ccc; + border : dotted 1px #eee; + font-weight : bold; + text-align : center; +} + +.treeTable tr.collapsed.selected td .expander, .treeTable tr.collapsed.accept td .expander { + background-image: url(toggle-expand-light.png); +} + +.treeTable tr.expanded.selected td .expander, .treeTable tr.expanded.accept td .expander { + background-image: url(toggle-collapse-light.png); +} + +.treeTable .ui-draggable-dragging { + color: #000; + z-index: 1; +} \ No newline at end of file diff --git a/example/static/editor/jquery.treeTable.js b/example/static/editor/jquery.treeTable.js new file mode 100644 index 0000000..522b2c5 --- /dev/null +++ b/example/static/editor/jquery.treeTable.js @@ -0,0 +1,458 @@ +/* + * jQuery treeTable Plugin 2.3.0 + * http://ludo.cubicphuse.nl/jquery-plugins/treeTable/ + * + * Copyright 2010, Ludo van den Boom + * Dual licensed under the MIT or GPL Version 2 licenses. + */ +(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; + var defaultPaddingLeft; + var ghost_row; + + $.fn.treeTable = function(opts) { + options = $.extend({}, $.fn.treeTable.defaults, opts); + + var _return = this.each(function() { + $(this).addClass("treeTable").find("tbody tr").each(function() { + // Initialize root nodes only if possible + if(!options.expandable || $(this)[0].className.search(options.childPrefix) == -1) { + // To optimize performance of indentation, I retrieve the padding-left + // value of the first root node. This way I only have to call +css+ + // once. + if (isNaN(defaultPaddingLeft)) { + defaultPaddingLeft = parseInt($($(this).children("td")[options.treeColumn]).css('padding-left'), 10); + } + + initialize($(this)); + } else if(options.initialState == "collapsed") { + this.style.display = "none"; // Performance! $(this).hide() is slow... + } + }); + }); + + if(options.draggable) + initDragDrop($(this)); + + return _return; + + }; + + $.fn.treeTable.defaults = { + childPrefix: "child-of-", + clickableNodeNames: false, + expandable: true, + indent: 19, + initialState: "collapsed", + treeColumn: 0, + draggable: false, + dragTarget: "tbody tr td.drag_handle", + dropTarget: "tbody tr", + dropCallback : function(node, newParent, e, ui){ }, + sortable: false, + sortableDropCallback : function(node, newPrevious, e, ui){ }, + branchMovedAsFirstChild : function(node, parent){ }, + branchMovedAsPrevSibling : function(node, nextSibling){ }, + branchMovedAsNextSibling : function(node, prevSibling){ } + }; + + // 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.style.display = "none"; // Performance! $(this).hide() is slow... + }); + recolor_lines(); + 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(); + }); + recolor_lines(); + return this; + }; + + // Reveal a node by expanding all ancestors + $.fn.reveal = function() { + $(ancestorsOf($(this)).reverse()).each(function() { + initialize($(this)); + $(this).expand().show(); + }); + recolor_lines(); + return this; + }; + + // Add an entire branch to +destination+ + $.fn.appendBranchTo = function(destination) { + var node = $(this); + var parent = parentOf(node); + + destination = $(destination); + 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); + add_expandable_widget($(parent)); + } + + node.addClass(options.childPrefix + destination.attr('id')); + move(node, destination); // Recursively move nodes to new location + indent(node, ancestorsOf(node).length * options.indent); + + add_expandable_widget(destination); + + options.branchMovedAsFirstChild(node, destination); + + } + + return this; + }; + + // Reshuffle an entire branch behind another one + $.fn.moveBranchBefore = function(destination) { + var node = $(this); + + // Move the node + node.insertBefore(destination); + childrenOf(node).reverse().each(function() { + move($(this), node[0]); + }); + + options.branchMovedAsPrevSibling(node, destination); + + return this; + + }; + + // Reshuffle an entire branch in front of another one + $.fn.moveBranchAfter = function(destination) { + var node = $(this); + + // Move the node + if(childrenOf(destination).length > 0) { + node.insertAfter(lastChildOf(destination)); + } else { + node.insertAfter(destination); + } + + childrenOf(node).reverse().each(function() { + move($(this), node[0]); + }); + + options.branchMovedAsNextSibling(node, destination); + + 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; + }; + + // Get the parent of the node + $.fn.parentOf = function() { + var classNames = $(this)[0].className.split(' '); + + for(key in classNames) { + if(classNames[key].match(options.childPrefix)) { + return $("#" + classNames[key].substring(9)); + } + } + }; + + + + var cur_level = 0; + + // This will serialise a table (given a selector) and return a string + // for passing to some sort of asyncronous request + $.fn.serializeTreeTable = function() { + var table_id = this[0].id; + var serialised_contents = []; + cur_level = 0; + + $(this).find("tbody tr").each(function() { + if($(this)[0].className.search("child-of-") == -1 && $(this)[0].className.search("ghost_row") == -1) { + serializeChildren(table_id, $(this), serialised_contents, ""); + cur_level++; + } + }); + + return serialised_contents.join("&"); + + }; + + function serializeChildren(table_id, parent, serialed, cur_child_string) { + + serialed.push(table_id+"["+cur_level+"]"+cur_child_string+"[id]="+parent[0].id); + var kids = $(parent).nextAll("tr.child-of-" + parent[0].id); + + cur_child_string += "[children]"; + + if(kids.length > 0) + { + var kid_level = 0; + kids.each(function() { + serializeChildren(table_id, $(this), serialed, cur_child_string + "["+kid_level+"]"); + kid_level++; + }); + } + } + + + // === 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 lastChildOf(node) { + return $("table.treeTable tbody tr." + options.childPrefix + node[0].id+":last"); + } + + function getPaddingLeft(node) { + var paddingLeft = parseInt(node[0].style.paddingLeft, 10); + return (isNaN(paddingLeft)) ? defaultPaddingLeft : paddingLeft; + } + + function indent(node, value) { + var cell = $(node.children("td")[options.treeColumn]); + cell[0].style.paddingLeft = getPaddingLeft(cell) + value + "px"; + + childrenOf(node).each(function() { + indent($(this), value); + }); + } + + function recolor_lines() { + $('tbody tr').removeClass('row1').removeClass('row2'); + $('tbody tr:visible:even').addClass('row1'); + $('tbody tr:visible:odd').addClass('row2'); + }; + + 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 = getPaddingLeft(cell) + options.indent; + + childNodes.each(function() { + $(this).children("td")[options.treeColumn].style.paddingLeft = padding + "px"; + }); + + add_expandable_widget(node); + + } + recolor_lines(); + } + } + + + function initDragDrop(table) { + + // Configure draggable nodes + $(options.dragTarget).draggable({ + helper: function () { + var helper = $(this).clone(); + helper.find('span.expander').remove(); + return helper; + }, + opacity: 0.75, + refreshPositions: true, // Performance? + revert: "invalid", + revertDuration: 300, + scroll: true + }); + + // If we want a sortable one then we need to create a ghosted row + if(options.sortable) + { + var cell_count = $(options.dropTarget).parents("tr").find("td").length; + ghost_row = $("Drop here to reorder"); + ghost_row.insertBefore($(table).find("tr:first")); + ghost_row.hide(); + } + + // Configure droppable nodes + $(options.dropTarget).droppable({ + accept: options.dragTarget, + drop: function(e, ui) { + + if(options.sortable) + ghost_row.hide(); + + var node = $($(ui.draggable).parents("tr")); + var newParent = $(this); + if (! newParent.is('tr')) { + newParent = newParent.parents('tr'); + } + // Move the branch when we drop on it + node.appendBranchTo(newParent); + + // Custom callback + options.dropCallback(node, newParent, e, ui); + + }, + hoverClass: "accept", + over: function(e, ui) { + + // Deal with displaying the ghost row when sorting + if(options.sortable) + { + var my_par = $(this).parentOf(); + var draggy_par = $(ui.draggable.parents("tr")).parentOf(); + + if(my_par != undefined && draggy_par != undefined) + { + if(my_par[0].id == draggy_par[0].id && $(this)[0].id != $(ui.draggable.parents("tr"))[0].id) + { + ghost_row.insertAfter(this); + ghost_row.show(); + } + else + $(ghost_row).hide(); + } + + } + + if(this.id != ui.draggable.parents("tr")[0].id && !$(this).is(".expanded")) + $(this).expand(); + + } + }); + + // Sort out resorting if we have it enabled + if(options.sortable) + { + + $("tr td span.ghost_text").each(function() { + $(this).parents("tr").droppable( { + accept: options.dragTarget, + drop: function(e, ui) { + ghost_row.hide(); + var node = $(ui.draggable.parents("tr")); + var newPrevious = $(this).prev("tr"); + node.moveBranchAfter(newPrevious); + + // Custom callback + options.sortableDropCallback(node, newPrevious, e, ui); + + } + }); + }); + + } + + } + + // Add expandable button to a node + function add_expandable_widget(node) + { + + if(options.expandable) { + + var cell = $(node.children("td")[options.treeColumn]); + + if(childrenOf(node).length == 0) + { + cell.find("span.expander").remove(); + return; + } + cell.prepend(''); + $(cell[0].firstChild).click(function() { + node.toggleBranch(); + }); + + if(options.clickableNodeNames) { + cell[0].style.cursor = "pointer"; + $(cell).click(function(e) { + // Don't double-toggle if the click is on the existing expander icon + if (e.target.className != 'expander') { + 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("expanded")) { + node.expand(); + } + } + } + + function move(node, destination) { + node.insertAfter(destination); + childrenOf(node).reverse().each(function() { + move($(this), node[0]); + }); + } + + function parentOf(node) { + return $(node).parentOf(); + } +})(django.jQuery); \ No newline at end of file diff --git a/example/static/editor/toggle-collapse-dark.png b/example/static/editor/toggle-collapse-dark.png new file mode 100644 index 0000000..76577a5 Binary files /dev/null and b/example/static/editor/toggle-collapse-dark.png differ diff --git a/example/static/editor/toggle-collapse-light.png b/example/static/editor/toggle-collapse-light.png new file mode 100644 index 0000000..ed1612f Binary files /dev/null and b/example/static/editor/toggle-collapse-light.png differ diff --git a/example/static/editor/toggle-expand-dark.png b/example/static/editor/toggle-expand-dark.png new file mode 100644 index 0000000..cfb42a4 Binary files /dev/null and b/example/static/editor/toggle-expand-dark.png differ diff --git a/example/static/editor/toggle-expand-light.png b/example/static/editor/toggle-expand-light.png new file mode 100644 index 0000000..27b5234 Binary files /dev/null and b/example/static/editor/toggle-expand-light.png differ