summaryrefslogtreecommitdiffstats
path: root/mailnews/base/content/jsTreeView.js
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/base/content/jsTreeView.js')
-rw-r--r--mailnews/base/content/jsTreeView.js235
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;
+ }
+};