allow new values with chosen.js multiple select

JqueryJquery ChosenMultiple Select

Jquery Problem Overview


I'm using the chosen.js plugin http://harvesthq.github.com/chosen/ with jQuery to allow the user to select multiple options from a select. However, I now want to be able to let them create values that aren't already present - any idea how to go about this?

EDIT: something similar to SO's own tag selection/creation bar would be close to what I'm after

Preferably without changing or editing the plugin, but will do if required.

The code: HTML:

<p>Select something</p>
<select name="theSelect[]" multiple="multiple">
    <option value="First Option">First Option</option>
    <option value="Second Option">Second Option</option>
</select>

Javascript:

$(function(){
    $('select').chosen();
});

So if a user were to type in "Third Option", i'd like to add that to the list and have it selected. The value and display name are / will be the same so that's not a concern

Jquery Solutions


Solution 1 - Jquery

According to the documentation you can try doing something like this:

$('select').append('<option>test</option>');
$('select').trigger('liszt:updated');

As Tony stated in the comments below:

"Starting with version 1.0 which the trigger is now "chosen:updated". See harvesthq.github.io/chosen/#change-update-events"

Solution 2 - Jquery

I stumbled upon this looking for the same ideas. Seems like its a pretty popular feature request, and a couple of forks have implemented it. Looks like it'll be merged into the master branch soon enough.

+1 for this particular pull which worked a charm: https://github.com/harvesthq/chosen/pull/166

You can view Koenpunt's fork here: https://github.com/koenpunt/chosen

Solution 3 - Jquery

Here's a simple way that I did it:

$(".search-field").find("input").live( "keydown", function (evt) {
	var stroke;
	stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
	if (stroke == 9) { // 9 = tab key
		$('#tags').append('<option value="' + $(this).val() + '" selected="selected">' + $(this).val() + '</option>');
		$('#tags').trigger('chosen:updated');
	}
});

Solution 4 - Jquery

I was just trying to solve the same problem. I wound up modifying the source code a bit. Here's the new keyup_checker function. Take a look at case 13:

AbstractChosen.prototype.keyup_checker = function(evt) {
  var stroke, _ref;
  stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
  this.search_field_scale();
  switch (stroke) {
    case 8:
      if (this.is_multiple && this.backstroke_length < 1 && this.choices > 0) {
        return this.keydown_backstroke();
      } else if (!this.pending_backstroke) {
        this.result_clear_highlight();
        return this.results_search();
      }
      break;
    case 13:
      evt.preventDefault();
      if (this.results_showing) {
        if (!this.is_multiple || this.result_highlight) {
          return this.result_select(evt);
        }  

        $(this.form_field).append('<option>' + $(evt.target).val() + '</option>');
        $(this.form_field).trigger('liszt:updated');
        this.result_highlight = this.search_results.find('li.active-result').last();
        return this.result_select(evt);
      } 
      break;
    case 27:
      if (this.results_showing) this.results_hide();
      return true;
    case 9:
    case 38:
    case 40:
    case 16:
    case 91:
    case 17:
      break;
    default:
      return this.results_search();
  }
};

Solution 5 - Jquery

I know this not the answer, but, an alternate solution.

I was searching for the on-the-fly adding part and found that http://ivaynberg.github.com/select2/#tags provides the same thing as chosen + other stuffs like "Tagging".

Solution 6 - Jquery

You could just attach an event to the input text box to listen for a particular character code. After that add the option and trigger the update on the dropdown.

 var dropDown = $('select.chosen');
 dropDown.parent().find('.chzn-container .chzn-search input[type=text]').keydown( function (evt) {
           var stroke, _ref, target, list;
           // get keycode
           stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
           target = $(evt.target);               
           // get the list of current options
           list = target.parents('.chzn-container').find('.chzn-choices li.search-choice > span').map(function () { return $(this).text(); }).get();
           if (stroke === 9 || stroke === 13) {
              var value = $.trim(target.val());
              // if the option does not exists
              if ($.inArray(value,list) < 0) {
                 var option = $('<option>');
                 option.text(value).val(value).appendTo(dropDown);
                 option.attr('selected','selected');
                 // add the option and set as selected
              }
              // trigger the update event
              dropDown.trigger("liszt:updated");
              return true;
           }
        });

