1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/shared/widgets/FastListWidget.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,250 @@ 1.4 +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 +"use strict"; 1.10 + 1.11 +const EventEmitter = require("devtools/toolkit/event-emitter"); 1.12 +const { Cu, Ci } = require("chrome"); 1.13 +const { ViewHelpers } = Cu.import("resource:///modules/devtools/ViewHelpers.jsm", {}); 1.14 + 1.15 +/** 1.16 + * A list menu widget that attempts to be very fast. 1.17 + * 1.18 + * Note: this widget should be used in tandem with the WidgetMethods in 1.19 + * ViewHelpers.jsm. 1.20 + * 1.21 + * @param nsIDOMNode aNode 1.22 + * The element associated with the widget. 1.23 + */ 1.24 +const FastListWidget = module.exports = function FastListWidget(aNode) { 1.25 + this.document = aNode.ownerDocument; 1.26 + this.window = this.document.defaultView; 1.27 + this._parent = aNode; 1.28 + this._fragment = this.document.createDocumentFragment(); 1.29 + 1.30 + // This is a prototype element that each item added to the list clones. 1.31 + this._templateElement = this.document.createElement("hbox"); 1.32 + 1.33 + // Create an internal scrollbox container. 1.34 + this._list = this.document.createElement("scrollbox"); 1.35 + this._list.className = "fast-list-widget-container theme-body"; 1.36 + this._list.setAttribute("flex", "1"); 1.37 + this._list.setAttribute("orient", "vertical"); 1.38 + this._list.setAttribute("tabindex", "0"); 1.39 + this._list.addEventListener("keypress", e => this.emit("keyPress", e), false); 1.40 + this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false); 1.41 + this._parent.appendChild(this._list); 1.42 + 1.43 + this._orderedMenuElementsArray = []; 1.44 + this._itemsByElement = new Map(); 1.45 + 1.46 + // This widget emits events that can be handled in a MenuContainer. 1.47 + EventEmitter.decorate(this); 1.48 + 1.49 + // Delegate some of the associated node's methods to satisfy the interface 1.50 + // required by MenuContainer instances. 1.51 + ViewHelpers.delegateWidgetAttributeMethods(this, aNode); 1.52 + ViewHelpers.delegateWidgetEventMethods(this, aNode); 1.53 +} 1.54 + 1.55 +FastListWidget.prototype = { 1.56 + /** 1.57 + * Inserts an item in this container at the specified index, optionally 1.58 + * grouping by name. 1.59 + * 1.60 + * @param number aIndex 1.61 + * The position in the container intended for this item. 1.62 + * @param nsIDOMNode aContents 1.63 + * The node to be displayed in the container. 1.64 + * @param Object aAttachment [optional] 1.65 + * Extra data for the user. 1.66 + * @return nsIDOMNode 1.67 + * The element associated with the displayed item. 1.68 + */ 1.69 + insertItemAt: function(aIndex, aContents, aAttachment={}) { 1.70 + let element = this._templateElement.cloneNode(); 1.71 + element.appendChild(aContents); 1.72 + 1.73 + if (aIndex >= 0) { 1.74 + throw new Error("FastListWidget only supports appending items."); 1.75 + } 1.76 + 1.77 + this._fragment.appendChild(element); 1.78 + this._orderedMenuElementsArray.push(element); 1.79 + this._itemsByElement.set(element, this); 1.80 + 1.81 + return element; 1.82 + }, 1.83 + 1.84 + /** 1.85 + * This is a non-standard widget implementation method. When appending items, 1.86 + * they are queued in a document fragment. This method appends the document 1.87 + * fragment to the dom. 1.88 + */ 1.89 + flush: function() { 1.90 + this._list.appendChild(this._fragment); 1.91 + }, 1.92 + 1.93 + /** 1.94 + * Removes all of the child nodes from this container. 1.95 + */ 1.96 + removeAllItems: function() { 1.97 + let parent = this._parent; 1.98 + let list = this._list; 1.99 + 1.100 + while (list.hasChildNodes()) { 1.101 + list.firstChild.remove(); 1.102 + } 1.103 + 1.104 + this._selectedItem = null; 1.105 + 1.106 + this._orderedMenuElementsArray.length = 0; 1.107 + this._itemsByElement.clear(); 1.108 + }, 1.109 + 1.110 + /** 1.111 + * Remove the given item. 1.112 + */ 1.113 + removeChild: function(child) { 1.114 + throw new Error("Not yet implemented"); 1.115 + }, 1.116 + 1.117 + /** 1.118 + * Gets the currently selected child node in this container. 1.119 + * @return nsIDOMNode 1.120 + */ 1.121 + get selectedItem() { 1.122 + return this._selectedItem; 1.123 + }, 1.124 + 1.125 + /** 1.126 + * Sets the currently selected child node in this container. 1.127 + * @param nsIDOMNode child 1.128 + */ 1.129 + set selectedItem(child) { 1.130 + let menuArray = this._orderedMenuElementsArray; 1.131 + 1.132 + if (!child) { 1.133 + this._selectedItem = null; 1.134 + } 1.135 + for (let node of menuArray) { 1.136 + if (node == child) { 1.137 + node.classList.add("selected"); 1.138 + this._selectedItem = node; 1.139 + } else { 1.140 + node.classList.remove("selected"); 1.141 + } 1.142 + } 1.143 + 1.144 + this.ensureElementIsVisible(this.selectedItem); 1.145 + }, 1.146 + 1.147 + /** 1.148 + * Returns the child node in this container situated at the specified index. 1.149 + * 1.150 + * @param number index 1.151 + * The position in the container intended for this item. 1.152 + * @return nsIDOMNode 1.153 + * The element associated with the displayed item. 1.154 + */ 1.155 + getItemAtIndex: function(index) { 1.156 + return this._orderedMenuElementsArray[index]; 1.157 + }, 1.158 + 1.159 + /** 1.160 + * Adds a new attribute or changes an existing attribute on this container. 1.161 + * 1.162 + * @param string name 1.163 + * The name of the attribute. 1.164 + * @param string value 1.165 + * The desired attribute value. 1.166 + */ 1.167 + setAttribute: function(name, value) { 1.168 + this._parent.setAttribute(name, value); 1.169 + 1.170 + if (name == "emptyText") { 1.171 + this._textWhenEmpty = value; 1.172 + } 1.173 + }, 1.174 + 1.175 + /** 1.176 + * Removes an attribute on this container. 1.177 + * 1.178 + * @param string name 1.179 + * The name of the attribute. 1.180 + */ 1.181 + removeAttribute: function(name) { 1.182 + this._parent.removeAttribute(name); 1.183 + 1.184 + if (name == "emptyText") { 1.185 + this._removeEmptyText(); 1.186 + } 1.187 + }, 1.188 + 1.189 + /** 1.190 + * Ensures the specified element is visible. 1.191 + * 1.192 + * @param nsIDOMNode element 1.193 + * The element to make visible. 1.194 + */ 1.195 + ensureElementIsVisible: function(element) { 1.196 + if (!element) { 1.197 + return; 1.198 + } 1.199 + 1.200 + // Ensure the element is visible but not scrolled horizontally. 1.201 + let boxObject = this._list.boxObject.QueryInterface(Ci.nsIScrollBoxObject); 1.202 + boxObject.ensureElementIsVisible(element); 1.203 + boxObject.scrollBy(-this._list.clientWidth, 0); 1.204 + }, 1.205 + 1.206 + /** 1.207 + * Sets the text displayed in this container when empty. 1.208 + * @param string aValue 1.209 + */ 1.210 + set _textWhenEmpty(aValue) { 1.211 + if (this._emptyTextNode) { 1.212 + this._emptyTextNode.setAttribute("value", aValue); 1.213 + } 1.214 + this._emptyTextValue = aValue; 1.215 + this._showEmptyText(); 1.216 + }, 1.217 + 1.218 + /** 1.219 + * Creates and appends a label signaling that this container is empty. 1.220 + */ 1.221 + _showEmptyText: function() { 1.222 + if (this._emptyTextNode || !this._emptyTextValue) { 1.223 + return; 1.224 + } 1.225 + let label = this.document.createElement("label"); 1.226 + label.className = "plain fast-list-widget-empty-text"; 1.227 + label.setAttribute("value", this._emptyTextValue); 1.228 + 1.229 + this._parent.insertBefore(label, this._list); 1.230 + this._emptyTextNode = label; 1.231 + }, 1.232 + 1.233 + /** 1.234 + * Removes the label signaling that this container is empty. 1.235 + */ 1.236 + _removeEmptyText: function() { 1.237 + if (!this._emptyTextNode) { 1.238 + return; 1.239 + } 1.240 + this._parent.removeChild(this._emptyTextNode); 1.241 + this._emptyTextNode = null; 1.242 + }, 1.243 + 1.244 + window: null, 1.245 + document: null, 1.246 + _parent: null, 1.247 + _list: null, 1.248 + _selectedItem: null, 1.249 + _orderedMenuElementsArray: null, 1.250 + _itemsByElement: null, 1.251 + _emptyTextNode: null, 1.252 + _emptyTextValue: "" 1.253 +};