text: /*
   * 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.
   */
   if (typeof django == "undefined") {
       var django = {
           "jQuery": $.noConflict()
       };
   };
  (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 = $("<tr class=\"ghost_row\"><td colspan=\""+cell_count+"\"><span class=\"ghost_text\">Drop here to reorder</span></td></tr>");
        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.prepend('<span class="expander-9"></span>');
          cell.find("span.expander").remove();
          return;
        }    
  
        cell.prepend('<span class="expander"></span>');
        
        $(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);
  