r21321: - Allow pruning all of the children of a node without removing the node
[Samba.git] / webapps / qooxdoo-0.6.5-sdk / frontend / framework / source / class / qx / ui / treevirtual / SimpleTreeDataModel.js
blob875eeea6eccb9356e8d4b0b93bd90bf2e218329c
1 /* ************************************************************************
3    qooxdoo - the new era of web development
5    http://qooxdoo.org
7    Copyright:
8      2007 Derrell Lipman
10    License:
11      LGPL: http://www.gnu.org/licenses/lgpl.html
12      EPL: http://www.eclipse.org/org/documents/epl-v10.php
13      See the LICENSE file in the project's top-level directory for details.
15    Authors:
16      * Derrell Lipman (derrell)
18 ************************************************************************ */
20 /* ************************************************************************
22 #module(treevirtual)
24 ************************************************************************ */
29  * A simple tree data model used as the table model
30  *
31  * The object structure of a single node of the tree is:
32  *
33  * <pre>
34  * {
35  *   // USER-PROVIDED ATTRIBUTES
36  *   // ------------------------
37  *   type           : qx.ui.treevirtual.Type.LEAF,
38  *   parentNodeId   : 23,    // index in _nodeArr of the parent node
39  *   label          : "My Documents",
40  *   bSelected      : true,  // true if node is selected; false otherwise
41  *   bOpened        : true,  // true (-), false (+)
42  *   bHideOpenClose : false, // whether to hide the open/close button
43  *   icon           : "images/folder.gif",
44  *   iconSelected   : "images/folder_selected.gif",
45  *   children       : [ ],   // each value is an index into _nodeArr
46  *
47  *   cellStyle      : "background-color:cyan"
48  *   labelStyle     : "background-color:red;color:white"
49  *
50  *   // INTERNALLY-CALCULATED ATTRIBUTES
51  *   // --------------------------------
52  *   // The following properties need not (and should not) be set by the
53  *   // caller, but are automatically calculated.  Some are used internally,
54  *   // while others may be of use to event listeners.
55  *
56  *   nodeId         : 42,   // The index in _nodeArr, useful to event listeners
57  *
58  *   level          : 2,    // The indentation level of this tree node
59  *
60  *   bFirstChild    : true,
61  *   lastChild      : [ false ],  // Array where the index is the column of
62  *                                // indentation, and the value is a boolean.
63  *                                // These are used to locate the
64  *                                // appropriate "tree line" icon.
65  * }
66  * </pre>
67  */
68 qx.OO.defineClass("qx.ui.treevirtual.SimpleTreeDataModel",
69                   qx.ui.table.AbstractTableModel,
70 function()
72   qx.ui.table.AbstractTableModel.call(this);
74   this._rowArr = [ ];           // rows, resorted into tree order as necessary
75   this._nodeArr = [ ];          // tree nodes, organized with hierarchy
77   this._nodeRowMap = [ ];       // map nodeArr index to rowArr index.  The
78                                 // index of this array is the index of
79                                 // _nodeArr, and the values in this array are
80                                 // the indexes into _rowArr.
83   this._treeColumn = 0;         // default column for tree nodes
85   this._selections = { };       // list of indexes of selected nodes
87   this._nodeArr.push(           // the root node, needed to store its children
88     {
89       label     : "<virtual root>",
90       bOpened   : true,
91       children  : [ ]
92     });
93 });
96 // overridden
97 qx.Proto.setEditable = function(editable)
99   throw new Error("Tree columns can not be made editable");
103 // overridden
104 qx.Proto.setColumnEditable = function(columnIndex, editable)
106   throw new Error("Tree columns can not be made editable");
110 // overridden
111 qx.Proto.isColumnEditable = function(columnIndex)
113   return false;
117 // overridden
118 qx.Proto.isColumnSortable = function(columnIndex)
120   return false;
124 // overridden
125 qx.Proto.sortByColumn = function(columnIndex, ascending)
127   throw new Error("Trees can not be sorted by column");
132  * Returns the column index the model is sorted by. This model is never
133  * sorted, so -1 is returned.
135  * @return {Integer}
136  *   -1, to indicate that the model is not sorted.
137  */
138 qx.Proto.getSortColumnIndex = function()
140   return -1;
145  * Specifies which column the tree is to be displayed in.  The tree is
146  * displayed using the SimpleTreeDataCellRenderer.  Other columns may be
147  * provided which use different cell renderers.
149  * @param columnIndex {Integer}
150  *   The index of the column in which the tree should be displayed.
151  */
152 qx.Proto.setTreeColumn = function(columnIndex)
154   this._treeColumn = columnIndex;
159  * Get the column in which the tree is to be displayed.
161  * @return {Integer}
162  *   The column in whcih the tree is to be displayed
163  */
164 qx.Proto.getTreeColumn = function()
166   return this._treeColumn;
170 // overridden
171 qx.Proto.getRowCount = function()
173   return this._rowArr.length;
177 // overridden
178 qx.Proto.getRowData = function(rowIndex)
180   return this._rowArr[rowIndex];
184 // overridden
185 qx.Proto.getValue = function(columnIndex, rowIndex)
187   if (rowIndex < 0 || rowIndex >= this._rowArr.length)
188   {
189     throw new Error ("this._rowArr row " +
190                      "(" + rowIndex + ") out of bounds: " +
191                      this._rowArr +
192                      " (0.." +
193                      (this._rowArr.length - 1) + ")");b
194   }
196   if (columnIndex < 0 || columnIndex >= this._rowArr[rowIndex].length)
197   {
198     throw new Error ("this._rowArr column " +
199                      "(" + columnIndex + ") out of bounds: " +
200                      this._rowArr[rowIndex] +
201                      " (0.." +
202                      (this._rowArr[rowIndex].length - 1) + ")");
203   }
205   return this._rowArr[rowIndex][columnIndex];
210  * Add a node to the tree.
212  * NOTE: This method is for <b>internal use</b> and should not be called by
213  *       users of this class.  Instead, call {@link #addBranch} or {@link
214  *       #addLeaf}.  There is no guarantee that the interface to this method
215  *       will remain unchanged over time.
217  * @param parentNodeId {Integer}
218  *   The node id of the parent of the node being added
220  * @param label {String}
221  *   The string to display as the label for this node
223  * @param bOpened {Integer}
224  *   <i>true</i> if the tree should be rendered in its opened state;
225  *   <i>false</i> otherwise.
227  * @param bHideOpenCloseButton
228  *   <i>true</i> if the open/close button should be hidden (not displayed);
229  *   </i>false</i> to display the open/close button for this node.
231  * @param type {Integer}
232  *   The type of node being added.  The type determines whether children may
233  *   be added, and determines the default icons to use.  This parameter must
234  *   be one of the following values:
235  *   <dl>
236  *     <dt>qx.ui.treevirtual.SimpleTreeDataModel.Type.BRANCH</dt>
237  *     <dd>
238  *       This node is a branch.  A branch node may have children.
239  *     </dd>
240  *     <dt>qx.ui.treevirtual.SimpleTreeDataModel.Type.LEAF</dt>
241  *     <dd>
242  *       This node is a leaf, and may not have children
243  *     </dd>
244  *   </dl>
246  * @param icon {String}
247  *   The relative (subject to alias expansion) or full path of the icon to
248  *   display for this node when it is not a selected node.
250  * @param iconSelected {String}
251  *   The relative (subject to alias expansion) or full path of the icon to
252  *   display for this node when it is a selected node.
254  * @return {Integer}
255  *   The node id of the newly-added node.
256  */
257 qx.Proto._addNode = function(parentNodeId,
258                              label,
259                              bOpened,
260                              bHideOpenCloseButton,
261                              type,
262                              icon,
263                              iconSelected)
265   var parentNode;
267   // Ensure that if parent was specified, it exists
268   if (parentNodeId)
269   {
270     parentNode = this._nodeArr[parentNodeId];
271     if (! parentNode)
272     {
273         throw new Error("Request to add a child to a non-existent parent");
274     }
276     // Ensure parent isn't a leaf
277     if (parentNode.type == qx.ui.treevirtual.SimpleTreeDataModel.Type.LEAF)
278     {
279       throw new Error("Sorry, a LEAF may not have children.");
280     }
281   }
282   else
283   {
284     // This is a child of the root
285     parentNode = this._nodeArr[0];
286     parentNodeId = 0;
287   }
289   // If this is a leaf, we don't present open/close icon
290   if (type == qx.ui.treevirtual.SimpleTreeDataModel.Type.LEAF)
291   {
292     // mask off the opened bit but retain the hide open/close button bit
293     bOpened = false;
294     bHideOpenClose = false;
295   }
297   // Determine the node id of this new node
298   var nodeId = this._nodeArr.length;
300   // Set the data for this node.
301   var node =
302     {
303       type           : type,
304       parentNodeId   : parentNodeId,
305       label          : label,
306       bSelected      : false,
307       bOpened        : bOpened,
308       bHideOpenClose : bHideOpenCloseButton,
309       icon           : icon,
310       iconSelected   : iconSelected,
311       children       : [ ],
312       columnData     : [ ]
313     };
315   // Add this node to the array
316   this._nodeArr.push(node);
318   // Add this node to its parent's child array.
319   parentNode.children.push(nodeId);
321   // Return the node id we just added
322   return nodeId;
328  * Add a branch to the tree.
330  * @param parentNodeId {Integer}
331  *   The node id of the parent of the node being added
333  * @param label {String}
334  *   The string to display as the label for this node
336  * @param bOpened {Boolean}
337  *   <i>True</i> if the branch should be rendered in its opened state;
338  *   <i>false</i> otherwise.
340  * @param bHideOpenCloseButton {Boolean}
341  *   <i>True</i> if the open/close button should not be displayed;
342  *   <i>false</i> if the open/close button should be displayed
344  * @param icon {String}
345  *   The relative (subject to alias expansion) or full path of the icon to
346  *   display for this node when it is not a selected node.
348  * @param iconSelected {String}
349  *   The relative (subject to alias expansion) or full path of the icon to
350  *   display for this node when it is a selected node.
352  * @return {Integer}
353  *   The node id of the newly-added branch.
354  */
355 qx.Proto.addBranch = function(parentNodeId,
356                               label,
357                               bOpened,
358                               bHideOpenCloseButton,
359                               icon,
360                               iconSelected)
362   return this._addNode(parentNodeId,
363                        label,
364                        bOpened,
365                        bHideOpenCloseButton,
366                        qx.ui.treevirtual.SimpleTreeDataModel.Type.BRANCH,
367                        icon,
368                        iconSelected);
373  * Add a leaf to the tree.
375  * @param parentNodeId {Integer}
376  *   The node id of the parent of the node being added
378  * @param label {String}
379  *   The string to display as the label for this node
381  * @param icon {String}
382  *   The relative (subject to alias expansion) or full path of the icon to
383  *   display for this node when it is not a selected node.
385  * @param iconSelected {String}
386  *   The relative (subject to alias expansion) or full path of the icon to
387  *   display for this node when it is a selected node.
389  * @return {Integer}
390  *   The node id of the newly-added leaf.
391  */
392 qx.Proto.addLeaf = function(parentNodeId,
393                             label,
394                             icon,
395                             iconSelected)
397   return this._addNode(parentNodeId,
398                        label,
399                        false,
400                        false,
401                        qx.ui.treevirtual.SimpleTreeDataModel.Type.LEAF,
402                        icon,
403                        iconSelected);
408  * Prune the tree by removing, recursively, all of a node's children.  If
409  * requested, also remove the node itself.
411  * @param nodeId {Integer}
412  *   The node id, previously returned by {@link #addLeaf} or {@link
413  *   #addBranch}, of the node (and its children) to be pruned from the tree.
415  * @param bSelfAlso {Boolean}
416  *   If <i>true</i> then remove the node identified by <i>nodeId</i> as well
417  *   as all of the children.
418  */
419 qx.Proto.prune = function(nodeId, bSelfAlso)
421   // First, recursively remove all children
422   for (var i = 0; i < this._nodeArr[nodeId].children.length; i++)
423   {
424     this.prune(this._nodeArr[nodeId].children[i], true);
425   }
427   if (bSelfAlso)
428   {
429     // Delete ourself from our parent's children list
430     var node = this._nodeArr[nodeId];
431     qx.lang.Array.remove(this._nodeArr[node.parentNodeId].children, nodeId);
433     // Delete ourself from the selections list, if we're in it.
434     if (this._selections[nodeId])
435     {
436       delete this._selections[nodeId];
437     }
439     // We can't splice the node itself out, because that would muck up the
440     // nodeId == index correspondence.  Instead, just replace the node with
441     // null so its index just becomes unused.
442     this._nodeArr[nodeId] = null;
443   }
449  * Sets the whole data en bulk, or notifies the data model that node
450  * modifications are complete.
452  * @param nodeArr {Array | null}
453  *   Pass either an Array of node objects, or null.
454  *   </p><p>
455  *   If non-null, nodeArr is an array of node objects containing the entire
456  *   tree to be displayed.  If loading the whole data en bulk in this way, it
457  *   is assumed that the data is correct!  No error checking or validation is
458  *   done.  You'd better know what you're doing!  Caveat emptor.
459  *   </p><p>
460  *   If nodeArr is null, then this call is a notification that the user has
461  *   completed building or modifying a tree by issuing a series of calls to
462  *   {@link #addBranch} and/or {@link #addLeaf}.
463  *   <p>
464  */
465 qx.Proto.setData = function(nodeArr)
467   var _this = this;
469   function render()
470   {
471     var inorder = function(nodeId, level)
472     {
473       var child = null;
474       var childNodeId;
476       // For each child of the specified node...
477       var numChildren = _this._nodeArr[nodeId].children.length;
478       for (var i = 0; i < numChildren; i++)
479       {
480         // Determine the node id of this child
481         childNodeId = _this._nodeArr[nodeId].children[i];
483         // Get the child node
484         child = _this._nodeArr[childNodeId];
486         // Skip deleted nodes
487         if (child == null)
488         {
489           continue;
490         }
492         // Listeners will need to know a node's id when they receive an event
493         child.nodeId = childNodeId;
495         // (Re-)assign this node's level
496         child.level = level;
498         // Determine if we're the first child of our parent
499         child.bFirstChild = (i == 0);
501         // Determine if we're the last child of our parent
502         child.lastChild = [ i == numChildren - 1 ];
504         // Get our parent.
505         var parent = _this._nodeArr[child.parentNodeId];
507         // For each parent node, determine if it is a last child
508         while (parent.nodeId)
509         {
510           var bLast = parent.lastChild[parent.lastChild.length - 1];
511           child.lastChild.unshift(bLast);
512           parent = _this._nodeArr[parent.parentNodeId];
513         }
515         // Ensure there's an entry in the columnData array for each column
516         if (! child.columnData)
517         {
518           child.columnData = [ ];
519         }
521         if (child.columnData.length < _this.getColumnCount())
522         {
523           child.columnData[_this.getColumnCount() - 1] = null;
524         }
526         // Add this node to the row array.  Initialize a row data array.
527         var rowData = [ ];
529         // If additional column data is provided...
530         if (child.columnData)
531         {
532           // ... then add each column data.
533           for (var j = 0; j < child.columnData.length; j++)
534           {
535             // Is this the tree column?
536             if (j == _this._treeColumn)
537             {
538               // Yup.  Add the tree node data
539               rowData.push(child);
540             }
541             else
542             {
543               // Otherwise, add the column data verbatim.
544               rowData.push(child.columnData[j]);
545             }
546           }
547         }
548         else
549         {
550           // No column data.  Just add the tree node.
551           rowData.push(child);
552         }
554         // If this node is selected, ...
555         if (child.bSelected)
556         {
557           // ... indicate so for the row.
558           rowData.selected = true;
559         }
561         // Track the _rowArr index for each node so we can handle selections
562         _this._nodeRowMap[child.nodeId] = _this._rowArr.length;
564         // Add the row data to the row array
565         _this._rowArr.push(rowData)
567         // If this child is opened, ...
568         if (child.bOpened)
569         {
570           // ... then add its children too.
571           inorder(childNodeId, level + 1);
572         }
573       }
574     }
576     // Reset the row array
577     _this._rowArr = [];
579     // Reset the _nodeArr -> _rowArr map
580     _this._nodeRowMap = [ ];
582     // Begin in-order traversal of the tree from the root to regenerate _rowArr
583     inorder(0, 1);
585     // Inform the listeners
586     if (_this.hasEventListeners(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED))
587     {
588       var data =
589         {
590           firstRow        : 0,
591           lastRow         : _this._rowArr.length - 1,
592           firstColumn     : 0,
593           lastColumn      : _this.getColumnCount() - 1
594         };
596       _this.dispatchEvent(new qx.event.type.DataEvent(
597                             qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED,
598                             data),
599                           true);
600     }
601   }
603   if (nodeArr instanceof Array)
604   {
605     // Determine the set of selected nodes
606     for (i = 0; i < nodeArr.length; i++)
607     {
608       if (nodeArr[i].selected)
609       {
610         this._selections[i] = true;
611       }
612     }
614     // Save the user-supplied data.
615     this._nodeArr = nodeArr;
616   }
617   else if (nodeArr !== null && nodeArr !== undefined)
618   {
619     throw new Error("Expected array of node objects or null/undefined; got " +
620                     typeof(nodeArr));
621   }
623   // Re-render the row array
624   render();
629  * Return the array of node data.
631  * @return {Array}
632  *   Array of node objects.  See {@link qx.ui.treevirtual.SimpleTreeDataModel}
633  *   for a description nodes in this array.
634  */
635 qx.Proto.getData = function()
637   return this._nodeArr;
643  * Add data to an additional column (a column other than the tree column) of
644  * the tree.
646  * @param nodeId
647  *   A node identifier, as previously returned by {@link #addBranch} or {@link
648  *   addLeaf}.
650  * @param columnIndex
651  *   The column number to which the provided data applies
653  * @param data
654  *   The cell data for the specified column
655  */
656 qx.Proto.setColumnData = function(nodeId, columnIndex, data)
658   this._nodeArr[nodeId].columnData[columnIndex] = data;
663  * Set state attributes of a node.
665  * @param nodeId {Integer}
666  *   A node identifier, as previously returned by {@link #addBranch} or {@link
667  *   addLeaf}.
669  * @param attributes {Map}
670  *   Each property name in the map may correspond to the property names of a
671  *   node which are specified as <i>USER-PROVIDED ATTRIBUTES</i> in {@link
672  *   #SimpleTreeDataModel}.  Each property value will be assigned to the
673  *   corresponding property of the node specified by nodeId.
674  */
675 qx.Proto.setState = function(nodeId, attributes)
677   for (var attribute in attributes)
678   {
679     // If the selected state is changing...
680     if (attribute == "bSelected")
681     {
682       // ... then keep track of what is selected
683       if (attributes[attribute])
684       {
685         this._selections[nodeId] = true;
686       }
687       else
688       {
689         delete this._selections[nodeId];
690       }
691     }
693     this._nodeArr[nodeId][attribute] = attributes[attribute];
694   }
699  * Return the mapping of nodes to rendered rows.  This function is intended
700  * for use by the cell renderer, not by users of this class.
702  * @return {Array}
703  *   The array containing mappings of nodes to rendered rows.
704  */
705 qx.Proto.getNodeRowMap = function()
707   return this._nodeRowMap;
712  * Clear all selections in the data model.  This method does not clear
713  * selections displayed in the widget, and is intended for internal use, not
714  * by users of this class.
715  */
716 qx.Proto._clearSelections = function()
718   // Clear selected state for any selected nodes.
719   for (var selection in this._selections)
720   {
721     this._nodeArr[selection].bSelected = false;
722   }
724   // Reinitialize selections array.
725   this._selections = { };
730  * Return the nodes that are currently selected.
732  * @return {Array}
733  *   An array containing the nodes that are currently selected.
734  */
735 qx.Proto.getSelectedNodes = function()
737   var nodes = [ ];
739   for (var nodeId in this._selections)
740   {
741     nodes.push(this._nodeArr[nodeId]);
742   }
744   return nodes;
748 // We currently support these types of tree nodes
749 qx.Class.Type = {};
750 qx.Class.Type.LEAF            = 1;
751 qx.Class.Type.BRANCH          = 2;