diff options
Diffstat (limited to 'mailnews/base/content/jsTreeView.js')
-rw-r--r-- | mailnews/base/content/jsTreeView.js | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/mailnews/base/content/jsTreeView.js b/mailnews/base/content/jsTreeView.js new file mode 100644 index 000000000..abec7df4a --- /dev/null +++ b/mailnews/base/content/jsTreeView.js @@ -0,0 +1,235 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file contains a prototype object designed to make the implementation of + * nsITreeViews in javascript simpler. This object requires that consumers + * override the _rebuild function. This function must set the _rowMap object to + * an array of objects fitting the following interface: + * + * readonly attribute string id - a unique identifier for the row/object + * readonly attribute integer level - the hierarchy level of the row + * attribute boolean open - whether or not this item's children are exposed + * string getText(aColName) - return the text to display for this row in the + * specified column + * string getProperties() - return the css-selectors + * attribute array children - return an array of child-objects also meeting this + * interface + */ + +function PROTO_TREE_VIEW() { + this._tree = null; + this._rowMap = []; + this._persistOpenMap = []; +} + +PROTO_TREE_VIEW.prototype = { + get rowCount() { + return this._rowMap.length; + }, + + /** + * CSS files will cue off of these. Note that we reach into the rowMap's + * items so that custom data-displays can define their own properties + */ + getCellProperties: function jstv_getCellProperties(aRow, aCol) { + return this._rowMap[aRow].getProperties(aCol); + }, + + /** + * The actual text to display in the tree + */ + getCellText: function jstv_getCellText(aRow, aCol) { + return this._rowMap[aRow].getText(aCol.id); + }, + + /** + * The jstv items take care of assigning this when building children lists + */ + getLevel: function jstv_getLevel(aIndex) { + return this._rowMap[aIndex].level; + }, + + /** + * This is easy since the jstv items assigned the _parent property when making + * the child lists + */ + getParentIndex: function jstv_getParentIndex(aIndex) { + return this._rowMap.indexOf(this._rowMap[aIndex]._parent); + }, + + /** + * This is duplicative for our normal jstv views, but custom data-displays may + * want to do something special here + */ + getRowProperties: function jstv_getRowProperties(aRow) { + return this._rowMap[aRow].getProperties(); + }, + + /** + * If an item in our list has the same level and parent as us, it's a sibling + */ + hasNextSibling: function jstv_hasNextSibling(aIndex, aNextIndex) { + let targetLevel = this._rowMap[aIndex].level; + for (let i = aNextIndex + 1; i < this._rowMap.length; i++) { + if (this._rowMap[i].level == targetLevel) + return true; + if (this._rowMap[i].level < targetLevel) + return false; + } + return false; + }, + + /** + * If we have a child-list with at least one element, we are a container. + */ + isContainer: function jstv_isContainer(aIndex) { + return this._rowMap[aIndex].children.length > 0; + }, + + isContainerEmpty: function jstv_isContainerEmpty(aIndex) { + // If the container has no children, the container is empty. + return !this._rowMap[aIndex].children.length; + }, + + /** + * Just look at the jstv item here + */ + isContainerOpen: function jstv_isContainerOpen(aIndex) { + return this._rowMap[aIndex].open; + }, + + isEditable: function jstv_isEditable(aRow, aCol) { + // We don't support editing rows in the tree yet. + return false; + }, + + isSeparator: function jstv_isSeparator(aIndex) { + // There are no separators in our trees + return false; + }, + + isSorted: function jstv_isSorted() { + // We do our own customized sorting + return false; + }, + + setTree: function jstv_setTree(aTree) { + this._tree = aTree; + }, + + recursivelyAddToMap: function jstv_recursivelyAddToMap(aChild, aNewIndex) { + // When we add sub-children, we're going to need to increase our index + // for the next add item at our own level. + let currentCount = this._rowMap.length; + if (aChild.children.length && aChild.open) { + for (let [i, child] in Iterator(this._rowMap[aNewIndex].children)) { + let index = aNewIndex + i + 1; + this._rowMap.splice(index, 0, child); + aNewIndex += this.recursivelyAddToMap(child, index); + } + } + return this._rowMap.length - currentCount; + }, + + /** + * Opens or closes a container with children. The logic here is a bit hairy, so + * be very careful about changing anything. + */ + toggleOpenState: function jstv_toggleOpenState(aIndex) { + + // Ok, this is a bit tricky. + this._rowMap[aIndex]._open = !this._rowMap[aIndex].open; + + if (!this._rowMap[aIndex].open) { + // We're closing the current container. Remove the children + + // Note that we can't simply splice out children.length, because some of + // them might have children too. Find out how many items we're actually + // going to splice + let level = this._rowMap[aIndex].level; + let row = aIndex + 1; + while (row < this._rowMap.length && this._rowMap[row].level > level) { + row++; + } + let count = row - aIndex - 1; + this._rowMap.splice(aIndex + 1, count); + + // Remove us from the persist map + let index = this._persistOpenMap.indexOf(this._rowMap[aIndex].id); + if (index != -1) + this._persistOpenMap.splice(index, 1); + + // Notify the tree of changes + if (this._tree) { + this._tree.rowCountChanged(aIndex + 1, -count); + } + } else { + // We're opening the container. Add the children to our map + + // Note that these children may have been open when we were last closed, + // and if they are, we also have to add those grandchildren to the map + let oldCount = this._rowMap.length; + this.recursivelyAddToMap(this._rowMap[aIndex], aIndex); + + // Add this container to the persist map + let id = this._rowMap[aIndex].id; + if (this._persistOpenMap.indexOf(id) == -1) + this._persistOpenMap.push(id); + + // Notify the tree of changes + if (this._tree) + this._tree.rowCountChanged(aIndex + 1, this._rowMap.length - oldCount); + } + + // Invalidate the toggled row, so that the open/closed marker changes + if (this._tree) + this._tree.invalidateRow(aIndex); + }, + + // We don't implement any of these at the moment + canDrop: function jstv_canDrop(aIndex, aOrientation) {}, + drop: function jstv_drop(aRow, aOrientation) {}, + performAction: function jstv_performAction(aAction) {}, + performActionOnCell: function jstv_performActionOnCell(aAction, aRow, aCol) {}, + performActionOnRow: function jstv_performActionOnRow(aAction, aRow) {}, + selectionChanged: function jstv_selectionChanged() {}, + setCellText: function jstv_setCellText(aRow, aCol, aValue) {}, + setCellValue: function jstv_setCellValue(aRow, aCol, aValue) {}, + getCellValue: function jstv_getCellValue(aRow, aCol) {}, + getColumnProperties: function jstv_getColumnProperties(aCol) { return ""; }, + getImageSrc: function jstv_getImageSrc(aRow, aCol) {}, + getProgressMode: function jstv_getProgressMode(aRow, aCol) {}, + cycleCell: function jstv_cycleCell(aRow, aCol) {}, + cycleHeader: function jstv_cycleHeader(aCol) {}, + + _tree: null, + + /** + * An array of jstv items, where each item corresponds to a row in the tree + */ + _rowMap: null, + + /** + * This is a javascript map of which containers we had open, so that we can + * persist their state over-time. It is designed to be used as a JSON object. + */ + _persistOpenMap: null, + + _restoreOpenStates: function jstv__restoreOpenStates() { + // Note that as we iterate through here, .length may grow + for (let i = 0; i < this._rowMap.length; i++) { + if (this._persistOpenMap.indexOf(this._rowMap[i].id) != -1) + this.toggleOpenState(i); + } + }, + + QueryInterface: function QueryInterface(aIID) { + if (aIID.equals(Components.interfaces.nsITreeView) || + aIID.equals(Components.interfaces.nsISupports)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + } +}; |