@qooxdoo/framework
Version:
The JS Framework for Coders
1,212 lines (1,050 loc) • 35.6 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2007-2010 Derrell Lipman
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Derrell Lipman (derrell)
************************************************************************ */
/**
* A simple tree data model used as the table model
*
* The object structure of a single node of the tree is:
*
* <pre class='javascript'>
* {
* // USER-PROVIDED ATTRIBUTES
* // ------------------------
* type : qx.ui.treevirtual.MTreePrimitive.Type.LEAF,
* parentNodeId : 23, // index of the parent node in _nodeArr
*
* label : "My Documents",
* bSelected : true, // true if node is selected; false otherwise.
* bOpened : true, // true (-), false (+)
* bHideOpenClose : false, // whether to hide the open/close button
* icon : "images/folder.gif",
* iconSelected : "images/folder_selected.gif",
*
* cellStyle : "background-color:cyan"
* labelStyle : "background-color:red;color:white"
*
* // USER-PROVIDED COLUMN DATA
* columnData : [
* null, // null at index of tree column (typically 0)
* "text of column 1",
* "text of column 2"
* ],
*
* // APPLICATION-, MIXIN-, and SUBCLASS-PROVIDED CUSTOM DATA
* data : {
* application :
* {
* // application-specific user data goes in here
* foo: "bar",
* ...
* },
* MDragAndDropSupport :
* {
* // Data required for the Drag & Drop mixin.
* // When a mixin is included, its constructor
* // should create this object, named according
* // to the mixin or subclass name (empty or
* // otherwise)
* },
* ... // Additional mixins or subclasses.
* },
*
* // INTERNALLY-CALCULATED ATTRIBUTES
* // --------------------------------
* // The following properties need not (and should not) be set by the
* // caller, but are automatically calculated. Some are used internally,
* // while others may be of use to event listeners.
*
* nodeId : 42, // The index in _nodeArr, useful to event listeners.
* children : [ ], // each value is an index into _nodeArr
*
* level : 2, // The indentation level of this tree node
*
* bFirstChild : true,
* lastChild : [ false ], // Array where the index is the column of
* // indentation, and the value is a boolean.
* // These are used to locate the
* // appropriate "tree line" icon.
* }
* </pre>
*/
qx.Class.define("qx.ui.treevirtual.SimpleTreeDataModel",
{
extend : qx.ui.table.model.Abstract,
include : qx.ui.treevirtual.MTreePrimitive,
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
construct : function()
{
this.base(arguments);
this._rowArr = []; // rows, resorted into tree order as necessary
this._nodeArr = []; // tree nodes, organized with hierarchy
this._nodeRowMap = []; // map nodeArr index to rowArr index. The
// index of this array is the index of
// _nodeArr, and the values in this array are
// the indexes into _rowArr.
this._treeColumn = 0; // default column for tree nodes
this._selections = {}; // list of indexes of selected nodes
// the root node, needed to store its children
this._nodeArr.push(qx.ui.treevirtual.MTreePrimitive._getEmptyTree());
// Track which columns are editable
this.__editableColArr = null;
},
properties :
{
/**
* Gives the user the opportunity to filter the model. The filter
* function is called for every node in the model. It gets as an argument the
* <code>node</code> object and has to return
* <code>true</code> if the given data should be shown and
* <code>false</code> if the given data should be ignored.
*/
filter :
{
check : "Function",
nullable : true,
apply : "_applyFilter"
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members :
{
__tree : null,
__editableColArr : null,
__tempTreeData : null,
__recalculateLastChildFlags : null,
/** Rows, resorted into tree order as necessary */
_rowArr : null,
/** Tree nodes, organized with hierarchy */
_nodeArr : null,
/**
* Map nodeArr index to rowArr index. The index of this array is the
* index of _nodeArr, and the values in this array are the indexes into
* _rowArr.
*/
_nodeRowMap : null,
/** Column for tree nodes */
_treeColumn : null,
/** list of indexes of selected nodes */
_selections : null,
/**
* Set the tree object for which this data model is used.
*
* @param tree {qx.ui.treevirtual.TreeVirtual}
* The tree used to render the data in this model.
*
*/
setTree : function(tree)
{
this.__tree = tree;
},
/**
* Get the tree object for which this data model is used.
*
* @return {qx.ui.treevirtual.TreeVirtual}
*/
getTree : function()
{
return this.__tree;
},
/**
* Sets all columns editable or not editable.
*
* @param editable {Boolean}
* Whether all columns are editable.
*
*/
setEditable : function(editable)
{
this.__editableColArr = [];
for (var col=0; col<this.getColumnCount(); col++)
{
this.__editableColArr[col] = editable;
}
this.fireEvent("metaDataChanged");
},
/**
* Sets whether a column is editable.
*
* @param columnIndex {Integer}
* The column of which to set the editable state.
*
* @param editable {Boolean}
* Whether the column should be editable.
*
*/
setColumnEditable : function(columnIndex, editable)
{
if (editable != this.isColumnEditable(columnIndex))
{
if (this.__editableColArr == null)
{
this.__editableColArr = [];
}
this.__editableColArr[columnIndex] = editable;
this.fireEvent("metaDataChanged");
}
},
// overridden
isColumnEditable : function(columnIndex)
{
// The tree column is not editable
if (columnIndex == this._treeColumn)
{
return false;
}
return(this.__editableColArr
? this.__editableColArr[columnIndex] == true
: false);
},
// overridden
isColumnSortable : function(columnIndex)
{
return false;
},
/**
* Sorts the model by a column.
*
* @param columnIndex {Integer} the column to sort by.
* @param ascending {Boolean} whether to sort ascending.
* @throws {Error} If one tries to sort the tree by column
*/
sortByColumn : function(columnIndex, ascending)
{
throw new Error("Trees can not be sorted by column");
},
/**
* Returns the column index the model is sorted by. This model is never
* sorted, so -1 is returned.
*
* @return {Integer}
* -1, to indicate that the model is not sorted.
*/
getSortColumnIndex : function()
{
return -1;
},
/**
* Specifies which column the tree is to be displayed in. The tree is
* displayed using the SimpleTreeDataCellRenderer. Other columns may be
* provided which use different cell renderers.
*
* Setting the tree column involves more than simply setting this column
* index; it also requires setting an appropriate cell renderer for this
* column, that knows how to render a tree. The expected and typical
* method of setting the tree column is to provide it in the 'custom'
* parameter to the TreeVirtual constructor, which also initializes the
* proper cell renderers. This method does not set any cell renderers. If
* you wish to call this method on your own, you should also manually set
* the cell renderer for the specified column, and likely also set the
* cell renderer for column 0 (the former tree column) to something
* appropriate to your data.
*
*
* @param columnIndex {Integer}
* The index of the column in which the tree should be displayed.
*
*/
setTreeColumn : function(columnIndex)
{
this._treeColumn = columnIndex;
},
/**
* Get the column in which the tree is to be displayed.
*
* @return {Integer}
* The column in which the tree is to be displayed
*/
getTreeColumn : function()
{
return this._treeColumn;
},
// overridden
getRowCount : function()
{
return this._rowArr.length;
},
// overridden
getRowData : function(rowIndex)
{
return this._rowArr[rowIndex];
},
/**
* Returns a cell value by column index.
*
* @throws {Error} if the row index is out of bounds.
* @param columnIndex {Integer} the index of the column.
* @param rowIndex {Integer} the index of the row.
* @return {var} The value of the cell.
* @see #getValueById
*/
getValue : function(columnIndex, rowIndex)
{
if (rowIndex < 0 || rowIndex >= this._rowArr.length)
{
throw new Error("this._rowArr row " +
"(" + rowIndex + ") out of bounds: " +
this._rowArr +
" (0.." + (this._rowArr.length - 1) + ")");
}
if (columnIndex < 0 || columnIndex >= this._rowArr[rowIndex].length)
{
throw new Error("this._rowArr column " +
"(" + columnIndex + ") out of bounds: " +
this._rowArr[rowIndex] +
" (0.." + (this._rowArr[rowIndex].length - 1) + ")");
}
return this._rowArr[rowIndex][columnIndex];
},
// overridden
setValue : function(columnIndex, rowIndex, value)
{
if (columnIndex == this._treeColumn)
{
// Ignore requests to set the tree column data using this method
return;
}
// convert from rowArr to nodeArr, and get the requested node
var node = this.getNodeFromRow(rowIndex);
if (node.columnData[columnIndex] != value)
{
node.columnData[columnIndex] = value;
this.setData();
// Inform the listeners
if (this.hasListener("dataChanged"))
{
var data =
{
firstRow : rowIndex,
lastRow : rowIndex,
firstColumn : columnIndex,
lastColumn : columnIndex
};
this.fireDataEvent("dataChanged", data);
}
}
},
/**
* Returns the node object specific to a currently visible row. In this
* simple tree data model, that's the same as retrieving the value of the
* tree column of the specified row.
*
* @throws {Error}
* Thrown if the row index is out of bounds.
*
* @param rowIndex {Integer}
* The index of the row.
*
* @return {Object}
* The node object associated with the specified row.
*/
getNode : function(rowIndex)
{
if (rowIndex < 0 || rowIndex >= this._rowArr.length)
{
throw new Error("this._rowArr row " +
"(" + rowIndex + ") out of bounds: " +
this._rowArr +
" (0.." + (this._rowArr.length - 1) + ")");
}
return this._rowArr[rowIndex][this._treeColumn];
},
/**
* Add a branch to the tree.
*
* @param parentNodeId {Integer}
* The node id of the parent of the node being added
*
* @param label {String}
* The string to display as the label for this node
*
* @param bOpened {Boolean}
* <i>True</i> if the branch should be rendered in its opened state;
* <i>false</i> otherwise.
*
* @param bHideOpenCloseButton {Boolean}
* <i>True</i> if the open/close button should not be displayed;
* <i>false</i> if the open/close button should be displayed
*
* @param icon {String}
* The relative (subject to alias expansion) or full path of the icon to
* display for this node when it is not a selected node.
*
* @param iconSelected {String}
* The relative (subject to alias expansion) or full path of the icon to
* display for this node when it is a selected node.
*
* @return {Integer}
* The node id of the newly-added branch.
*/
addBranch : function(parentNodeId,
label,
bOpened,
bHideOpenCloseButton,
icon,
iconSelected)
{
return qx.ui.treevirtual.MTreePrimitive._addNode(
this._nodeArr,
parentNodeId,
label,
bOpened,
bHideOpenCloseButton,
qx.ui.treevirtual.MTreePrimitive.Type.BRANCH,
icon,
iconSelected);
},
/**
* Add a leaf to the tree.
*
* @param parentNodeId {Integer}
* The node id of the parent of the node being added
*
* @param label {String}
* The string to display as the label for this node
*
* @param icon {String}
* The relative (subject to alias expansion) or full path of the icon to
* display for this node when it is not a selected node.
*
* @param iconSelected {String}
* The relative (subject to alias expansion) or full path of the icon to
* display for this node when it is a selected node.
*
* @return {Integer} The node id of the newly-added leaf.
*/
addLeaf : function(parentNodeId,
label,
icon,
iconSelected)
{
return qx.ui.treevirtual.MTreePrimitive._addNode(
this._nodeArr,
parentNodeId,
label,
false,
false,
qx.ui.treevirtual.MTreePrimitive.Type.LEAF,
icon,
iconSelected);
},
/**
* Prune the tree by removing, recursively, all of a node's children. If
* requested, also remove the node itself.
*
* @param nodeReference {Object | Integer}
* The node to be pruned from the tree. The node can be represented
* either by the node object, or the node id (as would have been
* returned by addBranch(), addLeaf(), etc.)
*
* @param bSelfAlso {Boolean}
* If <i>true</i> then remove the node identified by <i>nodeId</i> as
* well as all of the children.
*
* @throws {Error} If the node object or id is not valid.
*
*/
prune : function(nodeReference, bSelfAlso)
{
var node;
var nodeId;
if (typeof(nodeReference) == "object")
{
node = nodeReference;
nodeId = node.nodeId;
}
else if (typeof(nodeReference) == "number")
{
nodeId = nodeReference;
}
else
{
throw new Error("Expected node object or node id");
}
// First, recursively remove all children
for (var i=this._nodeArr[nodeId].children.length-1; i>=0; i--)
{
this.prune(this._nodeArr[nodeId].children[i], true);
}
// Now remove ourself, if requested. (Don't try to remove the root node)
if (bSelfAlso && nodeId != 0)
{
// Delete ourself from our parent's children list
node = this._nodeArr[nodeId];
qx.lang.Array.remove(this._nodeArr[node.parentNodeId].children,
nodeId);
// Delete ourself from the selections list, if we're in it.
if (this._selections[nodeId])
{
delete this._selections[nodeId];
}
// We can't splice the node itself out, because that would muck up the
// nodeId == index correspondence. Instead, just replace the node
// with null so its index just becomes unused.
this._nodeArr[nodeId] = null;
}
},
/**
* Move a node in the tree.
*
* @param moveNodeReference {Object | Integer}
* The node to be moved. The node can be represented
* either by the node object, or the node id (as would have been
* returned by addBranch(), addLeaf(), etc.)
*
* @param parentNodeReference {Object | Integer}
* The new parent node, which must not be a LEAF. The node can be
* represented either by the node object, or the node id (as would have
* been returned by addBranch(), addLeaf(), etc.)
*
* @throws {Error} If the node object or id is not valid.
* @throws {Error} If one tries to add a child to a non-existent parent.
* @throws {Error} If one tries to add a node to a leaf.
*/
move : function(moveNodeReference,
parentNodeReference)
{
var moveNode;
var moveNodeId;
var parentNode;
var parentNodeId;
// Replace null parent with node id 0
parentNodeReference = parentNodeReference || 0;
if (typeof(moveNodeReference) == "object")
{
moveNode = moveNodeReference;
moveNodeId = moveNode.nodeId;
}
else if (typeof(moveNodeReference) == "number")
{
moveNodeId = moveNodeReference;
moveNode = this._nodeArr[moveNodeId];
}
else
{
throw new Error("Expected move node object or node id");
}
if (typeof(parentNodeReference) == "object")
{
parentNode = parentNodeReference;
parentNodeId = parentNode.nodeId;
}
else if (typeof(parentNodeReference) == "number")
{
parentNodeId = parentNodeReference;
parentNode = this._nodeArr[parentNodeId];
}
else
{
throw new Error("Expected parent node object or node id");
}
// Ensure parent isn't a leaf
if (parentNode.type == qx.ui.treevirtual.MTreePrimitive.Type.LEAF)
{
throw new Error("Sorry, a LEAF may not have children.");
}
// Remove the node from its current parent's children list
var oldParent = this._nodeArr[moveNode.parentNodeId];
qx.lang.Array.remove(oldParent.children, moveNodeId);
// Add the node to its new parent's children list
parentNode.children.push(moveNodeId);
// Replace this node's parent reference
this._nodeArr[moveNodeId].parentNodeId = parentNodeId;
},
/**
* Orders the node and creates all data needed to render the tree.
*
* @param nodeId {Integer}
* A node identifier, as previously returned by {@link #addBranch} or
* {@link #addLeaf}.
* @param level {Integer} the level in the hierarchy
*/
__inorder : function(nodeId, level)
{
var filter = this.getFilter();
var child = null;
var childNodeId;
// For each child of the specified node...
var numChildren = this._nodeArr[nodeId].children.length;
var index = 0;
var children = this.__tempTreeData[nodeId] = [];
for (var i=0; i<numChildren; i++)
{
// Determine the node id of this child
childNodeId = this._nodeArr[nodeId].children[i];
// Get the child node
child = this._nodeArr[childNodeId];
// Skip deleted nodes or apply the filter
if (child == null || (filter && !filter.call(this, child))) {
this.__recalculateLastChildFlags = true;
continue;
}
// Remember the children so that we can add the lastChild flags later
children.push(child);
// (Re-)assign this node's level
child.level = level;
// Determine if we're the first child of our parent
child.bFirstChild = (index == 0);
// Set the last child flag of the node only when no node was skipped.
// Otherwise we will have to recalculate the last child flags, as
// the parent or sibling node might become the first child.
if (!this.__recalculateLastChildFlags) {
this.__setLastChildFlag(child, i == numChildren - 1);
}
// Ensure there's an entry in the columnData array for each column
if (!child.columnData)
{
child.columnData = [ ];
}
if (child.columnData.length < this.getColumnCount())
{
child.columnData[this.getColumnCount() - 1] = null;
}
// Add this node to the row array. Initialize a row data array.
var rowData = [ ];
// If additional column data is provided...
if (child.columnData)
{
// ... then add each column data.
for (var j=0; j<child.columnData.length; j++)
{
// Is this the tree column?
if (j == this._treeColumn)
{
// Yup. Add the tree node data
rowData.push(child);
}
else
{
// Otherwise, add the column data verbatim.
rowData.push(child.columnData[j]);
}
}
}
else
{
// No column data. Just add the tree node.
rowData.push(child);
}
// Track the _rowArr index for each node so we can handle
// selections.
this._nodeRowMap[child.nodeId] = this._rowArr.length;
// Add the row data to the row array
this._rowArr.push(rowData);
// If this node is selected, ...
if (child.bSelected)
{
// ... indicate so for the row.
rowData.selected = true;
this._selections[child.nodeId] = true;
}
// If this child is opened, ...
if (child.bOpened)
{
// ... then add its children too.
this.__inorder(childNodeId, level + 1);
}
index++;
}
},
/**
* Calculates the lastChild flags to the nodes, so that the tree can render the
* tree lines right.
*
* @param nodeId {Integer}
* A node identifier, as previously returned by {@link #addBranch} or
* {@link #addLeaf}.
*/
__calculateLastChildFlags : function(nodeId)
{
var tempTreeData = this.__tempTreeData;
var children = tempTreeData[nodeId];
var numChildren = children.length;
for (var i = 0; i < numChildren; i++)
{
var child = children[i];
this.__setLastChildFlag(child, i == numChildren - 1);
var hasChildren = tempTreeData[child.nodeId] && tempTreeData[child.nodeId].length > 0;
if (hasChildren) {
this.__calculateLastChildFlags(child.nodeId);
}
}
},
/**
* Sets the last child flag for a node and all it's parents.
*
* @param node {Object} the node object
* @param isLastChild {Boolean} whether the node is the last child
*/
__setLastChildFlag : function(node, isLastChild)
{
// Determine if we're the last child of our parent
node.lastChild = [ isLastChild ];
// Get our parent.
var parent = this._nodeArr[node.parentNodeId];
// For each parent node, determine if it is a last child
while (parent.nodeId)
{
var bLast = parent.lastChild[parent.lastChild.length - 1];
node.lastChild.unshift(bLast);
parent = this._nodeArr[parent.parentNodeId];
}
},
/**
* Renders the tree data.
*/
__render : function()
{
// Reset the __tempTreeData
this.__tempTreeData = [];
this.__recalculateLastChildFlags = false;
// Reset the row array
this._rowArr = [];
// Reset the _nodeArr -> _rowArr map
this._nodeRowMap = [];
// Reset the set of selections
this._selections = {};
// Begin in-order traversal of the tree from the root to regenerate
// _rowArr.
this.__inorder(0, 1);
// Reset the lastChild flags when needed, so that the tree can render the
// tree lines right.
if (this.__recalculateLastChildFlags) {
this.__calculateLastChildFlags(0);
}
// Give the memory free
this.__tempTreeData = null;
// Inform the listeners
if (this.hasListener("dataChanged"))
{
var data =
{
firstRow : 0,
lastRow : this._rowArr.length - 1,
firstColumn : 0,
lastColumn : this.getColumnCount() - 1
};
this.fireDataEvent("dataChanged", data);
}
},
/**
* Sets the whole data en bulk, or notifies the data model that node
* modifications are complete.
*
* @param nodeArr {Array | null}
* Pass either an Array of node objects, or null.
*
* If non-null, nodeArr is an array of node objects containing the
* entire tree to be displayed. If loading the whole data en bulk in
* this way, it is assumed that the data is correct! No error checking
* or validation is done. You'd better know what you're doing! Caveat
* emptor.
*
*
* If nodeArr is null, then this call is a notification that the user
* has completed building or modifying a tree by issuing a series of
* calls to {@link #addBranch} and/or {@link #addLeaf}.
*
*
* @throws {Error} If the parameter has the wrong type.
*/
setData : function(nodeArr)
{
if (nodeArr instanceof Array)
{
// Save the user-supplied data.
this._nodeArr = nodeArr;
}
else if (nodeArr !== null && nodeArr !== undefined)
{
throw new Error("Expected array of node objects or null/undefined; " +
"got " + typeof (nodeArr));
}
// Re-render the row array
this.__render();
// Set selections in the selection model now
var selectionModel = this.getTree().getSelectionModel();
var selections = this._selections;
for (var nodeId in selections)
{
var nRowIndex = this.getRowFromNodeId(nodeId);
selectionModel.setSelectionInterval(nRowIndex, nRowIndex);
}
},
/**
* Return the array of node data.
*
* @return {Array}
* Array of node objects.
* See {@link qx.ui.treevirtual.SimpleTreeDataModel} for a description
* nodes in this array.
*/
getData : function()
{
return this._nodeArr;
},
/**
* Clears the tree of all nodes
*
*/
clearData : function ()
{
this._clearSelections();
this.setData([ qx.ui.treevirtual.MTreePrimitive._getEmptyTree() ]);
},
/**
* Add data to an additional column (a column other than the tree column)
* of the tree.
*
* @param nodeId {Integer}
* A node identifier, as previously returned by {@link #addBranch} or
* {@link #addLeaf}.
*
* @param columnIndex {Integer}
* The column number to which the provided data applies
*
* @param data {var}
* The cell data for the specified column
*
*/
setColumnData : function(nodeId, columnIndex, data)
{
this._nodeArr[nodeId].columnData[columnIndex] = data;
},
/**
* Retrieve the data from an additional column (a column other than the
* tree column) of the tree.
*
* @param nodeId {Integer}
* A node identifier, as previously returned by {@link #addBranch} or
* {@link #addLeaf}.
*
* @param columnIndex {Integer}
* The column number to which the provided data applies
*
* @return {var} The cell data for the specified column
*/
getColumnData : function(nodeId, columnIndex)
{
return this._nodeArr[nodeId].columnData[columnIndex];
},
/**
* Set state attributes of a node.
*
* @param nodeReference {Object | Integer}
* The node to have its attributes set. The node can be represented
* either by the node object, or the node id (as would have been
* returned by addBranch(), addLeaf(), etc.)
*
* @param attributes {Map}
* Each property name in the map may correspond to the property names of
* a node which are specified as <i>USER-PROVIDED ATTRIBUTES</i> in
* {@link SimpleTreeDataModel}. Each property value will be assigned
* to the corresponding property of the node specified by nodeId.
*
* @throws {Error} If the node object or id is not valid.
*/
setState : function(nodeReference, attributes)
{
var node;
var nodeId;
if (typeof(nodeReference) == "object")
{
node = nodeReference;
nodeId = node.nodeId;
}
else if (typeof(nodeReference) == "number")
{
nodeId = nodeReference;
node = this._nodeArr[nodeId];
}
else
{
throw new Error("Expected node object or node id");
}
for (var attribute in attributes)
{
// Do any attribute-specific processing
switch(attribute)
{
case "bSelected":
var nRowIndex = this.getRowFromNodeId(nodeId);
var selectionModel = this.getTree().getSelectionModel();
var TV = qx.ui.treevirtual.TreeVirtual;
var bChangeSelection =
(typeof(nRowIndex) === "number" &&
this.getTree().getSelectionMode() != TV.SelectionMode.NONE);
// The selected state is changing. Keep track of what is selected
if (attributes[attribute])
{
this._selections[nodeId] = true;
// Add selection range for node
if (bChangeSelection &&
! selectionModel.isSelectedIndex(nRowIndex))
{
selectionModel.setSelectionInterval(nRowIndex, nRowIndex);
}
}
else
{
delete this._selections[nodeId];
// Delete selection range for node
if (bChangeSelection &&
selectionModel.isSelectedIndex(nRowIndex))
{
selectionModel.removeSelectionInterval(nRowIndex, nRowIndex);
}
}
break;
case "bOpened":
// Don't do anything if the requested state is the same as the
// current state.
if (attributes[attribute] == node.bOpened)
{
break;
}
// Get the tree to which this data model is attached
var tree = this.__tree;
// Are we opening or closing?
if (node.bOpened)
{
// We're closing. If there are listeners, generate a treeClose
// event.
tree.fireDataEvent("treeClose", node);
}
else
{
// We're opening. Are there any children?
if (node.children.length > 0)
{
// Yup. If there any listeners, generate a "treeOpenWithContent"
// event.
tree.fireDataEvent("treeOpenWithContent", node);
}
else
{
// No children. If there are listeners, generate a
// "treeOpenWhileEmpty" event.
tree.fireDataEvent("treeOpenWhileEmpty", node);
}
}
// Event handler may have modified the opened state. Check before
// toggling.
if (!node.bHideOpenClose)
{
// It's still boolean. Toggle the state
node.bOpened = !node.bOpened;
// Clear the old selections in the tree
tree.getSelectionModel()._resetSelection();
}
// Re-render the row data since formerly visible rows may now be
// invisible, or vice versa.
this.setData();
break;
default:
// no attribute-specific processing required
break;
}
// Set the new attribute value
node[attribute] = attributes[attribute];
}
},
/**
* Return the mapping of nodes to rendered rows. This function is intended
* for use by the cell renderer, not by users of this class.
* It is also useful to select a node.
*
* @return {Array}
* The array containing mappings of nodes to rendered rows.
*/
getNodeRowMap : function()
{
return this._nodeRowMap;
},
/**
* This operation maps nodes to rowIndexes. It does the opposite job to {@link #getNodeFromRow}.
*
* @param nodeId {Integer}
* The id of the node (as would have been returned by addBranch(),
* addLeaf(), etc.) to get the row index for.
* @return {Integer} row index for the given node ID
*/
getRowFromNodeId : function(nodeId)
{
return this._nodeRowMap[nodeId];
},
/**
* This operation maps rowIndexes to nodes. It does the opposite job to {@link #getRowFromNodeId}.
* This function is useful to map selection (row based) to nodes.
*
* @param rowIndex {Integer} zero-based row index.
* @return {Object} node associated to <tt>rowIndex</tt>.
*/
getNodeFromRow : function(rowIndex)
{
return this._nodeArr[this._rowArr[rowIndex][this._treeColumn].nodeId];
},
/**
* Clear all selections in the data model. This method does not clear
* selections displayed in the widget, and is intended for internal use,
* not by users of this class.
*
*/
_clearSelections : function()
{
// Clear selected state for any selected nodes.
for (var selection in this._selections)
{
this._nodeArr[selection].bSelected = false;
}
// Reinitialize selections array.
this._selections = { };
},
/**
* Return the nodes that are currently selected.
*
* @return {Array}
* An array containing the nodes that are currently selected.
*/
getSelectedNodes : function()
{
var nodes = [ ];
for (var nodeId in this._selections)
{
nodes.push(this._nodeArr[nodeId]);
}
return nodes;
},
// property apply
_applyFilter : function(value, old)
{
this.setData();
}
},
destruct : function()
{
this._rowArr = this._nodeArr = this._nodeRowMap = this._selections =
this.__tree = this.__tempTreeData = null;
},
defer : function(statics)
{
// For backward compatibility, ensure the Type values are available from
// this class as well as from the mixin.
statics.Type = qx.ui.treevirtual.MTreePrimitive.Type;
}
});