suggest.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. /*
  2. * jquery.suggest 1.1b - 2007-08-06
  3. * Patched by Mark Jaquith with Alexander Dick's "multiple items" patch to allow for auto-suggesting of more than one tag before submitting
  4. * See: http://www.vulgarisoip.com/2007/06/29/jquerysuggest-an-alternative-jquery-based-autocomplete-library/#comment-7228
  5. *
  6. * Uses code and techniques from following libraries:
  7. * 1. http://www.dyve.net/jquery/?autocomplete
  8. * 2. http://dev.jquery.com/browser/trunk/plugins/interface/iautocompleter.js
  9. *
  10. * All the new stuff written by Peter Vulgaris (www.vulgarisoip.com)
  11. * Feel free to do whatever you want with this file
  12. *
  13. */
  14. (function($) {
  15. $.suggest = function(input, options) {
  16. var $input, $results, timeout, prevLength, cache, cacheSize;
  17. $input = $(input).attr("autocomplete", "off");
  18. $results = $("<ul/>");
  19. timeout = false; // hold timeout ID for suggestion results to appear
  20. prevLength = 0; // last recorded length of $input.val()
  21. cache = []; // cache MRU list
  22. cacheSize = 0; // size of cache in chars (bytes?)
  23. $results.addClass(options.resultsClass).appendTo('body');
  24. resetPosition();
  25. $(window)
  26. .on( 'load', resetPosition ) // just in case user is changing size of page while loading
  27. .on( 'resize', resetPosition );
  28. $input.blur(function() {
  29. setTimeout(function() { $results.hide() }, 200);
  30. });
  31. $input.keydown(processKey);
  32. function resetPosition() {
  33. // requires jquery.dimension plugin
  34. var offset = $input.offset();
  35. $results.css({
  36. top: (offset.top + input.offsetHeight) + 'px',
  37. left: offset.left + 'px'
  38. });
  39. }
  40. function processKey(e) {
  41. // handling up/down/escape requires results to be visible
  42. // handling enter/tab requires that AND a result to be selected
  43. if ((/27$|38$|40$/.test(e.keyCode) && $results.is(':visible')) ||
  44. (/^13$|^9$/.test(e.keyCode) && getCurrentResult())) {
  45. if (e.preventDefault)
  46. e.preventDefault();
  47. if (e.stopPropagation)
  48. e.stopPropagation();
  49. e.cancelBubble = true;
  50. e.returnValue = false;
  51. switch(e.keyCode) {
  52. case 38: // up
  53. prevResult();
  54. break;
  55. case 40: // down
  56. nextResult();
  57. break;
  58. case 9: // tab
  59. case 13: // return
  60. selectCurrentResult();
  61. break;
  62. case 27: // escape
  63. $results.hide();
  64. break;
  65. }
  66. } else if ($input.val().length != prevLength) {
  67. if (timeout)
  68. clearTimeout(timeout);
  69. timeout = setTimeout(suggest, options.delay);
  70. prevLength = $input.val().length;
  71. }
  72. }
  73. function suggest() {
  74. var q = $.trim($input.val()), multipleSepPos, items;
  75. if ( options.multiple ) {
  76. multipleSepPos = q.lastIndexOf(options.multipleSep);
  77. if ( multipleSepPos != -1 ) {
  78. q = $.trim(q.substr(multipleSepPos + options.multipleSep.length));
  79. }
  80. }
  81. if (q.length >= options.minchars) {
  82. cached = checkCache(q);
  83. if (cached) {
  84. displayItems(cached['items']);
  85. } else {
  86. $.get(options.source, {q: q}, function(txt) {
  87. $results.hide();
  88. items = parseTxt(txt, q);
  89. displayItems(items);
  90. addToCache(q, items, txt.length);
  91. });
  92. }
  93. } else {
  94. $results.hide();
  95. }
  96. }
  97. function checkCache(q) {
  98. var i;
  99. for (i = 0; i < cache.length; i++)
  100. if (cache[i]['q'] == q) {
  101. cache.unshift(cache.splice(i, 1)[0]);
  102. return cache[0];
  103. }
  104. return false;
  105. }
  106. function addToCache(q, items, size) {
  107. var cached;
  108. while (cache.length && (cacheSize + size > options.maxCacheSize)) {
  109. cached = cache.pop();
  110. cacheSize -= cached['size'];
  111. }
  112. cache.push({
  113. q: q,
  114. size: size,
  115. items: items
  116. });
  117. cacheSize += size;
  118. }
  119. function displayItems(items) {
  120. var html = '', i;
  121. if (!items)
  122. return;
  123. if (!items.length) {
  124. $results.hide();
  125. return;
  126. }
  127. resetPosition(); // when the form moves after the page has loaded
  128. for (i = 0; i < items.length; i++)
  129. html += '<li>' + items[i] + '</li>';
  130. $results.html(html).show();
  131. $results
  132. .children('li')
  133. .mouseover(function() {
  134. $results.children('li').removeClass(options.selectClass);
  135. $(this).addClass(options.selectClass);
  136. })
  137. .click(function(e) {
  138. e.preventDefault();
  139. e.stopPropagation();
  140. selectCurrentResult();
  141. });
  142. }
  143. function parseTxt(txt, q) {
  144. var items = [], tokens = txt.split(options.delimiter), i, token;
  145. // parse returned data for non-empty items
  146. for (i = 0; i < tokens.length; i++) {
  147. token = $.trim(tokens[i]);
  148. if (token) {
  149. token = token.replace(
  150. new RegExp(q, 'ig'),
  151. function(q) { return '<span class="' + options.matchClass + '">' + q + '</span>' }
  152. );
  153. items[items.length] = token;
  154. }
  155. }
  156. return items;
  157. }
  158. function getCurrentResult() {
  159. var $currentResult;
  160. if (!$results.is(':visible'))
  161. return false;
  162. $currentResult = $results.children('li.' + options.selectClass);
  163. if (!$currentResult.length)
  164. $currentResult = false;
  165. return $currentResult;
  166. }
  167. function selectCurrentResult() {
  168. $currentResult = getCurrentResult();
  169. if ($currentResult) {
  170. if ( options.multiple ) {
  171. if ( $input.val().indexOf(options.multipleSep) != -1 ) {
  172. $currentVal = $input.val().substr( 0, ( $input.val().lastIndexOf(options.multipleSep) + options.multipleSep.length ) ) + ' ';
  173. } else {
  174. $currentVal = "";
  175. }
  176. $input.val( $currentVal + $currentResult.text() + options.multipleSep + ' ' );
  177. $input.focus();
  178. } else {
  179. $input.val($currentResult.text());
  180. }
  181. $results.hide();
  182. $input.trigger('change');
  183. if (options.onSelect)
  184. options.onSelect.apply($input[0]);
  185. }
  186. }
  187. function nextResult() {
  188. $currentResult = getCurrentResult();
  189. if ($currentResult)
  190. $currentResult
  191. .removeClass(options.selectClass)
  192. .next()
  193. .addClass(options.selectClass);
  194. else
  195. $results.children('li:first-child').addClass(options.selectClass);
  196. }
  197. function prevResult() {
  198. var $currentResult = getCurrentResult();
  199. if ($currentResult)
  200. $currentResult
  201. .removeClass(options.selectClass)
  202. .prev()
  203. .addClass(options.selectClass);
  204. else
  205. $results.children('li:last-child').addClass(options.selectClass);
  206. }
  207. }
  208. $.fn.suggest = function(source, options) {
  209. if (!source)
  210. return;
  211. options = options || {};
  212. options.multiple = options.multiple || false;
  213. options.multipleSep = options.multipleSep || ",";
  214. options.source = source;
  215. options.delay = options.delay || 100;
  216. options.resultsClass = options.resultsClass || 'ac_results';
  217. options.selectClass = options.selectClass || 'ac_over';
  218. options.matchClass = options.matchClass || 'ac_match';
  219. options.minchars = options.minchars || 2;
  220. options.delimiter = options.delimiter || '\n';
  221. options.onSelect = options.onSelect || false;
  222. options.maxCacheSize = options.maxCacheSize || 65536;
  223. this.each(function() {
  224. new $.suggest(this, options);
  225. });
  226. return this;
  227. };
  228. })(jQuery);