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.

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

mercurial