Home | History | Annotate | Download | only in help
      1 // Copyright 2013 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 
      5 // Helper base class for all help pages and overlays, which controls
      6 // overlays, focus and scroll. This class is partially based on
      7 // OptionsPage, but simpler and contains only overlay- and focus-
      8 // handling logic. As in OptionsPage each page can be an overlay itself,
      9 // but each page contains its own list of registered overlays which can be
     10 // displayed over it.
     11 //
     12 // TODO (ygorshenin@): crbug.com/313244.
     13 cr.define('help', function() {
     14   function HelpBasePage() {
     15   }
     16 
     17   HelpBasePage.prototype = {
     18     __proto__: HTMLDivElement.prototype,
     19 
     20     /**
     21      * name of the page, should be the same as an id of the
     22      * corresponding HTMLDivElement.
     23      */
     24     name: null,
     25 
     26     /**
     27      * HTML counterpart of this page.
     28      */
     29     pageDiv: null,
     30 
     31     /**
     32      * True if current page is overlay.
     33      */
     34     isOverlay: false,
     35 
     36     /**
     37      * HTMLElement that was last focused when this page was the
     38      * topmost.
     39      */
     40     lastFocusedElement: null,
     41 
     42     /**
     43      * State of vertical scrollbars when this page was the topmost.
     44      */
     45     lastScrollTop: 0,
     46 
     47     /**
     48      * Dictionary of registered overlays.
     49      */
     50     registeredOverlays: {},
     51 
     52     /**
     53      * Stores currently focused element.
     54      * @private
     55      */
     56     storeLastFocusedElement_: function() {
     57       if (this.pageDiv.contains(document.activeElement))
     58         this.lastFocusedElement = document.activeElement;
     59     },
     60 
     61     /**
     62      * Restores focus to the last focused element on this page.
     63      * @private
     64      */
     65     restoreLastFocusedElement_: function() {
     66       if (this.lastFocusedElement)
     67         this.lastFocusedElement.focus();
     68       else
     69         this.focus();
     70     },
     71 
     72     /**
     73      * Shows or hides current page iff it's an overlay.
     74      * @param {boolean} visible True if overlay should be displayed.
     75      * @private
     76      */
     77     setOverlayVisible_: function(visible) {
     78       assert(this.isOverlay);
     79       this.container.hidden = !visible;
     80       if (visible)
     81         this.pageDiv.classList.add('showing');
     82       else
     83         this.pageDiv.classList.remove('showing');
     84     },
     85 
     86     /**
     87      * @return {HTMLDivElement}  visible non-overlay page or
     88      * null, if there are no visible non-overlay pages.
     89      * @private
     90      */
     91     getVisibleNonOverlay_: function() {
     92       if (this.isOverlay || !this.visible)
     93         return null;
     94       return this;
     95     },
     96 
     97     /**
     98      * @return {HTMLDivElement} Visible overlay page, or null,
     99      * if there are no visible overlay pages.
    100      * @private
    101      */
    102     getVisibleOverlay_: function() {
    103       for (var name in this.registeredOverlays) {
    104         var overlay = this.registeredOverlays[name];
    105         if (overlay.visible)
    106           return overlay;
    107       }
    108       return null;
    109     },
    110 
    111     /**
    112      * Freezes current page, makes it impossible to scroll it.
    113      * @param {boolean} freeze True if the page should be frozen.
    114      * @private
    115      */
    116     freeze_: function(freeze) {
    117       var scrollLeft = scrollLeftForDocument(document);
    118       if (freeze) {
    119         this.lastScrollTop = scrollTopForDocument(document);
    120         document.body.style.overflow = 'hidden';
    121         window.scroll(scrollLeft, 0);
    122       } else {
    123         document.body.style.overflow = 'auto';
    124         window.scroll(scrollLeft, this.lastScrollTop);
    125       }
    126     },
    127 
    128     /**
    129      * Initializes current page.
    130      * @param {string} name Name of the current page.
    131      */
    132     initialize: function(name) {
    133       this.name = name;
    134       this.pageDiv = $(name);
    135     },
    136 
    137     /**
    138      * Called before overlay is displayed.
    139      */
    140     onBeforeShow: function() {
    141     },
    142 
    143     /**
    144      * @return {HTMLDivElement} Topmost visible page, or null, if
    145      * there are no visible pages.
    146      */
    147     getTopmostVisiblePage: function() {
    148       return this.getVisibleOverlay_() || this.getVisibleNonOverlay_();
    149     },
    150 
    151     /**
    152      * Registers overlay.
    153      * @param {HelpBasePage} overlay Overlay that should be registered.
    154      */
    155     registerOverlay: function(overlay) {
    156       this.registeredOverlays[overlay.name] = overlay;
    157       overlay.isOverlay = true;
    158     },
    159 
    160     /**
    161      * Shows or hides current page.
    162      * @param {boolean} visible True if current page should be displayed.
    163      */
    164     set visible(visible) {
    165       if (this.visible == visible)
    166         return;
    167 
    168       if (!visible)
    169         this.storeLastFocusedElement_();
    170 
    171       if (this.isOverlay)
    172         this.setOverlayVisible_(visible);
    173       else
    174         this.pageDiv.hidden = !visible;
    175 
    176       if (visible)
    177         this.restoreLastFocusedElement_();
    178 
    179       if (visible)
    180         this.onBeforeShow();
    181     },
    182 
    183     /**
    184      * Returns true if current page is visible.
    185      * @return {boolean} True if current page is visible.
    186      */
    187     get visible() {
    188       if (this.isOverlay)
    189         return this.pageDiv.classList.contains('showing');
    190       return !this.pageDiv.hidden;
    191     },
    192 
    193     /**
    194      * This method returns overlay container, it should be called only
    195      * on overlays.
    196      * @return {HTMLDivElement} overlay container.
    197      */
    198     get container() {
    199       assert(this.isOverlay);
    200       return this.pageDiv.parentNode;
    201     },
    202 
    203     /**
    204      * Shows registered overlay.
    205      * @param {string} name Name of registered overlay to show.
    206      */
    207     showOverlay: function(name) {
    208       var currentPage = this.getTopmostVisiblePage();
    209       currentPage.storeLastFocusedElement_();
    210       currentPage.freeze_(true);
    211 
    212       var overlay = this.registeredOverlays[name];
    213       if (!overlay)
    214         return;
    215       overlay.visible = true;
    216     },
    217 
    218     /**
    219      * Hides currently displayed overlay.
    220      */
    221     closeOverlay: function() {
    222       var overlay = this.getVisibleOverlay_();
    223       if (!overlay)
    224         return;
    225       overlay.visible = false;
    226 
    227       var currentPage = this.getTopmostVisiblePage();
    228       currentPage.restoreLastFocusedElement_();
    229       currentPage.freeze_(false);
    230     },
    231 
    232     /**
    233      * If the page does not contain focused elements, focuses on the
    234      * first appropriate.
    235      */
    236     focus: function() {
    237       if (this.pageDiv.contains(document.activeElement))
    238         return;
    239       var elements = this.pageDiv.querySelectorAll(
    240         'input, list, select, textarea, button');
    241       for (var i = 0; i < elements.length; i++) {
    242         var element = elements[i];
    243         element.focus();
    244         if (document.activeElement == element)
    245           return;
    246       }
    247     },
    248   };
    249 
    250   // Export
    251   return {
    252     HelpBasePage: HelpBasePage
    253   };
    254 });
    255