/* Script: TextboxList.Autocomplete.js TextboxList Autocomplete plugin Authors: Guillermo Rauch Note: TextboxList is not priceless for commercial use. See Purchase to remove this message. */ (function(){ $.TextboxList.Autocomplete = function(textboxlist, _options){ var index, prefix, method, container, list, values = [], searchValues = [], results = [], placeholder = false, current, currentInput, hidetimer, doAdd, currentSearch, currentRequest; var options = $.extend(true, { minLength: 1, maxResults: 10, insensitive: true, highlight: true, highlightSelector: null, mouseInteraction: true, onlyFromValues: false, queryRemote: false, remote: { url: '', param: 'search', extraParams: {}, loadPlaceholder: 'Please wait...' }, method: 'standard', placeholder: 'Type to receive suggestions' }, _options); var init = function(){ textboxlist.addEvent('bitEditableAdd', setupBit) .addEvent('bitEditableFocus', search) .addEvent('bitEditableBlur', hide) .setOptions({bitsOptions: {editable: {addKeys: false, stopEnter: false}}}); // @simo removing $.browser depedency if ($.browser.msie) textboxlist.setOptions({bitsOptions: {editable: {addOnBlur: false}}}); if (/MSIE (\d+\.\d+);/.test(navigator.userAgent) || navigator.userAgent.indexOf("Trident/") > -1 ){ textboxlist.setOptions({bitsOptions: {editable: {addOnBlur: false}}}); } prefix = textboxlist.getOptions().prefix + '-autocomplete'; method = $.TextboxList.Autocomplete.Methods[options.method]; container = $('
').width(textboxlist.getContainer().width()).appendTo(textboxlist.getContainer()); if (chk(options.placeholder)) placeholder = $('
').html(options.placeholder).appendTo(container); list = $('
    ').appendTo(container).click(function(ev){ ev.stopPropagation(); ev.preventDefault(); }); }; var setupBit = function(bit){ bit.toElement().keydown(navigate).keyup(function(){ search(); }); }; var search = function(bit){ // console.log('search'); if (bit) currentInput = bit; if (!options.queryRemote && !values.length) return; var search = $.trim(currentInput.getValue()[1]); search.length == 0 ? showPlaceholder() : hidePlaceholder(); if (search.length < options.minLength) current = null; if (search == currentSearch) return; currentSearch = search; list.css('display', 'none'); if (search.length < options.minLength) return; if (options.queryRemote){ if (searchValues[search]){ values = searchValues[search]; } else { var data = options.remote.extraParams; data[options.remote.param] = search; if (currentRequest) currentRequest.abort(); currentRequest = $.ajax({ url: options.remote.url, data: data, dataType: 'json', success: function(r){ searchValues[search] = r; values = r; showResults(search); } }); } } showResults(search); }; var showResults = function(search){ var results = method.filter(values, search, options.insensitive, options.maxResults); if (textboxlist.getOptions().unique){ results = $.grep(results, function(v){ return textboxlist.isDuplicate(v) == -1; }); } hidePlaceholder(); if (!results.length) { current = null; return; } blur(); list.empty().css('display', 'block'); $.each(results, function(i, r){ addResult(r, search); }); if (options.onlyFromValues) focusFirst(); results = results; }; var addResult = function(r, searched){ var element = $('
  • ').html(r[3] ? r[3] : r[1]).data('textboxlist:auto:value', r); element.appendTo(list); if (options.highlight) $(options.highlightSelector ? element.find(options.highlightSelector) : element).each(function(){ if ($(this).html()) method.highlight($(this), searched, options.insensitive, prefix + '-highlight'); }); if (options.mouseInteraction){ element.css('cursor', 'pointer').hover(function(){ focus(element); }).mousedown(function(ev){ ev.stopPropagation(); ev.preventDefault(); clearTimeout(hidetimer); doAdd = true; }).mouseup(function(){ if (doAdd){ addCurrent(); currentInput.focus(); search(); doAdd = false; } }); if (!options.onlyFromValues) element.mouseleave(function(){ if (current && (current.get(0) == element.get(0))) blur(); }); } }; // @ simo to prevent use of ""'$.browser.msie ? 150 : 0'"" if (/MSIE (\d+\.\d+);/.test(navigator.userAgent) || navigator.userAgent.indexOf("Trident/") > -1 ){ var timeout = 150; } else var timeout = 0; var hide = function(){ hidetimer = setTimeout(function(){ hidePlaceholder(); list.css('display', 'none'); currentSearch = null; }, timeout); }; var showPlaceholder = function(){ if (placeholder) placeholder.css('display', 'block'); }; var hidePlaceholder = function(){ if (placeholder) placeholder.css('display', 'none'); }; var focus = function(element){ if (!element || !element.length) return; blur(); current = element.addClass(prefix + '-result-focus'); }; var blur = function(){ if (current && current.length){ current.removeClass(prefix + '-result-focus'); current = null; } }; var focusFirst = function(){ return focus(list.find(':first')); }; var focusRelative = function(dir){ if (!current || !current.length) return self; return focus(current[dir]()); }; var addCurrent = function(){ var value = current.data('textboxlist:auto:value'); var b = textboxlist.create('box', value.slice(0, 3)); if (b){ b.autoValue = value; if ($.isArray(index)) index.push(value); currentInput.setValue([null, '', null]); b.inject(currentInput.toElement(), 'before'); } blur(); return self; }; var navigate = function(ev){ var evStop = function(){ ev.stopPropagation(); ev.preventDefault(); }; switch (ev.which){ case 38: //up evStop(); (!options.onlyFromValues && current && current.get(0) === list.find(':first').get(0)) ? blur() : focusRelative('prev'); break; case 40: //down evStop(); (current && current.length) ? focusRelative('next') : focusFirst(); break; case 39: //right if ((!current || current.length == 0) || (currentInput.getCaret() < currentInput.getValue()[1].length)) break; case 9: //tab evStop(); if (current && current.length) addCurrent(); else if (!options.onlyFromValues){ var value = currentInput.getValue(); var b = textboxlist.create('box', value); if (b){ b.inject(currentInput.toElement(), 'before'); currentInput.setValue([null, '', null]); } } break; case 13: //enter evStop(); if (current && current.length) addCurrent(); else if (!options.onlyFromValues){ var value = currentInput.getValue(); var b = textboxlist.create('box', value); if (b){ b.inject(currentInput.toElement(), 'before'); currentInput.setValue([null, '', null]); } } } }; this.setValues = function(v){ values = v; }; init(); }; $.TextboxList.Autocomplete.Methods = { standard: { filter: function(values, search, insensitive, max){ var newvals = []; // fix @simo // regexp = new RegExp('\\b' + escapeRegExp(search), insensitive ? 'i' : ''); // http://stackoverflow.com/questions/2127124/utf-8-problem-in-using-jquery-autocomplete-tags regexp = new RegExp('' + escapeRegExp(search), insensitive ? 'i' : ''); for (var i = 0; i < values.length; i++){ if (newvals.length === max) break; if (regexp.test(values[i][1])) newvals.push(values[i]); } return newvals; }, highlight: function(element, search, insensitive, klass){ // var regex = new RegExp('(<[^>]*>)|(\\b'+ escapeRegExp(search) +')', insensitive ? 'ig' : 'g'); var regex = new RegExp('(<[^>]*>)|('+ escapeRegExp(search) +')', insensitive ? 'ig' : 'g'); return element.html(element.html().replace(regex, function(a, b, c){ return (a.charAt(0) == '<') ? a : '' + c + ''; })); } } }; var chk = function(v){ return !!(v || v === 0); }; var escapeRegExp = function(str){ return str.replace(/([-.*+?^${}()|[\]\/\\])/g, "\\$1"); }; })();