1*eb8dc403SDave Cobbley/*
2*eb8dc403SDave Cobbley * jQuery treetable Plugin 3.1.0
3*eb8dc403SDave Cobbley * http://ludo.cubicphuse.nl/jquery-treetable
4*eb8dc403SDave Cobbley *
5*eb8dc403SDave Cobbley * Copyright 2013, Ludo van den Boom
6*eb8dc403SDave Cobbley * Dual licensed under the MIT or GPL Version 2 licenses.
7*eb8dc403SDave Cobbley */
8*eb8dc403SDave Cobbley(function() {
9*eb8dc403SDave Cobbley  var $, Node, Tree, methods;
10*eb8dc403SDave Cobbley
11*eb8dc403SDave Cobbley  $ = jQuery;
12*eb8dc403SDave Cobbley
13*eb8dc403SDave Cobbley  Node = (function() {
14*eb8dc403SDave Cobbley    function Node(row, tree, settings) {
15*eb8dc403SDave Cobbley      var parentId;
16*eb8dc403SDave Cobbley
17*eb8dc403SDave Cobbley      this.row = row;
18*eb8dc403SDave Cobbley      this.tree = tree;
19*eb8dc403SDave Cobbley      this.settings = settings;
20*eb8dc403SDave Cobbley
21*eb8dc403SDave Cobbley      // TODO Ensure id/parentId is always a string (not int)
22*eb8dc403SDave Cobbley      this.id = this.row.data(this.settings.nodeIdAttr);
23*eb8dc403SDave Cobbley
24*eb8dc403SDave Cobbley      // TODO Move this to a setParentId function?
25*eb8dc403SDave Cobbley      parentId = this.row.data(this.settings.parentIdAttr);
26*eb8dc403SDave Cobbley      if (parentId != null && parentId !== "") {
27*eb8dc403SDave Cobbley        this.parentId = parentId;
28*eb8dc403SDave Cobbley      }
29*eb8dc403SDave Cobbley
30*eb8dc403SDave Cobbley      this.treeCell = $(this.row.children(this.settings.columnElType)[this.settings.column]);
31*eb8dc403SDave Cobbley      this.expander = $(this.settings.expanderTemplate);
32*eb8dc403SDave Cobbley      this.indenter = $(this.settings.indenterTemplate);
33*eb8dc403SDave Cobbley      this.children = [];
34*eb8dc403SDave Cobbley      this.initialized = false;
35*eb8dc403SDave Cobbley      this.treeCell.prepend(this.indenter);
36*eb8dc403SDave Cobbley    }
37*eb8dc403SDave Cobbley
38*eb8dc403SDave Cobbley    Node.prototype.addChild = function(child) {
39*eb8dc403SDave Cobbley      return this.children.push(child);
40*eb8dc403SDave Cobbley    };
41*eb8dc403SDave Cobbley
42*eb8dc403SDave Cobbley    Node.prototype.ancestors = function() {
43*eb8dc403SDave Cobbley      var ancestors, node;
44*eb8dc403SDave Cobbley      node = this;
45*eb8dc403SDave Cobbley      ancestors = [];
46*eb8dc403SDave Cobbley      while (node = node.parentNode()) {
47*eb8dc403SDave Cobbley        ancestors.push(node);
48*eb8dc403SDave Cobbley      }
49*eb8dc403SDave Cobbley      return ancestors;
50*eb8dc403SDave Cobbley    };
51*eb8dc403SDave Cobbley
52*eb8dc403SDave Cobbley    Node.prototype.collapse = function() {
53*eb8dc403SDave Cobbley      if (this.collapsed()) {
54*eb8dc403SDave Cobbley        return this;
55*eb8dc403SDave Cobbley      }
56*eb8dc403SDave Cobbley
57*eb8dc403SDave Cobbley      this.row.removeClass("expanded").addClass("collapsed");
58*eb8dc403SDave Cobbley
59*eb8dc403SDave Cobbley      this._hideChildren();
60*eb8dc403SDave Cobbley      this.expander.attr("title", this.settings.stringExpand);
61*eb8dc403SDave Cobbley
62*eb8dc403SDave Cobbley      if (this.initialized && this.settings.onNodeCollapse != null) {
63*eb8dc403SDave Cobbley        this.settings.onNodeCollapse.apply(this);
64*eb8dc403SDave Cobbley      }
65*eb8dc403SDave Cobbley
66*eb8dc403SDave Cobbley      return this;
67*eb8dc403SDave Cobbley    };
68*eb8dc403SDave Cobbley
69*eb8dc403SDave Cobbley    Node.prototype.collapsed = function() {
70*eb8dc403SDave Cobbley      return this.row.hasClass("collapsed");
71*eb8dc403SDave Cobbley    };
72*eb8dc403SDave Cobbley
73*eb8dc403SDave Cobbley    // TODO destroy: remove event handlers, expander, indenter, etc.
74*eb8dc403SDave Cobbley
75*eb8dc403SDave Cobbley    Node.prototype.expand = function() {
76*eb8dc403SDave Cobbley      if (this.expanded()) {
77*eb8dc403SDave Cobbley        return this;
78*eb8dc403SDave Cobbley      }
79*eb8dc403SDave Cobbley
80*eb8dc403SDave Cobbley      this.row.removeClass("collapsed").addClass("expanded");
81*eb8dc403SDave Cobbley
82*eb8dc403SDave Cobbley      if (this.initialized && this.settings.onNodeExpand != null) {
83*eb8dc403SDave Cobbley        this.settings.onNodeExpand.apply(this);
84*eb8dc403SDave Cobbley      }
85*eb8dc403SDave Cobbley
86*eb8dc403SDave Cobbley      if ($(this.row).is(":visible")) {
87*eb8dc403SDave Cobbley        this._showChildren();
88*eb8dc403SDave Cobbley      }
89*eb8dc403SDave Cobbley
90*eb8dc403SDave Cobbley      this.expander.attr("title", this.settings.stringCollapse);
91*eb8dc403SDave Cobbley
92*eb8dc403SDave Cobbley      return this;
93*eb8dc403SDave Cobbley    };
94*eb8dc403SDave Cobbley
95*eb8dc403SDave Cobbley    Node.prototype.expanded = function() {
96*eb8dc403SDave Cobbley      return this.row.hasClass("expanded");
97*eb8dc403SDave Cobbley    };
98*eb8dc403SDave Cobbley
99*eb8dc403SDave Cobbley    Node.prototype.hide = function() {
100*eb8dc403SDave Cobbley      this._hideChildren();
101*eb8dc403SDave Cobbley      this.row.hide();
102*eb8dc403SDave Cobbley      return this;
103*eb8dc403SDave Cobbley    };
104*eb8dc403SDave Cobbley
105*eb8dc403SDave Cobbley    Node.prototype.isBranchNode = function() {
106*eb8dc403SDave Cobbley      if(this.children.length > 0 || this.row.data(this.settings.branchAttr) === true) {
107*eb8dc403SDave Cobbley        return true;
108*eb8dc403SDave Cobbley      } else {
109*eb8dc403SDave Cobbley        return false;
110*eb8dc403SDave Cobbley      }
111*eb8dc403SDave Cobbley    };
112*eb8dc403SDave Cobbley
113*eb8dc403SDave Cobbley    Node.prototype.updateBranchLeafClass = function(){
114*eb8dc403SDave Cobbley      this.row.removeClass('branch');
115*eb8dc403SDave Cobbley      this.row.removeClass('leaf');
116*eb8dc403SDave Cobbley      this.row.addClass(this.isBranchNode() ? 'branch' : 'leaf');
117*eb8dc403SDave Cobbley    };
118*eb8dc403SDave Cobbley
119*eb8dc403SDave Cobbley    Node.prototype.level = function() {
120*eb8dc403SDave Cobbley      return this.ancestors().length;
121*eb8dc403SDave Cobbley    };
122*eb8dc403SDave Cobbley
123*eb8dc403SDave Cobbley    Node.prototype.parentNode = function() {
124*eb8dc403SDave Cobbley      if (this.parentId != null) {
125*eb8dc403SDave Cobbley        return this.tree[this.parentId];
126*eb8dc403SDave Cobbley      } else {
127*eb8dc403SDave Cobbley        return null;
128*eb8dc403SDave Cobbley      }
129*eb8dc403SDave Cobbley    };
130*eb8dc403SDave Cobbley
131*eb8dc403SDave Cobbley    Node.prototype.removeChild = function(child) {
132*eb8dc403SDave Cobbley      var i = $.inArray(child, this.children);
133*eb8dc403SDave Cobbley      return this.children.splice(i, 1)
134*eb8dc403SDave Cobbley    };
135*eb8dc403SDave Cobbley
136*eb8dc403SDave Cobbley    Node.prototype.render = function() {
137*eb8dc403SDave Cobbley      var handler,
138*eb8dc403SDave Cobbley          settings = this.settings,
139*eb8dc403SDave Cobbley          target;
140*eb8dc403SDave Cobbley
141*eb8dc403SDave Cobbley      if (settings.expandable === true && this.isBranchNode()) {
142*eb8dc403SDave Cobbley        handler = function(e) {
143*eb8dc403SDave Cobbley          $(this).parents("table").treetable("node", $(this).parents("tr").data(settings.nodeIdAttr)).toggle();
144*eb8dc403SDave Cobbley          return e.preventDefault();
145*eb8dc403SDave Cobbley        };
146*eb8dc403SDave Cobbley
147*eb8dc403SDave Cobbley        this.indenter.html(this.expander);
148*eb8dc403SDave Cobbley        target = settings.clickableNodeNames === true ? this.treeCell : this.expander;
149*eb8dc403SDave Cobbley
150*eb8dc403SDave Cobbley        target.off("click.treetable").on("click.treetable", handler);
151*eb8dc403SDave Cobbley        target.off("keydown.treetable").on("keydown.treetable", function(e) {
152*eb8dc403SDave Cobbley          if (e.keyCode == 13) {
153*eb8dc403SDave Cobbley            handler.apply(this, [e]);
154*eb8dc403SDave Cobbley          }
155*eb8dc403SDave Cobbley        });
156*eb8dc403SDave Cobbley      }
157*eb8dc403SDave Cobbley
158*eb8dc403SDave Cobbley      this.indenter[0].style.paddingLeft = "" + (this.level() * settings.indent) + "px";
159*eb8dc403SDave Cobbley
160*eb8dc403SDave Cobbley      return this;
161*eb8dc403SDave Cobbley    };
162*eb8dc403SDave Cobbley
163*eb8dc403SDave Cobbley    Node.prototype.reveal = function() {
164*eb8dc403SDave Cobbley      if (this.parentId != null) {
165*eb8dc403SDave Cobbley        this.parentNode().reveal();
166*eb8dc403SDave Cobbley      }
167*eb8dc403SDave Cobbley      return this.expand();
168*eb8dc403SDave Cobbley    };
169*eb8dc403SDave Cobbley
170*eb8dc403SDave Cobbley    Node.prototype.setParent = function(node) {
171*eb8dc403SDave Cobbley      if (this.parentId != null) {
172*eb8dc403SDave Cobbley        this.tree[this.parentId].removeChild(this);
173*eb8dc403SDave Cobbley      }
174*eb8dc403SDave Cobbley      this.parentId = node.id;
175*eb8dc403SDave Cobbley      this.row.data(this.settings.parentIdAttr, node.id);
176*eb8dc403SDave Cobbley      return node.addChild(this);
177*eb8dc403SDave Cobbley    };
178*eb8dc403SDave Cobbley
179*eb8dc403SDave Cobbley    Node.prototype.show = function() {
180*eb8dc403SDave Cobbley      if (!this.initialized) {
181*eb8dc403SDave Cobbley        this._initialize();
182*eb8dc403SDave Cobbley      }
183*eb8dc403SDave Cobbley      this.row.show();
184*eb8dc403SDave Cobbley      if (this.expanded()) {
185*eb8dc403SDave Cobbley        this._showChildren();
186*eb8dc403SDave Cobbley      }
187*eb8dc403SDave Cobbley      return this;
188*eb8dc403SDave Cobbley    };
189*eb8dc403SDave Cobbley
190*eb8dc403SDave Cobbley    Node.prototype.toggle = function() {
191*eb8dc403SDave Cobbley      if (this.expanded()) {
192*eb8dc403SDave Cobbley        this.collapse();
193*eb8dc403SDave Cobbley      } else {
194*eb8dc403SDave Cobbley        this.expand();
195*eb8dc403SDave Cobbley      }
196*eb8dc403SDave Cobbley      return this;
197*eb8dc403SDave Cobbley    };
198*eb8dc403SDave Cobbley
199*eb8dc403SDave Cobbley    Node.prototype._hideChildren = function() {
200*eb8dc403SDave Cobbley      var child, _i, _len, _ref, _results;
201*eb8dc403SDave Cobbley      _ref = this.children;
202*eb8dc403SDave Cobbley      _results = [];
203*eb8dc403SDave Cobbley      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
204*eb8dc403SDave Cobbley        child = _ref[_i];
205*eb8dc403SDave Cobbley        _results.push(child.hide());
206*eb8dc403SDave Cobbley      }
207*eb8dc403SDave Cobbley      return _results;
208*eb8dc403SDave Cobbley    };
209*eb8dc403SDave Cobbley
210*eb8dc403SDave Cobbley    Node.prototype._initialize = function() {
211*eb8dc403SDave Cobbley      var settings = this.settings;
212*eb8dc403SDave Cobbley
213*eb8dc403SDave Cobbley      this.render();
214*eb8dc403SDave Cobbley
215*eb8dc403SDave Cobbley      if (settings.expandable === true && settings.initialState === "collapsed") {
216*eb8dc403SDave Cobbley        this.collapse();
217*eb8dc403SDave Cobbley      } else {
218*eb8dc403SDave Cobbley        this.expand();
219*eb8dc403SDave Cobbley      }
220*eb8dc403SDave Cobbley
221*eb8dc403SDave Cobbley      if (settings.onNodeInitialized != null) {
222*eb8dc403SDave Cobbley        settings.onNodeInitialized.apply(this);
223*eb8dc403SDave Cobbley      }
224*eb8dc403SDave Cobbley
225*eb8dc403SDave Cobbley      return this.initialized = true;
226*eb8dc403SDave Cobbley    };
227*eb8dc403SDave Cobbley
228*eb8dc403SDave Cobbley    Node.prototype._showChildren = function() {
229*eb8dc403SDave Cobbley      var child, _i, _len, _ref, _results;
230*eb8dc403SDave Cobbley      _ref = this.children;
231*eb8dc403SDave Cobbley      _results = [];
232*eb8dc403SDave Cobbley      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
233*eb8dc403SDave Cobbley        child = _ref[_i];
234*eb8dc403SDave Cobbley        _results.push(child.show());
235*eb8dc403SDave Cobbley      }
236*eb8dc403SDave Cobbley      return _results;
237*eb8dc403SDave Cobbley    };
238*eb8dc403SDave Cobbley
239*eb8dc403SDave Cobbley    return Node;
240*eb8dc403SDave Cobbley  })();
241*eb8dc403SDave Cobbley
242*eb8dc403SDave Cobbley  Tree = (function() {
243*eb8dc403SDave Cobbley    function Tree(table, settings) {
244*eb8dc403SDave Cobbley      this.table = table;
245*eb8dc403SDave Cobbley      this.settings = settings;
246*eb8dc403SDave Cobbley      this.tree = {};
247*eb8dc403SDave Cobbley
248*eb8dc403SDave Cobbley      // Cache the nodes and roots in simple arrays for quick access/iteration
249*eb8dc403SDave Cobbley      this.nodes = [];
250*eb8dc403SDave Cobbley      this.roots = [];
251*eb8dc403SDave Cobbley    }
252*eb8dc403SDave Cobbley
253*eb8dc403SDave Cobbley    Tree.prototype.collapseAll = function() {
254*eb8dc403SDave Cobbley      var node, _i, _len, _ref, _results;
255*eb8dc403SDave Cobbley      _ref = this.nodes;
256*eb8dc403SDave Cobbley      _results = [];
257*eb8dc403SDave Cobbley      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
258*eb8dc403SDave Cobbley        node = _ref[_i];
259*eb8dc403SDave Cobbley        _results.push(node.collapse());
260*eb8dc403SDave Cobbley      }
261*eb8dc403SDave Cobbley      return _results;
262*eb8dc403SDave Cobbley    };
263*eb8dc403SDave Cobbley
264*eb8dc403SDave Cobbley    Tree.prototype.expandAll = function() {
265*eb8dc403SDave Cobbley      var node, _i, _len, _ref, _results;
266*eb8dc403SDave Cobbley      _ref = this.nodes;
267*eb8dc403SDave Cobbley      _results = [];
268*eb8dc403SDave Cobbley      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
269*eb8dc403SDave Cobbley        node = _ref[_i];
270*eb8dc403SDave Cobbley        _results.push(node.expand());
271*eb8dc403SDave Cobbley      }
272*eb8dc403SDave Cobbley      return _results;
273*eb8dc403SDave Cobbley    };
274*eb8dc403SDave Cobbley
275*eb8dc403SDave Cobbley    Tree.prototype.findLastNode = function (node) {
276*eb8dc403SDave Cobbley      if (node.children.length > 0) {
277*eb8dc403SDave Cobbley        return this.findLastNode(node.children[node.children.length - 1]);
278*eb8dc403SDave Cobbley      } else {
279*eb8dc403SDave Cobbley        return node;
280*eb8dc403SDave Cobbley      }
281*eb8dc403SDave Cobbley    };
282*eb8dc403SDave Cobbley
283*eb8dc403SDave Cobbley    Tree.prototype.loadRows = function(rows) {
284*eb8dc403SDave Cobbley      var node, row, i;
285*eb8dc403SDave Cobbley
286*eb8dc403SDave Cobbley      if (rows != null) {
287*eb8dc403SDave Cobbley        for (i = 0; i < rows.length; i++) {
288*eb8dc403SDave Cobbley          row = $(rows[i]);
289*eb8dc403SDave Cobbley
290*eb8dc403SDave Cobbley          if (row.data(this.settings.nodeIdAttr) != null) {
291*eb8dc403SDave Cobbley            node = new Node(row, this.tree, this.settings);
292*eb8dc403SDave Cobbley            this.nodes.push(node);
293*eb8dc403SDave Cobbley            this.tree[node.id] = node;
294*eb8dc403SDave Cobbley
295*eb8dc403SDave Cobbley            if (node.parentId != null) {
296*eb8dc403SDave Cobbley              this.tree[node.parentId].addChild(node);
297*eb8dc403SDave Cobbley            } else {
298*eb8dc403SDave Cobbley              this.roots.push(node);
299*eb8dc403SDave Cobbley            }
300*eb8dc403SDave Cobbley          }
301*eb8dc403SDave Cobbley        }
302*eb8dc403SDave Cobbley      }
303*eb8dc403SDave Cobbley
304*eb8dc403SDave Cobbley      for (i = 0; i < this.nodes.length; i++) {
305*eb8dc403SDave Cobbley        node = this.nodes[i].updateBranchLeafClass();
306*eb8dc403SDave Cobbley      }
307*eb8dc403SDave Cobbley
308*eb8dc403SDave Cobbley      return this;
309*eb8dc403SDave Cobbley    };
310*eb8dc403SDave Cobbley
311*eb8dc403SDave Cobbley    Tree.prototype.move = function(node, destination) {
312*eb8dc403SDave Cobbley      // Conditions:
313*eb8dc403SDave Cobbley      // 1: +node+ should not be inserted as a child of +node+ itself.
314*eb8dc403SDave Cobbley      // 2: +destination+ should not be the same as +node+'s current parent (this
315*eb8dc403SDave Cobbley      //    prevents +node+ from being moved to the same location where it already
316*eb8dc403SDave Cobbley      //    is).
317*eb8dc403SDave Cobbley      // 3: +node+ should not be inserted in a location in a branch if this would
318*eb8dc403SDave Cobbley      //    result in +node+ being an ancestor of itself.
319*eb8dc403SDave Cobbley      var nodeParent = node.parentNode();
320*eb8dc403SDave Cobbley      if (node !== destination && destination.id !== node.parentId && $.inArray(node, destination.ancestors()) === -1) {
321*eb8dc403SDave Cobbley        node.setParent(destination);
322*eb8dc403SDave Cobbley        this._moveRows(node, destination);
323*eb8dc403SDave Cobbley
324*eb8dc403SDave Cobbley        // Re-render parentNode if this is its first child node, and therefore
325*eb8dc403SDave Cobbley        // doesn't have the expander yet.
326*eb8dc403SDave Cobbley        if (node.parentNode().children.length === 1) {
327*eb8dc403SDave Cobbley          node.parentNode().render();
328*eb8dc403SDave Cobbley        }
329*eb8dc403SDave Cobbley      }
330*eb8dc403SDave Cobbley
331*eb8dc403SDave Cobbley      if(nodeParent){
332*eb8dc403SDave Cobbley        nodeParent.updateBranchLeafClass();
333*eb8dc403SDave Cobbley      }
334*eb8dc403SDave Cobbley      if(node.parentNode()){
335*eb8dc403SDave Cobbley        node.parentNode().updateBranchLeafClass();
336*eb8dc403SDave Cobbley      }
337*eb8dc403SDave Cobbley      node.updateBranchLeafClass();
338*eb8dc403SDave Cobbley      return this;
339*eb8dc403SDave Cobbley    };
340*eb8dc403SDave Cobbley
341*eb8dc403SDave Cobbley    Tree.prototype.removeNode = function(node) {
342*eb8dc403SDave Cobbley      // Recursively remove all descendants of +node+
343*eb8dc403SDave Cobbley      this.unloadBranch(node);
344*eb8dc403SDave Cobbley
345*eb8dc403SDave Cobbley      // Remove node from DOM (<tr>)
346*eb8dc403SDave Cobbley      node.row.remove();
347*eb8dc403SDave Cobbley
348*eb8dc403SDave Cobbley      // Clean up Tree object (so Node objects are GC-ed)
349*eb8dc403SDave Cobbley      delete this.tree[node.id];
350*eb8dc403SDave Cobbley      this.nodes.splice($.inArray(node, this.nodes), 1);
351*eb8dc403SDave Cobbley    }
352*eb8dc403SDave Cobbley
353*eb8dc403SDave Cobbley    Tree.prototype.render = function() {
354*eb8dc403SDave Cobbley      var root, _i, _len, _ref;
355*eb8dc403SDave Cobbley      _ref = this.roots;
356*eb8dc403SDave Cobbley      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
357*eb8dc403SDave Cobbley        root = _ref[_i];
358*eb8dc403SDave Cobbley
359*eb8dc403SDave Cobbley        // Naming is confusing (show/render). I do not call render on node from
360*eb8dc403SDave Cobbley        // here.
361*eb8dc403SDave Cobbley        root.show();
362*eb8dc403SDave Cobbley      }
363*eb8dc403SDave Cobbley      return this;
364*eb8dc403SDave Cobbley    };
365*eb8dc403SDave Cobbley
366*eb8dc403SDave Cobbley    Tree.prototype.sortBranch = function(node, sortFun) {
367*eb8dc403SDave Cobbley      // First sort internal array of children
368*eb8dc403SDave Cobbley      node.children.sort(sortFun);
369*eb8dc403SDave Cobbley
370*eb8dc403SDave Cobbley      // Next render rows in correct order on page
371*eb8dc403SDave Cobbley      this._sortChildRows(node);
372*eb8dc403SDave Cobbley
373*eb8dc403SDave Cobbley      return this;
374*eb8dc403SDave Cobbley    };
375*eb8dc403SDave Cobbley
376*eb8dc403SDave Cobbley    Tree.prototype.unloadBranch = function(node) {
377*eb8dc403SDave Cobbley      var children, i;
378*eb8dc403SDave Cobbley
379*eb8dc403SDave Cobbley      for (i = 0; i < node.children.length; i++) {
380*eb8dc403SDave Cobbley        this.removeNode(node.children[i]);
381*eb8dc403SDave Cobbley      }
382*eb8dc403SDave Cobbley
383*eb8dc403SDave Cobbley      // Reset node's collection of children
384*eb8dc403SDave Cobbley      node.children = [];
385*eb8dc403SDave Cobbley
386*eb8dc403SDave Cobbley      node.updateBranchLeafClass();
387*eb8dc403SDave Cobbley
388*eb8dc403SDave Cobbley      return this;
389*eb8dc403SDave Cobbley    };
390*eb8dc403SDave Cobbley
391*eb8dc403SDave Cobbley    Tree.prototype._moveRows = function(node, destination) {
392*eb8dc403SDave Cobbley      var children = node.children, i;
393*eb8dc403SDave Cobbley
394*eb8dc403SDave Cobbley      node.row.insertAfter(destination.row);
395*eb8dc403SDave Cobbley      node.render();
396*eb8dc403SDave Cobbley
397*eb8dc403SDave Cobbley      // Loop backwards through children to have them end up on UI in correct
398*eb8dc403SDave Cobbley      // order (see #112)
399*eb8dc403SDave Cobbley      for (i = children.length - 1; i >= 0; i--) {
400*eb8dc403SDave Cobbley        this._moveRows(children[i], node);
401*eb8dc403SDave Cobbley      }
402*eb8dc403SDave Cobbley    };
403*eb8dc403SDave Cobbley
404*eb8dc403SDave Cobbley    // Special _moveRows case, move children to itself to force sorting
405*eb8dc403SDave Cobbley    Tree.prototype._sortChildRows = function(parentNode) {
406*eb8dc403SDave Cobbley      return this._moveRows(parentNode, parentNode);
407*eb8dc403SDave Cobbley    };
408*eb8dc403SDave Cobbley
409*eb8dc403SDave Cobbley    return Tree;
410*eb8dc403SDave Cobbley  })();
411*eb8dc403SDave Cobbley
412*eb8dc403SDave Cobbley  // jQuery Plugin
413*eb8dc403SDave Cobbley  methods = {
414*eb8dc403SDave Cobbley    init: function(options, force) {
415*eb8dc403SDave Cobbley      var settings;
416*eb8dc403SDave Cobbley
417*eb8dc403SDave Cobbley      settings = $.extend({
418*eb8dc403SDave Cobbley        branchAttr: "ttBranch",
419*eb8dc403SDave Cobbley        clickableNodeNames: false,
420*eb8dc403SDave Cobbley        column: 0,
421*eb8dc403SDave Cobbley        columnElType: "td", // i.e. 'td', 'th' or 'td,th'
422*eb8dc403SDave Cobbley        expandable: false,
423*eb8dc403SDave Cobbley        expanderTemplate: "<a href='#'>&nbsp;</a>",
424*eb8dc403SDave Cobbley        indent: 10,
425*eb8dc403SDave Cobbley        indenterTemplate: "<span class='indenter'></span>",
426*eb8dc403SDave Cobbley        initialState: "collapsed",
427*eb8dc403SDave Cobbley        nodeIdAttr: "ttId", // maps to data-tt-id
428*eb8dc403SDave Cobbley        parentIdAttr: "ttParentId", // maps to data-tt-parent-id
429*eb8dc403SDave Cobbley        stringExpand: "Expand",
430*eb8dc403SDave Cobbley        stringCollapse: "Collapse",
431*eb8dc403SDave Cobbley
432*eb8dc403SDave Cobbley        // Events
433*eb8dc403SDave Cobbley        onInitialized: null,
434*eb8dc403SDave Cobbley        onNodeCollapse: null,
435*eb8dc403SDave Cobbley        onNodeExpand: null,
436*eb8dc403SDave Cobbley        onNodeInitialized: null
437*eb8dc403SDave Cobbley      }, options);
438*eb8dc403SDave Cobbley
439*eb8dc403SDave Cobbley      return this.each(function() {
440*eb8dc403SDave Cobbley        var el = $(this), tree;
441*eb8dc403SDave Cobbley
442*eb8dc403SDave Cobbley        if (force || el.data("treetable") === undefined) {
443*eb8dc403SDave Cobbley          tree = new Tree(this, settings);
444*eb8dc403SDave Cobbley          tree.loadRows(this.rows).render();
445*eb8dc403SDave Cobbley
446*eb8dc403SDave Cobbley          el.addClass("treetable").data("treetable", tree);
447*eb8dc403SDave Cobbley
448*eb8dc403SDave Cobbley          if (settings.onInitialized != null) {
449*eb8dc403SDave Cobbley            settings.onInitialized.apply(tree);
450*eb8dc403SDave Cobbley          }
451*eb8dc403SDave Cobbley        }
452*eb8dc403SDave Cobbley
453*eb8dc403SDave Cobbley        return el;
454*eb8dc403SDave Cobbley      });
455*eb8dc403SDave Cobbley    },
456*eb8dc403SDave Cobbley
457*eb8dc403SDave Cobbley    destroy: function() {
458*eb8dc403SDave Cobbley      return this.each(function() {
459*eb8dc403SDave Cobbley        return $(this).removeData("treetable").removeClass("treetable");
460*eb8dc403SDave Cobbley      });
461*eb8dc403SDave Cobbley    },
462*eb8dc403SDave Cobbley
463*eb8dc403SDave Cobbley    collapseAll: function() {
464*eb8dc403SDave Cobbley      this.data("treetable").collapseAll();
465*eb8dc403SDave Cobbley      return this;
466*eb8dc403SDave Cobbley    },
467*eb8dc403SDave Cobbley
468*eb8dc403SDave Cobbley    collapseNode: function(id) {
469*eb8dc403SDave Cobbley      var node = this.data("treetable").tree[id];
470*eb8dc403SDave Cobbley
471*eb8dc403SDave Cobbley      if (node) {
472*eb8dc403SDave Cobbley        node.collapse();
473*eb8dc403SDave Cobbley      } else {
474*eb8dc403SDave Cobbley        throw new Error("Unknown node '" + id + "'");
475*eb8dc403SDave Cobbley      }
476*eb8dc403SDave Cobbley
477*eb8dc403SDave Cobbley      return this;
478*eb8dc403SDave Cobbley    },
479*eb8dc403SDave Cobbley
480*eb8dc403SDave Cobbley    expandAll: function() {
481*eb8dc403SDave Cobbley      this.data("treetable").expandAll();
482*eb8dc403SDave Cobbley      return this;
483*eb8dc403SDave Cobbley    },
484*eb8dc403SDave Cobbley
485*eb8dc403SDave Cobbley    expandNode: function(id) {
486*eb8dc403SDave Cobbley      var node = this.data("treetable").tree[id];
487*eb8dc403SDave Cobbley
488*eb8dc403SDave Cobbley      if (node) {
489*eb8dc403SDave Cobbley        if (!node.initialized) {
490*eb8dc403SDave Cobbley          node._initialize();
491*eb8dc403SDave Cobbley        }
492*eb8dc403SDave Cobbley
493*eb8dc403SDave Cobbley        node.expand();
494*eb8dc403SDave Cobbley      } else {
495*eb8dc403SDave Cobbley        throw new Error("Unknown node '" + id + "'");
496*eb8dc403SDave Cobbley      }
497*eb8dc403SDave Cobbley
498*eb8dc403SDave Cobbley      return this;
499*eb8dc403SDave Cobbley    },
500*eb8dc403SDave Cobbley
501*eb8dc403SDave Cobbley    loadBranch: function(node, rows) {
502*eb8dc403SDave Cobbley      var settings = this.data("treetable").settings,
503*eb8dc403SDave Cobbley          tree = this.data("treetable").tree;
504*eb8dc403SDave Cobbley
505*eb8dc403SDave Cobbley      // TODO Switch to $.parseHTML
506*eb8dc403SDave Cobbley      rows = $(rows);
507*eb8dc403SDave Cobbley
508*eb8dc403SDave Cobbley      if (node == null) { // Inserting new root nodes
509*eb8dc403SDave Cobbley        this.append(rows);
510*eb8dc403SDave Cobbley      } else {
511*eb8dc403SDave Cobbley        var lastNode = this.data("treetable").findLastNode(node);
512*eb8dc403SDave Cobbley        rows.insertAfter(lastNode.row);
513*eb8dc403SDave Cobbley      }
514*eb8dc403SDave Cobbley
515*eb8dc403SDave Cobbley      this.data("treetable").loadRows(rows);
516*eb8dc403SDave Cobbley
517*eb8dc403SDave Cobbley      // Make sure nodes are properly initialized
518*eb8dc403SDave Cobbley      rows.filter("tr").each(function() {
519*eb8dc403SDave Cobbley        tree[$(this).data(settings.nodeIdAttr)].show();
520*eb8dc403SDave Cobbley      });
521*eb8dc403SDave Cobbley
522*eb8dc403SDave Cobbley      if (node != null) {
523*eb8dc403SDave Cobbley        // Re-render parent to ensure expander icon is shown (#79)
524*eb8dc403SDave Cobbley        node.render().expand();
525*eb8dc403SDave Cobbley      }
526*eb8dc403SDave Cobbley
527*eb8dc403SDave Cobbley      return this;
528*eb8dc403SDave Cobbley    },
529*eb8dc403SDave Cobbley
530*eb8dc403SDave Cobbley    move: function(nodeId, destinationId) {
531*eb8dc403SDave Cobbley      var destination, node;
532*eb8dc403SDave Cobbley
533*eb8dc403SDave Cobbley      node = this.data("treetable").tree[nodeId];
534*eb8dc403SDave Cobbley      destination = this.data("treetable").tree[destinationId];
535*eb8dc403SDave Cobbley      this.data("treetable").move(node, destination);
536*eb8dc403SDave Cobbley
537*eb8dc403SDave Cobbley      return this;
538*eb8dc403SDave Cobbley    },
539*eb8dc403SDave Cobbley
540*eb8dc403SDave Cobbley    node: function(id) {
541*eb8dc403SDave Cobbley      return this.data("treetable").tree[id];
542*eb8dc403SDave Cobbley    },
543*eb8dc403SDave Cobbley
544*eb8dc403SDave Cobbley    removeNode: function(id) {
545*eb8dc403SDave Cobbley      var node = this.data("treetable").tree[id];
546*eb8dc403SDave Cobbley
547*eb8dc403SDave Cobbley      if (node) {
548*eb8dc403SDave Cobbley        this.data("treetable").removeNode(node);
549*eb8dc403SDave Cobbley      } else {
550*eb8dc403SDave Cobbley        throw new Error("Unknown node '" + id + "'");
551*eb8dc403SDave Cobbley      }
552*eb8dc403SDave Cobbley
553*eb8dc403SDave Cobbley      return this;
554*eb8dc403SDave Cobbley    },
555*eb8dc403SDave Cobbley
556*eb8dc403SDave Cobbley    reveal: function(id) {
557*eb8dc403SDave Cobbley      var node = this.data("treetable").tree[id];
558*eb8dc403SDave Cobbley
559*eb8dc403SDave Cobbley      if (node) {
560*eb8dc403SDave Cobbley        node.reveal();
561*eb8dc403SDave Cobbley      } else {
562*eb8dc403SDave Cobbley        throw new Error("Unknown node '" + id + "'");
563*eb8dc403SDave Cobbley      }
564*eb8dc403SDave Cobbley
565*eb8dc403SDave Cobbley      return this;
566*eb8dc403SDave Cobbley    },
567*eb8dc403SDave Cobbley
568*eb8dc403SDave Cobbley    sortBranch: function(node, columnOrFunction) {
569*eb8dc403SDave Cobbley      var settings = this.data("treetable").settings,
570*eb8dc403SDave Cobbley          prepValue,
571*eb8dc403SDave Cobbley          sortFun;
572*eb8dc403SDave Cobbley
573*eb8dc403SDave Cobbley      columnOrFunction = columnOrFunction || settings.column;
574*eb8dc403SDave Cobbley      sortFun = columnOrFunction;
575*eb8dc403SDave Cobbley
576*eb8dc403SDave Cobbley      if ($.isNumeric(columnOrFunction)) {
577*eb8dc403SDave Cobbley        sortFun = function(a, b) {
578*eb8dc403SDave Cobbley          var extractValue, valA, valB;
579*eb8dc403SDave Cobbley
580*eb8dc403SDave Cobbley          extractValue = function(node) {
581*eb8dc403SDave Cobbley            var val = node.row.find("td:eq(" + columnOrFunction + ")").text();
582*eb8dc403SDave Cobbley            // Ignore trailing/leading whitespace and use uppercase values for
583*eb8dc403SDave Cobbley            // case insensitive ordering
584*eb8dc403SDave Cobbley            return $.trim(val).toUpperCase();
585*eb8dc403SDave Cobbley          }
586*eb8dc403SDave Cobbley
587*eb8dc403SDave Cobbley          valA = extractValue(a);
588*eb8dc403SDave Cobbley          valB = extractValue(b);
589*eb8dc403SDave Cobbley
590*eb8dc403SDave Cobbley          if (valA < valB) return -1;
591*eb8dc403SDave Cobbley          if (valA > valB) return 1;
592*eb8dc403SDave Cobbley          return 0;
593*eb8dc403SDave Cobbley        };
594*eb8dc403SDave Cobbley      }
595*eb8dc403SDave Cobbley
596*eb8dc403SDave Cobbley      this.data("treetable").sortBranch(node, sortFun);
597*eb8dc403SDave Cobbley      return this;
598*eb8dc403SDave Cobbley    },
599*eb8dc403SDave Cobbley
600*eb8dc403SDave Cobbley    unloadBranch: function(node) {
601*eb8dc403SDave Cobbley      this.data("treetable").unloadBranch(node);
602*eb8dc403SDave Cobbley      return this;
603*eb8dc403SDave Cobbley    }
604*eb8dc403SDave Cobbley  };
605*eb8dc403SDave Cobbley
606*eb8dc403SDave Cobbley  $.fn.treetable = function(method) {
607*eb8dc403SDave Cobbley    if (methods[method]) {
608*eb8dc403SDave Cobbley      return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
609*eb8dc403SDave Cobbley    } else if (typeof method === 'object' || !method) {
610*eb8dc403SDave Cobbley      return methods.init.apply(this, arguments);
611*eb8dc403SDave Cobbley    } else {
612*eb8dc403SDave Cobbley      return $.error("Method " + method + " does not exist on jQuery.treetable");
613*eb8dc403SDave Cobbley    }
614*eb8dc403SDave Cobbley  };
615*eb8dc403SDave Cobbley
616*eb8dc403SDave Cobbley  // Expose classes to world
617*eb8dc403SDave Cobbley  this.TreeTable || (this.TreeTable = {});
618*eb8dc403SDave Cobbley  this.TreeTable.Node = Node;
619*eb8dc403SDave Cobbley  this.TreeTable.Tree = Tree;
620*eb8dc403SDave Cobbley}).call(this);
621