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