1 /* ************************************************************************
3 qooxdoo - the new era of web development
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.
16 * Derrell Lipman (derrell)
18 ************************************************************************ */
20 /* ************************************************************************
24 ************************************************************************ */
29 * A simple tree data model used as the table model
31 * The object structure of a single node of the tree is:
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
47 * cellStyle : "background-color:cyan"
48 * labelStyle : "background-color:red;color:white"
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.
56 * nodeId : 42, // The index in _nodeArr, useful to event listeners
58 * level : 2, // The indentation level of this tree node
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.
68 qx.OO.defineClass("qx.ui.treevirtual.SimpleTreeDataModel",
69 qx.ui.table.AbstractTableModel,
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
89 label : "<virtual root>",
97 qx.Proto.setEditable = function(editable)
99 throw new Error("Tree columns can not be made editable");
104 qx.Proto.setColumnEditable = function(columnIndex, editable)
106 throw new Error("Tree columns can not be made editable");
111 qx.Proto.isColumnEditable = function(columnIndex)
118 qx.Proto.isColumnSortable = function(columnIndex)
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.
136 * -1, to indicate that the model is not sorted.
138 qx.Proto.getSortColumnIndex = function()
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.
152 qx.Proto.setTreeColumn = function(columnIndex)
154 this._treeColumn = columnIndex;
159 * Get the column in which the tree is to be displayed.
162 * The column in whcih the tree is to be displayed
164 qx.Proto.getTreeColumn = function()
166 return this._treeColumn;
171 qx.Proto.getRowCount = function()
173 return this._rowArr.length;
178 qx.Proto.getRowData = function(rowIndex)
180 return this._rowArr[rowIndex];
185 qx.Proto.getValue = function(columnIndex, rowIndex)
187 if (rowIndex < 0 || rowIndex >= this._rowArr.length)
189 throw new Error ("this._rowArr row " +
190 "(" + rowIndex + ") out of bounds: " +
193 (this._rowArr.length - 1) + ")");b
196 if (columnIndex < 0 || columnIndex >= this._rowArr[rowIndex].length)
198 throw new Error ("this._rowArr column " +
199 "(" + columnIndex + ") out of bounds: " +
200 this._rowArr[rowIndex] +
202 (this._rowArr[rowIndex].length - 1) + ")");
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:
236 * <dt>qx.ui.treevirtual.SimpleTreeDataModel.Type.BRANCH</dt>
238 * This node is a branch. A branch node may have children.
240 * <dt>qx.ui.treevirtual.SimpleTreeDataModel.Type.LEAF</dt>
242 * This node is a leaf, and may not have children
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.
255 * The node id of the newly-added node.
257 qx.Proto._addNode = function(parentNodeId,
260 bHideOpenCloseButton,
267 // Ensure that if parent was specified, it exists
270 parentNode = this._nodeArr[parentNodeId];
273 throw new Error("Request to add a child to a non-existent parent");
276 // Ensure parent isn't a leaf
277 if (parentNode.type == qx.ui.treevirtual.SimpleTreeDataModel.Type.LEAF)
279 throw new Error("Sorry, a LEAF may not have children.");
284 // This is a child of the root
285 parentNode = this._nodeArr[0];
289 // If this is a leaf, we don't present open/close icon
290 if (type == qx.ui.treevirtual.SimpleTreeDataModel.Type.LEAF)
292 // mask off the opened bit but retain the hide open/close button bit
294 bHideOpenClose = false;
297 // Determine the node id of this new node
298 var nodeId = this._nodeArr.length;
300 // Set the data for this node.
304 parentNodeId : parentNodeId,
308 bHideOpenClose : bHideOpenCloseButton,
310 iconSelected : iconSelected,
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
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.
353 * The node id of the newly-added branch.
355 qx.Proto.addBranch = function(parentNodeId,
358 bHideOpenCloseButton,
362 return this._addNode(parentNodeId,
365 bHideOpenCloseButton,
366 qx.ui.treevirtual.SimpleTreeDataModel.Type.BRANCH,
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.
390 * The node id of the newly-added leaf.
392 qx.Proto.addLeaf = function(parentNodeId,
397 return this._addNode(parentNodeId,
401 qx.ui.treevirtual.SimpleTreeDataModel.Type.LEAF,
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.
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++)
424 this.prune(this._nodeArr[nodeId].children[i], true);
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])
436 delete this._selections[nodeId];
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;
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.
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.
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}.
465 qx.Proto.setData = function(nodeArr)
471 var inorder = function(nodeId, level)
476 // For each child of the specified node...
477 var numChildren = _this._nodeArr[nodeId].children.length;
478 for (var i = 0; i < numChildren; i++)
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
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
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 ];
505 var parent = _this._nodeArr[child.parentNodeId];
507 // For each parent node, determine if it is a last child
508 while (parent.nodeId)
510 var bLast = parent.lastChild[parent.lastChild.length - 1];
511 child.lastChild.unshift(bLast);
512 parent = _this._nodeArr[parent.parentNodeId];
515 // Ensure there's an entry in the columnData array for each column
516 if (! child.columnData)
518 child.columnData = [ ];
521 if (child.columnData.length < _this.getColumnCount())
523 child.columnData[_this.getColumnCount() - 1] = null;
526 // Add this node to the row array. Initialize a row data array.
529 // If additional column data is provided...
530 if (child.columnData)
532 // ... then add each column data.
533 for (var j = 0; j < child.columnData.length; j++)
535 // Is this the tree column?
536 if (j == _this._treeColumn)
538 // Yup. Add the tree node data
543 // Otherwise, add the column data verbatim.
544 rowData.push(child.columnData[j]);
550 // No column data. Just add the tree node.
554 // If this node is selected, ...
557 // ... indicate so for the row.
558 rowData.selected = true;
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, ...
570 // ... then add its children too.
571 inorder(childNodeId, level + 1);
576 // Reset the row array
579 // Reset the _nodeArr -> _rowArr map
580 _this._nodeRowMap = [ ];
582 // Begin in-order traversal of the tree from the root to regenerate _rowArr
585 // Inform the listeners
586 if (_this.hasEventListeners(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED))
591 lastRow : _this._rowArr.length - 1,
593 lastColumn : _this.getColumnCount() - 1
596 _this.dispatchEvent(new qx.event.type.DataEvent(
597 qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED,
603 if (nodeArr instanceof Array)
605 // Determine the set of selected nodes
606 for (i = 0; i < nodeArr.length; i++)
608 if (nodeArr[i].selected)
610 this._selections[i] = true;
614 // Save the user-supplied data.
615 this._nodeArr = nodeArr;
617 else if (nodeArr !== null && nodeArr !== undefined)
619 throw new Error("Expected array of node objects or null/undefined; got " +
623 // Re-render the row array
629 * Return the array of node data.
632 * Array of node objects. See {@link qx.ui.treevirtual.SimpleTreeDataModel}
633 * for a description nodes in this array.
635 qx.Proto.getData = function()
637 return this._nodeArr;
643 * Add data to an additional column (a column other than the tree column) of
647 * A node identifier, as previously returned by {@link #addBranch} or {@link
651 * The column number to which the provided data applies
654 * The cell data for the specified column
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
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.
675 qx.Proto.setState = function(nodeId, attributes)
677 for (var attribute in attributes)
679 // If the selected state is changing...
680 if (attribute == "bSelected")
682 // ... then keep track of what is selected
683 if (attributes[attribute])
685 this._selections[nodeId] = true;
689 delete this._selections[nodeId];
693 this._nodeArr[nodeId][attribute] = attributes[attribute];
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.
703 * The array containing mappings of nodes to rendered rows.
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.
716 qx.Proto._clearSelections = function()
718 // Clear selected state for any selected nodes.
719 for (var selection in this._selections)
721 this._nodeArr[selection].bSelected = false;
724 // Reinitialize selections array.
725 this._selections = { };
730 * Return the nodes that are currently selected.
733 * An array containing the nodes that are currently selected.
735 qx.Proto.getSelectedNodes = function()
739 for (var nodeId in this._selections)
741 nodes.push(this._nodeArr[nodeId]);
748 // We currently support these types of tree nodes
750 qx.Class.Type.LEAF = 1;
751 qx.Class.Type.BRANCH = 2;