/* -*- 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 };