Thursday, December 18, 2008

AutoComplete Bug: item has comma won't select (but I have workaround)

Found an AutoComplete bug:

I've just found a bug with AutoComplete plugin. If one of the items in the list retrieved has an embedded comma in the string, the string is not considered 'matched'. This is with options 'multiple' false.

This I discovered because I've implemented the 'afterNoMatch()' method (see an other post to this list). If I select an item from the list that has an embedded comma, it will trigger the afterNoMatch() and consider the item not matched.

This could impact otherse even if they don't have this patch because this means the mustMatch option will not trigger as they think it will.

I haven't found the true cause, but it would seem to have something to do with multiple selections - like it's on by default anyway. So the workaround is to specify another multipleSeparator character. Here, I use backtick:

$("#mcOrgName").setOptions({multipleSeparator: '`' });

where mcOrgName is the jquery name of the field I'm using autocomplete for.

Here's hoping the author does a bugfix, but in the mean time, this workaround should enable full functionality presuming you don't need backticks in the text you're selecting.

Enjoy!

Thursday, December 11, 2008

JQuery AutoComplete: code/diff for new option afterNoMatch

Below please find a mod for adding an option to AutoComplete.

The option is 'afterNoMatch'. It specifies a function to call if the value provided does not match any value in the dropdown box. This has been mentioned as a problem, and I needed a solution. So, here it is.

If the autocomplete cannot return a value (it's not found by the JSON lookup), the callback is called. At this point, I reset the values of a div and a hidden field holding the ID of the thing I was looking for.

This may not be perfect, but it does get the job done.

First, how to call it:

<script type="text/javascript">

var autocompleteJSON = function(raw) {
var json = typeof(raw) === "array" ? raw : raw.resultSet;
var parsed = [];
for (var i=0; i <json.length; i++) {
var row =json[i];
parsed.push({
data: row,
value: row["orgName"] + ' [' + row["id"] + ']',
result: row["orgName"]
});
}
return parsed;
};

function afterNoMatch() {
document.forms[0].mcOrgID.value = 'nomatch';
locationDiv = document.getElementById('mcOrgLocationDiv')
locationDiv.innerHTML = "<b>Manually Entered Organization Name - No location.</b>";
return;
}

function formatCompanyName(row) {
ret = row["orgName"] + " (id: " + row["id"] + ") " + row["orgCity"] + ", " + row["orgState"]
document.forms[0].mcOrgID.value = row['id'];
return ret
}

</script>

<script>
$(document).ready(function(){
var data = "Core Selectors Attributes Traversing Manipulation CSS Events Effects Ajax Utilities".split(" ");
$("#mcOrgName").autocomplete('BrowseOrgsJSON.py')
.result(function(event, data, formatted) {
//alert("Got data back from server: name=" + data['orgName'] + ", id=" + data['id'] + ", city=" + data['orgCity'] + ", state=" + data['orgState'] + ", formatted=" + formatted);
$("#mcOrgLocationDiv").html('Location: ' + data['orgCity'] + ', ' + data['orgState'] + ' (Textura Organization id: ' + data['id'] + ')' );
document.forms[0].mcOrgID.value = data['id'];
});

$("#mcOrgName").setOptions({scrollHeight : 400 });
$("#mcOrgName").setOptions({queryArgument : "search" });
$("#mcOrgName").setOptions({formatItem : formatCompanyName });
// $("#mcOrgName").setOptions({autoFill : true }); CANNOT DO THIS WITH SUBSTRING SEARCH.
$("#mcOrgName").setOptions({mustMatch : false });
$("#mcOrgName").setOptions({dataType : "json" });
$("#mcOrgName").setOptions({parse : autocompleteJSON });
$("#mcOrgName").setOptions({selectFirst : false });
$("#mcOrgName").setOptions({extraParams : {'includeOffSystem': 'False'} });
$("#mcOrgName").setOptions({afterNoMatch : afterNoMatch });
});
</script>


Now, here's the code. I'll start off with the diff and then cut/paste the section the code is in case the code changes between now and the time you're reading this.

In jquery.autocomplete.js, the function hideResultsNow() is changed to be the following:


function hideResultsNow() {
var wasVisible = select.visible();
select.hide();
clearTimeout(timeout);
stopLoading();
if (options.mustMatch) {
// call search and run callback
$input.search(
function (result){
// if no value found, clear the input box
if( !result ) {
if (options.multiple) {
var words = trimWords($input.val()).slice(0, -1);
$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
}
else
$input.val( "" );
}
}
);
}
else
{
// call search and run callback
$input.search(
function (result){
if( !result ) {
options.afterNoMatch();
}
}
);
}

if (wasVisible)
// position cursor at end of input field
$.Autocompleter.Selection(input, input.value.length, input.value.length);

};


and one option is added to the options section:


...
max: 100,
mustMatch: false,
afterNoMatch: function() { return; },
extraParams: {},
selectFirst: true,
...


Now for the technical diff:


Index: resources/js/jquery/autocomplete/jquery.autocomplete.js
==============================================================
307a308,319
> else
> {
> // call search and run callback
> $input.search(
> function (result){
> if( !result ) {
> options.afterNoMatch();
> }
> }
> );
> }
>
310a323
>
403a417
> afterNoMatch: function() { return; },