OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 cr.define('options', function() { | 5 cr.define('options', function() { |
6 ///////////////////////////////////////////////////////////////////////////// | 6 ///////////////////////////////////////////////////////////////////////////// |
7 // OptionsPage class: | 7 // OptionsPage class: |
8 | 8 |
9 /** | 9 /** |
10 * Base class for options page. | 10 * Base class for options page. |
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
122 this.registeredOverlayPages[overlayName].visible = true; | 122 this.registeredOverlayPages[overlayName].visible = true; |
123 } | 123 } |
124 }; | 124 }; |
125 | 125 |
126 /** | 126 /** |
127 * Returns whether or not an overlay is visible. | 127 * Returns whether or not an overlay is visible. |
128 * @return {boolean} True if an overlay is visible. | 128 * @return {boolean} True if an overlay is visible. |
129 * @private | 129 * @private |
130 */ | 130 */ |
131 OptionsPage.isOverlayVisible_ = function() { | 131 OptionsPage.isOverlayVisible_ = function() { |
132 for (var name in this.registeredOverlayPages) { | 132 return this.getVisibleOverlay_() != null; |
133 if (this.registeredOverlayPages[name].visible) | |
134 return true; | |
135 } | |
136 return false; | |
137 }; | 133 }; |
138 | 134 |
139 /** | 135 /** |
| 136 * Returns the currently visible overlay, or null if no page is visible. |
| 137 * @return {OptionPage} The visible overlay. |
| 138 */ |
| 139 OptionsPage.getVisibleOverlay_ = function() { |
| 140 for (var name in this.registeredOverlayPages) { |
| 141 var page = this.registeredOverlayPages[name]; |
| 142 if (page.visible) |
| 143 return page; |
| 144 } |
| 145 return null; |
| 146 }; |
| 147 |
| 148 /** |
140 * Clears overlays (i.e. hide all overlays). | 149 * Clears overlays (i.e. hide all overlays). |
141 */ | 150 */ |
142 OptionsPage.clearOverlays = function() { | 151 OptionsPage.clearOverlays = function() { |
143 for (var name in this.registeredOverlayPages) { | 152 for (var name in this.registeredOverlayPages) { |
144 var page = this.registeredOverlayPages[name]; | 153 var page = this.registeredOverlayPages[name]; |
145 page.visible = false; | 154 page.visible = false; |
146 } | 155 } |
147 }; | 156 }; |
148 | 157 |
149 /** | 158 /** |
150 * Returns the topmost visible page, or null if no page is visible. | 159 * Returns the topmost visible page, or null if no page is visible. |
151 * @return {OptionPage} The topmost visible page. | 160 * @return {OptionPage} The topmost visible page. |
152 */ | 161 */ |
153 OptionsPage.getTopmostVisiblePage = function() { | 162 OptionsPage.getTopmostVisiblePage = function() { |
154 var topPage = null; | 163 var topPage = null; |
155 for (var name in this.registeredPages) { | 164 for (var name in this.registeredPages) { |
156 var page = this.registeredPages[name]; | 165 var page = this.registeredPages[name]; |
157 if (page.visible && | 166 if (page.visible && |
158 (!topPage || page.nestingLevel > topPage.nestingLevel)) | 167 (!topPage || page.nestingLevel > topPage.nestingLevel)) |
159 topPage = page; | 168 topPage = page; |
160 } | 169 } |
161 return topPage; | 170 return topPage; |
162 } | 171 }; |
163 | 172 |
164 /** | 173 /** |
165 * Closes the topmost open subpage, if any. | 174 * Closes the topmost open subpage, if any. |
166 */ | 175 */ |
167 OptionsPage.closeTopSubPage = function() { | 176 OptionsPage.closeTopSubPage = function() { |
168 var topPage = this.getTopmostVisiblePage(); | 177 var topPage = this.getTopmostVisiblePage(); |
169 if (topPage && topPage.parentPage) | 178 if (topPage && topPage.parentPage) |
170 topPage.visible = false; | 179 topPage.visible = false; |
171 }; | 180 }; |
172 | 181 |
(...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
342 // Hook up the close buttons. | 351 // Hook up the close buttons. |
343 subpageCloseButtons = document.querySelectorAll('.close-subpage'); | 352 subpageCloseButtons = document.querySelectorAll('.close-subpage'); |
344 for (var i = 0; i < subpageCloseButtons.length; i++) { | 353 for (var i = 0; i < subpageCloseButtons.length; i++) { |
345 subpageCloseButtons[i].onclick = function() { | 354 subpageCloseButtons[i].onclick = function() { |
346 self.closeTopSubPage(); | 355 self.closeTopSubPage(); |
347 }; | 356 }; |
348 }; | 357 }; |
349 | 358 |
350 // Install handler for key presses. | 359 // Install handler for key presses. |
351 document.addEventListener('keydown', this.keyDownEventHandler_.bind(this)); | 360 document.addEventListener('keydown', this.keyDownEventHandler_.bind(this)); |
| 361 |
| 362 document.addEventListener('focus', this.manageFocusChange_.bind(this), |
| 363 true); |
352 }; | 364 }; |
353 | 365 |
354 /** | 366 /** |
355 * Returns a function to handle clicks behind a subpage at level |level| by | 367 * Returns a function to handle clicks behind a subpage at level |level| by |
356 * closing all subpages down to |level| - 1. | 368 * closing all subpages down to |level| - 1. |
357 * @param {number} level The level of the subpage being handled. | 369 * @param {number} level The level of the subpage being handled. |
358 * @return {Function} a function to handle clicks outside the given subpage. | 370 * @return {Function} a function to handle clicks outside the given subpage. |
359 * @private | 371 * @private |
360 */ | 372 */ |
361 OptionsPage.subPageClosingClickHandler_ = function(level) { | 373 OptionsPage.subPageClosingClickHandler_ = function(level) { |
362 var self = this; | 374 var self = this; |
363 return function(event) { | 375 return function(event) { |
364 // Clicks on the narrow strip between the left of the subpage sheet and | 376 // Clicks on the narrow strip between the left of the subpage sheet and |
365 // that shows part of the parent page should close the overlay, but | 377 // that shows part of the parent page should close the overlay, but |
366 // not fall through to the parent page. | 378 // not fall through to the parent page. |
367 if (!$('subpage-sheet-' + level).contains(event.target)) { | 379 if (!$('subpage-sheet-' + level).contains(event.target)) { |
368 self.closeSubPagesToLevel(level - 1); | 380 self.closeSubPagesToLevel(level - 1); |
369 event.stopPropagation(); | 381 event.stopPropagation(); |
370 event.preventDefault(); | 382 event.preventDefault(); |
371 } | 383 } |
372 }; | 384 }; |
373 }; | 385 }; |
374 | 386 |
375 /** | 387 /** |
| 388 * Called when focus changes; ensures that focus doesn't move outside |
| 389 * the topmost subpage/overlay. |
| 390 * @param {Event} e The focus change event. |
| 391 * @private |
| 392 */ |
| 393 OptionsPage.manageFocusChange_ = function(e) { |
| 394 var focusableItemsRoot; |
| 395 // If an overlay is visible, that defines the tab loop. |
| 396 var topPage = this.getVisibleOverlay_(); |
| 397 if (topPage) |
| 398 focusableItemsRoot = topPage.pageDiv; |
| 399 // If a subpage is visible, use its parent as the tab loop constraint. |
| 400 // (The parent is used because it contains the close button.) |
| 401 if (!topPage) { |
| 402 var topPage = this.getTopmostVisiblePage(); |
| 403 if (topPage && topPage.nestingLevel > 0) |
| 404 focusableItemsRoot = topPage.pageDiv.parentNode; |
| 405 } |
| 406 |
| 407 if (focusableItemsRoot && !focusableItemsRoot.contains(e.target)) |
| 408 topPage.focusFirstElement(); |
| 409 }; |
| 410 |
| 411 /** |
376 * A function to handle mouse events (mousedown or click) on the html body by | 412 * A function to handle mouse events (mousedown or click) on the html body by |
377 * closing subpages and/or stopping event propagation. | 413 * closing subpages and/or stopping event propagation. |
378 * @return {Event} a mousedown or click event. | 414 * @return {Event} a mousedown or click event. |
379 * @private | 415 * @private |
380 */ | 416 */ |
381 OptionsPage.bodyMouseEventHandler_ = function(event) { | 417 OptionsPage.bodyMouseEventHandler_ = function(event) { |
382 // Do nothing if a subpage isn't showing. | 418 // Do nothing if a subpage isn't showing. |
383 var topPage = this.getTopmostVisiblePage(); | 419 var topPage = this.getTopmostVisiblePage(); |
384 if (!(topPage && topPage.parentPage)) | 420 if (!(topPage && topPage.parentPage)) |
385 return; | 421 return; |
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
535 } | 571 } |
536 if (this.tab) { | 572 if (this.tab) { |
537 this.tab.classList.remove('navbar-item-selected'); | 573 this.tab.classList.remove('navbar-item-selected'); |
538 } | 574 } |
539 } | 575 } |
540 | 576 |
541 cr.dispatchPropertyChange(this, 'visible', visible, !visible); | 577 cr.dispatchPropertyChange(this, 'visible', visible, !visible); |
542 }, | 578 }, |
543 | 579 |
544 /** | 580 /** |
| 581 * Focuses the first control on the page. |
| 582 */ |
| 583 focusFirstElement: function() { |
| 584 // Sets focus on the first interactive element in the page. |
| 585 var focusElement = |
| 586 this.pageDiv.querySelector('button, input, list, select'); |
| 587 if (focusElement) |
| 588 focusElement.focus(); |
| 589 }, |
| 590 |
| 591 /** |
545 * The nesting level of this page. | 592 * The nesting level of this page. |
546 * @type {number} The nesting level of this page (0 for top-level page) | 593 * @type {number} The nesting level of this page (0 for top-level page) |
547 */ | 594 */ |
548 get nestingLevel() { | 595 get nestingLevel() { |
549 var level = 0; | 596 var level = 0; |
550 var parent = this.parentPage; | 597 var parent = this.parentPage; |
551 while (parent) { | 598 while (parent) { |
552 level++; | 599 level++; |
553 parent = parent.parentPage; | 600 parent = parent.parentPage; |
554 } | 601 } |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
591 OptionsPage.showOverlay(hash); | 638 OptionsPage.showOverlay(hash); |
592 }, | 639 }, |
593 }; | 640 }; |
594 | 641 |
595 // Export | 642 // Export |
596 return { | 643 return { |
597 OptionsPage: OptionsPage | 644 OptionsPage: OptionsPage |
598 }; | 645 }; |
599 | 646 |
600 }); | 647 }); |
OLD | NEW |