views:

638

answers:

3

I've got a jquery autocomplete text box (multiple: true) used to set categories on an item, pretty much exactly like the stackoverflow tags.

The autocomplete source is a list of objects, with formatItem overriden to do the correct display.

What I'm trying to do is pull two lists out of it: one list of selected category IDs (array of integers), and a list of any "new" categories they've entered that weren't in the original list. This is what I tried:

var categories = [];
var newCategoryNames = [];

var collectData = function(event, itemData, formatted) {
    if (itemData == null)
    {
        // no match -- new category
        newCategoryNames[newCategoryNames.length] = formatted;
    }
    else
    {
        categories[categories.length] = itemData.iCategoryID;
    }
};
$('#txtCategories').result(collectData).search().unbind('result');

This works fine for the pre-existing categories ("else" clause), but fails on the new items ("if" clause). This is because not only is itemData passed in null in that case, but the formatted parameter is null as well. I had thought it would still be passed in as the user-entered text but apparently not.

So what can I do? That callback IS called on the non-matching item, but it doesn't seem to give me any information that will tell me what that non-matching item actually was.

A: 

Worst case you should be able to pull the value from the input yourself:

if (itemData == null)
{
    // no match -- new category
    newCategoryNames[newCategoryNames.length] = event.target.value;
}
Prestaul
Unfortunately event.target.value just has the full contents of the text box. If I were going to use that, I could bypass all this code entirely and try splitting $('#txtbox').val(); But I was hoping to use the already-existing code in the autocomplete to map between display values and IDs.
Clyde
+1  A: 

How are you able to get 'result' event to fire when 'multiple' is set to true and user types an entry which doesn't belong in the existing list? In my test page, I could not do it.

Anyway, I debugged through the autocomplete plugin source code and found that it doesn't handle the scenario where you have 'multiple = true', 'mustmatch = false' and user types in an entry which doesn't belong to your autocomplete list.

Here's the debugging information:

There is a code which checks for key pressed by user, if it is COMMA or your multipleSeparator, it fires the selectCurrent() method.

  // matches also semicolon
  case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
  case KEY.TAB:
  case KEY.RETURN:
   if( selectCurrent() ) {
    // stop default to prevent a form submit, Opera needs special handling
    event.preventDefault();
    blockSubmit = true;
    return false;
   }
   break;

This is how the selectCurrent method looks like. It tries to get currently selected value. Here the select object is the 'autocomplete' dropdown created by the plugin. If user has typed in a word which doesn't belong to the list, it returns false and 'result' event doesn't fire.

function selectCurrent() {
 var selected = select.selected();

    if( !selected ) //selected is NULL if user types in comma after typing a word which doesn't belong in the list. And that is why I was surprised that your result event was even triggered.
  return false;

 var v = selected.result;
 previousValue = v;

 if ( options.multiple ) {
  var words = trimWords($input.val());
  if ( words.length > 1 ) {
   v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
  }
  v += options.multipleSeparator;
 }

 $input.val(v);
 hideResultsNow();
 $input.trigger("result", [selected.data, selected.value]);
 return true;
}

To fix this, you should check for options.multiple. Here's the final code which will do the right thing:

function getLastWord()
{
 var words = trimWords($input.val());
 return words[words.length - 1];
};

function selectCurrent() {
 var selected = select.selected();

    //options.multiple BUGFIX START

    //We don't have to check for options.mustMatch because the 'select' component
    //already handles it.
 if(! selected && options.multiple)
 {
  var lastWord = getLastWord();
  //Below code is similar to how the Cache component generates the data.
  selected = {
      data : lastWord,       
      value : options.formatMatch(lastWord, -1, options.data.length),
      result : options.formatResult && options.formatResult(lastWord) || lastWord

  };
 }
    //options.multiple BUGFIX END

 if( !selected )
  return false;

 var v = selected.result;
 previousValue = v;

 if ( options.multiple ) {
  var words = trimWords($input.val());
  if ( words.length > 1 ) {
   v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
  }
  v += options.multipleSeparator;
 }

 $input.val(v);
 hideResultsNow();
 $input.trigger("result", [selected.data, selected.value]);
 return true;
}

So you can modify your version of Autocomplete plugin or get the value of the textbox and do the parsing yourself.

SolutionYogi
I did end up just parsing myself (it's really just a split on the separation char, and then searching the list for the string). But thanks, this is very interesting
Clyde
A: 

1) In my case how you will get the Id of the ItemSelected or present in the textbox after selection. Id should not be shown in selected list it should attached from behind to each list value. 2) Does the jquery autocomplete contains multiple columns list structure (For example : I want : ItemCode, Name , Desc in which the search is based on Item Code.)