browser/devtools/shared/widgets/FastListWidget.js

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:901b86a7e940
1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 "use strict";
7
8 const EventEmitter = require("devtools/toolkit/event-emitter");
9 const { Cu, Ci } = require("chrome");
10 const { ViewHelpers } = Cu.import("resource:///modules/devtools/ViewHelpers.jsm", {});
11
12 /**
13 * A list menu widget that attempts to be very fast.
14 *
15 * Note: this widget should be used in tandem with the WidgetMethods in
16 * ViewHelpers.jsm.
17 *
18 * @param nsIDOMNode aNode
19 * The element associated with the widget.
20 */
21 const FastListWidget = module.exports = function FastListWidget(aNode) {
22 this.document = aNode.ownerDocument;
23 this.window = this.document.defaultView;
24 this._parent = aNode;
25 this._fragment = this.document.createDocumentFragment();
26
27 // This is a prototype element that each item added to the list clones.
28 this._templateElement = this.document.createElement("hbox");
29
30 // Create an internal scrollbox container.
31 this._list = this.document.createElement("scrollbox");
32 this._list.className = "fast-list-widget-container theme-body";
33 this._list.setAttribute("flex", "1");
34 this._list.setAttribute("orient", "vertical");
35 this._list.setAttribute("tabindex", "0");
36 this._list.addEventListener("keypress", e => this.emit("keyPress", e), false);
37 this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false);
38 this._parent.appendChild(this._list);
39
40 this._orderedMenuElementsArray = [];
41 this._itemsByElement = new Map();
42
43 // This widget emits events that can be handled in a MenuContainer.
44 EventEmitter.decorate(this);
45
46 // Delegate some of the associated node's methods to satisfy the interface
47 // required by MenuContainer instances.
48 ViewHelpers.delegateWidgetAttributeMethods(this, aNode);
49 ViewHelpers.delegateWidgetEventMethods(this, aNode);
50 }
51
52 FastListWidget.prototype = {
53 /**
54 * Inserts an item in this container at the specified index, optionally
55 * grouping by name.
56 *
57 * @param number aIndex
58 * The position in the container intended for this item.
59 * @param nsIDOMNode aContents
60 * The node to be displayed in the container.
61 * @param Object aAttachment [optional]
62 * Extra data for the user.
63 * @return nsIDOMNode
64 * The element associated with the displayed item.
65 */
66 insertItemAt: function(aIndex, aContents, aAttachment={}) {
67 let element = this._templateElement.cloneNode();
68 element.appendChild(aContents);
69
70 if (aIndex >= 0) {
71 throw new Error("FastListWidget only supports appending items.");
72 }
73
74 this._fragment.appendChild(element);
75 this._orderedMenuElementsArray.push(element);
76 this._itemsByElement.set(element, this);
77
78 return element;
79 },
80
81 /**
82 * This is a non-standard widget implementation method. When appending items,
83 * they are queued in a document fragment. This method appends the document
84 * fragment to the dom.
85 */
86 flush: function() {
87 this._list.appendChild(this._fragment);
88 },
89
90 /**
91 * Removes all of the child nodes from this container.
92 */
93 removeAllItems: function() {
94 let parent = this._parent;
95 let list = this._list;
96
97 while (list.hasChildNodes()) {
98 list.firstChild.remove();
99 }
100
101 this._selectedItem = null;
102
103 this._orderedMenuElementsArray.length = 0;
104 this._itemsByElement.clear();
105 },
106
107 /**
108 * Remove the given item.
109 */
110 removeChild: function(child) {
111 throw new Error("Not yet implemented");
112 },
113
114 /**
115 * Gets the currently selected child node in this container.
116 * @return nsIDOMNode
117 */
118 get selectedItem() {
119 return this._selectedItem;
120 },
121
122 /**
123 * Sets the currently selected child node in this container.
124 * @param nsIDOMNode child
125 */
126 set selectedItem(child) {
127 let menuArray = this._orderedMenuElementsArray;
128
129 if (!child) {
130 this._selectedItem = null;
131 }
132 for (let node of menuArray) {
133 if (node == child) {
134 node.classList.add("selected");
135 this._selectedItem = node;
136 } else {
137 node.classList.remove("selected");
138 }
139 }
140
141 this.ensureElementIsVisible(this.selectedItem);
142 },
143
144 /**
145 * Returns the child node in this container situated at the specified index.
146 *
147 * @param number index
148 * The position in the container intended for this item.
149 * @return nsIDOMNode
150 * The element associated with the displayed item.
151 */
152 getItemAtIndex: function(index) {
153 return this._orderedMenuElementsArray[index];
154 },
155
156 /**
157 * Adds a new attribute or changes an existing attribute on this container.
158 *
159 * @param string name
160 * The name of the attribute.
161 * @param string value
162 * The desired attribute value.
163 */
164 setAttribute: function(name, value) {
165 this._parent.setAttribute(name, value);
166
167 if (name == "emptyText") {
168 this._textWhenEmpty = value;
169 }
170 },
171
172 /**
173 * Removes an attribute on this container.
174 *
175 * @param string name
176 * The name of the attribute.
177 */
178 removeAttribute: function(name) {
179 this._parent.removeAttribute(name);
180
181 if (name == "emptyText") {
182 this._removeEmptyText();
183 }
184 },
185
186 /**
187 * Ensures the specified element is visible.
188 *
189 * @param nsIDOMNode element
190 * The element to make visible.
191 */
192 ensureElementIsVisible: function(element) {
193 if (!element) {
194 return;
195 }
196
197 // Ensure the element is visible but not scrolled horizontally.
198 let boxObject = this._list.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
199 boxObject.ensureElementIsVisible(element);
200 boxObject.scrollBy(-this._list.clientWidth, 0);
201 },
202
203 /**
204 * Sets the text displayed in this container when empty.
205 * @param string aValue
206 */
207 set _textWhenEmpty(aValue) {
208 if (this._emptyTextNode) {
209 this._emptyTextNode.setAttribute("value", aValue);
210 }
211 this._emptyTextValue = aValue;
212 this._showEmptyText();
213 },
214
215 /**
216 * Creates and appends a label signaling that this container is empty.
217 */
218 _showEmptyText: function() {
219 if (this._emptyTextNode || !this._emptyTextValue) {
220 return;
221 }
222 let label = this.document.createElement("label");
223 label.className = "plain fast-list-widget-empty-text";
224 label.setAttribute("value", this._emptyTextValue);
225
226 this._parent.insertBefore(label, this._list);
227 this._emptyTextNode = label;
228 },
229
230 /**
231 * Removes the label signaling that this container is empty.
232 */
233 _removeEmptyText: function() {
234 if (!this._emptyTextNode) {
235 return;
236 }
237 this._parent.removeChild(this._emptyTextNode);
238 this._emptyTextNode = null;
239 },
240
241 window: null,
242 document: null,
243 _parent: null,
244 _list: null,
245 _selectedItem: null,
246 _orderedMenuElementsArray: null,
247 _itemsByElement: null,
248 _emptyTextNode: null,
249 _emptyTextValue: ""
250 };

mercurial