Chosen.js dropdowns | how preserve data-placeholder?

771 Views Asked by At

I have five multiple selects, created dynamically with javascript. Next to each filter I have two links: "all" and "none". "All" places all the filters into the chosen box, to make it easier for users to exclude a value. "None" removes all options.

When I use "all" to add the filters, and then manually remove them, the data placeholder disappears and I get a blank filter box. If I click "all" and then "none", the data-placeholder persists. If I add and remove filters manually, it persists.

https://jsfiddle.net/09bmrq31/6/

The only DOM difference I can see is that the input field (div.chosen-container ul.chosen-choices li.search-field input) typically has a class of "default". This class is rendered "" when clicking "all". When clicking "none", the class goes back to "default". So I think this class is the culprit. Changing the class back to "default" in the DOM has no effect by itself but I guess some javascript process depends on this class.

I'm using chosen-material-design on top of chosen. But I've searched this CSS file and the regular chosen file for ".default" and it's not there, so I don't think this is a CSS issue.

Does anyone know what sets (or unsets) this class? This is a problem across three different apps and has been going on for weeks. Grateful for any help!

Javascript select code:

f.innerHTML += '<select id="'+filters[0][i]+'" class="chosen-select" multiple style="width:80%" data-placeholder="'+filters[1][i]+'"><option value=""></option></select>';

Chosen code:

  $(".chosen-select").chosen({
    disable_search_threshold: 10,
    allow_single_deselect: true,
    no_results_text: "Oops, nothing found!"
  });

Javascript all/none code:

d3.selectAll("div.all").on("click",function(){
  var whichSelect = this.id.substr(0,this.id.length-4);
  $("[id='"+whichSelect+"'] option").prop('selected',true);
  $("[id='"+whichSelect+"']").trigger('chosen:updated');

  var tempIndex = filters[0].indexOf(whichSelect);//whether it's company, portfolio, industry or country
  for (var i=0; i<filters[2][tempIndex].length; i++) { if (filters[6][tempIndex].indexOf(filters[2][tempIndex][i])==-1) { filters[6][tempIndex].push(filters[2][tempIndex][i]); }}
  filters[5][tempIndex].filterFunction(function(d){ return filters[6][tempIndex].indexOf(d)!=-1; });

  update();
});


d3.selectAll("div.none").on("click",function(){
  var whichSelect = this.id.substr(0,this.id.length-5);
  $("[id='"+whichSelect+"'] option").prop('selected',false);
  $("[id='"+whichSelect+"']").trigger('chosen:updated');
  var tempIndex = filters[0].indexOf(whichSelect);//whether it's company, portfolio, industry or country
  filters[6][tempIndex] = [];
  filters[5][tempIndex].filterAll();

  update();

});

Image of DOM with data-placeholder showing and class of "default":

DOM of chosen select and div

1

There are 1 best solutions below

0
Hugues M. On BEST ANSWER

I have updated your fiddle (revision 8), with a fix.

Problem: your original HTML has an empty element <option value=""></option> in the options, and your JS code to add all options (when user clicks on your "all" link/button) also includes that empty option, and that trips the library.

Solution

You should exclude that option element when you call .prop('selected', true)

In terms of code, you do that:

$("[id='"+whichSelect+"'] option").prop('selected',true);

And that unfortunately include the empty option. So, do that instead:

$("[id='"+whichSelect+"'] option:not([value=''])").prop('selected',true);
//                              ^^^^^^^^^^^^^^^^
//                              This excludes the empty option

You could use any other way, perhaps it would be easier to add a class to that specific element, and use the class to select it more easily than with the selector above.


Why does that happen?

There is an internal consistency issue in the plugin (although it only applies for "incorrect" usage, so it's only half bad): some parts of the code seem to purposefully ignore this empty option, but another part takes it into account for total count of selected options. Result: depending on the previous state, this empty & invisible option is counted, and despite visually empty input field the library computes choices_count() = 1 (instead of 0).

And behold the function that updates this default class, at line 942 of chosen.jquery.js:

Chosen.prototype.show_search_field_default = function() {
  if (this.is_multiple && this.choices_count() < 1 && !this.active_field) {
    this.search_field.val(this.default_text);
    return this.search_field.addClass("default");
  } else {
    this.search_field.val("");
    return this.search_field.removeClass("default");
  }
};

Because choices_count() is 1 instead of 0, the class is not added back, and you don't see the placeholder.

In short: make sure your code does not include empty option in the list of selected items