widget.js revision cedac228d2dd51db4b79ea1e72c7f249408ee061
1// Copyright 2014 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/**
6 * @fileoverview Base class for all ChromeVox widgets.
7 *
8 * Widgets are keyboard driven and modal mediums for ChromeVox to expose
9 * additional features such as lists, interactive search, or grids.
10 */
11
12goog.provide('cvox.Widget');
13
14goog.require('cvox.AbstractEarcons');
15goog.require('cvox.ApiImplementation');
16goog.require('cvox.ChromeVox');
17goog.require('cvox.SpokenMessages');
18
19/**
20 * Keeps a reference to a currently or formerly active widget. This enforces
21 * the singleton nature of widgets.
22 * @type {cvox.Widget}
23 * @private
24 */
25cvox.Widget.ref_;
26
27
28/**
29 * @constructor
30 */
31cvox.Widget = function() {
32  /**
33   * @type {boolean}
34   * @protected
35   */
36  this.active = false;
37
38
39  /**
40   * Keeps a reference to a node which should receive focus once a widget hides.
41   * @type {Node}
42   * @protected
43   */
44  this.initialFocus = null;
45
46  /**
47   * Keeps a reference to a node which should receive selection once a widget
48   * hides.
49   * @type {Node}
50   * @protected
51   */
52  this.initialNode = null;
53
54  // Checks to see if there is a current widget in use.
55  if (!cvox.Widget.ref_ || !cvox.Widget.ref_.isActive()) {
56    cvox.Widget.ref_ = this;
57  }
58};
59
60
61/**
62 * Returns whether or not the widget is active.
63 * @return {boolean} Whether the widget is active.
64 */
65cvox.Widget.prototype.isActive = function() {
66  return this.active;
67};
68
69
70/**
71 * Visual/aural display of this widget.
72 */
73cvox.Widget.prototype.show = function() {
74  if (this.isActive()) {
75    // Only one widget should be shown at any given time.
76    this.hide(true);
77  }
78  this.onKeyDown = goog.bind(this.onKeyDown, this);
79  this.onKeyPress = goog.bind(this.onKeyPress, this);
80  window.addEventListener('keydown', this.onKeyDown, true);
81  window.addEventListener('keypress', this.onKeyPress, true);
82
83  this.initialNode =
84      cvox.ChromeVox.navigationManager.getCurrentNode();
85  this.initialFocus = document.activeElement;
86  this.isStickyOn = cvox.ChromeVox.isStickyOn;
87
88  // Widgets do not respond to sticky key.
89  cvox.ChromeVox.host.sendToBackgroundPage({
90      'target': 'Prefs',
91      'action': 'setPref',
92      'pref': 'sticky',
93      'value': false,
94      'announce': false});
95
96  if (this.getNameMsg() && this.getHelpMsg()) {
97    cvox.$m(this.getNameMsg()).
98        andPause().
99        andMessage(this.getHelpMsg()).
100        speakFlush();
101  }
102  cvox.ChromeVox.earcons.playEarcon(cvox.AbstractEarcons.OBJECT_OPEN);
103
104  this.active = true;
105};
106
107
108/**
109 * Visual/aural hide of this widget.
110 * @param {boolean=} opt_noSync Whether to attempt to sync to the node before
111 * this widget was first shown. If left unspecified or false, an attempt to sync
112 * will be made.
113 */
114cvox.Widget.prototype.hide = function(opt_noSync) {
115  window.removeEventListener('keypress', this.onKeyPress, true);
116  window.removeEventListener('keydown', this.onKeyDown, true);
117
118  cvox.ChromeVox.host.sendToBackgroundPage({
119      'target': 'Prefs',
120      'action': 'setPref',
121      'pref': 'sticky',
122      'value': this.isStickyOn,
123      'announce': false});
124
125  cvox.ChromeVox.earcons.playEarcon(cvox.AbstractEarcons.OBJECT_CLOSE);
126  if (!opt_noSync) {
127    this.initialNode = this.initialNode.nodeType == 1 ?
128        this.initialNode : this.initialNode.parentNode;
129    cvox.ApiImplementation.syncToNode(this.initialNode,
130                                      true,
131                                      cvox.AbstractTts.QUEUE_MODE_QUEUE);
132  }
133
134  this.active = false;
135};
136
137
138/**
139 * Toggle between showing and hiding.
140 */
141cvox.Widget.prototype.toggle = function() {
142  if (this.isActive()) {
143    this.hide();
144  } else {
145    this.show();
146  }
147};
148
149
150/**
151 * The name of the widget.
152 * @return {!Array} The message id referencing the name of the widget in an
153 * array argument form passable to cvox.ChromeVox.msgs.getMsg.apply.
154 */
155cvox.Widget.prototype.getNameMsg = goog.abstractMethod;
156
157
158/**
159 * Gets the help message for the widget.
160 * The help message succintly describes how to use the widget.
161 * @return {string} The message id referencing the help for the widget.
162 */
163cvox.Widget.prototype.getHelpMsg = goog.abstractMethod;
164
165
166/**
167 * The default widget key down handler.
168 *
169 * @param {Event} evt The keyDown event.
170 * @return {boolean} Whether or not the event was handled.
171 *
172 * @protected
173 */
174cvox.Widget.prototype.onKeyDown = function(evt) {
175  if (evt.keyCode == 27) { // Escape
176    this.hide();
177    evt.preventDefault();
178    return true;
179  } else if (evt.keyCode == 9) { // Tab
180    this.hide();
181    return true;
182  } else if (evt.keyCode == 17) {
183    cvox.ChromeVox.tts.stop();
184  }
185
186  evt.stopPropagation();
187  return true;
188};
189
190
191/**
192 * The default widget key press handler.
193 *
194 * @param {Event} evt The keyPress event.
195 * @return {boolean} Whether or not the event was handled.
196 *
197 * @protected
198 */
199cvox.Widget.prototype.onKeyPress = function(evt) {
200  return false;
201};
202/**
203 * @return {boolean} True if any widget is currently active.
204 */
205cvox.Widget.isActive = function() {
206  return (cvox.Widget.ref_ && cvox.Widget.ref_.isActive()) || false;
207};
208