11
votes

I am using jquery-select2-4 to search an external database and present the user with search results that he can choose from.

I have a working version running on this jsfiddle.

But if, for example, only 1 search result is returned I want to skip the whole selection process, and just add the returned search result to the list of selected options. According to the select2 docs I can add a new option like this:

option = new Option("Sample text", "123", true, true);
select2_element.append(option);
select2_element.trigger('change');

This seems to work to some extent. But there are a few problems.

  • I can't clear the search field upon adding the option.
  • I can't add anything else that an id and a text.
  • The added option is displayed to the user as undefined.

I realize that this question contains 3 facets, but all 3 facets probably refers back to this 1 question:

How do you programmatically add a new jquery-select2-4 option and reset the search field?

For your reference, this is the context of the code I'm asking about:

var formatRepo, formatRepoSelection, selectRepos;

formatRepoSelection = function(element) {
  return element.name + ' ' + element.forks + ' ' + element.id;
};

formatRepo = function(element) {
  var markup;
  if (!element.loading) {
    return markup = element.name + ' ' + element.id;
  }
};

selectRepos = function() {
  var option, select2_element;
  select2_element = $('#select2_element');
  select2_element.select2({
    ajax: {
      url: "https://api.github.com/search/repositories",
      dataType: 'json',
      data: function (params) {
        return {
          q: params.term,
          page: params.page
        };
      },
      processResults: function (data, params) {

        if (data.items.length === 1) {
          // START: The code I am asking about.
          // Add the search result directly as an option.
          option = new Option("Sample text", "123", true, true);
          select2_element.append(option);
          return select2_element.trigger('change');
          // END: The code I am asking about.
        } else {
          params.page = params.page || 1;

          return {
            results: data.items,
            pagination: {
              more: (params.page * 30) < data.total_count
            }
          };
        }
      },
      cache: true
    },

    escapeMarkup: function(markup) {
      return markup;
    },
    templateResult: formatRepo,
    templateSelection: formatRepoSelection
  });
};

$(function() {
  return selectRepos();
});
1
Without seeing your JS used to initialize Select2, questions 2 and 3 can't be answered.Kevin Brown
It is in the jsfiddl. I could add it to th question if you'd like, but I left it out because it follows standard setup, and I didn't want to crowd the question with standard code.Cjoerg
I have put in the code context now.Cjoerg

1 Answers

2
votes

I made two changes to the original code:

1. Simulate the selection of the only remaining item

As you indicated, the creation of an option element has its limitations: you can only specify the id and text properties, leaving no possibility to have access to the selected item's other properties.

Also, it generates undefined values. This is because the callback function formatRepoSelection tries to access properties (name and forks) that are undefined for the object created for the option element. You could have tried to work around this and use the text property in that case, but still you would have no solution for the above limitations.

The solution I suggest here has a different approach. Instead of creating the tag directly, you could simulate the user's selection of that last item, sending a mouseup event to that list item.

This has as immediate advantage that the normal selection behaviour is applied as if the user had clicked the item, and so it solves immediately all three issues you had:

  • the filter text is cleared automatically;
  • the formatRepoSelection function receives the usual object, and the label of the tag is therefore as expected;
  • the same properties are available to you as for any item.

Here is the code implementing this:

    if (data.items.length === 1) {
      // Change 1:
      // Allow the list to update
      setTimeout(function() {
          // ... and then send a click event to the first list item
          // Note the used id has the SELECT id in the middle.
          $("#select2-select2_element-results li:first-child").trigger('mouseup');
      }, 0);

The disadvantage of this solution is that you make yourself dependent on an implementation aspect; a future version of select2 may organise the results differently in terms of HTML elements and properties.

But it should not be a big deal to update the code whenever you decide to go with a newer version of select2.

2. Return a correct object

Your code generated an error in the select2 library:

TypeError: b is undefined, at select2.min.js:2:9842

This is because of this statement:

return select2_element.trigger('change');

This returns the jQuery select2_element object, but library expects the return value to have a results property.

As that trigger is not needed any more, this can be fixed by replacing the above with:

  // Change 2:
  // Return empty results array
  return {
    results: data.items
  };

Don't be tempted to set results: [], because we still need the item to be there to simulate the mouse event with.

And... that is all.

Here is the working solution: JS fiddle.