Solution 7 - Jquery

An update to leogdion's answer which works with later versions of chosen:

        var dropDown = $('#select_chosen');
        // Make the chosen drop-down dynamic. If a given option is not in the list, the user can still add it
        dropDown.parent().find('.chosen-container .search-field input[type=text]').keydown( 
            function (evt) {
               var stroke, _ref, target, list;
               // get keycode
               stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
               // If enter or tab key
               if (stroke === 9 || stroke === 13) {
                   target = $(evt.target);               
                   // get the list of current options
                   chosenList = target.parents('.chosen-container').find('.chosen-choices li.search-choice > span').map(function () { return $(this).text(); }).get();
                   // get the list of matches from the existing drop-down
                   matchList = target.parents('.chosen-container').find('.chosen-results li').map(function () { return $(this).text(); }).get();
                   // highlighted option
                   highlightedList = target.parents('.chosen-container').find('.chosen-results li.highlighted').map(function () { return $(this).text(); }).get();
                   // Get the value which the user has typed in
                   var newString = $.trim(target.val());
                   // if the option does not exists, and the text doesn't exactly match an existing option, and there is not an option highlighted in the list
                   if ($.inArray(newString,matchList) < 0 && $.inArray(newString,chosenList) < 0 && highlightedList.length == 0) {
                     // Create a new option and add it to the list (but don't make it selected)
                     var newOption = '<option value="' + newString + '">' + newString + '</option>';
                     $("#select").prepend(newOption);
                     // trigger the update event
                     $("#select").trigger("chosen:updated");
                     // tell chosen to close the list box
                     $("#select").trigger("chosen:close");
                     return true;
                  } 
                  // otherwise, just let the event bubble up
                  return true;
               }
            }
        )

Solution 8 - Jquery

I have updated the code from 3nochroot once more. Now I have only one selector to find the multiselect input.

$(document).ready(function() {
$(".js-choicelist").chosen({
    //config comes here
}).parent().find('.chosen-container .search-field input[type=text]').keydown(
    function (evt) {
        var stroke, _ref, target, list;
        // get keycode
        stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
        // If enter or tab key
        if (stroke === 9 || stroke === 13) {
            target = $(evt.target);
            // get the list of current options
            chosenList = target.parents('.chosen-container').find('.chosen-choices li.search-choice > span').map(function () { return $(this).text(); }).get();
            // get the list of matches from the existing drop-down
            matchList = target.parents('.chosen-container').find('.chosen-results li').map(function () { return $(this).text(); }).get();
            // highlighted option
            highlightedList = target.parents('.chosen-container').find('.chosen-results li.highlighted').map(function () { return $(this).text(); }).get();
            // Get the value which the user has typed in
            var newString = $.trim(target.val());
            // if the option does not exists, and the text doesn't exactly match an existing option, and there is not an option highlighted in the list
            if ($.inArray(newString,matchList) < 0 && $.inArray(newString,chosenList) < 0 && highlightedList.length == 0) {
                // Create a new option and add it to the list (but don't make it selected)
                var newOption = '<option value="' + newString + '">' + newString + '</option>';
                var choiceSelect = target.parents('.select-multiple').find('select');
                choiceSelect.prepend(newOption);
                // trigger the update event
                choiceSelect.trigger("chosen:updated");
                // tell chosen to close the list box
                choiceSelect.trigger("chosen:close");
                return true;
            }
            // otherwise, just let the event bubble up
            return true;
        }
    }
)

})

Solution 9 - Jquery

