I recently migrated a few of my Autocomplete plugins from the one produced by bassistance to the jQuery UI autocomplete.
How can the "mustMatch" and "selectFirst" be implemented with just callbacks and other options without modifying the core autocomplete code itself?
I think I solved both features...
To make things easier, I used a common custom selector:
$.expr[':'].textEquals = function (a, i, m) {
return $(a).text().match("^" + m[3] + "$");
};
The rest of the code:
$(function () {
$("#tags").autocomplete({
source: '/get_my_data/',
change: function (event, ui) {
//if the value of the textbox does not match a suggestion, clear its value
if ($(".ui-autocomplete li:textEquals('" + $(this).val() + "')").size() == 0) {
$(this).val('');
}
}
}).live('keydown', function (e) {
var keyCode = e.keyCode || e.which;
//if TAB or RETURN is pressed and the text in the textbox does not match a suggestion, set the value of the textbox to the text of the first suggestion
if((keyCode == 9 || keyCode == 13) && ($(".ui-autocomplete li:textEquals('" + $(this).val() + "')").size() == 0)) {
$(this).val($(".ui-autocomplete li:visible:first").text());
}
});
});
If any of your autocomplete suggestions contain any 'special' characters used by regexp, you must escape those characters within m[3] in the custom selector:
function escape_regexp(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
and change the custom selector:
$.expr[':'].textEquals = function (a, i, m) {
return $(a).text().match("^" + escape_regexp(m[3]) + "$");
};
I used something as simple as this for mustMatch and it works. I hope it helps someone.
change: function (event, ui) {
if (!ui.item) {
$(this).val('');
}
}
I think I got the mustMatch working with this code... It needs thorough test though:
<script type="text/javascript">
$(function() {
$("#my_input_id").autocomplete({
source: '/get_my_data/',
minChars: 3,
change: function(event, ui) {
// provide must match checking if what is in the input
// is in the list of results. HACK!
var source = $(this).val();
var found = $('.ui-autocomplete li').text().search(source);
console.debug('found:' + found);
if(found < 0) {
$(this).val('');
}
}
});
});
</script>
I found this question to be useful.
I thought I'd post up the code I'm now using (adapted from Esteban Feldman's answer).
I've added my own mustMatch option, and a CSS class to highlight the issue before resetting the textbox value.
change: function (event, ui) {
if (options.mustMatch) {
var found = $('.ui-autocomplete li').text().search($(this).val());
if (found < 0) {
$(this).addClass('ui-autocomplete-nomatch').val('');
$(this).delay(1500).removeClass('ui-autocomplete-nomatch', 500);
}
}
}
CSS
.ui-autocomplete-nomatch { background: white url('../Images/AutocompleteError.gif') right center no-repeat; }
The solution I've used to implement 'mustMatch':
<script type="text/javascript">
...
$('#recipient_name').autocomplete({
source: friends,
change: function (event, ui) {
if ($('#message_recipient_id').attr('rel') != $(this).val()) {
$(this).val('');
$('#message_recipient_id').val('');
$('#message_recipient_id').attr('rel', '');
}
},
select: function(event, ui) {
$('#message_recipient_id').val(ui.item.user_id);
$('#message_recipient_id').attr('rel', ui.item.label);
}
});
...
</script>
I discovered one issue. While the suggestion list is active you can submit your form even if the value doesn't match the suggestion. To dissallow this I added:
$('form').submit(function() {
if ($(".ui-autocomplete li:textEquals('" + $(this).val() + "')").size() == 0) {
$(this).val('');
$("span").text("Select a valid city").show();
return false;
}
});
This prevents the form from being submitted and displays a message.
This JQuery-UI official demo has mustMatch, amongst other cool stuff: http://jqueryui.com/demos/autocomplete/#combobox
I've updated it to add autoFill, and a few other things.
Javascript:
/* stolen from http://jqueryui.com/demos/autocomplete/#combobox * * and these options added. * * - autoFill (default: true): select first value rather than clearing if there's a match * * - clearButton (default: true): add a "clear" button * * - adjustWidth (default: true): if true, will set the autocomplete width the same as * the old select. (requires jQuery 1.4.4 to work on IE8) * * - uiStyle (default: false): if true, will add classes so that the autocomplete input * takes a jQuery-UI style */ (function( $ ) { $.widget( "ui.combobox", { options: { autoFill: true, clearButton: true, adjustWidth: true, uiStyle: false, selected: null, }, _create: function() { var self = this, select = this.element.hide(), selected = select.children( ":selected" ), value = selected.val() ? selected.text() : "", found = false; var input = this.input = $( "" ) .attr('title', '' + select.attr("title") + '') .insertAfter( select ) .val( value ) .autocomplete({ delay: 0, minLength: 0, source: function( request, response ) { var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), "i" ); var resp = select.children( "option" ).map(function() { var text = $( this ).text(); if ( this.value && ( !request.term || matcher.test(text) ) ) return { label: text.replace( new RegExp( "(?![^&;]+;)(?!]*)(" + $.ui.autocomplete.escapeRegex(request.term) + ")(?![^]*>)(?![^&;]+;)", "gi" ), "$1" ), value: text, option: this }; }); found = resp.length > 0; response( resp ); }, select: function( event, ui ) { ui.item.option.selected = true; self._trigger( "selected", event, { item: ui.item.option }); }, change: function( event, ui ) { if ( !ui.item ) { var matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( $(this).val() ) + "$", "i" ), valid = false; select.children( "option" ).each(function() { if ( $( this ).text().match( matcher ) ) { this.selected = valid = true; return false; } }); if ( !valid || input.data("autocomplete").term=="" ) { // set to first suggestion, unless blank or autoFill is turned off var suggestion; if(!self.options.autoFill || input.data("autocomplete").term=="") found=false; if(found) { suggestion = jQuery(input.data("autocomplete").widget()).find("li:first")[0]; var option = select.find("option[text="+suggestion.innerText+"]").attr('selected', true); $(this).val(suggestion.innerText); input.data("autocomplete").term = suggestion.innerText; self._trigger( "selected", event, { item: option[0] }); } else { suggestion={innerText: ''}; select.find("option:selected").removeAttr("selected"); $(this).val(''); input.data( "autocomplete" ).term = ''; self._trigger( "selected", event, { item: null }); } return found; } } } }); if( self.options.adjustWidth ) { input.width(select.width()); } if( self.options.uiStyle ) { input.addClass( "ui-widget ui-widget-content ui-corner-left" ); } input.data( "autocomplete" )._renderItem = function( ul, item ) { return $( "
CSS (.hjq-combobox is a wrapping span)
.hjq-combobox .ui-button { margin-left: -1px; } .hjq-combobox .ui-button-icon-only .ui-button-text { padding: 0; } .hjq-combobox button.ui-button-icon-only { width: 20px; } .hjq-combobox .ui-autocomplete-input { margin-right: 0; } .hjq-combobox {white-space: nowrap;}
Note: this code is being updated and maintained here: https://github.com/tablatom/hobo/blob/master/hobo_jquery_ui/vendor/assets/javascripts/combobox.js
Maybe it's just because this is an old issue, but I found that the easiest solution is already there in the plugin, you just need to use the proper functions to access it.
This code will handle the cases when the autocomplete loses focus with an invalid value:
change: function(e, ui) {
if (!ui.item) {
$(this).val("");
}
}
And this code, much like the original functionality from bassistance, will handle the cases when there are no matches while typing in the autocomplete:
response: function(e, ui) {
if (ui.content.length == 0) {
$(this).val("");
}
}
This works well with either a static array source, or a JSON data source. Combined with the autoFocus: true
option, it seems to do everything needed in an efficient manner.
The last case that you may want to handle is what to do when the ESCAPE key is pressed with an invalid value in the textbox. What I do is use the value of the first matched result. And this is how I do that...
First, declare a variable to hold the best match. Do this outside of your autocomplete plugin.
var bestMatch = "";
Then use the following option:
open: function(e, ui) {
bestMatch = "";
var acData = $(this).data('uiAutocomplete');
acData.menu.element.find("A").each(function () {
var me = $(this);
if (me.parent().index() == 0) {
bestMatch = me.text();
}
});
}
Lastly, add the following event to your autocomplete:
.on("keydown", function(e) {
if (e.keyCode == 27) // ESCAPE key
{
$(this).val(bestMatch);
}
})
You can just as easily force the field to be empty when the escape key is pressed. All you have to do is set the value to an empty string when the key is pressed instead of the bestMatch
variable (which isn't needed at all if you choose to empty the field).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With