searchAutocomplete.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. define(["keywords", "searchHistoryItems", "options", "jquery", "jquery.ui"], function(keywordsInfo, searchHistory, options, $) {
  2. // Install search autocomplete
  3. $(document).ready(function () {
  4. if (options.getBoolean("webhelp.enable.search.autocomplete")) {
  5. var searchFunction = function (request, response) {
  6. var searchTerm = request.term.toLowerCase();
  7. // Get history proposals.
  8. var historyItems = getHistoryProposals(searchTerm);
  9. var titlePhrases = [];
  10. var phraseIds = [];
  11. var keywords = keywordsInfo.keywords;
  12. var ph = keywordsInfo.ph;
  13. var words = searchTerm.split(" ");
  14. var sameHi;
  15. // Iterate over words
  16. for (var wi = 0; wi < words.length; wi++) {
  17. var cw = words[wi].trim();
  18. if (cw.length > 0) {
  19. // Iterate over keywords to find the ones that contains the word
  20. var newPhraseIds = [];
  21. for (var i = 0; i < keywords.length; i++) {
  22. if (keywords[i].w.toLowerCase().indexOf(cw) == 0) {
  23. // Word was found
  24. var phIds = keywords[i].p;
  25. for (var pj = 0; pj < phIds.length; pj++) {
  26. var pid = phIds[pj];
  27. if (wi == 0) {
  28. newPhraseIds.push(pid);
  29. } else {
  30. // Keep only phrase indices that contains all words
  31. if (phraseIds.indexOf(pid) != -1) {
  32. newPhraseIds.push(pid);
  33. }
  34. }
  35. }
  36. }
  37. }
  38. phraseIds = newPhraseIds;
  39. }
  40. }
  41. if (phraseIds.length > 0) {
  42. // Compute proposals from titles/keywords
  43. for (var pi = 0; pi < phraseIds.length; pi++) {
  44. var wIdx = ph[phraseIds[pi]];
  45. var pStr = "";
  46. for (var wi = 0; wi < wIdx.length; wi++) {
  47. var word = keywords[wIdx[wi]].w;
  48. if (wi == 0) {
  49. word = word.charAt(0).toUpperCase() + word.substr(1);
  50. }
  51. pStr += word;
  52. if (wi < wIdx.length - 1) {
  53. pStr += " ";
  54. }
  55. }
  56. // Test if items is already in history proposals
  57. for (var i = 0; i < historyItems.length; i++) {
  58. if (pStr.toLocaleLowerCase() == historyItems[i]) {
  59. sameHi = true;
  60. break;
  61. }
  62. }
  63. if (sameHi == null) {
  64. var hp = {
  65. label: pStr.toLowerCase(),
  66. value: pStr.toLowerCase(),
  67. type: "title"
  68. };
  69. titlePhrases.push(hp);
  70. }
  71. }
  72. } else {
  73. var lastWord = words[words.length - 1];
  74. var beforeLastWord = request.term.substring(0, searchTerm.lastIndexOf(lastWord));
  75. for (var i = 0; i < keywords.length; i++) {
  76. if (keywords[i].w.toLowerCase().indexOf(cw) == 0) {
  77. var proposal = beforeLastWord + keywords[i].w;
  78. // Test if items is already in history proposals
  79. for (var j = 0; j < historyItems.length; j++) {
  80. if (proposal.toLocaleLowerCase() == historyItems[j]) {
  81. sameHi = true;
  82. break;
  83. }
  84. }
  85. if (sameHi == null) {
  86. var hp = {
  87. label: proposal.toLowerCase(),
  88. value: proposal.toLowerCase(),
  89. type: "keyword"
  90. };
  91. titlePhrases.push(hp);
  92. }
  93. }
  94. }
  95. }
  96. var res = [];
  97. res = res.concat(historyItems);
  98. res = res.concat(titlePhrases);
  99. response(res);
  100. };
  101. // Uncomment the following code if you want to take into account the border radius of the search input
  102. /*
  103. var leftMargin = parseInt($("#textToSearch").css("border-bottom-left-radius"));
  104. var rightMargin = parseInt($("#textToSearch").css("border-bottom-right-radius"));
  105. */
  106. var autocompleteObj = $("#textToSearch").autocomplete({
  107. source: searchFunction,
  108. minLength: 3
  109. // Uncomment the following code if you want to take into account the border radius of the search input
  110. /*
  111. ,
  112. position: {my : "left+" + leftMargin + " top"}
  113. */
  114. });
  115. // Close autocomplete on ENTER
  116. $("#textToSearch").keydown(function (event) {
  117. if (event.which == 13 && $("#textToSearch").length > 1) {
  118. $("#textToSearch").autocomplete("close");
  119. $("#searchForm").submit();
  120. }
  121. });
  122. var auObj = autocompleteObj.data("ui-autocomplete");
  123. // Set width of the autocomplete area
  124. // Uncomment the following code if you want to take into account the border radius of the search input
  125. /*
  126. auObj._resizeMenu = function(){
  127. this.menu.element.outerWidth( parseInt($("#textToSearch").outerWidth()) - leftMargin - rightMargin );
  128. };
  129. */
  130. // Install a renderer
  131. auObj._renderItem = function (ul, item) {
  132. // Text to search
  133. var tts = $("#textToSearch").val();
  134. tts = tts.toLowerCase();
  135. var words = tts.split(" ");
  136. /*console.log("Render item:", item);*/
  137. var proposal = item.label;
  138. // Highlight words from search query
  139. var pw = proposal.split(" ");
  140. var newProposal = "";
  141. for (var pwi = 0; pwi < pw.length; pwi++) {
  142. var cpw = pw[pwi];
  143. if (cpw.trim().length > 0) {
  144. // Iterate over words
  145. var added = false;
  146. for (var wi = 0; wi < words.length; wi++) {
  147. var w = words[wi].trim();
  148. if (w.length > 0) {
  149. // Iterate over keywords to find the ones
  150. // Highlight the text to search
  151. try {
  152. w = w.replace("\\", "\\\\")
  153. .replace(")", "\\)")
  154. .replace("(", "\\(");
  155. var cpwh = cpw.replace(
  156. new RegExp("(" + w + ")", 'i'),
  157. "<span class='search-autocomplete-proposal-hg'>$1</span>");
  158. } catch (e) {
  159. debug(e);
  160. }
  161. if (cpwh != cpw) {
  162. newProposal += cpwh;
  163. added = true;
  164. break;
  165. }
  166. }
  167. }
  168. if (!added) {
  169. newProposal += cpw;
  170. }
  171. if (pwi < pw.length - 1) {
  172. newProposal += " ";
  173. }
  174. }
  175. }
  176. var icon = "&nbsp;";
  177. if (item.type == 'history') {
  178. icon = "h";
  179. }
  180. var proposalIcon =
  181. $("<span>", {
  182. class: "search-autocomplete-proposal-icon " + item.type,
  183. html: icon
  184. });
  185. // span with proposal label
  186. var proposalLabel =
  187. $("<span>", {
  188. class: "search-autocomplete-proposal-label",
  189. "data-value": item.value,
  190. html: newProposal
  191. });
  192. // span with remove from history
  193. var removeButton;
  194. if (item.type == 'history') {
  195. removeButton =
  196. $("<span>", {
  197. class: "search-autocomplete-proposal-type-history",
  198. html: "<a data-value='" + item.value + "' class='oxy-icon oxy-icon-remove' />"
  199. });
  200. $(removeButton).find("a").on("click", function (event) {
  201. removeHistoryItem(this);
  202. // Do not close the menu
  203. event.preventDefault();
  204. event.stopPropagation();
  205. return false;
  206. });
  207. }
  208. var li = $("<li>", {
  209. class: "ui-menu-item",
  210. "data-value": item.value
  211. });
  212. var divWrapper = $("<div>", {
  213. class: "ui-menu-item-wrapper"
  214. });
  215. li.append(divWrapper);
  216. divWrapper.append(proposalIcon).append(proposalLabel);
  217. if (removeButton != null) {
  218. divWrapper.append(removeButton);
  219. }
  220. // If a search suggestion is chosen the form is submitted
  221. li.find(".ui-menu-item-wrapper").on("click", function (event) {
  222. $("#textToSearch").val($(this).find(".search-autocomplete-proposal-label").attr('data-value'));
  223. $("#searchForm").submit();
  224. });
  225. return li.appendTo(ul);
  226. };
  227. $(window).resize(function () {
  228. var autocompleteObj = $("#textToSearch").autocomplete("instance");
  229. autocompleteObj.search();
  230. });
  231. }
  232. });
  233. /**
  234. * Remove from local storage a history item.
  235. *
  236. * @param hi The renderer element.
  237. * @returns {boolean}
  238. */
  239. function removeHistoryItem(hi) {
  240. var historyItem = hi.getAttribute("data-value");
  241. var removed = searchHistory.removeSearchHistoryItem(historyItem);
  242. // Change label
  243. if (removed) {
  244. $(hi).attr("class", "oxy-icon oxy-icon-ok");
  245. $(hi).parents("div").find(".search-autocomplete-proposal-label").addClass("removed-from-history");
  246. }
  247. // Do not close the menu
  248. event.preventDefault();
  249. event.stopPropagation();
  250. return false;
  251. }
  252. /**
  253. * Compute search proposals from history items.
  254. *
  255. * @param searchQuery The search query.
  256. * @returns {Array} The array with search proposals.
  257. */
  258. function getHistoryProposals(searchQuery) {
  259. var toRet = [];
  260. var historyItems = searchHistory.getHistorySearchItems();
  261. if (historyItems != null) {
  262. var words = searchQuery.split(" ");
  263. for (var i = 0; i < historyItems.length; i++) {
  264. /*console.log("History item", historyItems[i]);*/
  265. // Test if history item match the serch query
  266. if (matchSearchHistoryItem(historyItems[i], words)) {
  267. var hp = {
  268. label: historyItems[i],
  269. value: historyItems[i],
  270. type: "history"
  271. };
  272. toRet.push(hp);
  273. } else {
  274. /*console.log("History item does not match...");*/
  275. }
  276. }
  277. }
  278. return toRet;
  279. }
  280. /**
  281. * Test if a history item match all words from search query.
  282. *
  283. * @param historyPhrase The history phrase.
  284. * @param words The list with words from search query.
  285. * @returns {boolean} True if history item matches the search words.
  286. */
  287. function matchSearchHistoryItem(historyPhrase, words) {
  288. // Iterate over words
  289. var historyWords = historyPhrase.split(" ");
  290. var allWordsMatch = true;
  291. for (var wi = 0; wi < words.length && allWordsMatch; wi++) {
  292. var cw = words[wi].trim();
  293. if (cw.length > 0) {
  294. // Iterate over keywords to find the ones that contains the word
  295. var wordFound = false;
  296. for (var i = 0; i < historyWords.length; i++) {
  297. if (historyWords[i].toLowerCase().indexOf(cw.toLowerCase()) == 0) {
  298. wordFound = true;
  299. break;
  300. }
  301. }
  302. allWordsMatch = allWordsMatch && wordFound;
  303. }
  304. }
  305. return allWordsMatch;
  306. }
  307. });