michael@0: /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: "use strict"; michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); michael@0: michael@0: this.EXPORTED_SYMBOLS = ["SimpleListWidget"]; michael@0: michael@0: /** michael@0: * A very simple vertical list view. michael@0: * michael@0: * Note: this widget should be used in tandem with the WidgetMethods in michael@0: * ViewHelpers.jsm. michael@0: * michael@0: * @param nsIDOMNode aNode michael@0: * The element associated with the widget. michael@0: */ michael@0: function SimpleListWidget(aNode) { michael@0: this.document = aNode.ownerDocument; michael@0: this.window = this.document.defaultView; michael@0: this._parent = aNode; michael@0: michael@0: // Create an internal list container. michael@0: this._list = this.document.createElement("scrollbox"); michael@0: this._list.className = "simple-list-widget-container theme-body"; michael@0: this._list.setAttribute("flex", "1"); michael@0: this._list.setAttribute("orient", "vertical"); michael@0: this._parent.appendChild(this._list); michael@0: michael@0: // Delegate some of the associated node's methods to satisfy the interface michael@0: // required by WidgetMethods instances. michael@0: ViewHelpers.delegateWidgetAttributeMethods(this, aNode); michael@0: ViewHelpers.delegateWidgetEventMethods(this, aNode); michael@0: } michael@0: michael@0: SimpleListWidget.prototype = { michael@0: /** michael@0: * Inserts an item in this container at the specified index. michael@0: * michael@0: * @param number aIndex michael@0: * The position in the container intended for this item. michael@0: * @param nsIDOMNode aContents michael@0: * The node displayed in the container. michael@0: * @return nsIDOMNode michael@0: * The element associated with the displayed item. michael@0: */ michael@0: insertItemAt: function(aIndex, aContents) { michael@0: aContents.classList.add("simple-list-widget-item"); michael@0: michael@0: let list = this._list; michael@0: return list.insertBefore(aContents, list.childNodes[aIndex]); michael@0: }, michael@0: michael@0: /** michael@0: * Returns the child node in this container situated at the specified index. michael@0: * michael@0: * @param number aIndex michael@0: * The position in the container intended for this item. michael@0: * @return nsIDOMNode michael@0: * The element associated with the displayed item. michael@0: */ michael@0: getItemAtIndex: function(aIndex) { michael@0: return this._list.childNodes[aIndex]; michael@0: }, michael@0: michael@0: /** michael@0: * Immediately removes the specified child node from this container. michael@0: * michael@0: * @param nsIDOMNode aChild michael@0: * The element associated with the displayed item. michael@0: */ michael@0: removeChild: function(aChild) { michael@0: this._list.removeChild(aChild); michael@0: michael@0: if (this._selectedItem == aChild) { michael@0: this._selectedItem = null; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Removes all of the child nodes from this container. michael@0: */ michael@0: removeAllItems: function() { michael@0: let list = this._list; michael@0: let parent = this._parent; michael@0: michael@0: while (list.hasChildNodes()) { michael@0: list.firstChild.remove(); michael@0: } michael@0: michael@0: parent.scrollTop = 0; michael@0: parent.scrollLeft = 0; michael@0: this._selectedItem = null; michael@0: }, michael@0: michael@0: /** michael@0: * Gets the currently selected child node in this container. michael@0: * @return nsIDOMNode michael@0: */ michael@0: get selectedItem() { michael@0: return this._selectedItem; michael@0: }, michael@0: michael@0: /** michael@0: * Sets the currently selected child node in this container. michael@0: * @param nsIDOMNode aChild michael@0: */ michael@0: set selectedItem(aChild) { michael@0: let childNodes = this._list.childNodes; michael@0: michael@0: if (!aChild) { michael@0: this._selectedItem = null; michael@0: } michael@0: for (let node of childNodes) { michael@0: if (node == aChild) { michael@0: node.classList.add("selected"); michael@0: this._selectedItem = node; michael@0: } else { michael@0: node.classList.remove("selected"); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Adds a new attribute or changes an existing attribute on this container. michael@0: * michael@0: * @param string aName michael@0: * The name of the attribute. michael@0: * @param string aValue michael@0: * The desired attribute value. michael@0: */ michael@0: setAttribute: function(aName, aValue) { michael@0: this._parent.setAttribute(aName, aValue); michael@0: michael@0: if (aName == "emptyText") { michael@0: this._textWhenEmpty = aValue; michael@0: } else if (aName == "headerText") { michael@0: this._textAsHeader = aValue; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Removes an attribute on this container. michael@0: * michael@0: * @param string aName michael@0: * The name of the attribute. michael@0: */ michael@0: removeAttribute: function(aName) { michael@0: this._parent.removeAttribute(aName); michael@0: michael@0: if (aName == "emptyText") { michael@0: this._removeEmptyText(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Ensures the specified element is visible. michael@0: * michael@0: * @param nsIDOMNode aElement michael@0: * The element to make visible. michael@0: */ michael@0: ensureElementIsVisible: function(aElement) { michael@0: if (!aElement) { michael@0: return; michael@0: } michael@0: michael@0: // Ensure the element is visible but not scrolled horizontally. michael@0: let boxObject = this._list.boxObject.QueryInterface(Ci.nsIScrollBoxObject); michael@0: boxObject.ensureElementIsVisible(aElement); michael@0: boxObject.scrollBy(-this._list.clientWidth, 0); michael@0: }, michael@0: michael@0: /** michael@0: * Sets the text displayed permanently in this container as a header. michael@0: * @param string aValue michael@0: */ michael@0: set _textAsHeader(aValue) { michael@0: if (this._headerTextNode) { michael@0: this._headerTextNode.setAttribute("value", aValue); michael@0: } michael@0: this._headerTextValue = aValue; michael@0: this._showHeaderText(); michael@0: }, michael@0: michael@0: /** michael@0: * Sets the text displayed in this container when empty. michael@0: * @param string aValue michael@0: */ michael@0: set _textWhenEmpty(aValue) { michael@0: if (this._emptyTextNode) { michael@0: this._emptyTextNode.setAttribute("value", aValue); michael@0: } michael@0: this._emptyTextValue = aValue; michael@0: this._showEmptyText(); michael@0: }, michael@0: michael@0: /** michael@0: * Creates and appends a label displayed as this container's header. michael@0: */ michael@0: _showHeaderText: function() { michael@0: if (this._headerTextNode || !this._headerTextValue) { michael@0: return; michael@0: } michael@0: let label = this.document.createElement("label"); michael@0: label.className = "plain simple-list-widget-perma-text"; michael@0: label.setAttribute("value", this._headerTextValue); michael@0: michael@0: this._parent.insertBefore(label, this._list); michael@0: this._headerTextNode = label; michael@0: }, michael@0: michael@0: /** michael@0: * Creates and appends a label signaling that this container is empty. michael@0: */ michael@0: _showEmptyText: function() { michael@0: if (this._emptyTextNode || !this._emptyTextValue) { michael@0: return; michael@0: } michael@0: let label = this.document.createElement("label"); michael@0: label.className = "plain simple-list-widget-empty-text"; michael@0: label.setAttribute("value", this._emptyTextValue); michael@0: michael@0: this._parent.appendChild(label); michael@0: this._emptyTextNode = label; michael@0: }, michael@0: michael@0: /** michael@0: * Removes the label signaling that this container is empty. michael@0: */ michael@0: _removeEmptyText: function() { michael@0: if (!this._emptyTextNode) { michael@0: return; michael@0: } michael@0: this._parent.removeChild(this._emptyTextNode); michael@0: this._emptyTextNode = null; michael@0: }, michael@0: michael@0: window: null, michael@0: document: null, michael@0: _parent: null, michael@0: _list: null, michael@0: _selectedItem: null, michael@0: _headerTextNode: null, michael@0: _headerTextValue: "", michael@0: _emptyTextNode: null, michael@0: _emptyTextValue: "" michael@0: };