|
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 Ci = Components.interfaces; |
|
9 const Cu = Components.utils; |
|
10 |
|
11 const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms |
|
12 |
|
13 Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); |
|
14 Cu.import("resource://gre/modules/devtools/event-emitter.js"); |
|
15 |
|
16 this.EXPORTED_SYMBOLS = ["BreadcrumbsWidget"]; |
|
17 |
|
18 /** |
|
19 * A breadcrumb-like list of items. |
|
20 * |
|
21 * Note: this widget should be used in tandem with the WidgetMethods in |
|
22 * ViewHelpers.jsm. |
|
23 * |
|
24 * @param nsIDOMNode aNode |
|
25 * The element associated with the widget. |
|
26 */ |
|
27 this.BreadcrumbsWidget = function BreadcrumbsWidget(aNode) { |
|
28 this.document = aNode.ownerDocument; |
|
29 this.window = this.document.defaultView; |
|
30 this._parent = aNode; |
|
31 |
|
32 // Create an internal arrowscrollbox container. |
|
33 this._list = this.document.createElement("arrowscrollbox"); |
|
34 this._list.className = "breadcrumbs-widget-container"; |
|
35 this._list.setAttribute("flex", "1"); |
|
36 this._list.setAttribute("orient", "horizontal"); |
|
37 this._list.setAttribute("clicktoscroll", "true") |
|
38 this._list.addEventListener("keypress", e => this.emit("keyPress", e), false); |
|
39 this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false); |
|
40 this._parent.appendChild(this._list); |
|
41 |
|
42 // By default, hide the arrows. We let the arrowscrollbox show them |
|
43 // in case of overflow. |
|
44 this._list._scrollButtonUp.collapsed = true; |
|
45 this._list._scrollButtonDown.collapsed = true; |
|
46 this._list.addEventListener("underflow", this._onUnderflow.bind(this), false); |
|
47 this._list.addEventListener("overflow", this._onOverflow.bind(this), false); |
|
48 |
|
49 |
|
50 // These separators are used for CSS purposes only, and are positioned |
|
51 // off screen, but displayed with -moz-element. |
|
52 this._separators = this.document.createElement("box"); |
|
53 this._separators.className = "breadcrumb-separator-container"; |
|
54 this._separators.innerHTML = |
|
55 "<box id='breadcrumb-separator-before'></box>" + |
|
56 "<box id='breadcrumb-separator-after'></box>" + |
|
57 "<box id='breadcrumb-separator-normal'></box>"; |
|
58 this._parent.appendChild(this._separators); |
|
59 |
|
60 // This widget emits events that can be handled in a MenuContainer. |
|
61 EventEmitter.decorate(this); |
|
62 |
|
63 // Delegate some of the associated node's methods to satisfy the interface |
|
64 // required by MenuContainer instances. |
|
65 ViewHelpers.delegateWidgetAttributeMethods(this, aNode); |
|
66 ViewHelpers.delegateWidgetEventMethods(this, aNode); |
|
67 }; |
|
68 |
|
69 BreadcrumbsWidget.prototype = { |
|
70 /** |
|
71 * Inserts an item in this container at the specified index. |
|
72 * |
|
73 * @param number aIndex |
|
74 * The position in the container intended for this item. |
|
75 * @param nsIDOMNode aContents |
|
76 * The node displayed in the container. |
|
77 * @return nsIDOMNode |
|
78 * The element associated with the displayed item. |
|
79 */ |
|
80 insertItemAt: function(aIndex, aContents) { |
|
81 let list = this._list; |
|
82 let breadcrumb = new Breadcrumb(this, aContents); |
|
83 return list.insertBefore(breadcrumb._target, list.childNodes[aIndex]); |
|
84 }, |
|
85 |
|
86 /** |
|
87 * Returns the child node in this container situated at the specified index. |
|
88 * |
|
89 * @param number aIndex |
|
90 * The position in the container intended for this item. |
|
91 * @return nsIDOMNode |
|
92 * The element associated with the displayed item. |
|
93 */ |
|
94 getItemAtIndex: function(aIndex) { |
|
95 return this._list.childNodes[aIndex]; |
|
96 }, |
|
97 |
|
98 /** |
|
99 * Removes the specified child node from this container. |
|
100 * |
|
101 * @param nsIDOMNode aChild |
|
102 * The element associated with the displayed item. |
|
103 */ |
|
104 removeChild: function(aChild) { |
|
105 this._list.removeChild(aChild); |
|
106 |
|
107 if (this._selectedItem == aChild) { |
|
108 this._selectedItem = null; |
|
109 } |
|
110 }, |
|
111 |
|
112 /** |
|
113 * Removes all of the child nodes from this container. |
|
114 */ |
|
115 removeAllItems: function() { |
|
116 let list = this._list; |
|
117 |
|
118 while (list.hasChildNodes()) { |
|
119 list.firstChild.remove(); |
|
120 } |
|
121 |
|
122 this._selectedItem = null; |
|
123 }, |
|
124 |
|
125 /** |
|
126 * Gets the currently selected child node in this container. |
|
127 * @return nsIDOMNode |
|
128 */ |
|
129 get selectedItem() { |
|
130 return this._selectedItem; |
|
131 }, |
|
132 |
|
133 /** |
|
134 * Sets the currently selected child node in this container. |
|
135 * @param nsIDOMNode aChild |
|
136 */ |
|
137 set selectedItem(aChild) { |
|
138 let childNodes = this._list.childNodes; |
|
139 |
|
140 if (!aChild) { |
|
141 this._selectedItem = null; |
|
142 } |
|
143 for (let node of childNodes) { |
|
144 if (node == aChild) { |
|
145 node.setAttribute("checked", ""); |
|
146 this._selectedItem = node; |
|
147 } else { |
|
148 node.removeAttribute("checked"); |
|
149 } |
|
150 } |
|
151 }, |
|
152 |
|
153 /** |
|
154 * Returns the value of the named attribute on this container. |
|
155 * |
|
156 * @param string aName |
|
157 * The name of the attribute. |
|
158 * @return string |
|
159 * The current attribute value. |
|
160 */ |
|
161 getAttribute: function(aName) { |
|
162 if (aName == "scrollPosition") return this._list.scrollPosition; |
|
163 if (aName == "scrollWidth") return this._list.scrollWidth; |
|
164 return this._parent.getAttribute(aName); |
|
165 }, |
|
166 |
|
167 /** |
|
168 * Ensures the specified element is visible. |
|
169 * |
|
170 * @param nsIDOMNode aElement |
|
171 * The element to make visible. |
|
172 */ |
|
173 ensureElementIsVisible: function(aElement) { |
|
174 if (!aElement) { |
|
175 return; |
|
176 } |
|
177 |
|
178 // Repeated calls to ensureElementIsVisible would interfere with each other |
|
179 // and may sometimes result in incorrect scroll positions. |
|
180 setNamedTimeout("breadcrumb-select", ENSURE_SELECTION_VISIBLE_DELAY, () => { |
|
181 if (this._list.ensureElementIsVisible) { |
|
182 this._list.ensureElementIsVisible(aElement); |
|
183 } |
|
184 }); |
|
185 }, |
|
186 |
|
187 /** |
|
188 * The underflow and overflow listener for the arrowscrollbox container. |
|
189 */ |
|
190 _onUnderflow: function({ target }) { |
|
191 if (target != this._list) { |
|
192 return; |
|
193 } |
|
194 target._scrollButtonUp.collapsed = true; |
|
195 target._scrollButtonDown.collapsed = true; |
|
196 target.removeAttribute("overflows"); |
|
197 }, |
|
198 |
|
199 /** |
|
200 * The underflow and overflow listener for the arrowscrollbox container. |
|
201 */ |
|
202 _onOverflow: function({ target }) { |
|
203 if (target != this._list) { |
|
204 return; |
|
205 } |
|
206 target._scrollButtonUp.collapsed = false; |
|
207 target._scrollButtonDown.collapsed = false; |
|
208 target.setAttribute("overflows", ""); |
|
209 }, |
|
210 |
|
211 window: null, |
|
212 document: null, |
|
213 _parent: null, |
|
214 _list: null, |
|
215 _selectedItem: null |
|
216 }; |
|
217 |
|
218 /** |
|
219 * A Breadcrumb constructor for the BreadcrumbsWidget. |
|
220 * |
|
221 * @param BreadcrumbsWidget aWidget |
|
222 * The widget to contain this breadcrumb. |
|
223 * @param nsIDOMNode aContents |
|
224 * The node displayed in the container. |
|
225 */ |
|
226 function Breadcrumb(aWidget, aContents) { |
|
227 this.document = aWidget.document; |
|
228 this.window = aWidget.window; |
|
229 this.ownerView = aWidget; |
|
230 |
|
231 this._target = this.document.createElement("hbox"); |
|
232 this._target.className = "breadcrumbs-widget-item"; |
|
233 this._target.setAttribute("align", "center"); |
|
234 this.contents = aContents; |
|
235 } |
|
236 |
|
237 Breadcrumb.prototype = { |
|
238 /** |
|
239 * Sets the contents displayed in this item's view. |
|
240 * |
|
241 * @param string | nsIDOMNode aContents |
|
242 * The string or node displayed in the container. |
|
243 */ |
|
244 set contents(aContents) { |
|
245 // If there are already some contents displayed, replace them. |
|
246 if (this._target.hasChildNodes()) { |
|
247 this._target.replaceChild(aContents, this._target.firstChild); |
|
248 return; |
|
249 } |
|
250 // These are the first contents ever displayed. |
|
251 this._target.appendChild(aContents); |
|
252 }, |
|
253 |
|
254 window: null, |
|
255 document: null, |
|
256 ownerView: null, |
|
257 _target: null |
|
258 }; |