Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
|
stevenjb
2014/07/28 22:27:23
We should provide some sort of top level comment d
michaelpg
2014/07/29 01:08:31
How's this?
| |
| 5 cr.define('cr.ui.pageManager', function() { | |
| 6 /** @const */ var FocusOutlineManager = cr.ui.FocusOutlineManager; | |
| 7 | |
| 8 var PageManager = { | |
| 9 /** | |
| 10 * Offset of page container in pixels, to allow room for side menu. | |
| 11 * Simplified settings pages can override this if they don't use the menu. | |
| 12 * The default (155) comes from -webkit-margin-start in uber_shared.css | |
| 13 * TODO(michaelpg@): Remove dependency on uber menu (crbug.com/313244). | |
|
stevenjb
2014/07/28 22:27:23
TODO(michaelpg)
michaelpg
2014/07/29 01:08:30
Done.
| |
| 14 * @private | |
| 15 */ | |
| 16 horizontalOffset: 155, | |
| 17 | |
| 18 /** | |
| 19 * Main level option pages. Maps lower-case page names to the respective | |
| 20 * page object. | |
| 21 * @protected | |
| 22 */ | |
| 23 registeredPages: {}, | |
| 24 | |
| 25 /** | |
| 26 * Pages which are meant to behave like modal dialogs. Maps lower-case | |
| 27 * overlay names to the respective overlay object. | |
| 28 * @protected | |
| 29 */ | |
| 30 registeredOverlayPages: {}, | |
| 31 | |
| 32 /** | |
| 33 * True if page is served from a dialog. | |
| 34 */ | |
| 35 isDialog: false, | |
| 36 | |
| 37 /** | |
| 38 * Gets the default page (to be shown on initial load). | |
| 39 */ | |
| 40 getDefaultPage: function() { | |
| 41 return options.BrowserOptions.getInstance(); | |
|
Dan Beam
2014/07/28 20:57:59
this should probably be abstract (e.g. just assert
michaelpg
2014/07/29 01:08:31
I'd rather not derive from PageManager, instead Op
| |
| 42 }, | |
| 43 | |
| 44 /** | |
| 45 * Shows the default page. | |
| 46 */ | |
| 47 showDefaultPage: function() { | |
| 48 this.navigateToPage(this.getDefaultPage().name); | |
| 49 }, | |
| 50 | |
| 51 /** | |
| 52 * "Navigates" to a page, meaning that the page will be shown and the | |
| 53 * appropriate entry is placed in the history. | |
| 54 * @param {string} pageName Page name. | |
| 55 */ | |
| 56 navigateToPage: function(pageName) { | |
| 57 this.showPageByName(pageName, true); | |
|
stevenjb
2014/07/28 22:27:22
JS novice question: Could this be done by giving u
michaelpg
2014/07/29 01:08:30
Done.
| |
| 58 }, | |
| 59 | |
| 60 /** | |
| 61 * Shows a registered page. This handles both top-level and overlay pages. | |
| 62 * @param {string} pageName Page name. | |
| 63 * @param {boolean} updateHistory True if we should update the history after | |
| 64 * showing the page. | |
| 65 * @param {Object=} opt_propertyBag An optional bag of properties including | |
| 66 * replaceState (if history state should be replaced instead of pushed). | |
| 67 * @private | |
|
Dan Beam
2014/07/28 20:58:00
this isn't private
michaelpg
2014/07/29 01:08:31
Done.
| |
| 68 */ | |
| 69 showPageByName: function(pageName, | |
| 70 updateHistory, | |
| 71 opt_propertyBag) { | |
| 72 // If |opt_propertyBag| is non-truthy, homogenize to object. | |
| 73 opt_propertyBag = opt_propertyBag || {}; | |
|
stevenjb
2014/07/28 22:27:23
JS novice question: Is this preferable to declarin
michaelpg
2014/07/29 01:08:30
Not sure what you are asking exactly:
function(fo
Dan Beam
2014/07/30 01:00:01
opt_propertyBag = opt_propertyBag || {};
simplifi
stevenjb
2014/07/30 22:36:21
Nevermind. I somehow thought you could provide def
| |
| 74 | |
| 75 // If a bubble is currently being shown, hide it. | |
| 76 this.hideBubble(); | |
| 77 | |
| 78 // Find the currently visible root-level page. | |
| 79 var rootPage = null; | |
| 80 for (var name in this.registeredPages) { | |
| 81 var page = this.registeredPages[name]; | |
| 82 if (page.visible && !page.parentPage) { | |
| 83 rootPage = page; | |
| 84 break; | |
| 85 } | |
| 86 } | |
| 87 | |
| 88 // Find the target page. | |
| 89 var targetPage = this.registeredPages[pageName.toLowerCase()]; | |
| 90 if (!targetPage || !targetPage.canShowPage()) { | |
| 91 // If it's not a page, try it as an overlay. | |
| 92 if (!targetPage && this.showOverlay_(pageName, rootPage)) { | |
| 93 if (updateHistory) | |
| 94 this.updateHistoryState_(!!opt_propertyBag.replaceState); | |
| 95 this.updateTitle_(); | |
| 96 return; | |
| 97 } else { | |
|
stevenjb
2014/07/28 22:27:23
nit: no else after return
michaelpg
2014/07/29 01:08:30
Done.
| |
| 98 targetPage = this.getDefaultPage(); | |
| 99 } | |
| 100 } | |
| 101 | |
| 102 pageName = targetPage.name.toLowerCase(); | |
| 103 var targetPageWasVisible = targetPage.visible; | |
| 104 | |
| 105 // Determine if the root page is 'sticky', meaning that it | |
| 106 // shouldn't change when showing an overlay. This can happen for special | |
| 107 // pages like Search. | |
| 108 var isRootPageLocked = | |
| 109 rootPage && rootPage.sticky && targetPage.parentPage; | |
| 110 | |
| 111 var allPageNames = Array.prototype.concat.call( | |
| 112 Object.keys(this.registeredPages), | |
| 113 Object.keys(this.registeredOverlayPages)); | |
| 114 | |
| 115 // Notify pages if they will be hidden. | |
| 116 for (var i = 0; i < allPageNames.length; ++i) { | |
| 117 var name = allPageNames[i]; | |
| 118 var page = this.registeredPages[name] || | |
| 119 this.registeredOverlayPages[name]; | |
| 120 if (!page.parentPage && isRootPageLocked) | |
| 121 continue; | |
| 122 if (page.willHidePage && name != pageName && | |
| 123 !page.isAncestorOfPage(targetPage)) { | |
| 124 page.willHidePage(); | |
| 125 } | |
| 126 } | |
| 127 | |
| 128 // Update visibilities to show only the hierarchy of the target page. | |
| 129 for (var i = 0; i < allPageNames.length; ++i) { | |
| 130 var name = allPageNames[i]; | |
| 131 var page = this.registeredPages[name] || | |
| 132 this.registeredOverlayPages[name]; | |
| 133 if (!page.parentPage && isRootPageLocked) | |
| 134 continue; | |
| 135 page.visible = name == pageName || page.isAncestorOfPage(targetPage); | |
| 136 } | |
| 137 | |
| 138 // Update the history and current location. | |
| 139 if (updateHistory) | |
| 140 this.updateHistoryState_(!!opt_propertyBag.replaceState); | |
| 141 | |
| 142 // Update focus if any other control was focused on the previous page, | |
| 143 // or the previous page is not known. | |
| 144 if (document.activeElement != document.body && | |
| 145 (!rootPage || rootPage.pageDiv.contains(document.activeElement))) { | |
| 146 targetPage.focus(); | |
| 147 } | |
| 148 | |
| 149 // Notify pages if they were shown. | |
| 150 for (var i = 0; i < allPageNames.length; ++i) { | |
| 151 var name = allPageNames[i]; | |
| 152 var page = this.registeredPages[name] || | |
| 153 this.registeredOverlayPages[name]; | |
| 154 if (!page.parentPage && isRootPageLocked) | |
| 155 continue; | |
|
stevenjb
2014/07/28 22:27:22
nit: We use the same pattern 3 times here, maybe c
michaelpg
2014/07/29 01:08:31
Sure. But I don't want to stray too far from the o
| |
| 156 if (!targetPageWasVisible && page.didShowPage && | |
| 157 (name == pageName || page.isAncestorOfPage(targetPage))) { | |
| 158 page.didShowPage(); | |
| 159 } | |
| 160 } | |
| 161 | |
| 162 // Update the document title. Do this after didShowPage was called, in | |
| 163 // case a page decides to change its title. | |
| 164 this.updateTitle_(); | |
| 165 }, | |
| 166 | |
| 167 /** | |
| 168 * Scrolls the page to the correct position (the top when opening an | |
| 169 * overaly, or the old scroll position a previously hidden overlay | |
| 170 * becomes visible). | |
| 171 * @private | |
| 172 */ | |
| 173 updateScrollPosition_: function() { | |
| 174 var container = $('page-container'); | |
| 175 var scrollTop = container.oldScrollTop || 0; | |
| 176 container.oldScrollTop = undefined; | |
| 177 window.scroll(scrollLeftForDocument(document), scrollTop); | |
| 178 }, | |
| 179 | |
| 180 /** | |
| 181 * Updates the title to title of the current page. | |
| 182 * @private | |
| 183 */ | |
| 184 updateTitle_: function() { | |
| 185 var page = this.getTopmostVisiblePage(); | |
| 186 // TODO(michaelpg@): Remove dependency on uber (crbug.com/313244). | |
|
Dan Beam
2014/07/28 20:58:00
same thing regarding uber deps: you should be able
stevenjb
2014/07/28 22:27:23
TODO(michaelpg)
michaelpg
2014/07/29 01:08:30
Done.
michaelpg
2014/07/29 01:08:30
Yes, setting up an observer interface was my plan.
| |
| 187 uber.setTitle(page.title); | |
| 188 }, | |
| 189 | |
| 190 /** | |
| 191 * Pushes the current page onto the history stack, replacing the current | |
| 192 * entry if appropriate. | |
| 193 * @param {boolean} replace If true, allow no history events to be created. | |
| 194 * @param {object=} opt_params A bag of optional params, including: | |
| 195 * {boolean} ignoreHash Whether to include the hash or not. | |
| 196 * @private | |
| 197 */ | |
| 198 updateHistoryState_: function(replace, opt_params) { | |
| 199 if (PageManager.isDialog) | |
| 200 return; | |
| 201 | |
| 202 var page = this.getTopmostVisiblePage(); | |
| 203 var path = window.location.pathname + window.location.hash; | |
| 204 if (path) { | |
| 205 // Remove trailing slash. | |
| 206 path = path.slice(1).replace(/\/(?:#|$)/, ''); | |
| 207 } | |
| 208 | |
| 209 // If the page is already in history (the user may have clicked the same | |
| 210 // link twice, or this is the initial load), do nothing. | |
| 211 var hash = opt_params && opt_params.ignoreHash ? | |
| 212 '' : window.location.hash; | |
| 213 var newPath = (page == this.getDefaultPage() ? '' : page.name) + hash; | |
| 214 if (path == newPath) | |
| 215 return; | |
| 216 | |
| 217 // TODO(michaelpg@): Remove dependency on uber (crbug.com/313244). | |
|
stevenjb
2014/07/28 22:27:22
TODO(michaelpg)
michaelpg
2014/07/29 01:08:30
Done.
| |
| 218 var historyFunction = replace ? uber.replaceState : uber.pushState; | |
| 219 historyFunction.call(uber, {}, newPath); | |
| 220 }, | |
| 221 | |
| 222 /** | |
| 223 * Shows a registered Overlay page. Does not update history. | |
| 224 * @param {string} overlayName Page name. | |
| 225 * @param {OptionPage} rootPage The currently visible root-level page. | |
| 226 * @return {boolean} whether we showed an overlay. | |
|
Dan Beam
2014/07/28 20:57:59
all the methods that end with _ => @private
michaelpg
2014/07/29 01:08:30
Done.
| |
| 227 */ | |
| 228 showOverlay_: function(overlayName, rootPage) { | |
| 229 var overlay = this.registeredOverlayPages[overlayName.toLowerCase()]; | |
| 230 if (!overlay || !overlay.canShowPage()) | |
| 231 return false; | |
| 232 | |
| 233 // Save the currently focused element in the page for restoration later. | |
| 234 var currentPage = this.getTopmostVisiblePage(); | |
| 235 if (currentPage) | |
| 236 currentPage.lastFocusedElement = document.activeElement; | |
| 237 | |
| 238 if ((!rootPage || !rootPage.sticky) && | |
| 239 overlay.parentPage && | |
| 240 !overlay.parentPage.visible) { | |
| 241 this.showPageByName(overlay.parentPage.name, false); | |
| 242 } | |
| 243 | |
| 244 if (!overlay.visible) { | |
| 245 overlay.visible = true; | |
| 246 if (overlay.didShowPage) overlay.didShowPage(); | |
|
stevenjb
2014/07/28 22:27:23
nit: two lines? (Or is that a C++ only thing?)
michaelpg
2014/07/29 01:08:30
This isn't my code, but I think two lines is the c
| |
| 247 } | |
| 248 | |
| 249 // Change focus to the overlay if any other control was focused by | |
| 250 // keyboard before. Otherwise, no one should have focus. | |
| 251 if (document.activeElement != document.body) { | |
| 252 if (FocusOutlineManager.forDocument(document).visible) { | |
| 253 overlay.focus(); | |
| 254 } else if (!overlay.pageDiv.contains(document.activeElement)) { | |
| 255 document.activeElement.blur(); | |
| 256 } | |
| 257 } | |
| 258 | |
| 259 if ($('search-field') && $('search-field').value == '') { | |
| 260 var section = overlay.associatedSection; | |
| 261 if (section) | |
| 262 options.BrowserOptions.scrollToSection(section); | |
| 263 } | |
| 264 | |
| 265 return true; | |
| 266 }, | |
| 267 | |
| 268 /** | |
| 269 * Returns whether or not an overlay is visible. | |
| 270 * @return {boolean} True if an overlay is visible. | |
| 271 * @private | |
| 272 */ | |
| 273 isOverlayVisible_: function() { | |
| 274 return this.getVisibleOverlay_() != null; | |
| 275 }, | |
| 276 | |
| 277 /** | |
| 278 * Returns the currently visible overlay, or null if no page is visible. | |
| 279 * @return {OptionPage} The visible overlay. | |
| 280 */ | |
| 281 getVisibleOverlay_: function() { | |
| 282 var topmostPage = null; | |
| 283 for (var name in this.registeredOverlayPages) { | |
| 284 var page = this.registeredOverlayPages[name]; | |
| 285 if (page.visible && | |
| 286 (!topmostPage || page.nestingLevel > topmostPage.nestingLevel)) { | |
| 287 topmostPage = page; | |
| 288 } | |
| 289 } | |
| 290 return topmostPage; | |
| 291 }, | |
| 292 | |
| 293 /** | |
| 294 * Restores the last focused element on a given page. | |
| 295 */ | |
| 296 restoreLastFocusedElement_: function() { | |
| 297 var currentPage = this.getTopmostVisiblePage(); | |
| 298 if (currentPage.lastFocusedElement) | |
| 299 currentPage.lastFocusedElement.focus(); | |
| 300 }, | |
| 301 | |
| 302 /** | |
| 303 * Closes the visible overlay. Updates the history state after closing the | |
| 304 * overlay. | |
| 305 */ | |
| 306 closeOverlay: function() { | |
| 307 var overlay = this.getVisibleOverlay_(); | |
| 308 if (!overlay) | |
| 309 return; | |
| 310 | |
| 311 overlay.visible = false; | |
| 312 | |
| 313 if (overlay.didClosePage) overlay.didClosePage(); | |
|
stevenjb
2014/07/28 22:27:22
two lines?
michaelpg
2014/07/29 01:08:30
Done.
Dan Beam
2014/07/30 01:00:01
for future reference, 2 lines is more common but 1
| |
| 314 this.updateHistoryState_(false, {ignoreHash: true}); | |
| 315 this.updateTitle_(); | |
| 316 | |
| 317 this.restoreLastFocusedElement_(); | |
| 318 }, | |
| 319 | |
| 320 /** | |
| 321 * Closes all overlays and updates the history after each closed overlay. | |
| 322 */ | |
| 323 closeAllOverlays: function() { | |
| 324 while (this.isOverlayVisible_()) { | |
| 325 this.closeOverlay(); | |
| 326 } | |
| 327 }, | |
| 328 | |
| 329 /** | |
| 330 * Cancels (closes) the overlay, due to the user pressing <Esc>. | |
| 331 */ | |
| 332 cancelOverlay: function() { | |
| 333 // Blur the active element to ensure any changed pref value is saved. | |
| 334 document.activeElement.blur(); | |
| 335 var overlay = this.getVisibleOverlay_(); | |
| 336 // Let the overlay handle the <Esc> if it wants to. | |
| 337 if (overlay.handleCancel) { | |
| 338 overlay.handleCancel(); | |
| 339 this.restoreLastFocusedElement_(); | |
| 340 } else { | |
| 341 this.closeOverlay(); | |
| 342 } | |
| 343 }, | |
| 344 | |
| 345 /** | |
| 346 * Hides the visible overlay. Does not affect the history state. | |
| 347 * @private | |
| 348 */ | |
| 349 hideOverlay_: function() { | |
| 350 var overlay = this.getVisibleOverlay_(); | |
| 351 if (overlay) | |
| 352 overlay.visible = false; | |
| 353 }, | |
| 354 | |
| 355 /** | |
| 356 * Returns the pages which are currently visible, ordered by nesting level | |
| 357 * (ascending). | |
| 358 * @return {Array.OptionPage} The pages which are currently visible, ordered | |
| 359 * by nesting level (ascending). | |
| 360 */ | |
| 361 getVisiblePages_: function() { | |
| 362 var visiblePages = []; | |
| 363 for (var name in this.registeredPages) { | |
| 364 var page = this.registeredPages[name]; | |
| 365 if (page.visible) | |
| 366 visiblePages[page.nestingLevel] = page; | |
| 367 } | |
| 368 return visiblePages; | |
| 369 }, | |
| 370 | |
| 371 /** | |
| 372 * Returns the topmost visible page (overlays excluded). | |
| 373 * @return {OptionPage} The topmost visible page aside any overlay. | |
| 374 * @private | |
| 375 */ | |
| 376 getTopmostVisibleNonOverlayPage_: function() { | |
| 377 var topPage = null; | |
| 378 for (var name in this.registeredPages) { | |
| 379 var page = this.registeredPages[name]; | |
| 380 if (page.visible && | |
| 381 (!topPage || page.nestingLevel > topPage.nestingLevel)) | |
| 382 topPage = page; | |
| 383 } | |
| 384 | |
| 385 return topPage; | |
| 386 }, | |
| 387 | |
| 388 /** | |
| 389 * Returns the topmost visible page, or null if no page is visible. | |
| 390 * @return {OptionPage} The topmost visible page. | |
| 391 */ | |
| 392 getTopmostVisiblePage: function() { | |
| 393 // Check overlays first since they're top-most if visible. | |
| 394 return this.getVisibleOverlay_() || | |
| 395 this.getTopmostVisibleNonOverlayPage_(); | |
| 396 }, | |
| 397 | |
| 398 /** | |
| 399 * Returns the currently visible bubble, or null if no bubble is visible. | |
| 400 * @return {AutoCloseBubble} The bubble currently being shown. | |
| 401 */ | |
| 402 getVisibleBubble: function() { | |
| 403 var bubble = PageManager.bubble_; | |
| 404 return bubble && !bubble.hidden ? bubble : null; | |
| 405 }, | |
| 406 | |
| 407 /** | |
| 408 * Shows an informational bubble displaying |content| and pointing at the | |
| 409 * |target| element. If |content| has focusable elements, they join the | |
| 410 * current page's tab order as siblings of |domSibling|. | |
| 411 * @param {HTMLDivElement} content The content of the bubble. | |
| 412 * @param {HTMLElement} target The element at which the bubble points. | |
| 413 * @param {HTMLElement} domSibling The element after which the bubble is | |
| 414 * added to the DOM. | |
| 415 * @param {cr.ui.ArrowLocation} location The arrow location. | |
| 416 */ | |
| 417 showBubble: function(content, target, domSibling, location) { | |
| 418 PageManager.hideBubble(); | |
| 419 | |
| 420 var bubble = new cr.ui.AutoCloseBubble; | |
| 421 bubble.anchorNode = target; | |
| 422 bubble.domSibling = domSibling; | |
| 423 bubble.arrowLocation = location; | |
| 424 bubble.content = content; | |
| 425 bubble.show(); | |
| 426 PageManager.bubble_ = bubble; | |
| 427 }, | |
| 428 | |
| 429 /** | |
| 430 * Hides the currently visible bubble, if any. | |
| 431 */ | |
| 432 hideBubble: function() { | |
| 433 if (PageManager.bubble_) | |
| 434 PageManager.bubble_.hide(); | |
| 435 }, | |
| 436 | |
| 437 /** | |
| 438 * Registers new page. | |
| 439 * @param {cr.ui.page_manager.Page} page Page to register. | |
| 440 */ | |
| 441 register: function(page) { | |
| 442 this.registeredPages[page.name.toLowerCase()] = page; | |
| 443 page.initializePage(); | |
| 444 }, | |
| 445 | |
| 446 /** | |
| 447 * Find an enclosing section for an element if it exists. | |
| 448 * @param {Element} element Element to search. | |
| 449 * @return {OptionPage} The section element, or null. | |
| 450 * @private | |
| 451 */ | |
| 452 findSectionForNode_: function(node) { | |
| 453 while (node = node.parentNode) { | |
| 454 if (node.nodeName == 'SECTION') | |
| 455 return node; | |
| 456 } | |
| 457 return null; | |
| 458 }, | |
| 459 | |
| 460 /** | |
| 461 * Registers a new Overlay page. | |
| 462 * @param {cr.ui.page_manager.Page} overlay Overlay to register. | |
| 463 * @param {cr.ui.page_manager.Page} parentPage Associated parent page for | |
| 464 * this overlay. | |
| 465 * @param {Array} associatedControls Array of control elements associated | |
| 466 * with this page. | |
| 467 */ | |
| 468 registerOverlay: function(overlay, | |
| 469 parentPage, | |
| 470 associatedControls) { | |
| 471 this.registeredOverlayPages[overlay.name.toLowerCase()] = overlay; | |
| 472 overlay.parentPage = parentPage; | |
| 473 if (associatedControls) { | |
| 474 overlay.associatedControls = associatedControls; | |
| 475 if (associatedControls.length) { | |
| 476 overlay.associatedSection = | |
| 477 this.findSectionForNode_(associatedControls[0]); | |
| 478 } | |
| 479 | |
| 480 // Sanity check. | |
| 481 for (var i = 0; i < associatedControls.length; ++i) { | |
| 482 assert(associatedControls[i], 'Invalid element passed.'); | |
| 483 } | |
| 484 } | |
| 485 | |
| 486 // Reverse the button strip for Windows and CrOS. See the documentation of | |
| 487 // reverseButtonStripIfNecessary_() for an explanation of why this is | |
| 488 // done. | |
| 489 if (cr.isWindows || cr.isChromeOS) | |
| 490 this.reverseButtonStripIfNecessary_(overlay); | |
| 491 | |
| 492 overlay.tab = undefined; | |
| 493 overlay.isOverlay = true; | |
| 494 overlay.initializePage(); | |
| 495 }, | |
| 496 | |
| 497 /** | |
| 498 * Reverses the child elements of a button strip if it hasn't already been | |
| 499 * reversed. This is necessary because WebKit does not alter the tab order | |
| 500 * for elements that are visually reversed using | |
| 501 * -webkit-box-direction: reverse, and the button order is reversed for | |
| 502 * views. See http://webk.it/62664 for more information. | |
| 503 * @param {Object} overlay The overlay containing the button strip to | |
| 504 * reverse. | |
| 505 * @private | |
| 506 */ | |
| 507 reverseButtonStripIfNecessary_: function(overlay) { | |
| 508 var buttonStrips = | |
| 509 overlay.pageDiv.querySelectorAll('.button-strip:not([reversed])'); | |
| 510 | |
| 511 // Reverse all button-strips in the overlay. | |
| 512 for (var j = 0; j < buttonStrips.length; j++) { | |
| 513 var buttonStrip = buttonStrips[j]; | |
| 514 | |
| 515 var childNodes = buttonStrip.childNodes; | |
| 516 for (var i = childNodes.length - 1; i >= 0; i--) | |
| 517 buttonStrip.appendChild(childNodes[i]); | |
| 518 | |
| 519 buttonStrip.setAttribute('reversed', ''); | |
| 520 } | |
| 521 }, | |
| 522 | |
| 523 /** | |
| 524 * Returns the name of the page from the current path. | |
| 525 */ | |
| 526 getPageNameFromPath: function() { | |
| 527 var path = location.pathname; | |
| 528 if (path.length <= 1) | |
| 529 return this.getDefaultPage().name; | |
| 530 | |
| 531 // Skip starting slash and remove trailing slash (if any). | |
| 532 return path.slice(1).replace(/\/$/, ''); | |
| 533 }, | |
| 534 | |
| 535 /** | |
| 536 * Callback for window.onpopstate to handle back/forward navigations. | |
| 537 * @param {string} pageName The current page name. | |
| 538 * @param {Object} data State data pushed into history. | |
| 539 */ | |
| 540 setState: function(pageName, data) { | |
| 541 var currentOverlay = this.getVisibleOverlay_(); | |
| 542 var lowercaseName = pageName.toLowerCase(); | |
| 543 var newPage = this.registeredPages[lowercaseName] || | |
| 544 this.registeredOverlayPages[lowercaseName] || | |
| 545 this.getDefaultPage(); | |
| 546 if (currentOverlay && !currentOverlay.isAncestorOfPage(newPage)) { | |
| 547 currentOverlay.visible = false; | |
| 548 if (currentOverlay.didClosePage) currentOverlay.didClosePage(); | |
| 549 } | |
| 550 this.showPageByName(pageName, false); | |
| 551 }, | |
| 552 | |
| 553 /** | |
| 554 * Callback for window.onbeforeunload. Used to notify overlays that they | |
| 555 * will be closed. | |
| 556 */ | |
| 557 willClose: function() { | |
| 558 var overlay = this.getVisibleOverlay_(); | |
| 559 if (overlay && overlay.didClosePage) | |
| 560 overlay.didClosePage(); | |
| 561 }, | |
| 562 | |
| 563 /** | |
| 564 * Freezes/unfreezes the scroll position of the root page container. | |
| 565 * @param {boolean} freeze Whether the page should be frozen. | |
| 566 * @private | |
| 567 */ | |
| 568 setRootPageFrozen_: function(freeze) { | |
| 569 var container = $('page-container'); | |
| 570 if (container.classList.contains('frozen') == freeze) | |
| 571 return; | |
| 572 | |
| 573 if (freeze) { | |
| 574 // Lock the width, since auto width computation may change. | |
| 575 container.style.width = window.getComputedStyle(container).width; | |
| 576 container.oldScrollTop = scrollTopForDocument(document); | |
| 577 container.classList.add('frozen'); | |
| 578 var verticalPosition = | |
| 579 container.getBoundingClientRect().top - container.oldScrollTop; | |
| 580 container.style.top = verticalPosition + 'px'; | |
| 581 this.updateFrozenElementHorizontalPosition_(container); | |
| 582 } else { | |
| 583 container.classList.remove('frozen'); | |
| 584 container.style.top = ''; | |
| 585 container.style.left = ''; | |
| 586 container.style.right = ''; | |
| 587 container.style.width = ''; | |
| 588 } | |
| 589 }, | |
| 590 | |
| 591 /** | |
| 592 * Freezes/unfreezes the scroll position of the root page based on the | |
| 593 * current page stack. | |
| 594 */ | |
| 595 updateRootPageFreezeState: function() { | |
| 596 var topPage = PageManager.getTopmostVisiblePage(); | |
| 597 if (topPage) | |
| 598 this.setRootPageFrozen_(topPage.isOverlay); | |
| 599 }, | |
| 600 | |
| 601 /** | |
| 602 * Initializes the complete page. | |
| 603 */ | |
| 604 initialize: function() { | |
| 605 FocusOutlineManager.forDocument(document); | |
| 606 document.addEventListener('scroll', this.handleScroll_.bind(this)); | |
| 607 | |
| 608 // Trigger the scroll handler manually to set the initial state. | |
| 609 this.handleScroll_(); | |
| 610 | |
| 611 // Shake the dialog if the user clicks outside the dialog bounds. | |
| 612 var containers = [$('overlay-container-1'), $('overlay-container-2')]; | |
| 613 for (var i = 0; i < containers.length; i++) { | |
| 614 var overlay = containers[i]; | |
| 615 cr.ui.overlay.setupOverlay(overlay); | |
| 616 overlay.addEventListener('cancelOverlay', | |
| 617 PageManager.cancelOverlay.bind(PageManager)); | |
| 618 } | |
| 619 | |
| 620 cr.ui.overlay.globalInitialization(); | |
| 621 }, | |
| 622 | |
| 623 /** | |
| 624 * Does a bounds check for the element on the given x, y client coordinates. | |
| 625 * @param {Element} e The DOM element. | |
| 626 * @param {number} x The client X to check. | |
| 627 * @param {number} y The client Y to check. | |
| 628 * @return {boolean} True if the point falls within the element's bounds. | |
| 629 * @private | |
| 630 */ | |
| 631 elementContainsPoint_: function(e, x, y) { | |
| 632 var clientRect = e.getBoundingClientRect(); | |
| 633 return x >= clientRect.left && x <= clientRect.right && | |
| 634 y >= clientRect.top && y <= clientRect.bottom; | |
| 635 }, | |
| 636 | |
| 637 /** | |
| 638 * Called when the page is scrolled; moves elements that are position:fixed | |
| 639 * but should only behave as if they are fixed for vertical scrolling. | |
| 640 * @private | |
| 641 */ | |
| 642 handleScroll_: function() { | |
| 643 this.updateAllFrozenElementPositions_(); | |
| 644 }, | |
| 645 | |
| 646 /** | |
| 647 * Updates all frozen pages to match the horizontal scroll position. | |
| 648 * @private | |
| 649 */ | |
| 650 updateAllFrozenElementPositions_: function() { | |
| 651 var frozenElements = document.querySelectorAll('.frozen'); | |
| 652 for (var i = 0; i < frozenElements.length; i++) | |
| 653 this.updateFrozenElementHorizontalPosition_(frozenElements[i]); | |
| 654 }, | |
| 655 | |
| 656 /** | |
| 657 * Updates the given frozen element to match the horizontal scroll position. | |
| 658 * @param {HTMLElement} e The frozen element to update. | |
| 659 * @private | |
| 660 */ | |
| 661 updateFrozenElementHorizontalPosition_: function(e) { | |
| 662 if (isRTL()) { | |
| 663 e.style.right = PageManager.horizontalOffset + 'px'; | |
| 664 } else { | |
| 665 var scrollLeft = scrollLeftForDocument(document); | |
| 666 e.style.left = PageManager.horizontalOffset - scrollLeft + 'px'; | |
| 667 } | |
| 668 }, | |
| 669 | |
| 670 /** | |
| 671 * Change the horizontal offset used to reposition elements while showing an | |
| 672 * overlay from the default. | |
| 673 */ | |
| 674 setHorizontalOffset: function(value) { | |
| 675 PageManager.horizontalOffset = value; | |
| 676 }, | |
| 677 | |
| 678 /** | |
| 679 * Whether the page is still loading (i.e. onload hasn't finished running). | |
| 680 * @return {boolean} Whether the page is still loading. | |
| 681 */ | |
| 682 isLoading: function() { | |
| 683 return document.documentElement.classList.contains('loading'); | |
| 684 }, | |
|
Dan Beam
2014/07/28 20:58:00
arguably these methods should be in public => prot
michaelpg
2014/07/29 01:08:31
Done. And I tried to arrange related methods toget
| |
| 685 }; | |
| 686 | |
| 687 // Export | |
| 688 return { | |
| 689 PageManager: PageManager | |
| 690 }; | |
| 691 }); | |
| OLD | NEW |