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 * @event treeOpenWithContent {qx.event.type.DataEvent}
30 * @event treeOpenWhileEmpty {qx.event.type.DataEvent}
31 * @event treeClose {qx.event.type.DataEvent}
32 * @event changeSelection {qx.event.type.Event}
34 * WARNING: This widget is in active development and the interface to it is
35 * very likely to change, possibly on a daily basis, for a while. Do
36 * not use this widget yet.
39 qx.OO.defineClass("qx.ui.treevirtual.TreeVirtual", qx.ui.table.Table,
42 // Create a table model
43 var tableModel = new qx.ui.treevirtual.SimpleTreeDataModel();
45 // Specify the column headings. We accept a single string (one single
46 // column) or an array of strings (one or more columns).
47 if (typeof(headings) == "string")
49 headings = [ headings ];
51 tableModel.setColumns(headings);
53 this.setNewSelectionManager(
56 return new qx.ui.treevirtual.SelectionManager(obj);
59 // Call our superclass constructor
60 qx.ui.table.Table.call(this, tableModel);
63 this.setRowHeight(16);
64 this.setMetaColumnCounts([1, -1]);
66 // Set the data cell render. We use the SimpleTreeDataCellRenderer for the
67 // tree column, and our DefaultDataCellRenderer for all other columns.
68 var stdcr = new qx.ui.treevirtual.SimpleTreeDataCellRenderer();
69 var ddcr = new qx.ui.treevirtual.DefaultDataCellRenderer();
70 var tcm = this.getTableColumnModel();
71 var treeCol = this.getTableModel().getTreeColumn();
72 for (var i = 0; i < headings.length; i++)
74 tcm.setDataCellRenderer(i, i == treeCol ? stdcr : ddcr);
77 // Set the data row renderer.
78 this.setDataRowRenderer(new qx.ui.treevirtual.SimpleTreeDataRowRenderer());
80 // We need our cell renderer called on selection change, to update the icon
81 this.setAlwaysUpdateCells(true);
83 // Move the focus with the mouse
84 this.setFocusCellOnMouseMove(true);
86 // Change focus colors. Make them less obtrusive.
89 bgcolFocused : "#f0f0f0",
90 bgcolFocusedBlur : "#f0f0f0"
93 // Set the cell focus color
94 this.setCellFocusAttributes({ backgroundColor : "lightblue" });
97 // Use this instead, to help determine which does what
100 bgcolFocusedSelected : "cyan",
101 bgcolFocusedSelectedBlur : "green",
102 bgcolFocused : "yellow",
103 bgcolFocusedBlur : "blue",
104 bgcolSelected : "red",
105 bgcolSelectedBlur : "pink",
109 // Get the list of pane scrollers
110 var scrollers = this._getPaneScrollerArr();
112 // For each scroller...
113 for (var i = 0; i < scrollers.length; i++)
115 // ... remove the outline on focus,
116 scrollers[i]._focusIndicator.setAppearance("treevirtual-focus-indicator");
118 // ... and set the pane scrollers to handle the selection before
119 // displaying the focus, so we can manipulate the selected icon.
120 scrollers[i].setSelectBeforeFocus(true);
126 * Whether a click on the open/close button should also cause selection of the
131 name : "openCloseClickSelectsRow",
133 defaultValue : false,
134 getAlias : "openCloseClickSelectsRow"
139 * Return the data model for this tree.
141 qx.Proto.getDataModel = function()
143 return this.getTableModel();
148 * Set whether lines linking tree children shall be drawn on the tree.
151 * <i>true</i> if tree lines should be shown; <i>false</i> otherwise.
153 qx.Proto.setUseTreeLines = function(b)
155 var stdcm = this.getTableModel();
156 var treeCol = stdcm.getTreeColumn();
157 var dcr = this.getTableColumnModel().getDataCellRenderer(treeCol);
158 dcr.setUseTreeLines(b);
160 // Inform the listeners
161 if (stdcm.hasEventListeners(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED))
166 lastRow : stdcm._rowArr.length - 1,
168 lastColumn : stdcm.getColumnCount() - 1
171 stdcm.dispatchEvent(new qx.event.type.DataEvent(
172 qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED,
180 * Get whether lines linking tree children shall be drawn on the tree.
183 * <i>true</i> if tree lines are in use; <i>false</i> otherwise.
185 qx.Proto.getUseTreeLines = function()
187 var treeCol = this.getTableModel().getTreeColumn();
188 var dcr = this.getTableColumnModel().getDataCellRenderer(treeCol);
189 return dcr.getUseTreeLines();
194 * Set whether the open/close button should be displayed on a branch, even if
195 * the branch has no children.
198 * <i>true</i> if the open/close button should be shown; <i>false</i>
201 qx.Proto.setAlwaysShowOpenCloseSymbol = function(b)
203 var stdcm = this.getTableModel();
204 var treeCol = stdcm.getTreeColumn();
205 var dcr = this.getTableColumnModel().getDataCellRenderer(treeCol);
206 dcr.setAlwaysShowOpenCloseSymbol(b);
208 // Inform the listeners
209 if (stdcm.hasEventListeners(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED))
214 lastRow : stdcm._rowArr.length - 1,
216 lastColumn : stdcm.getColumnCount() - 1
219 stdcm.dispatchEvent(new qx.event.type.DataEvent(
220 qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED,
228 * Set whether drawing of first-level tree-node lines are disabled even if
229 * drawing of tree lines is enabled. (See also @link {#setUseTreeLines})
232 * <i>true</i> if first-level tree lines should be disabled;
233 * <i>false</i> for normal operation.
235 qx.Proto.setExcludeFirstLevelTreeLines = function(b)
237 var stdcm = this.getTableModel();
238 var treeCol = stdcm.getTreeColumn();
239 var dcr = this.getTableColumnModel().getDataCellRenderer(treeCol);
240 dcr.setExcludeFirstLevelTreeLines(b);
242 // Inform the listeners
243 if (stdcm.hasEventListeners(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED))
248 lastRow : stdcm._rowArr.length - 1,
250 lastColumn : stdcm.getColumnCount() - 1
253 stdcm.dispatchEvent(new qx.event.type.DataEvent(
254 qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED,
262 * Get whether drawing of first-level tree lines should be disabled even if
263 * drawing of tree lines is enabled. (See also {@link #getUseTreeLines})
266 * <i>true</i> if tree lines are in use; <i>false</i> otherwise.
268 qx.Proto.getExcludeFirstLevelTreeLines = function()
270 var treeCol = this.getTableModel().getTreeColumn();
271 var dcr = this.getTableColumnModel().getDataCellRenderer(treeCol);
272 return dcr.getExcludeFirstLevelTreeLines();
277 * Set whether the open/close button should be displayed on a branch, even if
278 * the branch has no children.
281 * <i>true</i> if tree lines are in use; <i>false</i> otherwise.
283 qx.Proto.getAlwaysShowOpenCloseSymbol = function()
285 var treeCol = this.getTableModel().getTreeColumn();
286 var dcr = this.getTableColumnModel().getDataCellRenderer(treeCol);
287 return dcr.getAlwaysShowOpenCloseSymbol();
292 * Set the selection mode.
294 * @param mode {Integer}
295 * The selection mode to be used. It may be any of:
297 * qx.ui.treevirtual.SelectionMode.NONE:
298 * Nothing can ever be selected.
300 * qx.ui.treevirtual.SelectionMode.SINGLE
301 * Allow only one selected item.
303 * qx.ui.treevirtual.SelectionMode.SINGLE_INTERVAL
304 * Allow one contiguous interval of selected items.
306 * qx.ui.treevirtual.SelectionMode.MULTIPLE_INTERVAL
307 * Allow any selected items, whether contiguous or not.
310 qx.Proto.setSelectionMode = function(mode)
312 this.getSelectionModel().setSelectionMode(mode);
316 * Get the selection mode currently in use.
319 * One of the values documented in {@link #setSelectionMode}
321 qx.Proto.getSelectionMode = function(mode)
323 return this.getSelectionModel().getSelectionMode();
328 * Toggle the opened state of the node: if the node is opened, close
329 * it; if it is closed, open it.
331 * @param node {Object}
332 * The object representing the node to have its opened/closed state
335 qx.Proto.toggleOpened = function(node)
337 // Are we opening or closing?
340 // We're closing. If there are listeners, generate a treeClose event.
341 this.createDispatchDataEvent("treeClose", node);
345 // We're opening. Are there any children?
346 if (node.children.length > 0)
348 // Yup. If there any listeners, generate a "treeOpenWithContent" event.
349 this.createDispatchDataEvent("treeOpenWithContent", node);
353 // No children. If there are listeners, generate a "treeOpenWhileEmpty"
355 this.createDispatchDataEvent("treeOpenWhileEmpty", node);
359 // Event handler may have modified the opened state. Check before toggling.
360 if (! node.bHideOpenClose)
362 // It's still boolean. Toggle the state
363 node.bOpened = ! node.bOpened;
365 // Get the selection model
366 var sm = this.getSelectionModel();
368 // Get the data model
369 var dm = this.getTableModel();
371 // Determine if this node was selected
372 var rowIndex = dm.getNodeRowMap()[node.nodeId];
374 // Is this row already selected?
375 var bSelected = sm.isSelectedIndex(rowIndex);
377 // Clear the old selections in the tree
378 this.getSelectionModel()._clearSelection();
381 // Clear the old selections in the data model
382 dm._clearSelections();
384 // If this row was selected, re-select it
387 this.setState(node.nodeId, { bSelected : true });
392 // Re-render the row data since formerly visible rows may now be invisible,
394 this.getTableModel().setData();
399 * Set state attributes of a tree node.
401 * @param nodeId {Integer}
402 * The node identifier (returned by addBranch() or addLeaf()) representing
403 * the node for which attributes are being set.
405 * @param attributes {Map}
406 * Map with the node properties to be set. The map may contain any of the
407 * properties described in {@link qx.ui.treevirtual.SimpleTreeDataModel}
409 qx.Proto.setState = function(nodeId, attributes)
411 this.getTableModel().setState(nodeId, attributes);
416 * Allow setting the tree row colors.
418 * @param colors {Map}
419 * The value of each property in the map is a string containing either a
420 * number (e.g. "#518ad3") or color name ("white") representing the color
421 * for that type of display. The map may contain any or all of the
422 * following properties:
424 * <li>bgcolFocusedSelected</li>
425 * <li>bgcolFocusedSelectedBlur</li>
426 * <li>bgcolFocused</li>
427 * <li>bgcolFocusedBlur</li>
428 * <li>bgcolSelected</li>
429 * <li>bgcolSelectedBlur</li>
432 * <li>colSelected</li>
436 qx.Proto.setRowColors = function(colors)
438 this.getDataRowRenderer().setRowColors(colors);
443 * Set the attributes used to indicate the cell that has the focus.
445 * @param attributes {Map}
446 * The set of attributes that the cell focus indicator should have. This is
447 * in the format required to call the <i>set()</i> method of a widget, e.g.
449 * { backgroundColor: blue }
451 * If not otherwise specified, the opacity is set to 0.2 so that the cell
452 * data can be seen "through" the cell focus indicator which overlays it.
454 * For no visible focus indicator, use { backgroundColor : "transparent" }
456 * The focus indicator is a box the size of the cell, which overlays the
457 * cell itself. There is no text in the focus indicator itself, so it makes
458 * no sense to set the color attribute or any other attribute that affects
461 qx.Proto.setCellFocusAttributes = function(attributes)
463 // Add an opacity attribute so what's below the focus can be seen
464 if (! attributes.opacity)
466 attributes.opacity = 0.2;
469 var scrollers = this._getPaneScrollerArr();
470 for (var i = 0; i < scrollers.length; i++)
472 scrollers[i]._focusIndicator.set(attributes);
478 * Event handler. Called when a key was pressed.
480 * We handle the Enter key to toggle opened/closed tree state. All
481 * other keydown events are passed to our superclass.
483 * @param evt {Map} the event.
485 qx.Proto._onkeydown = function(evt)
487 if (! this.getEnabled()) {
491 var identifier = evt.getKeyIdentifier();
493 var consumed = false;
494 var modifiers = evt.getModifiers();
500 // Get the data model
501 var dm = this.getTableModel();
503 // Get the focused node
504 var focusedRow = this.getFocusedRow();
505 var treeCol = dm.getTreeColumn();
506 var node = dm.getValue(treeCol, focusedRow);
508 if (! node.bHideOpenClose)
510 this.toggleOpened(node);
516 this.moveFocusedCell(-1, 0);
520 this.moveFocusedCell(1, 0);
524 else if (modifiers == qx.event.type.DomEvent.CTRL_MASK)
529 // Get the data model
530 var dm = this.getTableModel();
532 // Get the focused node
533 var focusedRow = this.getFocusedRow();
534 var treeCol = dm.getTreeColumn();
535 var node = dm.getValue(treeCol, focusedRow);
537 // If it's an open branch and open/close is allowed...
538 if (node.type == qx.ui.treevirtual.SimpleTreeDataModel.Type.BRANCH &&
539 ! node.bHideOpenClose &&
543 this.toggleOpened(node);
546 // Reset the focus to the current node
547 this.setFocusedCell(treeCol, focusedRow, true);
553 // Get the data model
554 var dm = this.getTableModel();
556 // Get the focused node
557 var focusedRow = this.getFocusedRow();
558 var treeCol = dm.getTreeColumn();
559 var node = dm.getValue(treeCol, focusedRow);
561 // If it's a closed branch and open/close is allowed...
562 if (node.type == qx.ui.treevirtual.SimpleTreeDataModel.Type.BRANCH &&
563 ! node.bHideOpenClose &&
567 this.toggleOpened(node);
570 // Reset the focus to the current node
571 this.setFocusedCell(treeCol, focusedRow, true);
577 else if (modifiers == qx.event.type.DomEvent.SHIFT_MASK)
582 // Get the data model
583 var dm = this.getTableModel();
585 // Get the focused node
586 var focusedRow = this.getFocusedRow();
587 var treeCol = dm.getTreeColumn();
588 var node = dm.getValue(treeCol, focusedRow);
590 // If we're not at the top-level already...
591 if (node.parentNodeId)
593 // Find out what rendered row our parent node is at
594 var rowIndex = dm.getNodeRowMap()[node.parentNodeId];
596 // Set the focus to our parent
597 this.setFocusedCell(this._focusedCol, rowIndex, true);
604 // Get the data model
605 var dm = this.getTableModel();
607 // Get the focused node
608 var focusedRow = this.getFocusedRow();
609 var treeCol = dm.getTreeColumn();
610 var node = dm.getValue(treeCol, focusedRow);
612 // If we're on a branch and open/close is allowed...
613 if (node.type == qx.ui.treevirtual.SimpleTreeDataModel.Type.BRANCH &&
614 ! node.bHideOpenClose)
616 // ... then first ensure the branch is open
619 this.toggleOpened(node);
622 // If this node has children...
623 if (node.children.length > 0)
625 // ... then move the focus to the first child
626 this.moveFocusedCell(0, 1);
635 // Was this one of our events that we handled?
638 // Yup. Don't propagate it.
639 evt.preventDefault();
640 evt.stopPropagation();
644 // It's not one of ours. Let our superclass handle this event
645 qx.ui.table.Table.prototype._onkeydown.call(this, evt);
650 qx.Proto._onkeypress = function(evt)
652 if (! this.getEnabled()) {
656 var consumed = false;
658 // Handle keys that are independant from the modifiers
659 var identifier = evt.getKeyIdentifier();
662 // Ignore events we already handled in _onkeydown
671 evt.preventDefault();
672 evt.stopPropagation();
676 // Let our superclass handle this event
677 qx.ui.table.Table.prototype._onkeypress.call(this, evt);
684 * Event handler. Called when the selection has changed.
686 * @param evt {Map} the event.
688 qx.Proto._onSelectionChanged = function(evt)
690 // Clear the old list of selected nodes
691 this.getTableModel()._clearSelections();
693 // If selections are allowed, pass an event to our listeners
694 if (this.getSelectionMode() !=
695 qx.ui.treevirtual.TreeVirtual.SelectionMode.NONE)
697 var selectedNodes = this._calculateSelectedNodes();
699 // Get the now-focused
700 this.createDispatchDataEvent("changeSelection", selectedNodes);
703 // Call the superclass method
704 qx.ui.table.Table.prototype._onSelectionChanged.call(this, evt);
709 * Obtain the entire hierarchy of labels from the root down to the specified
712 * @param nodeId {Integer}
713 * The node id of the node for which the hierarchy is desired.
716 * The returned array contains one string for each label in the hierarchy of
717 * the node specified by the parameter. Element 0 of the array contains the
718 * label of the root node, element 1 contains the label of the node
719 * immediately below root in the specified node's hierarchy, etc., down to
720 * the last element in the array contain the label of the node referenced by
723 qx.Proto.getHierarchy = function(nodeId)
726 var components = [ ];
728 function addHierarchy(nodeId)
730 // If we're at the root...
733 // ... then we're done
737 // Get the requested node
738 var node = _this.getTableModel().getData()[nodeId];
740 // Add its label to the hierarchy components
741 components.unshift(node.label);
743 // Call recursively to our parent node.
744 addHierarchy(node.parentNodeId);
747 addHierarchy(nodeId);
753 * Calculate and return the set of nodes which are currently selected by the
754 * user, on the screen. In the process of calculating which nodes are
755 * selected, the nodes corresponding to the selected rows on the screen are
756 * marked as selected by setting their <i>bSelected</i> property to true, and
757 * all previously-selected nodes have their <i>bSelected</i> property reset to
761 * An array of nodes matching the set of rows which are selected on the
764 qx.Proto._calculateSelectedNodes = function()
766 // Create an array of nodes that are now selected
767 var stdcm = this.getTableModel();
768 var selectedRanges = this.getSelectionModel().getSelectedRanges();
769 var selectedNodes = [ ];
772 for (var i = 0; i < selectedRanges.length; i++)
774 for (var j = selectedRanges[i].minIndex;
775 j <= selectedRanges[i].maxIndex;
778 node = stdcm.getValue(stdcm.getTreeColumn(), j);
779 stdcm.setState(node.nodeId, { bSelected : true });
780 selectedNodes.push(node);
784 return selectedNodes;
789 * Return the nodes that are currently selected.
792 * An array containing the nodes that are currently selected.
794 qx.Proto.getSelectedNodes = function()
796 return this.getTableModel().getSelectedNodes();
801 * Selection Modes {int}
804 * Nothing can ever be selected.
807 * Allow only one selected item.
810 * Allow one contiguous interval of selected items.
813 * Allow any set of selected items, whether contiguous or not.
815 qx.Class.SelectionMode =
818 qx.ui.table.SelectionModel.NO_SELECTION,
821 qx.ui.table.SelectionModel.SINGLE_SELECTION,
824 qx.ui.table.SelectionModel.SINGLE_INTERVAL_SELECTION,
827 qx.ui.table.SelectionModel.MULTIPLE_INTERVAL_SELECTION