/static/scripts/jquery.autocomplete.js

https://bitbucket.org/cistrome/cistrome-harvard/ · JavaScript · 832 lines · 659 code · 89 blank · 84 comment · 171 complexity · af399ceaf699e719818ad5af2e454db0 MD5 · raw file

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