/**
* @module ui/tree-list.reel
* @requires montage/ui/component
*/
var Component = require("ui/component").Component,
TreeNode = require("core/tree-controller").TreeNode,
WeakMap = require("collections/weak-map");
/**
* @class TreeList
* @extends Component
*/
exports.TreeList = Component.specialize(/** @lends TreeList.prototype */ {
_controller: {
value: null
},
controller: {
get: function () {
return this._controller;
},
set: function (value) {
if (this._controller !== value) {
this._controller = value;
this._heights = new WeakMap();
if (this._controller) {
this._controller.delegate = this;
}
}
}
},
_rowTopMargins: {
get: function () {
if (!this.__rowTopMargins) {
this.__rowTopMargins = [];
}
return this.__rowTopMargins;
}
},
_totalHeight: {
value: 0
},
_isRootExpanded: {
value: false
},
isRootExpanded: {
get: function () {
return this._isRootExpanded;
},
set: function (value) {
if (this._isRootExpanded !== value) {
this._isRootExpanded = value;
if (this._controller) {
this._controller.expandNode(this._controller.data);
}
}
}
},
_isRootVisible: {
value: true
},
isRootVisible: {
get: function () {
return this._isRootVisible;
},
set: function (value) {
if (this._isRootVisible !== value) {
this._isRootVisible = value;
this.handleTreeChange();
}
}
},
_data: {
value: null
},
handleTreeChange: {
value: function () {
var i, n;
if (this._controller) {
if (this._controller.data !== this._data) {
this._data = this._controller.data;
if (this.isRootExpanded || !this.isRootVisible) {
this._controller.expandNode(this._controller.data);
}
}
}
this._heights = new WeakMap();
if (this.repetition) {
this.repetitionController.content = this.getIterations();
}
if (this.controller &&
this.controller.data &&
this.controller.data.children &&
typeof this.rowHeight === "function") {
this._totalHeight = 0;
this._rowTopMargins.length = 0;
this._rowTopMargins.push(0);
for (i = 0, n = this.controller.data.children.length; i < n; i += 1) {
this._totalHeight += this.rowHeight(this.controller.data.children[i]);
this._rowTopMargins.push(this._totalHeight);
}
}
}
},
/**
Represents the range of visible rows in the view window as
an interval [startRow, endRow)
*/
_visibilityRange: {
value: [0, 0]
},
visibilityRange: {
get: function () {
return this._visibilityRange;
},
set: function (value) {
this._visibilityRange = value;
this.repetitionController.content = this.getIterations();
}
},
_getNodeHeight: {
value: function (node) {
var expansionMetadata,
height;
if (this._controller) {
expansionMetadata = this._controller._expansionMap.get(node);
if (expansionMetadata) {
height = this._heights.get(node);
if (!height) {
height = this._computeExpandedNodeHeight(node);
this._heights.set(node, height);
}
return height;
}
return 1;
}
}
},
_computeExpandedNodeHeight: {
value: function (node) {
if (this._controller) {
var children = this._controller.childrenFromNode(node),
height = 1,
length,
i;
if (children) {
length = children.length;
for (i = 0; i < length; i++) {
height += this._getNodeHeight(children[i]);
}
}
return height;
}
}
},
_addIterations: {
value: function (node, iterationsArray, row, depth, parent) {
var length,
children,
height,
treeNode,
i;
if (node) {
treeNode = new TreeNode(node, this._controller);
treeNode.height = this._getNodeHeight(node);
treeNode.parent = parent;
treeNode.row = row;
if (!this.isRootVisible && (node === this._controller.data)) {
iterationsArray.push(treeNode);
treeNode.depth = depth;
depth--;
} else {
treeNode.depth = depth;
iterationsArray.push(treeNode);
row++;
}
if (this._controller.isNodeExpanded(node)) {
children = this._controller.childrenFromNode(node);
if (children) {
length = children.length;
for (i = 0; i < length; i++) {
if (this._isVisible(row, height = this._getNodeHeight(children[i]))) {
row = this._addIterations(children[i], iterationsArray, row, depth + 1, treeNode);
} else {
row += height;
}
}
}
}
}
return row;
}
},
_isVisible: {
value: function (startRow, height) {
var endRow = startRow + height;
return ((startRow < this._visibilityRange[1]) && (endRow > this._visibilityRange[0]));
}
},
getIterations: {
value: function () {
var iterations = [];
if (this._controller.data && (this._visibilityRange[1] > this._visibilityRange[0])) {
if (this._isVisible(0, this._getNodeHeight(this._controller.data))) {
this._addIterations(this._controller.data, iterations, 0, 0);
}
}
return iterations;
}
},
templateDidLoad: {
value: function() {
var self = this;
this.repetition.willDraw = function () {
self.needsDraw = true;
};
}
},
enterDocument: {
value: function (firstTime) {
if (firstTime) {
window.addEventListener("resize", this, false);
this._element.addEventListener("scroll", this, false);
}
this.handleScroll();
this.handleTreeChange();
}
},
handleResize: {
value: function () {
// Here we use the window height instead of the element height for
// the calculation so that we still have enough rows even if the
// tree list is later resized (as long as it's not resized taller
// than the window). We're wasting a few rows when the tree list
// is shorter than the window, but not that many.
var startRow, height, endRow, index;
if (typeof this.rowHeight === "function") {
index = 0;
height = this._element.scrollTop;
while (this._rowTopMargins[index + 1] < height) {
index++;
}
startRow = index;
height = this._rowTopMargins[startRow] + window.innerHeight;
while (this._rowTopMargins[index] < height) {
index++;
}
endRow = index;
} else {
startRow = this._element.scrollTop / this._rowHeight;
height = window.innerHeight / this._rowHeight;
endRow = startRow + height;
}
this.visibilityRange = [startRow, endRow];
}
},
_rowHeight: {
value: 40
},
rowHeight: {
get: function () {
return this._rowHeight;
},
set: function (value) {
if (this._rowHeight !== value) {
this._rowHeight = value;
this.needsDraw = true;
if (this.repetitionController) {
this.handleTreeChange();
this.handleResize();
}
}
}
},
_indentationWidth: {
value: 30
},
indentationWidth: {
get: function () {
return this._indentationWidth;
},
set: function (value) {
if (this._indentationWidth !== value) {
this._indentationWidth = value;
this.needsDraw = true;
}
}
},
handleScroll: {
value: function () {
this.handleResize();
}
},
draw: {
value: function () {
var iteration,
element,
rowHeight,
i, length;
for (i = 0, length = this.repetition._drawnIterations.length; i < length; i++) {
iteration = this.repetition._drawnIterations[i];
element = iteration.cachedFirstElement || iteration.firstElement;
if (typeof this.rowHeight === "function") {
if (!this.isRootVisible && iteration.object.data === this.controller.data) {
rowHeight = 0;
element.style.marginTop = 0;
element.style.height = this._totalHeight + "px";
element.style.visibility = "hidden";
} else {
rowHeight = this._rowTopMargins[iteration.object.row + 1] - this._rowTopMargins[iteration.object.row];
element.style.height = rowHeight + "px";
element.style.marginTop = this._rowTopMargins[iteration.object.row] + "px";
element.style.visibility = "visible";
}
} else {
element.style.marginTop = this._rowHeight * iteration.object.row + "px";
if (!this.isRootVisible && iteration.object.data === this.controller.data) {
element.style.height = this._rowHeight * (iteration.object.height - 1) + "px";
element.style.visibility = "hidden";
} else {
element.style.height = this._rowHeight * iteration.object.height + "px";
element.style.visibility = "visible";
}
}
element.style.marginLeft = this._indentationWidth * iteration.object.depth + "px";
}
}
}
});