Why a function executed onRendered work or not

(issue is solved, but still a question in my last post)

Hi,

I create a navbar… with a menu which is loaded with templates and helpers.

I start to add some styles and hide/show behavior.

My menu is in a hidden block and I show after a click on a button…

In my Template.navbar.onRendered
I added a click behavior to open the menu…
No problem with this button.

My menu has a UL/LI DOM.

I hide all sub UL by css and show them after a click on the parent LI . (simple expand collapse)
All my LI has a link. I stop the propagation ( to avoid close the menu) and preventDefault to avoid to open the link if the link has children…
If it’s the last link of the tree without children, so I open and close my menu.

I added in Template.navbar.onRendered a function…

    $('.mainMenu [data-level] a').click(function (e) {
        var target = e.target

        if ((target.tagName === "A") && ($(target).siblings('[data-level]').length)) {
            e.stopPropagation();
            e.preventDefault();

            $(target).siblings('[data-level]').slideToggle('fast');
        } else {
            $('.mainMenu:visible').slideUp('fast');

        }
    });

From my browser, I load the page… sometime it work perfectly, sometime not.

If I refresh, sometime it work again sometime not… :slight_smile:
My click event is no more detected on my link. No idea why.

If I execute my function from the console, it work again…

I tested to put the function inside a $(document).ready, on with a “.on(‘click’)”… but same result.

here the onRendered code

Template.navbar.onRendered(function(){
    // OPEN/CLOSE the main menu
    $('#mainMenuBtn').click(function(e){
        e.stopPropagation();
        $('.mainMenu').slideToggle('fast')
    });

    // Close the menu if click outside
    $(document).click(function(e){
        if ($(e.target).parents('.mainMenu').length === 0) {
            $('.mainMenu:visible').slideUp('fast');
        }
    })



    // The menu is a tree ul>li>a with UL children.
    // If click on a link which has children, do not open the link and open the next child
    // If click on a link which has no children, so do native behavior, open the link and close the menu.

    $('.mainMenu [data-level] a').on("click", function(e){
        var target = e.target

        if ((target.tagName === "A") && ($(target).siblings('[data-level]').length)) {
            e.stopPropagation();
            e.preventDefault();

            $(target).siblings('[data-level]').slideToggle('fast');
        } else {
            $('.mainMenu:visible').slideUp('fast');

        }
    })

    
})

I’m almost sure it’s about the loaded order… But I don’t understand as the code is inside a onRendered and the button to hide/show the menu is working in all case…

an idea ? :slight_smile:

Thank’s for reading

Mickael

Note:
My template “nav” have another template “menuSubchildTree” inside. . So maybe I should use the onRendered on this template…

But it’s strange. If I use a click event in this template, when I click only one time on one link… it show like if I had click on a lot of time and execute the function as much :roll_eyes:

ok, seem I solved it by using


Template.navbar.events({
    "click .mainMenu [data-level] a" : function(e){
        var target = e.target
         console.log(e);
         console.log(this);
        if ((target.tagName === "A") && ($(target).siblings('[data-level]').length)) {
            e.stopPropagation();
            e.preventDefault();

            $(target).siblings('[data-level]').slideToggle('fast');
        } else {
            $('.mainMenu:visible').removeClass("slideInLeft").addClass("slideOutLeft");


        }
    }
})

But it will be nice if someone can explain the difference between a click event inside a onRendered and using the template.events…

In my opinion you should use jquery click events anywhere. You can just use the template events. Its way more cleaner.

Hi @imike57,

as you already found out, you should use Meteor events instead of jQuery events.

There’s one big misconception about onRendered() that I also had when I started developing. I thought that onRendered() would mean that the whole DOM had already been rendered and it would be safe to operate on it. But this isn’t true. In fact there’s no guarantee that the DOM is accessible to the browser once onRendered() fired. This is the reason why your attempts to hook jQuery events to them sometimes works and sometimes fails.

As a rule of thumb, you should avoid using jQuery to manipulate the DOM as much as possible. There is no need at all to use the jQuery event system, and it would interfere with Meteor’s. So use Template.name.events() instead.

Toggling classes is also very easy using Blaze, so you should avoid doing this in jQuery as well.

There’s only one really good reason to use jQuery in combination with Meteor: If you are using a jQuery plugin that relies on it, e.g. some component like the Slick carousel:

$('my-awesome-carousel').slick()

In this case, you will also face the problem that initializing the component inside an onRendered() handler sometimes fails. As a work-around, you can place the initialization code inside a Meteor.defer() callback:

Meteor.defer(() => $('my-awesome-carousel').slick());

This is basically a timeout with 0 seconds delay. If you do it this way, Blaze will render the DOM and then fire the initialization code for your jQuery component. (This might have also worked in your jQuery event hook examples, but it’s not a good idea to use them this way.)

Another recommendation is to put the jQuery component in its own Blaze sub-template. If the template is rather small, it’s more likely that it has already been set-up in the DOM when onRendered() triggers. You can also combine this with the defer() trick, just to be sure.

TL;DR: Avoid using jQuery as much as possible and learn how to do things “the Blaze way” instead.

1 Like