Reinitialize selectize options when subscription updates

Hi there, I’m trying to find a reliable way to call a jQuery plugin (selectize) method when a subscription changes. I’ve tried combinations of listening for the ready method of a subscription, autorun and other techniques.

I’ve found that I can sometimes get the desired outcome when subscription changes but other times it seems to try to run before the subscription is ready.

Does anyone have any solid strategies for updating something like selectize when the template loads as well as when the subscription updates? (Using Blaze)

Thanks!

I have managed to get it working albeit in a seemingly verbose and somewhat fragile way (use of setTimeout feels painful). This is in an autorun block provided by viewmodel

autorun() {
    
    const client = Clients.findOne()
    
    // Establish the jQuery selectors
    const clientSelectTarget = $('select[name="client"]')
    const contactSelectTarget = $('select[name="contact"]')
    
    // Set an initial client if one was found
    if (client && !this.clientId()) {
      this.clientId(client._id)
    }
    
    // Unpleasant having to set a timeout here (fragile!)
    // Have attempted to run it on this.clientSub.ready() but seems to return ready before actually being so
    Meteor.setTimeout(() => {

      // Set up the selects as selectize selects
      clientSelectTarget.selectize()
      contactSelectTarget.selectize()

      // Set the selectize objects
      const clientSelect = clientSelectTarget[0].selectize
      const contactSelect = contactSelectTarget[0].selectize

      if (contactSelect) {
        
        // Clear all current options and selections
        contactSelect.clear()
        contactSelect.clearOptions()
        
        // Prepare updated options from this.contacts() (reactive source)
        const options = this.contacts().map(contact => {
          const name = `${contact.firstName} ${contact.lastName}`
          return { text: name, value: contact._id }
        })
        
        // addOption() can also take an array
        contactSelect.addOption(options)
        
        // Now update the list
        contactSelect.refreshOptions()

        // Set the first contact by default if it exists
        if (options.length) {
          contactSelect.addItem(options[0].value)
        }
        
      }

    }, 200)

  },

The onReady callback of subscribe will be called whenever the subscription chages, so maybe that would be a better place than autorun.

Thanks, will take a look, I had tried with subscriptionHandle.ready() but it seemed to be firing before all the data was ready. Not sure if this is the same function?

For example, the following code using viewmodel is returning 0 as a count even though on the screen the clients have indeed loaded

this.templateInstance.subscribe('clients', {
  onReady: function() { console.log('ready', Clients.find().count()) } // returns 0
})

EDIT:

Hmm playing around I think it might be an issue to do with tmeasday:publish-counts, if I remove the Counts.publish() part of my publication, the subscription ready seems to work, interesting!

Just seen this https://github.com/percolatestudio/publish-counts#noready

First of all, do anything that has to do with DOM manipulation inside an onRendered callback, because in onCreated there’s no guarantee your element exists (you’d actually get an error if trying to use $).

Then use a method to populate the initial select list. Afterwards, I’d suggest using an observer, because it allows you fine grained control over changes (e.g. reacting only to certain field changes), and only for certain documents if you wish (according to the query):

Template.my_template.onRendered(function () {
  const $myElement = $(this.find('#myElement'));
  
  Meteor.call('get-my-objects-array', (err, result) => {
    // do jQuery stuff on $myElement;
  });
  
  this.subscribe('MyCollectionPublication');
  
  this.observer = MyCollection.find({}).observeChanges({
    changed(_id, fields) {
      if (fields.dataUpdatedAt || fields.myField > 613) {
        // do jQuery stuff on $myElement;
      }
    },
  });
});

You can even conditionally stop the observer, based on certain conditions with this.observer.stop().

This approach can coexist along multiple autorun blocks, and multiple observers.

It’s normal that Counts.publish() triggers your onReady callback twice. If you want to prevent this from happening, use the noReady option:

Counts.publish(this, 'MyCount', MyCollection.find({}), { noReady: true });
1 Like

Thanks for the detailed answer will take another look!