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!