blob: 578e260a5e40b731a6a96592644e6c9fb55913db [file] [log] [blame]
// 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
};
});