Use .onRendered globally?


#1

Situation

I’m using the semantic-ui package.
To use the dropdowns you need to initialize them globally like so:

$(document).ready(function() {
   $('.ui.dropdown').dropdown();
});

Now I’m trying to do this the meteor way, by using .onRendered instead or $(document).ready here.

Question

The only way I could get it to work was like this:

       Template.header_filter.onRendered(function () {
		$('.ui.dropdown').dropdown();
	});

	Template.header_account.onRendered(function () {
		$('.ui.dropdown').dropdown();
	});

	Template.product_controls.onRendered(function () {
		$('.ui.dropdown').dropdown();
	});

But now I have to copy and paste one of those code-blocks for each template I use a dropdown in.
Isn’t there a way to make this work globally (like in a $(document).ready function above)?

For example why isn’t something like this working?

        Template.body.onRendered(function () {
    		$('.ui.dropdown').dropdown();
    	});

#2

Templates render all the time during the app life cycle. So there could not be a global onRendered that is only called once. The solution to your problem is to put this into the templates that have dropdowns:

Template.myTemplate.onRendered(function () {
  this.$('.ui.dropdown').dropdown();
});

Note, that I use this.$. This will only find dropdowns in you template instead of all dropdowns on your page. I usually initialize each dropdown separately to pass different options if needed.


#3

Using the body's onRendered function with something like jqery.waituntilexists.js (https://gist.github.com/buu700/4200601) should work.


#4

@nilsdannemann,

One thing that I’ve already done is create an array of templates like:

['template1', 'template2', 'template3']

And then, for each item create a rendered callback, like this:

myTemplates.forEach ( function (template) {
 Template[template].onRendered();
}

That was easier to me to remeber, because it was a setting on project.

Another thing that you can try is to use this:

Meteor.startup(function(){
  for(var property in Template){
    // check if the property is actually a blaze template
    if(Blaze.isTemplate(Template[property])){
      var template=Template[property];
      // assign the template an onRendered callback who simply prints the view name
      template.onRendered(function(){
        console.log(this.view.name);
      });
    }
  }
});

As used here

In this way you can create the callback inside this code or populate an array to use my way.

Hope it helps,

PS: Thanks to MDG for changing .rendered to .onRendered so we could do this trick, I use it a lot.


What's up with Semantic-UI?
#5

One quite simple way would be to use a template as a block helper to wrap any dropdowns.

<template name="dropdown">
    {{>UI.ContentBlock}}
</template>
Template.dropdown.onRendered(function () {
    this.$('.ui.dropdown').dropdown();
});
{{#dropdown}}
    <!--  dropdown html here -->
{{/dropdown}}

#6

Learn something new every day! Thanks for posting that!


#7

I have the same problem to init $.material.init() the peerlibrary:blaze-components for all template.


#8

I just wanted to point out that the answer @moretti gave was great. Note that he uses this.view.name rather than the “property”. If you do this, it won’t work since JavaScript is function scoped not block scoped, so you’ll be referencing the last value set to property at the end of the look, since onRendered() is asynchronous. I ended up using a functional each loop to avoid ever referencing the name from a previous loop:

Meteor.startup ->
  _.each Object.keys(Template), (name) ->
    template = Template[name]
    if name.indexOf('_') != 0 and Blaze.isTemplate(template)
      console.log('template', name)
      template.onRendered ->
        console.log('rendered', name)