Update of leogdion answer for multiple select (gist)

  $(".chosen-select-with-add-new").chosen({
    no_results_text: "Click Enter or Tab to add new option",
    width: '100%'
  }).parent().find('.chosen-container .search-field input[type=text]').keydown(function (evt) {
    // get keycode
    const stroke = evt.which != null ? evt.which : evt.keyCode;
    // If enter or tab key
    if (stroke === 9 || stroke === 13) {
      const target = $(evt.target);
      // get the list of current options
      const chosenList = target.parents('.chosen-container').find('.chosen-choices li.search-choice > span').map(function () { return $(this).text(); }).get();
      // get the list of matches from the existing drop-down
      const matchList = target.parents('.chosen-container').find('.chosen-results li').map(function () { return $(this).text(); }).get();
      // highlighted option
      const highlightedList = target.parents('.chosen-container').find('.chosen-results li.highlighted').map(function () { return $(this).text(); }).get();
      // Get the value which the user has typed in
      const newString = $.trim(target.val());
      // if the option does not exists, and the text doesn't exactly match an existing option, and there is not an option highlighted in the list

      if ($.inArray(newString, matchList) < 0 && $.inArray(newString,chosenList) < 0 && highlightedList.length == 0) {
        // Create a new option and add it to the list (but don't make it selected)
        const newOption = '<option value="' + newString + '" selected="selected">' + newString + '</option>';
        const choiceSelect = target.parents('.chosen-container').siblings('.chosen-select-with-add-new');
        choiceSelect.append(newOption);
        // trigger the update event
        choiceSelect.trigger("chosen:updated");
        // tell chosen to close the list box
        choiceSelect.trigger("chosen:close");
        return true;
      }
      // otherwise, just let the event bubble up
      return true;
    }
  })

Example usage on rails (slim)

  .tag-list
    label.control-label.h5 Tag list
    = select_tag :tag_list, options_for_select(ActsAsTaggableOn::Tag.order('taggings_count desc').pluck(:name), @publication.tags.map(&:name)), multiple: true, data: { placeholder: 'north, east, south, west' }, class: 'chosen-select-with-add-new'

Solution 10 - Jquery

I tried several solutions given here and elsewhere, however none worked in chosen.js 1.8.5 (jQuery: 3.3.1) and so I ended up with the following since I didn't want to use a fork that might not always be up-to-date to the master branch:

For the case that I might not want any .chosen-select to allow new values, I added a new class .chosen-newValuesAllowed. I set an event handler on this class where CTRL + I adds the new value if it is not yet present. The focus on the input field does not get lost afterwards. In my example, I check the innerHTML of the

$(document).on("keydown", ".chosen-container.chosen-newValuesAllowed input", function(e) { 
if (e.ctrlKey === true && e.keyCode === 73) { // CTRL + I
    e.preventDefault();
    var newValue = $(this).val();
    if (newValue) {            
        try {
            // only add if there is no option having the content/text of "input" yet!
            // instead of filter() for the content of <option> you can check on its @value by: find("option[val='...']") 
            var $selectElement = $(e.target).closest("div.chosen-container").prev(); // the previous sibling should be the <select>. If not, grab it some other way, e.g. via @id
            if (!$selectElement.find("option").filter(function () { return $(this).html() === newValue; }).length) {
                if (!$selectElement.attr("multiple")) { // unselect for single-select 
                    $selectElement.val('');
                }
                $selectElement.append('<option val="' + newValue + '" selected>' + newValue + '</option>');
                $selectElement.trigger('chosen:updated');    
            }
        } catch(error) {
            // pass
        }
        e.target.focus();
    }        
    return false; 
}
});

Another solution would be to call the triggerable function chosen:no_results if new values should only be added if there explicitly is no result:

$(".chosen-select.chosen-newValuesAllowed").on("chosen:no_results", function(e, data){
       var newValue = data.chosen.get_search_text();
       ...
 });

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontotallyNotLizardsView Question on Stackoverflow
Solution 1 - JqueryjeffreydevView Answer on Stackoverflow
Solution 2 - JqueryGalaxyView Answer on Stackoverflow
Solution 3 - JqueryUltimationView Answer on Stackoverflow
Solution 4 - JquerySamoView Answer on Stackoverflow
Solution 5 - Jquerysk8terboi87 ツView Answer on Stackoverflow
Solution 6 - JqueryleogdionView Answer on Stackoverflow
Solution 7 - Jquery3nochrootView Answer on Stackoverflow
Solution 8 - JqueryRawburnerView Answer on Stackoverflow
Solution 9 - JquerysrghmaView Answer on Stackoverflow
Solution 10 - JquerymeistermuhView Answer on Stackoverflow