FindAction.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. /*
  2. *+------------------------------------------------------------------------+
  3. *| Licensed Materials - Property of IBM
  4. *| IBM Cognos Products: Viewer
  5. *| (C) Copyright IBM Corp. 2013
  6. *|
  7. *| US Government Users Restricted Rights - Use, duplication or
  8. *| disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  9. *|
  10. *+------------------------------------------------------------------------+
  11. */
  12. /**
  13. * FindAction - finds visible matching node and returns
  14. */
  15. function FindAction() {
  16. this.m_requestParams = null;
  17. this.m_sAction = 'find';
  18. this.findState = null;
  19. this.pageState = null;
  20. this.findConfig = null;
  21. }
  22. FindAction.prototype = new CognosViewerAction();
  23. FindAction.baseclass = CognosViewerAction.prototype;
  24. FindAction.prototype.setConfigAndState = function(params)
  25. {
  26. var cv = this.getCognosViewer();
  27. var cvState = cv.getState();
  28. this.findConfig = cv.getConfig().getFindActionConfig();
  29. this.pageState = cvState.getPageState();
  30. this.findState = cvState.getFindState();
  31. }
  32. FindAction.prototype.setRequestParms = function(params)
  33. {
  34. this.setConfigAndState();
  35. var cv = this.getCognosViewer();
  36. var cvState = cv.getState();
  37. if (cvState.getFindState()) {
  38. this.clearPreviousResult();
  39. }
  40. cvState.clearFindState();
  41. if (params) {
  42. if (this.pageState == null) {
  43. cvState.setPageState({});
  44. this.pageState = cvState.getPageState();
  45. }
  46. cvState.setFindState(params);
  47. this.findState = cvState.getFindState();
  48. }
  49. };
  50. FindAction.prototype.execute = function() {
  51. if (!this.findState) {
  52. return;
  53. }
  54. return this.findAndShow();
  55. }
  56. FindAction.prototype.findAndShow = function() {
  57. var cv = this.getCognosViewer();
  58. var hasNextPage = cv.hasNextPage();
  59. var hasPrevPage = cv.hasPrevPage();
  60. var currentPage = this.pageState.getCurrentPage();
  61. var matches = this.findOnClient();
  62. this.findState.updatePageInfo(matches, currentPage, hasNextPage, hasPrevPage);
  63. if (matches.length == 0) {
  64. if (this.findState.checkServerForNextMatch()) {
  65. return cv.executeAction('FindNextOnServer');
  66. } else {
  67. var callback = this.findConfig.getNoMatchFoundCallback();
  68. callback();
  69. return false;
  70. }
  71. } else {
  72. var currentFocus = this.findState.firstMatch();
  73. this.applyFocusStyle(currentFocus);
  74. }
  75. return true;
  76. }
  77. FindAction.prototype.clearPreviousResult = function(removeHighlights) {
  78. if (removeHighlights !== false) {
  79. this.removeHighlights();
  80. }
  81. this.findState.resetPageInfo();
  82. }
  83. FindAction.prototype.findOnClient = function() {
  84. var keyword = this.findState.getKeyword();
  85. //remove previous highlights
  86. this.removeHighlights();
  87. var matchingElements = new Array();
  88. var cv = this.getCognosViewer();
  89. var reportDiv = document.getElementById("CVReport" + cv.getId());
  90. this.findMatches(reportDiv, keyword, this.findState.isCaseSensitive(), matchingElements, 0, new FindPartialMatchHelper(), -1);
  91. this.applyMatchStyle(matchingElements);
  92. return matchingElements;
  93. };
  94. FindAction.prototype.applyMatchStyle = function(matchingElements) {
  95. var matchCount = matchingElements.length;
  96. for (var idx = matchCount-1 ; idx>= 0; idx-- ) {
  97. var matchObjArray = matchingElements[idx];
  98. for (var i = matchObjArray.length -1; i >=0 ;i--) {
  99. var matchObj = matchObjArray[i];
  100. var element = matchObj.element; //Element contains matching string
  101. var textNode = element.childNodes[0];
  102. var text = textNode.data;
  103. var textLength = text.length;
  104. var start = matchObj.start;
  105. var size = matchObj.matchedStr.length;
  106. var strBeforeMatch = (start>0) ? text.substring(0,start) : "";
  107. var strAfterMatch = (start+size < textLength) ? text.substring(start+size, textLength) : "";
  108. var matchSpan = document.createElement('span');
  109. matchSpan.setAttribute('tabIndex', 0);
  110. matchSpan.setAttribute('style', this.findConfig.getMatchUIStyle());
  111. matchSpan.style.background = this.findConfig.getMatchColor();//IE
  112. matchSpan.appendChild(document.createTextNode(matchObj.matchedStr));
  113. //build children backward: afterMatch - match span - beforeMatch
  114. textNode.data = strAfterMatch;
  115. element.insertBefore(matchSpan, textNode);
  116. if (strBeforeMatch.length >0) {
  117. var textNodeBeforeMatch = document.createTextNode(strBeforeMatch);
  118. element.insertBefore(textNodeBeforeMatch, matchSpan);
  119. }
  120. if (strAfterMatch.length == 0) {
  121. element.removeChild(textNode);
  122. }
  123. matchObj.element = matchSpan;//update element with the matchSpan just created
  124. }
  125. }
  126. };
  127. FindAction.prototype.removeHighlights = function() {
  128. var matches = this.findState.getMatches();
  129. var currentFocus = this.findState.getFocusedMatch();
  130. if (currentFocus) {
  131. this.restoreMatchStyle(currentFocus);
  132. }
  133. if (matches && matches.length>0) {
  134. var matchCount = matches.length;
  135. for(var i = 0; i< matchCount; i++) {
  136. var matchObjArray = matches[i];
  137. for (var j=0; j<matchObjArray.length; j++) {
  138. var matchObj = matchObjArray[j];
  139. var element = matchObj.element; //Span with match background color
  140. if (element) {
  141. var parentNode = element.parentNode;
  142. //Find a textNode sibling
  143. var textNode = null;
  144. var isTestNodeBeforeMatch = false;
  145. if (element.previousSibling && element.previousSibling.nodeType == 3) {
  146. textNode = element.previousSibling;
  147. isTestNodeBeforeMatch = true;
  148. } else if (element.nextSibling && element.nextSibling.nodeType == 3) {
  149. textNode = element.nextSibling;
  150. }
  151. if (textNode) {
  152. textNode.data = isTestNodeBeforeMatch ? (textNode.data + matchObj.matchedStr) : (matchObj.matchedStr + textNode.data);
  153. parentNode.removeChild(element);
  154. if (textNode.nextSibling && textNode.nextSibling.nodeType == 3) { // merge next textNode
  155. textNode.data = textNode.data + textNode.nextSibling.data;
  156. parentNode.removeChild(textNode.nextSibling);
  157. }
  158. } else {
  159. //Parent has only match span, replace with TextNode
  160. var textNodeMatch = document.createTextNode(matchObj.matchedStr);
  161. parentNode.insertBefore(textNodeMatch, element);
  162. parentNode.removeChild(element);
  163. }
  164. }
  165. }
  166. }
  167. }
  168. };
  169. FindAction.prototype.manageFocusStyle = function(matchObjArray, operation) {
  170. if (!matchObjArray) {
  171. return;
  172. }
  173. for (var i = matchObjArray.length -1; i >=0 ;i--) {
  174. var matchObj = matchObjArray[i];
  175. var element = matchObj.element;
  176. if ('restoreMatchStyle' === operation) {
  177. element.setAttribute('style', this.findConfig.getMatchUIStyle());
  178. element.style.background = this.findConfig.getMatchColor();//IE
  179. } else {
  180. element.setAttribute('style', this.findConfig.getFocusUIStyle());
  181. element.style.background = this.findConfig.getFocusColor();//IE
  182. element.focus();
  183. if (element.blur) {
  184. element.blur();
  185. }
  186. }
  187. }
  188. }
  189. FindAction.prototype.applyFocusStyle = function(matchObjArray) {
  190. this.manageFocusStyle(matchObjArray);
  191. }
  192. FindAction.prototype.restoreMatchStyle = function(matchObjArray) {
  193. this.manageFocusStyle(matchObjArray, 'restoreMatchStyle');
  194. }
  195. FindAction.prototype.isVisible = function ( node ) {
  196. var styleCheck = this.checkDisplayStyle(node);
  197. return ( !styleCheck.isVisibilityHidden && !styleCheck.isDisplayNone);
  198. }
  199. FindAction.prototype.checkDisplayStyle = function ( node ) {
  200. var visibility = null;
  201. var display = null;
  202. if (window.getComputedStyle) { //FF
  203. var style = window.getComputedStyle( node, "visibility" );
  204. if (!style) {
  205. style = window.getComputedStyle( node, "display" );
  206. }
  207. if (style) {
  208. visibility = style['visibility'];
  209. display = style['display'];
  210. }
  211. } else if ( node.currentStyle ) { //IE
  212. visibility = node.currentStyle.visibility;
  213. display = node.currentStyle.display;
  214. }
  215. var isDisplayInline = (display && display.indexOf('block') == -1 && display.indexOf('inline') >=0);
  216. var obj = {
  217. 'isVisibilityHidden': ('hidden' == visibility),
  218. 'isDisplayNone': ('none' == display),
  219. 'isDisplayInline': isDisplayInline,
  220. 'display' : display
  221. };
  222. return obj;
  223. }
  224. FindAction.prototype.findMatches = function(node, keyword, isCaseSensitive, resultArray, elementIndex, partialMatchHelper, blockDisplayParentIndex) {
  225. if (node.nodeType === 3) { // Text Node
  226. var text = node.data;
  227. if (text) {
  228. var parentTagName = node.parentNode.tagName.toUpperCase();
  229. if (parentTagName !== 'SCRIPT' && parentTagName !== 'STYLE') {
  230. var finalKeyword = isCaseSensitive? keyword : keyword.toUpperCase();
  231. var finalText = isCaseSensitive? text : text.toUpperCase();
  232. for(var startPosition = 0; startPosition< text.length; ) {
  233. startPosition = this.match(startPosition, node, finalText, finalKeyword, resultArray, elementIndex, partialMatchHelper, blockDisplayParentIndex);
  234. }
  235. }
  236. }
  237. } else if (node.nodeType === 1 ) { //Element Node
  238. var styleCheck = this.checkDisplayStyle(node);
  239. if (!styleCheck.isDisplayInline) {
  240. blockDisplayParentIndex = elementIndex;
  241. partialMatchHelper.reset();
  242. }
  243. if (styleCheck.isVisibilityHidden) {
  244. partialMatchHelper.reset();
  245. return; //skip the text
  246. }
  247. if (styleCheck.isDisplayNone) {
  248. return;//skip the text
  249. }
  250. for (var child = node.firstChild; child != null ; child = child.nextSibling) {
  251. this.findMatches(child, keyword, isCaseSensitive, resultArray, ++elementIndex, partialMatchHelper, blockDisplayParentIndex);
  252. }
  253. }
  254. };
  255. FindAction.prototype.match = function(startPosition, node, text, keyword, resultArray, elementIndex, partialMatchHelper, blockDisplayParentIndex) {
  256. if(!text || startPosition>=text.length) {
  257. return (startPosition +1); //End of string
  258. }
  259. var originalText = node.data;
  260. var startIndexOfKeyword = partialMatchHelper.startIndexOfKeyword;
  261. var matchObj = this.matchByLetter(text, keyword, originalText, startPosition, startIndexOfKeyword);
  262. if (!matchObj) {
  263. return startPosition+1;//no match
  264. }
  265. var matchedString = matchObj.matchedStr;
  266. if (matchedString.length == keyword.length) { //full match
  267. matchObj.update('full', elementIndex, node.parentNode, blockDisplayParentIndex)
  268. resultArray.push( [matchObj] );
  269. partialMatchHelper.reset();
  270. return (startPosition + matchedString.length); //Continue next letters
  271. }
  272. //Partial Match - head
  273. if (startIndexOfKeyword == 0) {
  274. matchObj.update('head', elementIndex, node.parentNode, blockDisplayParentIndex)
  275. partialMatchHelper.add(matchObj);
  276. return (startPosition + matchedString.length); //Continue next letters
  277. }
  278. var prevMatchObj = partialMatchHelper.partialMatches[partialMatchHelper.partialMatches.length-1];
  279. if (prevMatchObj.blockDisplayParentIndex != blockDisplayParentIndex) { //block display
  280. partialMatchHelper.reset();
  281. return (text.lenght+1); //Partial match is broken. Move on to next text
  282. }
  283. //Partial Match - middle
  284. if ( (startIndexOfKeyword + matchedString.length) < keyword.length){
  285. matchObj.update('middle', elementIndex, node.parentNode, blockDisplayParentIndex)
  286. partialMatchHelper.add(matchObj);
  287. return (startPosition + matchedString.length); //Continue next letters
  288. }
  289. //Partial Match - tail, partial matches
  290. if ( (startIndexOfKeyword + matchedString.length) == keyword.length){
  291. matchObj.update('tail', elementIndex, node.parentNode, blockDisplayParentIndex)
  292. partialMatchHelper.add(matchObj);
  293. resultArray.push( partialMatchHelper.partialMatches.slice(0) ); //Copy into resultArray
  294. partialMatchHelper.reset();
  295. }
  296. return (startPosition + matchedString.length); //Continue next letters
  297. }
  298. FindAction.prototype.matchByLetter = function(text, keyword, originalText, indexOfText, indexOfKeyword) {
  299. var startPosition = indexOfText;
  300. var compareNextLetter = true;
  301. var matchCount=0;
  302. for (; indexOfText<text.length && compareNextLetter ; indexOfText++, indexOfKeyword++) {
  303. if ( this.safeLetter(text.charCodeAt(indexOfText)) == this.safeLetter(keyword.charCodeAt(indexOfKeyword))) {
  304. compareNextLetter = true;
  305. if ( ++matchCount == keyword.length) {
  306. break; //full match
  307. }
  308. } else {
  309. compareNextLetter = false;
  310. }
  311. }
  312. if (!compareNextLetter) {
  313. return null;
  314. }
  315. return new FindMatch(startPosition, originalText.substr(startPosition, matchCount));
  316. }
  317. FindAction.prototype.safeLetter = function(c) {
  318. if(c == 160) {//non-breaking space &nbsp;
  319. return 32; //space
  320. } else {
  321. return c;
  322. }
  323. }
  324. //=========================
  325. /**
  326. * FindMatch class is used to keep a matched string and related information. A match can be full or partial match.
  327. */
  328. function FindMatch(start, matchedStr) {
  329. this.start = start; //Starting position of match
  330. this.matchedStr = matchedStr; //Matched string. it can be full string or partial of searching.
  331. this.index = null; //Unique id of element. An element can have many matches.
  332. this.element = null; //parent element of current match
  333. this.type = null; //full, head, middle, or tail
  334. this.blockDisplayParentIndex = null; //Partial matches must belong to same block display parent element
  335. this.orgStyle = null;
  336. }
  337. FindMatch.prototype = {};
  338. FindMatch.prototype.update = function(type, index, element, blockDisplayParentIndex) {
  339. this.index = index;
  340. this.element = element
  341. this.type = type;
  342. this.blockDisplayParentIndex = blockDisplayParentIndex;
  343. this.orgStyle = this.element.getAttribute('style');
  344. }
  345. /**
  346. * FindPartialMatchHelper class
  347. */
  348. function FindPartialMatchHelper() {
  349. this.startIndexOfKeyword = 0;
  350. this.partialMatches = null; //Array of matches
  351. }
  352. FindPartialMatchHelper.prototype = {};
  353. FindPartialMatchHelper.prototype.reset = function() {
  354. this.startIndexOfKeyword = 0;
  355. this.partialMatches = null;
  356. }
  357. FindPartialMatchHelper.prototype.add = function(matchObj) {
  358. if (!this.partialMatches) {
  359. this.partialMatches = [];
  360. }
  361. this.partialMatches.push(matchObj);
  362. this.startIndexOfKeyword += matchObj.matchedStr.length;
  363. }