/*
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"); };
})();