angelovcom.net

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

suggest.js (6991B)


      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 
     15 (function($) {
     16 
     17 	$.suggest = function(input, options) {
     18 		var $input, $results, timeout, prevLength, cache, cacheSize;
     19 
     20 		$input = $(input).attr("autocomplete", "off");
     21 		$results = $("<ul/>");
     22 
     23 		timeout = false;		// hold timeout ID for suggestion results to appear
     24 		prevLength = 0;			// last recorded length of $input.val()
     25 		cache = [];				// cache MRU list
     26 		cacheSize = 0;			// size of cache in chars (bytes?)
     27 
     28 		$results.addClass(options.resultsClass).appendTo('body');
     29 
     30 
     31 		resetPosition();
     32 		$(window)
     33 			.on( 'load', resetPosition ) // just in case user is changing size of page while loading
     34 			.on( 'resize', resetPosition );
     35 
     36 		$input.blur(function() {
     37 			setTimeout(function() { $results.hide() }, 200);
     38 		});
     39 
     40 		$input.keydown(processKey);
     41 
     42 		function resetPosition() {
     43 			// requires jquery.dimension plugin
     44 			var offset = $input.offset();
     45 			$results.css({
     46 				top: (offset.top + input.offsetHeight) + 'px',
     47 				left: offset.left + 'px'
     48 			});
     49 		}
     50 
     51 
     52 		function processKey(e) {
     53 
     54 			// handling up/down/escape requires results to be visible
     55 			// handling enter/tab requires that AND a result to be selected
     56 			if ((/27$|38$|40$/.test(e.keyCode) && $results.is(':visible')) ||
     57 				(/^13$|^9$/.test(e.keyCode) && getCurrentResult())) {
     58 
     59 				if (e.preventDefault)
     60 					e.preventDefault();
     61 				if (e.stopPropagation)
     62 					e.stopPropagation();
     63 
     64 				e.cancelBubble = true;
     65 				e.returnValue = false;
     66 
     67 				switch(e.keyCode) {
     68 
     69 					case 38: // up
     70 						prevResult();
     71 						break;
     72 
     73 					case 40: // down
     74 						nextResult();
     75 						break;
     76 
     77 					case 9:  // tab
     78 					case 13: // return
     79 						selectCurrentResult();
     80 						break;
     81 
     82 					case 27: //	escape
     83 						$results.hide();
     84 						break;
     85 
     86 				}
     87 
     88 			} else if ($input.val().length != prevLength) {
     89 
     90 				if (timeout)
     91 					clearTimeout(timeout);
     92 				timeout = setTimeout(suggest, options.delay);
     93 				prevLength = $input.val().length;
     94 
     95 			}
     96 
     97 
     98 		}
     99 
    100 
    101 		function suggest() {
    102 
    103 			var q = $.trim($input.val()), multipleSepPos, items;
    104 
    105 			if ( options.multiple ) {
    106 				multipleSepPos = q.lastIndexOf(options.multipleSep);
    107 				if ( multipleSepPos != -1 ) {
    108 					q = $.trim(q.substr(multipleSepPos + options.multipleSep.length));
    109 				}
    110 			}
    111 			if (q.length >= options.minchars) {
    112 
    113 				cached = checkCache(q);
    114 
    115 				if (cached) {
    116 
    117 					displayItems(cached['items']);
    118 
    119 				} else {
    120 
    121 					$.get(options.source, {q: q}, function(txt) {
    122 
    123 						$results.hide();
    124 
    125 						items = parseTxt(txt, q);
    126 
    127 						displayItems(items);
    128 						addToCache(q, items, txt.length);
    129 
    130 					});
    131 
    132 				}
    133 
    134 			} else {
    135 
    136 				$results.hide();
    137 
    138 			}
    139 
    140 		}
    141 
    142 
    143 		function checkCache(q) {
    144 			var i;
    145 			for (i = 0; i < cache.length; i++)
    146 				if (cache[i]['q'] == q) {
    147 					cache.unshift(cache.splice(i, 1)[0]);
    148 					return cache[0];
    149 				}
    150 
    151 			return false;
    152 
    153 		}
    154 
    155 		function addToCache(q, items, size) {
    156 			var cached;
    157 			while (cache.length && (cacheSize + size > options.maxCacheSize)) {
    158 				cached = cache.pop();
    159 				cacheSize -= cached['size'];
    160 			}
    161 
    162 			cache.push({
    163 				q: q,
    164 				size: size,
    165 				items: items
    166 				});
    167 
    168 			cacheSize += size;
    169 
    170 		}
    171 
    172 		function displayItems(items) {
    173 			var html = '', i;
    174 			if (!items)
    175 				return;
    176 
    177 			if (!items.length) {
    178 				$results.hide();
    179 				return;
    180 			}
    181 
    182 			resetPosition(); // when the form moves after the page has loaded
    183 
    184 			for (i = 0; i < items.length; i++)
    185 				html += '<li>' + items[i] + '</li>';
    186 
    187 			$results.html(html).show();
    188 
    189 			$results
    190 				.children('li')
    191 				.mouseover(function() {
    192 					$results.children('li').removeClass(options.selectClass);
    193 					$(this).addClass(options.selectClass);
    194 				})
    195 				.click(function(e) {
    196 					e.preventDefault();
    197 					e.stopPropagation();
    198 					selectCurrentResult();
    199 				});
    200 
    201 		}
    202 
    203 		function parseTxt(txt, q) {
    204 
    205 			var items = [], tokens = txt.split(options.delimiter), i, token;
    206 
    207 			// parse returned data for non-empty items
    208 			for (i = 0; i < tokens.length; i++) {
    209 				token = $.trim(tokens[i]);
    210 				if (token) {
    211 					token = token.replace(
    212 						new RegExp(q, 'ig'),
    213 						function(q) { return '<span class="' + options.matchClass + '">' + q + '</span>' }
    214 						);
    215 					items[items.length] = token;
    216 				}
    217 			}
    218 
    219 			return items;
    220 		}
    221 
    222 		function getCurrentResult() {
    223 			var $currentResult;
    224 			if (!$results.is(':visible'))
    225 				return false;
    226 
    227 			$currentResult = $results.children('li.' + options.selectClass);
    228 
    229 			if (!$currentResult.length)
    230 				$currentResult = false;
    231 
    232 			return $currentResult;
    233 
    234 		}
    235 
    236 		function selectCurrentResult() {
    237 
    238 			$currentResult = getCurrentResult();
    239 
    240 			if ($currentResult) {
    241 				if ( options.multiple ) {
    242 					if ( $input.val().indexOf(options.multipleSep) != -1 ) {
    243 						$currentVal = $input.val().substr( 0, ( $input.val().lastIndexOf(options.multipleSep) + options.multipleSep.length ) ) + ' ';
    244 					} else {
    245 						$currentVal = "";
    246 					}
    247 					$input.val( $currentVal + $currentResult.text() + options.multipleSep + ' ' );
    248 					$input.focus();
    249 				} else {
    250 					$input.val($currentResult.text());
    251 				}
    252 				$results.hide();
    253 				$input.trigger('change');
    254 
    255 				if (options.onSelect)
    256 					options.onSelect.apply($input[0]);
    257 
    258 			}
    259 
    260 		}
    261 
    262 		function nextResult() {
    263 
    264 			$currentResult = getCurrentResult();
    265 
    266 			if ($currentResult)
    267 				$currentResult
    268 					.removeClass(options.selectClass)
    269 					.next()
    270 						.addClass(options.selectClass);
    271 			else
    272 				$results.children('li:first-child').addClass(options.selectClass);
    273 
    274 		}
    275 
    276 		function prevResult() {
    277 			var $currentResult = getCurrentResult();
    278 
    279 			if ($currentResult)
    280 				$currentResult
    281 					.removeClass(options.selectClass)
    282 					.prev()
    283 						.addClass(options.selectClass);
    284 			else
    285 				$results.children('li:last-child').addClass(options.selectClass);
    286 
    287 		}
    288 	}
    289 
    290 	$.fn.suggest = function(source, options) {
    291 
    292 		if (!source)
    293 			return;
    294 
    295 		options = options || {};
    296 		options.multiple = options.multiple || false;
    297 		options.multipleSep = options.multipleSep || ",";
    298 		options.source = source;
    299 		options.delay = options.delay || 100;
    300 		options.resultsClass = options.resultsClass || 'ac_results';
    301 		options.selectClass = options.selectClass || 'ac_over';
    302 		options.matchClass = options.matchClass || 'ac_match';
    303 		options.minchars = options.minchars || 2;
    304 		options.delimiter = options.delimiter || '\n';
    305 		options.onSelect = options.onSelect || false;
    306 		options.maxCacheSize = options.maxCacheSize || 65536;
    307 
    308 		this.each(function() {
    309 			new $.suggest(this, options);
    310 		});
    311 
    312 		return this;
    313 
    314 	};
    315 
    316 })(jQuery);