PageRenderTime 70ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/corelib-search/src/test/resources/solr/search/conf/velocity/jquery.autocomplete.js

https://gitlab.com/Aaeinstein54/corelib
JavaScript | 762 lines | 619 code | 75 blank | 68 comment | 156 complexity | 7ec86551884a691ab8c2143197b3ed8d MD5 | raw file
  1. /*
  2. * Autocomplete - jQuery plugin 1.1pre
  3. *
  4. * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
  5. *
  6. * Dual licensed under the MIT and GPL licenses:
  7. * http://www.opensource.org/licenses/mit-license.php
  8. * http://www.gnu.org/licenses/gpl.html
  9. *
  10. * Revision: $Id: jquery.autocomplete.js 5785 2008-07-12 10:37:33Z joern.zaefferer $
  11. *
  12. */
  13. ;(function($) {
  14. $.fn.extend({
  15. autocomplete: function(urlOrData, options) {
  16. var isUrl = typeof urlOrData == "string";
  17. options = $.extend({}, $.Autocompleter.defaults, {
  18. url: isUrl ? urlOrData : null,
  19. data: isUrl ? null : urlOrData,
  20. delay: isUrl ? $.Autocompleter.defaults.delay : 10,
  21. max: options && !options.scroll ? 10 : 150
  22. }, options);
  23. // if highlight is set to false, replace it with a do-nothing function
  24. options.highlight = options.highlight || function(value) { return value; };
  25. // if the formatMatch option is not specified, then use formatItem for backwards compatibility
  26. options.formatMatch = options.formatMatch || options.formatItem;
  27. return this.each(function() {
  28. new $.Autocompleter(this, options);
  29. });
  30. },
  31. result: function(handler) {
  32. return this.bind("result", handler);
  33. },
  34. search: function(handler) {
  35. return this.trigger("search", [handler]);
  36. },
  37. flushCache: function() {
  38. return this.trigger("flushCache");
  39. },
  40. setOptions: function(options){
  41. return this.trigger("setOptions", [options]);
  42. },
  43. unautocomplete: function() {
  44. return this.trigger("unautocomplete");
  45. }
  46. });
  47. $.Autocompleter = function(input, options) {
  48. var KEY = {
  49. UP: 38,
  50. DOWN: 40,
  51. DEL: 46,
  52. TAB: 9,
  53. RETURN: 13,
  54. ESC: 27,
  55. COMMA: 188,
  56. PAGEUP: 33,
  57. PAGEDOWN: 34,
  58. BACKSPACE: 8
  59. };
  60. // Create $ object for input element
  61. var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
  62. var timeout;
  63. var previousValue = "";
  64. var cache = $.Autocompleter.Cache(options);
  65. var hasFocus = 0;
  66. var lastKeyPressCode;
  67. var config = {
  68. mouseDownOnSelect: false
  69. };
  70. var select = $.Autocompleter.Select(options, input, selectCurrent, config);
  71. var blockSubmit;
  72. // prevent form submit in opera when selecting with return key
  73. $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
  74. if (blockSubmit) {
  75. blockSubmit = false;
  76. return false;
  77. }
  78. });
  79. // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
  80. $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
  81. // track last key pressed
  82. lastKeyPressCode = event.keyCode;
  83. switch(event.keyCode) {
  84. case KEY.UP:
  85. event.preventDefault();
  86. if ( select.visible() ) {
  87. select.prev();
  88. } else {
  89. onChange(0, true);
  90. }
  91. break;
  92. case KEY.DOWN:
  93. event.preventDefault();
  94. if ( select.visible() ) {
  95. select.next();
  96. } else {
  97. onChange(0, true);
  98. }
  99. break;
  100. case KEY.PAGEUP:
  101. event.preventDefault();
  102. if ( select.visible() ) {
  103. select.pageUp();
  104. } else {
  105. onChange(0, true);
  106. }
  107. break;
  108. case KEY.PAGEDOWN:
  109. event.preventDefault();
  110. if ( select.visible() ) {
  111. select.pageDown();
  112. } else {
  113. onChange(0, true);
  114. }
  115. break;
  116. // matches also semicolon
  117. case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
  118. case KEY.TAB:
  119. case KEY.RETURN:
  120. if( selectCurrent() ) {
  121. // stop default to prevent a form submit, Opera needs special handling
  122. event.preventDefault();
  123. blockSubmit = true;
  124. return false;
  125. }
  126. break;
  127. case KEY.ESC:
  128. select.hide();
  129. break;
  130. default:
  131. clearTimeout(timeout);
  132. timeout = setTimeout(onChange, options.delay);
  133. break;
  134. }
  135. }).focus(function(){
  136. // track whether the field has focus, we shouldn't process any
  137. // results if the field no longer has focus
  138. hasFocus++;
  139. }).blur(function() {
  140. hasFocus = 0;
  141. if (!config.mouseDownOnSelect) {
  142. hideResults();
  143. }
  144. }).click(function() {
  145. // show select when clicking in a focused field
  146. if ( hasFocus++ > 1 && !select.visible() ) {
  147. onChange(0, true);
  148. }
  149. }).bind("search", function() {
  150. // TODO why not just specifying both arguments?
  151. var fn = (arguments.length > 1) ? arguments[1] : null;
  152. function findValueCallback(q, data) {
  153. var result;
  154. if( data && data.length ) {
  155. for (var i=0; i < data.length; i++) {
  156. if( data[i].result.toLowerCase() == q.toLowerCase() ) {
  157. result = data[i];
  158. break;
  159. }
  160. }
  161. }
  162. if( typeof fn == "function" ) fn(result);
  163. else $input.trigger("result", result && [result.data, result.value]);
  164. }
  165. $.each(trimWords($input.val()), function(i, value) {
  166. request(value, findValueCallback, findValueCallback);
  167. });
  168. }).bind("flushCache", function() {
  169. cache.flush();
  170. }).bind("setOptions", function() {
  171. $.extend(options, arguments[1]);
  172. // if we've updated the data, repopulate
  173. if ( "data" in arguments[1] )
  174. cache.populate();
  175. }).bind("unautocomplete", function() {
  176. select.unbind();
  177. $input.unbind();
  178. $(input.form).unbind(".autocomplete");
  179. });
  180. function selectCurrent() {
  181. var selected = select.selected();
  182. if( !selected )
  183. return false;
  184. var v = selected.result;
  185. previousValue = v;
  186. if ( options.multiple ) {
  187. var words = trimWords($input.val());
  188. if ( words.length > 1 ) {
  189. v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
  190. }
  191. v += options.multipleSeparator;
  192. }
  193. $input.val(v);
  194. hideResultsNow();
  195. $input.trigger("result", [selected.data, selected.value]);
  196. return true;
  197. }
  198. function onChange(crap, skipPrevCheck) {
  199. if( lastKeyPressCode == KEY.DEL ) {
  200. select.hide();
  201. return;
  202. }
  203. var currentValue = $input.val();
  204. if ( !skipPrevCheck && currentValue == previousValue )
  205. return;
  206. previousValue = currentValue;
  207. currentValue = lastWord(currentValue);
  208. if ( currentValue.length >= options.minChars) {
  209. $input.addClass(options.loadingClass);
  210. if (!options.matchCase)
  211. currentValue = currentValue.toLowerCase();
  212. request(currentValue, receiveData, hideResultsNow);
  213. } else {
  214. stopLoading();
  215. select.hide();
  216. }
  217. };
  218. function trimWords(value) {
  219. if ( !value ) {
  220. return [""];
  221. }
  222. var words = value.split( options.multipleSeparator );
  223. var result = [];
  224. $.each(words, function(i, value) {
  225. if ( $.trim(value) )
  226. result[i] = $.trim(value);
  227. });
  228. return result;
  229. }
  230. function lastWord(value) {
  231. if ( !options.multiple )
  232. return value;
  233. var words = trimWords(value);
  234. return words[words.length - 1];
  235. }
  236. // fills in the input box w/the first match (assumed to be the best match)
  237. // q: the term entered
  238. // sValue: the first matching result
  239. function autoFill(q, sValue){
  240. // autofill in the complete box w/the first match as long as the user hasn't entered in more data
  241. // if the last user key pressed was backspace, don't autofill
  242. if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
  243. // fill in the value (keep the case the user has typed)
  244. $input.val($input.val() + sValue.substring(lastWord(previousValue).length));
  245. // select the portion of the value not typed by the user (so the next character will erase)
  246. $.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
  247. }
  248. };
  249. function hideResults() {
  250. clearTimeout(timeout);
  251. timeout = setTimeout(hideResultsNow, 200);
  252. };
  253. function hideResultsNow() {
  254. var wasVisible = select.visible();
  255. select.hide();
  256. clearTimeout(timeout);
  257. stopLoading();
  258. if (options.mustMatch) {
  259. // call search and run callback
  260. $input.search(
  261. function (result){
  262. // if no value found, clear the input box
  263. if( !result ) {
  264. if (options.multiple) {
  265. var words = trimWords($input.val()).slice(0, -1);
  266. $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
  267. }
  268. else
  269. $input.val( "" );
  270. }
  271. }
  272. );
  273. }
  274. if (wasVisible)
  275. // position cursor at end of input field
  276. $.Autocompleter.Selection(input, input.value.length, input.value.length);
  277. };
  278. function receiveData(q, data) {
  279. if ( data && data.length && hasFocus ) {
  280. stopLoading();
  281. select.display(data, q);
  282. autoFill(q, data[0].value);
  283. select.show();
  284. } else {
  285. hideResultsNow();
  286. }
  287. };
  288. function request(term, success, failure) {
  289. if (!options.matchCase)
  290. term = term.toLowerCase();
  291. var data = cache.load(term);
  292. // recieve the cached data
  293. if (data && data.length) {
  294. success(term, data);
  295. // if an AJAX url has been supplied, try loading the data now
  296. } else if( (typeof options.url == "string") && (options.url.length > 0) ){
  297. var extraParams = {
  298. timestamp: +new Date()
  299. };
  300. $.each(options.extraParams, function(key, param) {
  301. extraParams[key] = typeof param == "function" ? param() : param;
  302. });
  303. $.ajax({
  304. // try to leverage ajaxQueue plugin to abort previous requests
  305. mode: "abort",
  306. // limit abortion to this input
  307. port: "autocomplete" + input.name,
  308. dataType: options.dataType,
  309. url: options.url,
  310. data: $.extend({
  311. q: lastWord(term),
  312. limit: options.max
  313. }, extraParams),
  314. success: function(data) {
  315. var parsed = options.parse && options.parse(data) || parse(data);
  316. cache.add(term, parsed);
  317. success(term, parsed);
  318. }
  319. });
  320. } else {
  321. // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
  322. select.emptyList();
  323. failure(term);
  324. }
  325. };
  326. function parse(data) {
  327. var parsed = [];
  328. var rows = data.split("\n");
  329. for (var i=0; i < rows.length; i++) {
  330. var row = $.trim(rows[i]);
  331. if (row) {
  332. row = row.split("|");
  333. parsed[parsed.length] = {
  334. data: row,
  335. value: row[0],
  336. result: options.formatResult && options.formatResult(row, row[0]) || row[0]
  337. };
  338. }
  339. }
  340. return parsed;
  341. };
  342. function stopLoading() {
  343. $input.removeClass(options.loadingClass);
  344. };
  345. };
  346. $.Autocompleter.defaults = {
  347. inputClass: "ac_input",
  348. resultsClass: "ac_results",
  349. loadingClass: "ac_loading",
  350. minChars: 1,
  351. delay: 400,
  352. matchCase: false,
  353. matchSubset: true,
  354. matchContains: false,
  355. cacheLength: 10,
  356. max: 100,
  357. mustMatch: false,
  358. extraParams: {},
  359. selectFirst: true,
  360. formatItem: function(row) { return row[0]; },
  361. formatMatch: null,
  362. autoFill: false,
  363. width: 0,
  364. multiple: false,
  365. multipleSeparator: ", ",
  366. highlight: function(value, term) {
  367. return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
  368. },
  369. scroll: true,
  370. scrollHeight: 180
  371. };
  372. $.Autocompleter.Cache = function(options) {
  373. var data = {};
  374. var length = 0;
  375. function matchSubset(s, sub) {
  376. if (!options.matchCase)
  377. s = s.toLowerCase();
  378. var i = s.indexOf(sub);
  379. if (options.matchContains == "word"){
  380. i = s.toLowerCase().search("\\b" + sub.toLowerCase());
  381. }
  382. if (i == -1) return false;
  383. return i == 0 || options.matchContains;
  384. };
  385. function add(q, value) {
  386. if (length > options.cacheLength){
  387. flush();
  388. }
  389. if (!data[q]){
  390. length++;
  391. }
  392. data[q] = value;
  393. }
  394. function populate(){
  395. if( !options.data ) return false;
  396. // track the matches
  397. var stMatchSets = {},
  398. nullData = 0;
  399. // no url was specified, we need to adjust the cache length to make sure it fits the local data store
  400. if( !options.url ) options.cacheLength = 1;
  401. // track all options for minChars = 0
  402. stMatchSets[""] = [];
  403. // loop through the array and create a lookup structure
  404. for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
  405. var rawValue = options.data[i];
  406. // if rawValue is a string, make an array otherwise just reference the array
  407. rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
  408. var value = options.formatMatch(rawValue, i+1, options.data.length);
  409. if ( value === false )
  410. continue;
  411. var firstChar = value.charAt(0).toLowerCase();
  412. // if no lookup array for this character exists, look it up now
  413. if( !stMatchSets[firstChar] )
  414. stMatchSets[firstChar] = [];
  415. // if the match is a string
  416. var row = {
  417. value: value,
  418. data: rawValue,
  419. result: options.formatResult && options.formatResult(rawValue) || value
  420. };
  421. // push the current match into the set list
  422. stMatchSets[firstChar].push(row);
  423. // keep track of minChars zero items
  424. if ( nullData++ < options.max ) {
  425. stMatchSets[""].push(row);
  426. }
  427. };
  428. // add the data items to the cache
  429. $.each(stMatchSets, function(i, value) {
  430. // increase the cache size
  431. options.cacheLength++;
  432. // add to the cache
  433. add(i, value);
  434. });
  435. }
  436. // populate any existing data
  437. setTimeout(populate, 25);
  438. function flush(){
  439. data = {};
  440. length = 0;
  441. }
  442. return {
  443. flush: flush,
  444. add: add,
  445. populate: populate,
  446. load: function(q) {
  447. if (!options.cacheLength || !length)
  448. return null;
  449. /*
  450. * if dealing w/local data and matchContains than we must make sure
  451. * to loop through all the data collections looking for matches
  452. */
  453. if( !options.url && options.matchContains ){
  454. // track all matches
  455. var csub = [];
  456. // loop through all the data grids for matches
  457. for( var k in data ){
  458. // don't search through the stMatchSets[""] (minChars: 0) cache
  459. // this prevents duplicates
  460. if( k.length > 0 ){
  461. var c = data[k];
  462. $.each(c, function(i, x) {
  463. // if we've got a match, add it to the array
  464. if (matchSubset(x.value, q)) {
  465. csub.push(x);
  466. }
  467. });
  468. }
  469. }
  470. return csub;
  471. } else
  472. // if the exact item exists, use it
  473. if (data[q]){
  474. return data[q];
  475. } else
  476. if (options.matchSubset) {
  477. for (var i = q.length - 1; i >= options.minChars; i--) {
  478. var c = data[q.substr(0, i)];
  479. if (c) {
  480. var csub = [];
  481. $.each(c, function(i, x) {
  482. if (matchSubset(x.value, q)) {
  483. csub[csub.length] = x;
  484. }
  485. });
  486. return csub;
  487. }
  488. }
  489. }
  490. return null;
  491. }
  492. };
  493. };
  494. $.Autocompleter.Select = function (options, input, select, config) {
  495. var CLASSES = {
  496. ACTIVE: "ac_over"
  497. };
  498. var listItems,
  499. active = -1,
  500. data,
  501. term = "",
  502. needsInit = true,
  503. element,
  504. list;
  505. // Create results
  506. function init() {
  507. if (!needsInit)
  508. return;
  509. element = $("<div/>")
  510. .hide()
  511. .addClass(options.resultsClass)
  512. .css("position", "absolute")
  513. .appendTo(document.body);
  514. list = $("<ul/>").appendTo(element).mouseover( function(event) {
  515. if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
  516. active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
  517. $(target(event)).addClass(CLASSES.ACTIVE);
  518. }
  519. }).click(function(event) {
  520. $(target(event)).addClass(CLASSES.ACTIVE);
  521. select();
  522. // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
  523. input.focus();
  524. return false;
  525. }).mousedown(function() {
  526. config.mouseDownOnSelect = true;
  527. }).mouseup(function() {
  528. config.mouseDownOnSelect = false;
  529. });
  530. if( options.width > 0 )
  531. element.css("width", options.width);
  532. needsInit = false;
  533. }
  534. function target(event) {
  535. var element = event.target;
  536. while(element && element.tagName != "LI")
  537. element = element.parentNode;
  538. // more fun with IE, sometimes event.target is empty, just ignore it then
  539. if(!element)
  540. return [];
  541. return element;
  542. }
  543. function moveSelect(step) {
  544. listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
  545. movePosition(step);
  546. var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
  547. if(options.scroll) {
  548. var offset = 0;
  549. listItems.slice(0, active).each(function() {
  550. offset += this.offsetHeight;
  551. });
  552. if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
  553. list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
  554. } else if(offset < list.scrollTop()) {
  555. list.scrollTop(offset);
  556. }
  557. }
  558. };
  559. function movePosition(step) {
  560. active += step;
  561. if (active < 0) {
  562. active = listItems.size() - 1;
  563. } else if (active >= listItems.size()) {
  564. active = 0;
  565. }
  566. }
  567. function limitNumberOfItems(available) {
  568. return options.max && options.max < available
  569. ? options.max
  570. : available;
  571. }
  572. function fillList() {
  573. list.empty();
  574. var max = limitNumberOfItems(data.length);
  575. for (var i=0; i < max; i++) {
  576. if (!data[i])
  577. continue;
  578. var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
  579. if ( formatted === false )
  580. continue;
  581. var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
  582. $.data(li, "ac_data", data[i]);
  583. }
  584. listItems = list.find("li");
  585. if ( options.selectFirst ) {
  586. listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
  587. active = 0;
  588. }
  589. // apply bgiframe if available
  590. if ( $.fn.bgiframe )
  591. list.bgiframe();
  592. }
  593. return {
  594. display: function(d, q) {
  595. init();
  596. data = d;
  597. term = q;
  598. fillList();
  599. },
  600. next: function() {
  601. moveSelect(1);
  602. },
  603. prev: function() {
  604. moveSelect(-1);
  605. },
  606. pageUp: function() {
  607. if (active != 0 && active - 8 < 0) {
  608. moveSelect( -active );
  609. } else {
  610. moveSelect(-8);
  611. }
  612. },
  613. pageDown: function() {
  614. if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
  615. moveSelect( listItems.size() - 1 - active );
  616. } else {
  617. moveSelect(8);
  618. }
  619. },
  620. hide: function() {
  621. element && element.hide();
  622. listItems && listItems.removeClass(CLASSES.ACTIVE);
  623. active = -1;
  624. },
  625. visible : function() {
  626. return element && element.is(":visible");
  627. },
  628. current: function() {
  629. return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
  630. },
  631. show: function() {
  632. var offset = $(input).offset();
  633. element.css({
  634. width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
  635. top: offset.top + input.offsetHeight,
  636. left: offset.left
  637. }).show();
  638. if(options.scroll) {
  639. list.scrollTop(0);
  640. list.css({
  641. maxHeight: options.scrollHeight,
  642. overflow: 'auto'
  643. });
  644. if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
  645. var listHeight = 0;
  646. listItems.each(function() {
  647. listHeight += this.offsetHeight;
  648. });
  649. var scrollbarsVisible = listHeight > options.scrollHeight;
  650. list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
  651. if (!scrollbarsVisible) {
  652. // IE doesn't recalculate width when scrollbar disappears
  653. listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
  654. }
  655. }
  656. }
  657. },
  658. selected: function() {
  659. var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
  660. return selected && selected.length && $.data(selected[0], "ac_data");
  661. },
  662. emptyList: function (){
  663. list && list.empty();
  664. },
  665. unbind: function() {
  666. element && element.remove();
  667. }
  668. };
  669. };
  670. $.Autocompleter.Selection = function(field, start, end) {
  671. if( field.createTextRange ){
  672. var selRange = field.createTextRange();
  673. selRange.collapse(true);
  674. selRange.moveStart("character", start);
  675. selRange.moveEnd("character", end);
  676. selRange.select();
  677. } else if( field.setSelectionRange ){
  678. field.setSelectionRange(start, end);
  679. } else {
  680. if( field.selectionStart ){
  681. field.selectionStart = start;
  682. field.selectionEnd = end;
  683. }
  684. }
  685. field.focus();
  686. };
  687. })(jQuery);