• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
13cr.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