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.
6 * @fileoverview A class for walking tables.
7 * NOTE: This class has a very different interface than the other walkers.
8 * This means it does not lend itself easily to e.g. decorators.
9 * TODO (stoarca): This might be able to be fixed by breaking it up into
10 * separate walkers for cell, row and column.
14 goog
.provide('cvox.TableWalker');
16 goog
.require('cvox.AbstractWalker');
17 goog
.require('cvox.BrailleUtil');
18 goog
.require('cvox.DescriptionUtil');
19 goog
.require('cvox.DomUtil');
20 goog
.require('cvox.NavDescription');
21 goog
.require('cvox.QueueMode');
22 goog
.require('cvox.TraverseTable');
26 * @extends {cvox.AbstractWalker}
28 cvox
.TableWalker = function() {
29 cvox
.AbstractWalker
.call(this);
32 * Only used as a cache for faster lookup.
33 * @type {!cvox.TraverseTable}
35 this.tt
= new cvox
.TraverseTable(null);
37 goog
.inherits(cvox
.TableWalker
, cvox
.AbstractWalker
);
42 cvox
.TableWalker
.prototype.next = function(sel
) {
43 // TODO (stoarca): See bug 6677953
44 return this.nextRow(sel
);
50 cvox
.TableWalker
.prototype.sync = function(sel
) {
51 return this.goTo_(sel
, goog
.bind(function(position
) {
52 return this.tt
.goToCell(position
);
58 * @suppress {checkTypes} actual parameter 2 of
59 * cvox.Msgs.prototype.getMsg does not match formal parameter
60 * found : Array<number>
61 * required: (Array<string>|null|undefined)
63 cvox
.TableWalker
.prototype.getDescription = function(prevSel
, sel
) {
64 var position
= this.syncPosition_(sel
);
68 this.tt
.goToCell(position
);
69 var descs
= cvox
.DescriptionUtil
.getCollectionDescription(prevSel
, sel
);
70 if (descs
.length
== 0) {
71 descs
.push(new cvox
.NavDescription({
72 annotation
: cvox
.ChromeVox
.msgs
.getMsg('empty_cell')
81 cvox
.TableWalker
.prototype.getBraille = function(prevSel
, sel
) {
82 var ret
= new cvox
.NavBraille({});
83 var position
= this.syncPosition_(sel
);
86 cvox
.BrailleUtil
.getTemplated(prevSel
.start
.node
, sel
.start
.node
);
87 text
.append(' ' + ++position
[0] + '/' + ++position
[1]);
89 return new cvox
.NavBraille({text
: text
});
95 cvox
.TableWalker
.prototype.getGranularityMsg
= goog
.abstractMethod
;
102 * Returns the first cell of the table that this selection is inside.
103 * @param {!cvox.CursorSelection} sel The selection.
104 * @return {cvox.CursorSelection} The selection for first cell of the table.
107 cvox
.TableWalker
.prototype.goToFirstCell = function(sel
) {
108 return this.goTo_(sel
, goog
.bind(function(position
) {
109 return this.tt
.goToCell([0, 0]);
114 * Returns the last cell of the table that this selection is inside.
115 * @param {!cvox.CursorSelection} sel The selection.
116 * @return {cvox.CursorSelection} The selection for the last cell of the table.
119 cvox
.TableWalker
.prototype.goToLastCell = function(sel
) {
120 return this.goTo_(sel
, goog
.bind(function(position
) {
121 return this.tt
.goToLastCell();
126 * Returns the first cell of the row that the selection is in.
127 * @param {!cvox.CursorSelection} sel The selection.
128 * @return {cvox.CursorSelection} The selection for the first cell in the row.
131 cvox
.TableWalker
.prototype.goToRowFirstCell = function(sel
) {
132 return this.goTo_(sel
, goog
.bind(function(position
) {
133 return this.tt
.goToCell([position
[0], 0]);
138 * Returns the last cell of the row that the selection is in.
139 * @param {!cvox.CursorSelection} sel The selection.
140 * @return {cvox.CursorSelection} The selection for the last cell in the row.
143 cvox
.TableWalker
.prototype.goToRowLastCell = function(sel
) {
144 return this.goTo_(sel
, goog
.bind(function(position
) {
145 return this.tt
.goToRowLastCell();
150 * Returns the first cell of the column that the selection is in.
151 * @param {!cvox.CursorSelection} sel The selection.
152 * @return {cvox.CursorSelection} The selection for the first cell in the col.
155 cvox
.TableWalker
.prototype.goToColFirstCell = function(sel
) {
156 return this.goTo_(sel
, goog
.bind(function(position
) {
157 return this.tt
.goToCell([0, position
[1]]);
162 * Returns the last cell of the column that the selection is in.
163 * @param {!cvox.CursorSelection} sel The selection.
164 * @return {cvox.CursorSelection} The selection for the last cell in the col.
167 cvox
.TableWalker
.prototype.goToColLastCell = function(sel
) {
168 return this.goTo_(sel
, goog
.bind(function(position
) {
169 return this.tt
.goToColLastCell();
174 * Returns the first cell in the row after the current selection.
175 * @param {!cvox.CursorSelection} sel The selection.
176 * @return {cvox.CursorSelection} The selection for the first cell in the next
180 cvox
.TableWalker
.prototype.nextRow = function(sel
) {
181 return this.goTo_(sel
, goog
.bind(function(position
) {
182 return this.tt
.goToCell([position
[0] + (sel
.isReversed() ? -1 : 1),
188 * Returns the first cell in the column after the current selection.
189 * @param {!cvox.CursorSelection} sel The selection.
190 * @return {cvox.CursorSelection} The selection for the first cell in the
194 cvox
.TableWalker
.prototype.nextCol = function(sel
) {
195 return this.goTo_(sel
, goog
.bind(function(position
) {
196 return this.tt
.goToCell([position
[0],
197 position
[1] + (sel
.isReversed() ? -1 : 1)]);
202 * @param {!cvox.CursorSelection} sel The current selection.
203 * @return {cvox.CursorSelection} The resulting selection.
206 cvox
.TableWalker
.prototype.announceHeaders = function(sel
) {
207 cvox
.ChromeVox
.tts
.speak(this.getHeaderText_(sel
),
208 cvox
.QueueMode
.FLUSH
,
209 cvox
.AbstractTts
.PERSONALITY_ANNOTATION
);
214 * @param {!cvox.CursorSelection} sel The current selection.
215 * @return {cvox.CursorSelection} The resulting selection.
218 cvox
.TableWalker
.prototype.speakTableLocation = function(sel
) {
219 cvox
.ChromeVox
.navigationManager
.speakDescriptionArray(
220 this.getLocationDescription_(sel
),
221 cvox
.QueueMode
.FLUSH
,
228 * @param {!cvox.CursorSelection} sel The current selection.
229 * @return {cvox.CursorSelection} The resulting selection.
232 cvox
.TableWalker
.prototype.exitShifterContent = function(sel
) {
233 var tableNode
= this.getTableNode_(sel
);
237 var nextNode
= cvox
.DomUtil
.directedNextLeafNode(tableNode
, false);
238 return cvox
.CursorSelection
.fromNode(nextNode
);
242 /** End of actions. */
246 * Returns the text content of the header(s) of the cell that contains sel.
247 * @param {!cvox.CursorSelection} sel The selection.
248 * @return {!string} The header text.
251 cvox
.TableWalker
.prototype.getHeaderText_ = function(sel
) {
252 this.tt
.initialize(this.getTableNode_(sel
));
253 var position
= this.tt
.findNearestCursor(sel
.start
.node
);
255 return cvox
.ChromeVox
.msgs
.getMsg('not_inside_table');
257 if (!this.tt
.goToCell(position
)) {
258 return cvox
.ChromeVox
.msgs
.getMsg('not_inside_table');
261 this.getRowHeaderText_(position
) +
263 this.getColHeaderText_(position
));
267 * Returns the location description.
268 * @param {!cvox.CursorSelection} sel A valid selection.
269 * @return {Array<cvox.NavDescription>} The location description.
270 * @suppress {checkTypes} actual parameter 2 of
271 * cvox.Msgs.prototype.getMsg does not match
273 * found : Array<number>
274 * required: (Array<string>|null|undefined)
277 cvox
.TableWalker
.prototype.getLocationDescription_ = function(sel
) {
278 var locationInfo
= this.getLocationInfo(sel
);
279 if (locationInfo
== null) {
282 return [new cvox
.NavDescription({
283 text
: cvox
.ChromeVox
.msgs
.getMsg('table_location', locationInfo
)
288 * Returns the text content of the row header(s) of the cell that contains sel.
289 * @param {!Array<number>} position The selection.
290 * @return {!string} The header text.
293 cvox
.TableWalker
.prototype.getRowHeaderText_ = function(position
) {
294 // TODO(stoarca): OPTMZ Replace with join();
295 var rowHeaderText
= '';
297 var rowHeaders
= this.tt
.getCellRowHeaders();
298 if (rowHeaders
.length
== 0) {
299 var firstCellInRow
= this.tt
.getCellAt([position
[0], 0]);
300 rowHeaderText
+= cvox
.DomUtil
.collapseWhitespace(
301 cvox
.DomUtil
.getValue(firstCellInRow
) + ' ' +
302 cvox
.DomUtil
.getName(firstCellInRow
));
303 return cvox
.ChromeVox
.msgs
.getMsg('row_header') + rowHeaderText
;
306 for (var i
= 0; i
< rowHeaders
.length
; ++i
) {
307 rowHeaderText
+= cvox
.DomUtil
.collapseWhitespace(
308 cvox
.DomUtil
.getValue(rowHeaders
[i
]) + ' ' +
309 cvox
.DomUtil
.getName(rowHeaders
[i
]));
311 if (rowHeaderText
== '') {
312 return cvox
.ChromeVox
.msgs
.getMsg('empty_row_header');
314 return cvox
.ChromeVox
.msgs
.getMsg('row_header') + rowHeaderText
;
318 * Returns the text content of the col header(s) of the cell that contains sel.
319 * @param {!Array<number>} position The selection.
320 * @return {!string} The header text.
323 cvox
.TableWalker
.prototype.getColHeaderText_ = function(position
) {
324 // TODO(stoarca): OPTMZ Replace with join();
325 var colHeaderText
= '';
327 var colHeaders
= this.tt
.getCellColHeaders();
328 if (colHeaders
.length
== 0) {
329 var firstCellInCol
= this.tt
.getCellAt([0, position
[1]]);
330 colHeaderText
+= cvox
.DomUtil
.collapseWhitespace(
331 cvox
.DomUtil
.getValue(firstCellInCol
) + ' ' +
332 cvox
.DomUtil
.getName(firstCellInCol
));
333 return cvox
.ChromeVox
.msgs
.getMsg('column_header') + colHeaderText
;
336 for (var i
= 0; i
< colHeaders
.length
; ++i
) {
337 colHeaderText
+= cvox
.DomUtil
.collapseWhitespace(
338 cvox
.DomUtil
.getValue(colHeaders
[i
]) + ' ' +
339 cvox
.DomUtil
.getName(colHeaders
[i
]));
341 if (colHeaderText
== '') {
342 return cvox
.ChromeVox
.msgs
.getMsg('empty_row_header');
344 return cvox
.ChromeVox
.msgs
.getMsg('column_header') + colHeaderText
;
348 * Returns the location info of sel within the containing table.
349 * @param {!cvox.CursorSelection} sel The selection.
350 * @return {Array<number>} The location info:
351 * [row index, row count, col index, col count].
353 cvox
.TableWalker
.prototype.getLocationInfo = function(sel
) {
354 this.tt
.initialize(this.getTableNode_(sel
));
355 var position
= this.tt
.findNearestCursor(sel
.start
.node
);
359 // + 1 to account for 0-indexed
365 ].map(function(x
) {return cvox
.ChromeVox
.msgs
.getNumber(x
);});
369 * Returns true if sel is inside a table.
370 * @param {!cvox.CursorSelection} sel The selection.
371 * @return {boolean} True if inside a table node.
373 cvox
.TableWalker
.prototype.isInTable = function(sel
) {
374 return this.getTableNode_(sel
) != null;
378 * Wrapper for going to somewhere so that boilerplate is not repeated.
379 * @param {!cvox.CursorSelection} sel The selection from which to base the
381 * @param {function(Array<number>):boolean} f The function to use for moving.
382 * Returns true on success and false on failure.
383 * @return {cvox.CursorSelection} The resulting selection.
386 cvox
.TableWalker
.prototype.goTo_ = function(sel
, f
) {
387 this.tt
.initialize(this.getTableNode_(sel
));
388 var position
= this.tt
.findNearestCursor(sel
.end
.node
);
392 this.tt
.goToCell(position
);
396 return cvox
.CursorSelection
.fromNode(this.tt
.getCell()).
397 setReversed(sel
.isReversed());
401 * Returns the nearest table node containing the end of the selection
402 * @param {!cvox.CursorSelection} sel The selection.
403 * @return {Node} The table node containing sel. null if not in a table.
406 cvox
.TableWalker
.prototype.getTableNode_ = function(sel
) {
407 return cvox
.DomUtil
.getContainingTable(sel
.end
.node
);
411 * Sync the backing traversal utility to the given selection.
412 * @param {!cvox.CursorSelection} sel The selection.
413 * @return {Array<number>} The position [x, y] of the selection.
416 cvox
.TableWalker
.prototype.syncPosition_ = function(sel
) {
417 var tableNode
= this.getTableNode_(sel
);
418 this.tt
.initialize(tableNode
);
419 // we need to align the TraverseTable with our sel because our walker
420 // uses parts of it (for example isSpanned relies on being at a specific cell)
421 return this.tt
.findNearestCursor(sel
.end
.node
);