Reactive dropdown menu using 'chosen'


#1

Hello,
I’m trying to display data from session reactively using a dropdown select element with ‘chosen’ library.
As usual when having jQuery libraries for displaying fancy elements with Meteor the issue surfaces with updating/rerendering them. To be specific, firing the update on the jQuery library side, at the correct moment when the DOM that the library modifies has finished rendering, has always been a bit problematic for me. Usually splitting the element into it’s own template and having a Tracker in onRendered() to update the jQuery element solves the issue. However, with chosen I am having hard time and I have a feeling there should be a better way to deal with such jQuery libs that I am not aware of.

What I’ve been trying so far:

<template name="parent">
    {{#each list}}
        {{>dropdownTemplate selectedValue=selectedValue otherValues=otherValues index=@index}}
    {{/each}}
</template>
<template name="dropdownTemplate">
    <select class="chosen-select">
        <option value="{{selectedValue}}">{{selectedValue}}</option>
       {{#each otherValues}}
            <option value="{{this}}"> {{this}} </option>  
       {{/each}}
    </select>
</template>

And js:

Template.parent.onRendered(function() {
     Tracker.autorun(() => {
          queryList();
          $('.chosen-select').chosen(); // initialize chosen for the first time
     });
});

Template.dropdownTemplate.onRendered(function() {
    Tracker.autorun(() => {
        let list = Session.get("list");
        if (list && this.data.index == list.length - 1) {  // run only once when at the last element
             $(".chosen-select").trigger("chosen:updated");   // update chosen to take the updated values into account
        }
    });
});

Template.parent.helpers({
    list: function() {
         return Session.get("list"); 
    }
});

Template.parent.events({
      "click #button": function(event, template) {
              // remove one element from Session
              Session.set("list", Session.get("list").splice(0, 1));
      }
}); 

function queryList() {
       let list = ... // get list etc
       Session.set("list", list);
}

What is the problem:

  1. When removing let’s say the first element from the list stored in Session, the dropdown menu’s keep their old values and will therefore have the values shifted by one.
  2. The value that was selected from the dropdown menu remains the same and doesn’t get updated even if chosen is updated manually from console. Also the same behaviour exists when not using chosen at all.

Something like this:

correct: b dropdown: a
correct: c dropdown: b
correct: d dropdown: c
etc…

The first problem can be solved by having setTimeout update ‘chosen’ after a certain time, but it’s really ugly hack.
Would be thankful for any ideas…


#2

In case somebody else stumbles upon similar problem, as I see onRendered + jQuery plugins cause a lot of confusion with Meteor:

Managed to get it behaving like I wanted using the separate template for select element + Meteor.defer() function. This solved the first problem. Defer at least seems a bit less like a hack, although as I have understood it’s not the main purpose of defer.

Second problem was with dropdown element keeping its selected value even though the data behind it changed. Started to think maybe it’s supposed to be more of a feature than a bug, so I just change the value by force of the select element during dropdownTemplate.onDestroyed().

Template.dropdownTemplate.onDestroyed(function() {
    // run in async after the rest of the tasks have been executed
    Meteor.defer(function () {
        // fixes the persistent dropdown selection
        let chosenElements = $(".chosen-select");
        let correctVal = chosenElements.eq(lastRemovedId).children().first().text();
        chosenElements.eq(lastRemovedId).val(correctVal);

        // update chosen
        chosenElements.trigger("chosen:updated");
    });
});

If there exists a better way, would be interesting to know.