diff options
Diffstat (limited to 'devtools/client/shared/widgets/BreadcrumbsWidget.jsm')
-rw-r--r-- | devtools/client/shared/widgets/BreadcrumbsWidget.jsm | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/devtools/client/shared/widgets/BreadcrumbsWidget.jsm b/devtools/client/shared/widgets/BreadcrumbsWidget.jsm new file mode 100644 index 000000000..900a125b0 --- /dev/null +++ b/devtools/client/shared/widgets/BreadcrumbsWidget.jsm @@ -0,0 +1,250 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const Cu = Components.utils; + +const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms + +const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); +const { ViewHelpers, setNamedTimeout } = require("devtools/client/shared/widgets/view-helpers"); +const EventEmitter = require("devtools/shared/event-emitter"); + +this.EXPORTED_SYMBOLS = ["BreadcrumbsWidget"]; + +/** + * A breadcrumb-like list of items. + * + * Note: this widget should be used in tandem with the WidgetMethods in + * view-helpers.js. + * + * @param nsIDOMNode aNode + * The element associated with the widget. + * @param Object aOptions + * - smoothScroll: specifies if smooth scrolling on selection is enabled. + */ +this.BreadcrumbsWidget = function BreadcrumbsWidget(aNode, aOptions = {}) { + this.document = aNode.ownerDocument; + this.window = this.document.defaultView; + this._parent = aNode; + + // Create an internal arrowscrollbox container. + this._list = this.document.createElement("arrowscrollbox"); + this._list.className = "breadcrumbs-widget-container"; + this._list.setAttribute("flex", "1"); + this._list.setAttribute("orient", "horizontal"); + this._list.setAttribute("clicktoscroll", "true"); + this._list.setAttribute("smoothscroll", !!aOptions.smoothScroll); + this._list.addEventListener("keypress", e => this.emit("keyPress", e), false); + this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false); + this._parent.appendChild(this._list); + + // By default, hide the arrows. We let the arrowscrollbox show them + // in case of overflow. + this._list._scrollButtonUp.collapsed = true; + this._list._scrollButtonDown.collapsed = true; + this._list.addEventListener("underflow", this._onUnderflow.bind(this), false); + this._list.addEventListener("overflow", this._onOverflow.bind(this), false); + + // This widget emits events that can be handled in a MenuContainer. + EventEmitter.decorate(this); + + // Delegate some of the associated node's methods to satisfy the interface + // required by MenuContainer instances. + ViewHelpers.delegateWidgetAttributeMethods(this, aNode); + ViewHelpers.delegateWidgetEventMethods(this, aNode); +}; + +BreadcrumbsWidget.prototype = { + /** + * Inserts an item in this container at the specified index. + * + * @param number aIndex + * The position in the container intended for this item. + * @param nsIDOMNode aContents + * The node displayed in the container. + * @return nsIDOMNode + * The element associated with the displayed item. + */ + insertItemAt: function (aIndex, aContents) { + let list = this._list; + let breadcrumb = new Breadcrumb(this, aContents); + return list.insertBefore(breadcrumb._target, list.childNodes[aIndex]); + }, + + /** + * Returns the child node in this container situated at the specified index. + * + * @param number aIndex + * The position in the container intended for this item. + * @return nsIDOMNode + * The element associated with the displayed item. + */ + getItemAtIndex: function (aIndex) { + return this._list.childNodes[aIndex]; + }, + + /** + * Removes the specified child node from this container. + * + * @param nsIDOMNode aChild + * The element associated with the displayed item. + */ + removeChild: function (aChild) { + this._list.removeChild(aChild); + + if (this._selectedItem == aChild) { + this._selectedItem = null; + } + }, + + /** + * Removes all of the child nodes from this container. + */ + removeAllItems: function () { + let list = this._list; + + while (list.hasChildNodes()) { + list.firstChild.remove(); + } + + this._selectedItem = null; + }, + + /** + * Gets the currently selected child node in this container. + * @return nsIDOMNode + */ + get selectedItem() { + return this._selectedItem; + }, + + /** + * Sets the currently selected child node in this container. + * @param nsIDOMNode aChild + */ + set selectedItem(aChild) { + let childNodes = this._list.childNodes; + + if (!aChild) { + this._selectedItem = null; + } + for (let node of childNodes) { + if (node == aChild) { + node.setAttribute("checked", ""); + this._selectedItem = node; + } else { + node.removeAttribute("checked"); + } + } + }, + + /** + * Returns the value of the named attribute on this container. + * + * @param string aName + * The name of the attribute. + * @return string + * The current attribute value. + */ + getAttribute: function (aName) { + if (aName == "scrollPosition") return this._list.scrollPosition; + if (aName == "scrollWidth") return this._list.scrollWidth; + return this._parent.getAttribute(aName); + }, + + /** + * Ensures the specified element is visible. + * + * @param nsIDOMNode aElement + * The element to make visible. + */ + ensureElementIsVisible: function (aElement) { + if (!aElement) { + return; + } + + // Repeated calls to ensureElementIsVisible would interfere with each other + // and may sometimes result in incorrect scroll positions. + setNamedTimeout("breadcrumb-select", ENSURE_SELECTION_VISIBLE_DELAY, () => { + if (this._list.ensureElementIsVisible) { + this._list.ensureElementIsVisible(aElement); + } + }); + }, + + /** + * The underflow and overflow listener for the arrowscrollbox container. + */ + _onUnderflow: function ({ target }) { + if (target != this._list) { + return; + } + target._scrollButtonUp.collapsed = true; + target._scrollButtonDown.collapsed = true; + target.removeAttribute("overflows"); + }, + + /** + * The underflow and overflow listener for the arrowscrollbox container. + */ + _onOverflow: function ({ target }) { + if (target != this._list) { + return; + } + target._scrollButtonUp.collapsed = false; + target._scrollButtonDown.collapsed = false; + target.setAttribute("overflows", ""); + }, + + window: null, + document: null, + _parent: null, + _list: null, + _selectedItem: null +}; + +/** + * A Breadcrumb constructor for the BreadcrumbsWidget. + * + * @param BreadcrumbsWidget aWidget + * The widget to contain this breadcrumb. + * @param nsIDOMNode aContents + * The node displayed in the container. + */ +function Breadcrumb(aWidget, aContents) { + this.document = aWidget.document; + this.window = aWidget.window; + this.ownerView = aWidget; + + this._target = this.document.createElement("hbox"); + this._target.className = "breadcrumbs-widget-item"; + this._target.setAttribute("align", "center"); + this.contents = aContents; +} + +Breadcrumb.prototype = { + /** + * Sets the contents displayed in this item's view. + * + * @param string | nsIDOMNode aContents + * The string or node displayed in the container. + */ + set contents(aContents) { + // If there are already some contents displayed, replace them. + if (this._target.hasChildNodes()) { + this._target.replaceChild(aContents, this._target.firstChild); + return; + } + // These are the first contents ever displayed. + this._target.appendChild(aContents); + }, + + window: null, + document: null, + ownerView: null, + _target: null +}; |