• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2012 Google Inc. All Rights Reserved.
2
3/**
4 * @fileoverview Helper functions.
5 */
6
7goog.provide('cvox.SearchUtil');
8
9/** Utility functions. */
10cvox.SearchUtil = function() {
11};
12
13/**
14 * Extracts the first URL from an element.
15 * @param {Node} node DOM element to extract from.
16 * @return {?string} URL.
17 */
18cvox.SearchUtil.extractURL = function(node) {
19  if (node) {
20    if (node.tagName === 'A') {
21      return node.href;
22    }
23    var anchor = node.querySelector('a');
24    if (anchor) {
25      return anchor.href;
26    }
27  }
28  return null;
29};
30
31/**
32 * Indicates whether or not the search widget has been activated.
33 * @return {boolean} Whether or not the search widget is active.
34 */
35cvox.SearchUtil.isSearchWidgetActive = function() {
36  var SEARCH_WIDGET_SELECT = '#cvox-search';
37  return document.querySelector(SEARCH_WIDGET_SELECT) !== null;
38};
39
40/**
41 * Adds one to and index with wrapping.
42 * @param {number} index Index to add to.
43 * @param {number} length Length to wrap at.
44 * @return {number} The new index++, wrapped if exceeding length.
45 */
46cvox.SearchUtil.addOneWrap = function(index, length) {
47  return (index + 1) % length;
48};
49
50/**
51 * Subtracts one to and index with wrapping.
52 * @param {number} index Index to subtract from.
53 * @param {number} length Length to wrap at.
54 * @return {number} The new index--, wrapped if below 0.
55 */
56cvox.SearchUtil.subOneWrap = function(index, length) {
57  return (index - 1 + length) % length;
58};
59
60/**
61 * Returns the id of a node's active descendant
62 * @param {Node} targetNode The node.
63 * @return {?string} The id of the active descendant.
64 * @private
65 */
66var getActiveDescendantId_ = function(targetNode) {
67  if (!targetNode.getAttribute) {
68    return null;
69  }
70
71  var activeId = targetNode.getAttribute('aria-activedescendant');
72  if (!activeId) {
73    return null;
74  }
75  return activeId;
76};
77
78/**
79 * If the node is an object with an active descendant, returns the
80 * descendant node.
81 *
82 * This function will fully resolve an active descendant chain. If a circular
83 * chain is detected, it will return null.
84 *
85 * @param {Node} targetNode The node to get descendant information for.
86 * @return {Node} The descendant node or null if no node exists.
87 */
88var getActiveDescendant = function(targetNode) {
89  var seenIds = {};
90  var node = targetNode;
91
92  while (node) {
93    var activeId = getActiveDescendantId_(node);
94    if (!activeId) {
95      break;
96    }
97    if (activeId in seenIds) {
98      // A circlar activeDescendant is an error, so return null.
99      return null;
100    }
101    seenIds[activeId] = true;
102    node = document.getElementById(activeId);
103  }
104
105  if (node == targetNode) {
106    return null;
107  }
108  return node;
109};
110
111/**
112 * Dispatches a left click event on the element that is the targetNode.
113 * Clicks go in the sequence of mousedown, mouseup, and click.
114 * @param {Node} targetNode The target node of this operation.
115 * @param {boolean=} shiftKey Specifies if shift is held down.
116 * @param {boolean=} callOnClickDirectly Specifies whether or not to directly
117 * invoke the onclick method if there is one.
118 * @param {boolean=} opt_double True to issue a double click.
119 */
120cvox.SearchUtil.clickElem = function(
121    targetNode, shiftKey, callOnClickDirectly, opt_double) {
122  // If there is an activeDescendant of the targetNode, then that is where the
123  // click should actually be targeted.
124  var activeDescendant = getActiveDescendant(targetNode);
125  if (activeDescendant) {
126    targetNode = activeDescendant;
127  }
128  if (callOnClickDirectly) {
129    var onClickFunction = null;
130    if (targetNode.onclick) {
131      onClickFunction = targetNode.onclick;
132    }
133    if (!onClickFunction && (targetNode.nodeType != 1) &&
134        targetNode.parentNode && targetNode.parentNode.onclick) {
135      onClickFunction = targetNode.parentNode.onclick;
136    }
137    var keepGoing = true;
138    if (onClickFunction) {
139      try {
140        keepGoing = onClickFunction();
141      } catch (exception) {
142        // Something went very wrong with the onclick method; we'll ignore it
143        // and just dispatch a click event normally.
144      }
145    }
146    if (!keepGoing) {
147      // The onclick method ran successfully and returned false, meaning the
148      // event should not bubble up, so we will return here.
149      return;
150    }
151  }
152
153  // Send a mousedown (or simply a double click if requested).
154  var evt = document.createEvent('MouseEvents');
155  var evtType = opt_double ? 'dblclick' : 'mousedown';
156  evt.initMouseEvent(evtType, true, true, document.defaultView,
157                     1, 0, 0, 0, 0, false, false, shiftKey, false, 0, null);
158  // Mark any events we generate so we don't try to process our own events.
159  evt.fromCvox = true;
160  try {
161    targetNode.dispatchEvent(evt);
162  } catch (e) {}
163  //Send a mouse up
164  evt = document.createEvent('MouseEvents');
165  evt.initMouseEvent('mouseup', true, true, document.defaultView,
166                     1, 0, 0, 0, 0, false, false, shiftKey, false, 0, null);
167  // Mark any events we generate so we don't try to process our own events.
168  evt.fromCvox = true;
169  try {
170    targetNode.dispatchEvent(evt);
171  } catch (e) {}
172  //Send a click
173  evt = document.createEvent('MouseEvents');
174  evt.initMouseEvent('click', true, true, document.defaultView,
175                     1, 0, 0, 0, 0, false, false, shiftKey, false, 0, null);
176  // Mark any events we generate so we don't try to process our own events.
177  evt.fromCvox = true;
178  try {
179    targetNode.dispatchEvent(evt);
180  } catch (e) {}
181};
182