suggest.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. // This file contains javascript associated with a autosuggest control
  2. function smc_AutoSuggest(oOptions)
  3. {
  4. this.opt = oOptions;
  5. // Store the handle to the text box.
  6. this.oTextHandle = document.getElementById(this.opt.sControlId);
  7. this.oRealTextHandle = null;
  8. this.oSuggestDivHandle = null;
  9. this.sLastSearch = '';
  10. this.sLastDirtySearch = '';
  11. this.oSelectedDiv = null;
  12. this.aCache = [];
  13. this.aDisplayData = [];
  14. this.sRetrieveURL = 'sRetrieveURL' in this.opt ? this.opt.sRetrieveURL : '%scripturl%action=suggest;suggest_type=%suggest_type%;search=%search%;%sessionVar%=%sessionID%;xml;time=%time%';
  15. // How many objects can we show at once?
  16. this.iMaxDisplayQuantity = 'iMaxDisplayQuantity' in this.opt ? this.opt.iMaxDisplayQuantity : 15;
  17. // How many characters shall we start searching on?
  18. this.iMinimumSearchChars = 'iMinimumSearchChars' in this.opt ? this.opt.iMinimumSearchChars : 3;
  19. // Should selected items be added to a list?
  20. this.bItemList = 'bItemList' in this.opt ? this.opt.bItemList : false;
  21. // Are there any items that should be added in advance?
  22. this.aListItems = 'aListItems' in this.opt ? this.opt.aListItems : [];
  23. this.sItemTemplate = 'sItemTemplate' in this.opt ? this.opt.sItemTemplate : '<input type="hidden" name="%post_name%[]" value="%item_id%" /><a href="%item_href%" class="extern" onclick="window.open(this.href, \'_blank\'); return false;">%item_name%</a>&nbsp;<img src="%images_url%/pm_recipient_delete.png" alt="%delete_text%" title="%delete_text%" onclick="return %self%.deleteAddedItem(%item_id%);" />';
  24. this.sTextDeleteItem = 'sTextDeleteItem' in this.opt ? this.opt.sTextDeleteItem : '';
  25. this.oCallback = {};
  26. this.bDoAutoAdd = false;
  27. this.iItemCount = 0;
  28. this.oHideTimer = null;
  29. this.bPositionComplete = false;
  30. this.oXmlRequestHandle = null;
  31. // Just make sure the page is loaded before calling the init.
  32. addLoadEvent(this.opt.sSelf + '.init();');
  33. }
  34. smc_AutoSuggest.prototype.init = function()
  35. {
  36. if (!window.XMLHttpRequest)
  37. return false;
  38. // Create a div that'll contain the results later on.
  39. this.oSuggestDivHandle = document.createElement('div');
  40. this.oSuggestDivHandle.className = 'auto_suggest_div';
  41. document.body.appendChild(this.oSuggestDivHandle);
  42. // Create a backup text input.
  43. this.oRealTextHandle = document.createElement('input');
  44. this.oRealTextHandle.type = 'hidden';
  45. this.oRealTextHandle.name = this.oTextHandle.name;
  46. this.oRealTextHandle.value = this.oTextHandle.value;
  47. this.oTextHandle.form.appendChild(this.oRealTextHandle);
  48. // Disable autocomplete in any browser by obfuscating the name.
  49. this.oTextHandle.name = 'dummy_' + Math.floor(Math.random() * 1000000);
  50. this.oTextHandle.autocomplete = 'off';
  51. this.oTextHandle.instanceRef = this;
  52. var fOnKeyDown = function (oEvent) {
  53. return this.instanceRef.handleKey(oEvent);
  54. };
  55. is_opera ? this.oTextHandle.onkeypress = fOnKeyDown : this.oTextHandle.onkeydown = fOnKeyDown;
  56. this.oTextHandle.onkeyup = function (oEvent) {
  57. return this.instanceRef.autoSuggestUpdate(oEvent);
  58. };
  59. this.oTextHandle.onchange = function (oEvent) {
  60. return this.instanceRef.autoSuggestUpdate(oEvent);
  61. };
  62. this.oTextHandle.onblur = function (oEvent) {
  63. return this.instanceRef.autoSuggestHide(oEvent);
  64. };
  65. this.oTextHandle.onfocus = function (oEvent) {
  66. return this.instanceRef.autoSuggestUpdate(oEvent);
  67. };
  68. if (this.bItemList)
  69. {
  70. if ('sItemListContainerId' in this.opt)
  71. this.oItemList = document.getElementById(this.opt.sItemListContainerId);
  72. else
  73. {
  74. this.oItemList = document.createElement('div');
  75. this.oTextHandle.parentNode.insertBefore(this.oItemList, this.oTextHandle.nextSibling);
  76. }
  77. }
  78. if (this.aListItems.length > 0)
  79. for (var i = 0, n = this.aListItems.length; i < n; i++)
  80. this.addItemLink(this.aListItems[i].sItemId, this.aListItems[i].sItemName);
  81. return true;
  82. }
  83. // Was it an enter key - if so assume they are trying to select something.
  84. smc_AutoSuggest.prototype.handleKey = function(oEvent)
  85. {
  86. // Grab the event object, one way or the other
  87. if (!oEvent)
  88. oEvent = window.event;
  89. // Get the keycode of the key that was pressed.
  90. var iKeyPress = 0;
  91. if ('keyCode' in oEvent)
  92. iKeyPress = oEvent.keyCode;
  93. else if ('which' in oEvent)
  94. iKeyPress = oEvent.which;
  95. switch (iKeyPress)
  96. {
  97. // Tab.
  98. case 9:
  99. if (this.aDisplayData.length > 0)
  100. {
  101. if (this.oSelectedDiv != null)
  102. this.itemClicked(this.oSelectedDiv);
  103. else
  104. this.handleSubmit();
  105. }
  106. // Continue to the next control.
  107. return true;
  108. break;
  109. // Enter.
  110. case 13:
  111. if (this.aDisplayData.length > 0 && this.oSelectedDiv != null)
  112. {
  113. this.itemClicked(this.oSelectedDiv);
  114. // Do our best to stop it submitting the form!
  115. return false;
  116. }
  117. else
  118. return true;
  119. break;
  120. // Up/Down arrow?
  121. case 38:
  122. case 40:
  123. if (this.aDisplayData.length && this.oSuggestDivHandle.style.visibility != 'hidden')
  124. {
  125. // Loop through the display data trying to find our entry.
  126. var bPrevHandle = false;
  127. var oToHighlight = null;
  128. for (var i = 0; i < this.aDisplayData.length; i++)
  129. {
  130. // If we're going up and yet the top one was already selected don't go around.
  131. if (this.oSelectedDiv != null && this.oSelectedDiv == this.aDisplayData[i] && i == 0 && iKeyPress == 38)
  132. {
  133. oToHighlight = this.oSelectedDiv;
  134. break;
  135. }
  136. // If nothing is selected and we are going down then we select the first one.
  137. if (this.oSelectedDiv == null && iKeyPress == 40)
  138. {
  139. oToHighlight = this.aDisplayData[i];
  140. break;
  141. }
  142. // If the previous handle was the actual previously selected one and we're hitting down then this is the one we want.
  143. if (bPrevHandle != false && bPrevHandle == this.oSelectedDiv && iKeyPress == 40)
  144. {
  145. oToHighlight = this.aDisplayData[i];
  146. break;
  147. }
  148. // If we're going up and this is the previously selected one then we want the one before, if there was one.
  149. if (bPrevHandle != false && this.aDisplayData[i] == this.oSelectedDiv && iKeyPress == 38)
  150. {
  151. oToHighlight = bPrevHandle;
  152. break;
  153. }
  154. // Make the previous handle this!
  155. bPrevHandle = this.aDisplayData[i];
  156. }
  157. // If we don't have one to highlight by now then it must be the last one that we're after.
  158. if (oToHighlight == null)
  159. oToHighlight = bPrevHandle;
  160. // Remove any old highlighting.
  161. if (this.oSelectedDiv != null)
  162. this.itemMouseOut(this.oSelectedDiv);
  163. // Mark what the selected div now is.
  164. this.oSelectedDiv = oToHighlight;
  165. this.itemMouseOver(this.oSelectedDiv);
  166. }
  167. break;
  168. }
  169. return true;
  170. }
  171. // Functions for integration.
  172. smc_AutoSuggest.prototype.registerCallback = function(sCallbackType, sCallback)
  173. {
  174. switch (sCallbackType)
  175. {
  176. case 'onBeforeAddItem':
  177. this.oCallback.onBeforeAddItem = sCallback;
  178. break;
  179. case 'onAfterAddItem':
  180. this.oCallback.onAfterAddItem = sCallback;
  181. break;
  182. case 'onAfterDeleteItem':
  183. this.oCallback.onAfterDeleteItem = sCallback;
  184. break;
  185. case 'onBeforeUpdate':
  186. this.oCallback.onBeforeUpdate = sCallback;
  187. break;
  188. }
  189. }
  190. // User hit submit?
  191. smc_AutoSuggest.prototype.handleSubmit = function()
  192. {
  193. var bReturnValue = true;
  194. var oFoundEntry = null;
  195. // Do we have something that matches the current text?
  196. for (var i = 0; i < this.aCache.length; i++)
  197. {
  198. if (this.sLastSearch.toLowerCase() == this.aCache[i].sItemName.toLowerCase().substr(0, this.sLastSearch.length))
  199. {
  200. // Exact match?
  201. if (this.sLastSearch.length == this.aCache[i].sItemName.length)
  202. {
  203. // This is the one!
  204. oFoundEntry = {
  205. sItemId: this.aCache[i].sItemId,
  206. sItemName: this.aCache[i].sItemName
  207. };
  208. break;
  209. }
  210. // Not an exact match, but it'll do for now.
  211. else
  212. {
  213. // If we have two matches don't find anything.
  214. if (oFoundEntry != null)
  215. bReturnValue = false;
  216. else
  217. oFoundEntry = {
  218. sItemId: this.aCache[i].sItemId,
  219. sItemName: this.aCache[i].sItemName
  220. };
  221. }
  222. }
  223. }
  224. if (oFoundEntry == null || bReturnValue == false)
  225. return bReturnValue;
  226. else
  227. {
  228. this.addItemLink(oFoundEntry.sItemId, oFoundEntry.sItemName, true);
  229. return false;
  230. }
  231. }
  232. // Positions the box correctly on the window.
  233. smc_AutoSuggest.prototype.positionDiv = function()
  234. {
  235. // Only do it once.
  236. if (this.bPositionComplete)
  237. return true;
  238. this.bPositionComplete = true;
  239. // Put the div under the text box.
  240. var aParentPos = smf_itemPos(this.oTextHandle);
  241. this.oSuggestDivHandle.style.left = aParentPos[0] + 'px';
  242. this.oSuggestDivHandle.style.top = (aParentPos[1] + this.oTextHandle.offsetHeight) + 'px';
  243. this.oSuggestDivHandle.style.width = this.oTextHandle.style.width;
  244. return true;
  245. }
  246. // Do something after clicking an item.
  247. smc_AutoSuggest.prototype.itemClicked = function(oCurElement)
  248. {
  249. // Is there a div that we are populating?
  250. if (this.bItemList)
  251. this.addItemLink(oCurElement.sItemId, oCurElement.innerHTML);
  252. // Otherwise clear things down.
  253. else
  254. this.oTextHandle.value = oCurElement.innerHTML.php_unhtmlspecialchars();
  255. this.oRealTextHandle.value = this.oTextHandle.value;
  256. this.autoSuggestActualHide();
  257. this.oSelectedDiv = null;
  258. }
  259. // Remove the last searched for name from the search box.
  260. smc_AutoSuggest.prototype.removeLastSearchString = function ()
  261. {
  262. // Remove the text we searched for from the div.
  263. var sTempText = this.oTextHandle.value.toLowerCase();
  264. var iStartString = sTempText.indexOf(this.sLastSearch.toLowerCase());
  265. // Just attempt to remove the bits we just searched for.
  266. if (iStartString != -1)
  267. {
  268. while (iStartString > 0)
  269. {
  270. if (sTempText.charAt(iStartString - 1) == '"' || sTempText.charAt(iStartString - 1) == ',' || sTempText.charAt(iStartString - 1) == ' ')
  271. {
  272. iStartString--;
  273. if (sTempText.charAt(iStartString - 1) == ',')
  274. break;
  275. }
  276. else
  277. break;
  278. }
  279. // Now remove anything from iStartString upwards.
  280. this.oTextHandle.value = this.oTextHandle.value.substr(0, iStartString);
  281. }
  282. // Just take it all.
  283. else
  284. this.oTextHandle.value = '';
  285. }
  286. // Add a result if not already done.
  287. smc_AutoSuggest.prototype.addItemLink = function (sItemId, sItemName, bFromSubmit)
  288. {
  289. // Increase the internal item count.
  290. this.iItemCount ++;
  291. // If there's a callback then call it.
  292. if ('oCallback' in this && 'onBeforeAddItem' in this.oCallback && typeof(this.oCallback.onBeforeAddItem) == 'string')
  293. {
  294. // If it returns false the item must not be added.
  295. if (!eval(this.oCallback.onBeforeAddItem + '(' + this.opt.sSelf + ', \'' + sItemId + '\');'))
  296. return;
  297. }
  298. var oNewDiv = document.createElement('div');
  299. oNewDiv.id = 'suggest_' + this.opt.sSuggestId + '_' + sItemId;
  300. setInnerHTML(oNewDiv, this.sItemTemplate.replace(/%post_name%/g, this.opt.sPostName).replace(/%item_id%/g, sItemId).replace(/%item_href%/g, smf_prepareScriptUrl(smf_scripturl) + this.opt.sURLMask.replace(/%item_id%/g, sItemId)).replace(/%item_name%/g, sItemName).replace(/%images_url%/g, smf_images_url).replace(/%self%/g, this.opt.sSelf).replace(/%delete_text%/g, this.sTextDeleteItem));
  301. this.oItemList.appendChild(oNewDiv);
  302. // If there's a registered callback, call it.
  303. if ('oCallback' in this && 'onAfterAddItem' in this.oCallback && typeof(this.oCallback.onAfterAddItem) == 'string')
  304. eval(this.oCallback.onAfterAddItem + '(' + this.opt.sSelf + ', \'' + oNewDiv.id + '\', ' + this.iItemCount + ');');
  305. // Clear the div a bit.
  306. this.removeLastSearchString();
  307. // If we came from a submit, and there's still more to go, turn on auto add for all the other things.
  308. this.bDoAutoAdd = this.oTextHandle.value != '' && bFromSubmit;
  309. // Update the fellow..
  310. this.autoSuggestUpdate();
  311. }
  312. // Delete an item that has been added, if at all?
  313. smc_AutoSuggest.prototype.deleteAddedItem = function (sItemId)
  314. {
  315. var oDiv = document.getElementById('suggest_' + this.opt.sSuggestId + '_' + sItemId);
  316. // Remove the div if it exists.
  317. if (typeof(oDiv) == 'object' && oDiv != null)
  318. {
  319. oDiv.parentNode.removeChild(document.getElementById('suggest_' + this.opt.sSuggestId + '_' + sItemId));
  320. // Decrease the internal item count.
  321. this.iItemCount --;
  322. // If there's a registered callback, call it.
  323. if ('oCallback' in this && 'onAfterDeleteItem' in this.oCallback && typeof(this.oCallback.onAfterDeleteItem) == 'string')
  324. eval(this.oCallback.onAfterDeleteItem + '(' + this.opt.sSelf + ', ' + this.iItemCount + ');');
  325. }
  326. return false;
  327. }
  328. // Hide the box.
  329. smc_AutoSuggest.prototype.autoSuggestHide = function ()
  330. {
  331. // Delay to allow events to propogate through....
  332. this.oHideTimer = setTimeout(this.opt.sSelf + '.autoSuggestActualHide();', 250);
  333. }
  334. // Do the actual hiding after a timeout.
  335. smc_AutoSuggest.prototype.autoSuggestActualHide = function()
  336. {
  337. this.oSuggestDivHandle.style.display = 'none';
  338. this.oSuggestDivHandle.style.visibility = 'hidden';
  339. this.oSelectedDiv = null;
  340. }
  341. // Show the box.
  342. smc_AutoSuggest.prototype.autoSuggestShow = function()
  343. {
  344. if (this.oHideTimer)
  345. {
  346. clearTimeout(this.oHideTimer);
  347. this.oHideTimer = false;
  348. }
  349. this.positionDiv();
  350. this.oSuggestDivHandle.style.visibility = 'visible';
  351. this.oSuggestDivHandle.style.display = '';
  352. }
  353. // Populate the actual div.
  354. smc_AutoSuggest.prototype.populateDiv = function(aResults)
  355. {
  356. // Cannot have any children yet.
  357. while (this.oSuggestDivHandle.childNodes.length > 0)
  358. {
  359. // Tidy up the events etc too.
  360. this.oSuggestDivHandle.childNodes[0].onmouseover = null;
  361. this.oSuggestDivHandle.childNodes[0].onmouseout = null;
  362. this.oSuggestDivHandle.childNodes[0].onclick = null;
  363. this.oSuggestDivHandle.removeChild(this.oSuggestDivHandle.childNodes[0]);
  364. }
  365. // Something to display?
  366. if (typeof(aResults) == 'undefined')
  367. {
  368. this.aDisplayData = [];
  369. return false;
  370. }
  371. var aNewDisplayData = [];
  372. for (var i = 0; i < (aResults.length > this.iMaxDisplayQuantity ? this.iMaxDisplayQuantity : aResults.length); i++)
  373. {
  374. // Create the sub element
  375. var oNewDivHandle = document.createElement('div');
  376. oNewDivHandle.sItemId = aResults[i].sItemId;
  377. oNewDivHandle.className = 'auto_suggest_item';
  378. oNewDivHandle.innerHTML = aResults[i].sItemName;
  379. //oNewDivHandle.style.width = this.oTextHandle.style.width;
  380. this.oSuggestDivHandle.appendChild(oNewDivHandle);
  381. // Attach some events to it so we can do stuff.
  382. oNewDivHandle.instanceRef = this;
  383. oNewDivHandle.onmouseover = function (oEvent)
  384. {
  385. this.instanceRef.itemMouseOver(this);
  386. }
  387. oNewDivHandle.onmouseout = function (oEvent)
  388. {
  389. this.instanceRef.itemMouseOut(this);
  390. }
  391. oNewDivHandle.onclick = function (oEvent)
  392. {
  393. this.instanceRef.itemClicked(this);
  394. }
  395. aNewDisplayData[i] = oNewDivHandle;
  396. }
  397. this.aDisplayData = aNewDisplayData;
  398. return true;
  399. }
  400. // Refocus the element.
  401. smc_AutoSuggest.prototype.itemMouseOver = function (oCurElement)
  402. {
  403. this.oSelectedDiv = oCurElement;
  404. oCurElement.className = 'auto_suggest_item_hover';
  405. }
  406. // Onfocus the element
  407. smc_AutoSuggest.prototype.itemMouseOut = function (oCurElement)
  408. {
  409. oCurElement.className = 'auto_suggest_item';
  410. }
  411. smc_AutoSuggest.prototype.onSuggestionReceived = function (oXMLDoc)
  412. {
  413. var sQuoteText = '';
  414. var aItems = oXMLDoc.getElementsByTagName('item');
  415. this.aCache = [];
  416. for (var i = 0; i < aItems.length; i++)
  417. {
  418. this.aCache[i] = {
  419. sItemId: aItems[i].getAttribute('id'),
  420. sItemName: aItems[i].childNodes[0].nodeValue
  421. };
  422. // If we're doing auto add and we find the exact person, then add them!
  423. if (this.bDoAutoAdd && this.sLastSearch == this.aCache[i].sItemName)
  424. {
  425. var oReturnValue = {
  426. sItemId: this.aCache[i].sItemId,
  427. sItemName: this.aCache[i].sItemName
  428. };
  429. this.aCache = [];
  430. return this.addItemLink(oReturnValue.sItemId, oReturnValue.sItemName, true);
  431. }
  432. }
  433. // Check we don't try to keep auto updating!
  434. this.bDoAutoAdd = false;
  435. // Populate the div.
  436. this.populateDiv(this.aCache);
  437. // Make sure we can see it - if we can.
  438. if (aItems.length == 0)
  439. this.autoSuggestHide();
  440. else
  441. this.autoSuggestShow();
  442. return true;
  443. }
  444. // Get a new suggestion.
  445. smc_AutoSuggest.prototype.autoSuggestUpdate = function ()
  446. {
  447. // If there's a callback then call it.
  448. if ('onBeforeUpdate' in this.oCallback && typeof(this.oCallback.onBeforeUpdate) == 'string')
  449. {
  450. // If it returns false the item must not be added.
  451. if (!eval(this.oCallback.onBeforeUpdate + '(' + this.opt.sSelf + ');'))
  452. return false;
  453. }
  454. this.oRealTextHandle.value = this.oTextHandle.value;
  455. if (isEmptyText(this.oTextHandle))
  456. {
  457. this.aCache = [];
  458. this.populateDiv();
  459. this.autoSuggestHide();
  460. return true;
  461. }
  462. // Nothing changed?
  463. if (this.oTextHandle.value == this.sLastDirtySearch)
  464. return true;
  465. this.sLastDirtySearch = this.oTextHandle.value;
  466. // We're only actually interested in the last string.
  467. var sSearchString = this.oTextHandle.value.replace(/^("[^"]+",[ ]*)+/, '').replace(/^([^,]+,[ ]*)+/, '');
  468. if (sSearchString.substr(0, 1) == '"')
  469. sSearchString = sSearchString.substr(1);
  470. // Stop replication ASAP.
  471. var sRealLastSearch = this.sLastSearch;
  472. this.sLastSearch = sSearchString;
  473. // Either nothing or we've completed a sentance.
  474. if (sSearchString == '' || sSearchString.substr(sSearchString.length - 1) == '"')
  475. {
  476. this.populateDiv();
  477. return true;
  478. }
  479. // Nothing?
  480. if (sRealLastSearch == sSearchString)
  481. return true;
  482. // Too small?
  483. else if (sSearchString.length < this.iMinimumSearchChars)
  484. {
  485. this.aCache = [];
  486. this.autoSuggestHide();
  487. return true;
  488. }
  489. else if (sSearchString.substr(0, sRealLastSearch.length) == sRealLastSearch)
  490. {
  491. // Instead of hitting the server again, just narrow down the results...
  492. var aNewCache = [];
  493. var j = 0;
  494. var sLowercaseSearch = sSearchString.toLowerCase();
  495. for (var k = 0; k < this.aCache.length; k++)
  496. {
  497. if (this.aCache[k].sItemName.substr(0, sSearchString.length).toLowerCase() == sLowercaseSearch)
  498. aNewCache[j++] = this.aCache[k];
  499. }
  500. this.aCache = [];
  501. if (aNewCache.length != 0)
  502. {
  503. this.aCache = aNewCache;
  504. // Repopulate.
  505. this.populateDiv(this.aCache);
  506. // Check it can be seen.
  507. this.autoSuggestShow();
  508. return true;
  509. }
  510. }
  511. // In progress means destroy!
  512. if (typeof(this.oXmlRequestHandle) == 'object' && this.oXmlRequestHandle != null)
  513. this.oXmlRequestHandle.abort();
  514. // Clean the text handle.
  515. sSearchString = sSearchString.php_to8bit().php_urlencode();
  516. // Get the document.
  517. sendXMLDocument.call(this, this.sRetrieveURL.replace(/%scripturl%/g, smf_prepareScriptUrl(smf_scripturl)).replace(/%suggest_type%/g, this.opt.sSearchType).replace(/%search%/g, sSearchString).replace(/%sessionVar%/g, this.opt.sSessionVar).replace(/%sessionID%/g, this.opt.sSessionId).replace(/%time%/g, new Date().getTime()), '', this.onSuggestionReceived);
  518. return true;
  519. }