browser/devtools/shared/SplitView.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* vim:set ts=2 sw=2 sts=2 et: */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 "use strict";
     8 this.EXPORTED_SYMBOLS = ["SplitView"];
    10 /* this must be kept in sync with CSS (ie. splitview.css) */
    11 const LANDSCAPE_MEDIA_QUERY = "(min-width: 551px)";
    13 let bindings = new WeakMap();
    15 /**
    16  * SplitView constructor
    17  *
    18  * Initialize the split view UI on an existing DOM element.
    19  *
    20  * A split view contains items, each of those having one summary and one details
    21  * elements.
    22  * It is adaptive as it behaves similarly to a richlistbox when there the aspect
    23  * ratio is narrow or as a pair listbox-box otherwise.
    24  *
    25  * @param DOMElement aRoot
    26  * @see appendItem
    27  */
    28 this.SplitView = function SplitView(aRoot)
    29 {
    30   this._root = aRoot;
    31   this._controller = aRoot.querySelector(".splitview-controller");
    32   this._nav = aRoot.querySelector(".splitview-nav");
    33   this._side = aRoot.querySelector(".splitview-side-details");
    34   this._activeSummary = null
    36   this._mql = aRoot.ownerDocument.defaultView.matchMedia(LANDSCAPE_MEDIA_QUERY);
    38   // items list focus and search-on-type handling
    39   this._nav.addEventListener("keydown", function onKeyCatchAll(aEvent) {
    40     function getFocusedItemWithin(nav) {
    41       let node = nav.ownerDocument.activeElement;
    42       while (node && node.parentNode != nav) {
    43         node = node.parentNode;
    44       }
    45       return node;
    46     }
    48     // do not steal focus from inside iframes or textboxes
    49     if (aEvent.target.ownerDocument != this._nav.ownerDocument ||
    50         aEvent.target.tagName == "input" ||
    51         aEvent.target.tagName == "textbox" ||
    52         aEvent.target.tagName == "textarea" ||
    53         aEvent.target.classList.contains("textbox")) {
    54       return false;
    55     }
    57     // handle keyboard navigation within the items list
    58     let newFocusOrdinal;
    59     if (aEvent.keyCode == aEvent.DOM_VK_PAGE_UP ||
    60         aEvent.keyCode == aEvent.DOM_VK_HOME) {
    61       newFocusOrdinal = 0;
    62     } else if (aEvent.keyCode == aEvent.DOM_VK_PAGE_DOWN ||
    63                aEvent.keyCode == aEvent.DOM_VK_END) {
    64       newFocusOrdinal = this._nav.childNodes.length - 1;
    65     } else if (aEvent.keyCode == aEvent.DOM_VK_UP) {
    66       newFocusOrdinal = getFocusedItemWithin(this._nav).getAttribute("data-ordinal");
    67       newFocusOrdinal--;
    68     } else if (aEvent.keyCode == aEvent.DOM_VK_DOWN) {
    69       newFocusOrdinal = getFocusedItemWithin(this._nav).getAttribute("data-ordinal");
    70       newFocusOrdinal++;
    71     }
    72     if (newFocusOrdinal !== undefined) {
    73       aEvent.stopPropagation();
    74       let el = this.getSummaryElementByOrdinal(newFocusOrdinal);
    75       if (el) {
    76         el.focus();
    77       }
    78       return false;
    79     }
    80   }.bind(this), false);
    81 }
    83 SplitView.prototype = {
    84   /**
    85     * Retrieve whether the UI currently has a landscape orientation.
    86     *
    87     * @return boolean
    88     */
    89   get isLandscape() this._mql.matches,
    91   /**
    92     * Retrieve the root element.
    93     *
    94     * @return DOMElement
    95     */
    96   get rootElement() this._root,
    98   /**
    99     * Retrieve the active item's summary element or null if there is none.
   100     *
   101     * @return DOMElement
   102     */
   103   get activeSummary() this._activeSummary,
   105   /**
   106     * Set the active item's summary element.
   107     *
   108     * @param DOMElement aSummary
   109     */
   110   set activeSummary(aSummary)
   111   {
   112     if (aSummary == this._activeSummary) {
   113       return;
   114     }
   116     if (this._activeSummary) {
   117       let binding = bindings.get(this._activeSummary);
   119       if (binding.onHide) {
   120         binding.onHide(this._activeSummary, binding._details, binding.data);
   121       }
   123       this._activeSummary.classList.remove("splitview-active");
   124       binding._details.classList.remove("splitview-active");
   125     }
   127     if (!aSummary) {
   128       return;
   129     }
   131     let binding = bindings.get(aSummary);
   132     aSummary.classList.add("splitview-active");
   133     binding._details.classList.add("splitview-active");
   135     this._activeSummary = aSummary;
   137     if (binding.onShow) {
   138       binding.onShow(aSummary, binding._details, binding.data);
   139     }
   140   },
   142   /**
   143     * Retrieve the active item's details element or null if there is none.
   144     * @return DOMElement
   145     */
   146   get activeDetails()
   147   {
   148     let summary = this.activeSummary;
   149     return summary ? bindings.get(summary)._details : null;
   150   },
   152   /**
   153    * Retrieve the summary element for a given ordinal.
   154    *
   155    * @param number aOrdinal
   156    * @return DOMElement
   157    *         Summary element with given ordinal or null if not found.
   158    * @see appendItem
   159    */
   160   getSummaryElementByOrdinal: function SEC_getSummaryElementByOrdinal(aOrdinal)
   161   {
   162     return this._nav.querySelector("* > li[data-ordinal='" + aOrdinal + "']");
   163   },
   165   /**
   166    * Append an item to the split view.
   167    *
   168    * @param DOMElement aSummary
   169    *        The summary element for the item.
   170    * @param DOMElement aDetails
   171    *        The details element for the item.
   172    * @param object aOptions
   173    *     Optional object that defines custom behavior and data for the item.
   174    *     All properties are optional :
   175    *     - function(DOMElement summary, DOMElement details, object data) onCreate
   176    *         Called when the item has been added.
   177    *     - function(summary, details, data) onShow
   178    *         Called when the item is shown/active.
   179    *     - function(summary, details, data) onHide
   180    *         Called when the item is hidden/inactive.
   181    *     - function(summary, details, data) onDestroy
   182    *         Called when the item has been removed.
   183    *     - object data
   184    *         Object to pass to the callbacks above.
   185    *     - number ordinal
   186    *         Items with a lower ordinal are displayed before those with a
   187    *         higher ordinal.
   188    */
   189   appendItem: function ASV_appendItem(aSummary, aDetails, aOptions)
   190   {
   191     let binding = aOptions || {};
   193     binding._summary = aSummary;
   194     binding._details = aDetails;
   195     bindings.set(aSummary, binding);
   197     this._nav.appendChild(aSummary);
   199     aSummary.addEventListener("click", function onSummaryClick(aEvent) {
   200       aEvent.stopPropagation();
   201       this.activeSummary = aSummary;
   202     }.bind(this), false);
   204     this._side.appendChild(aDetails);
   206     if (binding.onCreate) {
   207       // queue onCreate handler
   208       this._root.ownerDocument.defaultView.setTimeout(function () {
   209         binding.onCreate(aSummary, aDetails, binding.data);
   210       }, 0);
   211     }
   212   },
   214   /**
   215    * Append an item to the split view according to two template elements
   216    * (one for the item's summary and the other for the item's details).
   217    *
   218    * @param string aName
   219    *        Name of the template elements to instantiate.
   220    *        Requires two (hidden) DOM elements with id "splitview-tpl-summary-"
   221    *        and "splitview-tpl-details-" suffixed with aName.
   222    * @param object aOptions
   223    *        Optional object that defines custom behavior and data for the item.
   224    *        See appendItem for full description.
   225    * @return object{summary:,details:}
   226    *         Object with the new DOM elements created for summary and details.
   227    * @see appendItem
   228    */
   229   appendTemplatedItem: function ASV_appendTemplatedItem(aName, aOptions)
   230   {
   231     aOptions = aOptions || {};
   232     let summary = this._root.querySelector("#splitview-tpl-summary-" + aName);
   233     let details = this._root.querySelector("#splitview-tpl-details-" + aName);
   235     summary = summary.cloneNode(true);
   236     summary.id = "";
   237     if (aOptions.ordinal !== undefined) { // can be zero
   238       summary.style.MozBoxOrdinalGroup = aOptions.ordinal;
   239       summary.setAttribute("data-ordinal", aOptions.ordinal);
   240     }
   241     details = details.cloneNode(true);
   242     details.id = "";
   244     this.appendItem(summary, details, aOptions);
   245     return {summary: summary, details: details};
   246   },
   248   /**
   249     * Remove an item from the split view.
   250     *
   251     * @param DOMElement aSummary
   252     *        Summary element of the item to remove.
   253     */
   254   removeItem: function ASV_removeItem(aSummary)
   255   {
   256     if (aSummary == this._activeSummary) {
   257       this.activeSummary = null;
   258     }
   260     let binding = bindings.get(aSummary);
   261     aSummary.parentNode.removeChild(aSummary);
   262     binding._details.parentNode.removeChild(binding._details);
   264     if (binding.onDestroy) {
   265       binding.onDestroy(aSummary, binding._details, binding.data);
   266     }
   267   },
   269   /**
   270    * Remove all items from the split view.
   271    */
   272   removeAll: function ASV_removeAll()
   273   {
   274     while (this._nav.hasChildNodes()) {
   275       this.removeItem(this._nav.firstChild);
   276     }
   277   },
   279   /**
   280    * Set the item's CSS class name.
   281    * This sets the class on both the summary and details elements, retaining
   282    * any SplitView-specific classes.
   283    *
   284    * @param DOMElement aSummary
   285    *        Summary element of the item to set.
   286    * @param string aClassName
   287    *        One or more space-separated CSS classes.
   288    */
   289   setItemClassName: function ASV_setItemClassName(aSummary, aClassName)
   290   {
   291     let binding = bindings.get(aSummary);
   292     let viewSpecific;
   294     viewSpecific = aSummary.className.match(/(splitview\-[\w-]+)/g);
   295     viewSpecific = viewSpecific ? viewSpecific.join(" ") : "";
   296     aSummary.className = viewSpecific + " " + aClassName;
   298     viewSpecific = binding._details.className.match(/(splitview\-[\w-]+)/g);
   299     viewSpecific = viewSpecific ? viewSpecific.join(" ") : "";
   300     binding._details.className = viewSpecific + " " + aClassName;
   301   },
   302 };

mercurial