| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Helper base class for all help pages and overlays, which controls |
| // overlays, focus and scroll. This class is partially based on |
| // OptionsPage, but simpler and contains only overlay- and focus- |
| // handling logic. As in OptionsPage each page can be an overlay itself, |
| // but each page contains its own list of registered overlays which can be |
| // displayed over it. |
| // |
| // TODO (ygorshenin@): crbug.com/313244. |
| cr.define('help', function() { |
| function HelpBasePage() { |
| } |
| |
| HelpBasePage.prototype = { |
| __proto__: HTMLDivElement.prototype, |
| |
| /** |
| * name of the page, should be the same as an id of the |
| * corresponding HTMLDivElement. |
| */ |
| name: null, |
| |
| /** |
| * HTML counterpart of this page. |
| */ |
| pageDiv: null, |
| |
| /** |
| * True if current page is overlay. |
| */ |
| isOverlay: false, |
| |
| /** |
| * HTMLElement that was last focused when this page was the |
| * topmost. |
| */ |
| lastFocusedElement: null, |
| |
| /** |
| * State of vertical scrollbars when this page was the topmost. |
| */ |
| lastScrollTop: 0, |
| |
| /** |
| * Dictionary of registered overlays. |
| */ |
| registeredOverlays: {}, |
| |
| /** |
| * Stores currently focused element. |
| * @private |
| */ |
| storeLastFocusedElement_: function() { |
| if (this.pageDiv.contains(document.activeElement)) |
| this.lastFocusedElement = document.activeElement; |
| }, |
| |
| /** |
| * Restores focus to the last focused element on this page. |
| * @private |
| */ |
| restoreLastFocusedElement_: function() { |
| if (this.lastFocusedElement) |
| this.lastFocusedElement.focus(); |
| else |
| this.focus(); |
| }, |
| |
| /** |
| * Shows or hides current page iff it's an overlay. |
| * @param {boolean} visible True if overlay should be displayed. |
| * @private |
| */ |
| setOverlayVisible_: function(visible) { |
| assert(this.isOverlay); |
| this.container.hidden = !visible; |
| if (visible) |
| this.pageDiv.classList.add('showing'); |
| else |
| this.pageDiv.classList.remove('showing'); |
| }, |
| |
| /** |
| * @return {HTMLDivElement} visible non-overlay page or |
| * null, if there are no visible non-overlay pages. |
| * @private |
| */ |
| getVisibleNonOverlay_: function() { |
| if (this.isOverlay || !this.visible) |
| return null; |
| return this; |
| }, |
| |
| /** |
| * @return {HTMLDivElement} Visible overlay page, or null, |
| * if there are no visible overlay pages. |
| * @private |
| */ |
| getVisibleOverlay_: function() { |
| for (var name in this.registeredOverlays) { |
| var overlay = this.registeredOverlays[name]; |
| if (overlay.visible) |
| return overlay; |
| } |
| return null; |
| }, |
| |
| /** |
| * Freezes current page, makes it impossible to scroll it. |
| * @param {boolean} freeze True if the page should be frozen. |
| * @private |
| */ |
| freeze_: function(freeze) { |
| var scrollLeft = scrollLeftForDocument(document); |
| if (freeze) { |
| this.lastScrollTop = scrollTopForDocument(document); |
| document.body.style.overflow = 'hidden'; |
| window.scroll(scrollLeft, 0); |
| } else { |
| document.body.style.overflow = 'auto'; |
| window.scroll(scrollLeft, this.lastScrollTop); |
| } |
| }, |
| |
| /** |
| * Initializes current page. |
| * @param {string} name Name of the current page. |
| */ |
| initialize: function(name) { |
| this.name = name; |
| this.pageDiv = $(name); |
| }, |
| |
| /** |
| * Called before overlay is displayed. |
| */ |
| onBeforeShow: function() { |
| }, |
| |
| /** |
| * @return {HTMLDivElement} Topmost visible page, or null, if |
| * there are no visible pages. |
| */ |
| getTopmostVisiblePage: function() { |
| return this.getVisibleOverlay_() || this.getVisibleNonOverlay_(); |
| }, |
| |
| /** |
| * Registers overlay. |
| * @param {HelpBasePage} overlay Overlay that should be registered. |
| */ |
| registerOverlay: function(overlay) { |
| this.registeredOverlays[overlay.name] = overlay; |
| overlay.isOverlay = true; |
| }, |
| |
| /** |
| * Shows or hides current page. |
| * @param {boolean} visible True if current page should be displayed. |
| */ |
| set visible(visible) { |
| if (this.visible == visible) |
| return; |
| |
| if (!visible) |
| this.storeLastFocusedElement_(); |
| |
| if (this.isOverlay) |
| this.setOverlayVisible_(visible); |
| else |
| this.pageDiv.hidden = !visible; |
| |
| if (visible) |
| this.restoreLastFocusedElement_(); |
| |
| if (visible) |
| this.onBeforeShow(); |
| }, |
| |
| /** |
| * Returns true if current page is visible. |
| * @return {boolean} True if current page is visible. |
| */ |
| get visible() { |
| if (this.isOverlay) |
| return this.pageDiv.classList.contains('showing'); |
| return !this.pageDiv.hidden; |
| }, |
| |
| /** |
| * This method returns overlay container, it should be called only |
| * on overlays. |
| * @return {HTMLDivElement} overlay container. |
| */ |
| get container() { |
| assert(this.isOverlay); |
| return this.pageDiv.parentNode; |
| }, |
| |
| /** |
| * Shows registered overlay. |
| * @param {string} name Name of registered overlay to show. |
| */ |
| showOverlay: function(name) { |
| var currentPage = this.getTopmostVisiblePage(); |
| currentPage.storeLastFocusedElement_(); |
| currentPage.freeze_(true); |
| |
| var overlay = this.registeredOverlays[name]; |
| if (!overlay) |
| return; |
| overlay.visible = true; |
| }, |
| |
| /** |
| * Hides currently displayed overlay. |
| */ |
| closeOverlay: function() { |
| var overlay = this.getVisibleOverlay_(); |
| if (!overlay) |
| return; |
| overlay.visible = false; |
| |
| var currentPage = this.getTopmostVisiblePage(); |
| currentPage.restoreLastFocusedElement_(); |
| currentPage.freeze_(false); |
| }, |
| |
| /** |
| * If the page does not contain focused elements, focuses on the |
| * first appropriate. |
| */ |
| focus: function() { |
| if (this.pageDiv.contains(document.activeElement)) |
| return; |
| var elements = this.pageDiv.querySelectorAll( |
| 'input, list, select, textarea, button'); |
| for (var i = 0; i < elements.length; i++) { |
| var element = elements[i]; |
| element.focus(); |
| if (document.activeElement == element) |
| return; |
| } |
| }, |
| }; |
| |
| // Export |
| return { |
| HelpBasePage: HelpBasePage |
| }; |
| }); |