Sage 9: $(document).trigger("EVENT"); across different route files not working

Hi there,

I started working with Sage 9 this weekend and love it. Thank you for the awesome work!
Now I face an issue, wich I do not appear to solve (I am stuck for a couple of hours):
I registered a couple of custom routes and have an issue with jQuery events.
Here is the setup:
in my custom route’s init method I have this:
console.log(‘My custom route init method works fine’);
$(document).on(“myEvent”, function(){
console.log(‘MyEvent Test works fine’);
});
in my common(.js) route file I have this in the finalize method:
$(document).trigger(“myEvent”);

I do not receive a JS error, but the second console.log in my custom route is not firing. If I however copy the same code in the init method of the common.js route file, both console logs work fine.

Is there any way to make events work across different route files?
(I am just a beginner, if anything is unclear, please let me know and I will try to add the information)

Thank you so much for your help.

  • Jan

My guess is this is a scope issue; I’m not super familiar with how the finalize actions are set up, but try re-defining your event in your custom route’s finalize method, and see if that works?

Hey @runofthemill,

Thank you for your reply. Unfortunately that does not work as well.
However, it does work in the finalize method of common.js, if
$(document).on(“myEvent”, function(){
console.log(‘MyEvent Test works fine’);
});
Is written before:
$(document).trigger(“myEvent”);

Perhaps you or anyone else has another idea?

Thank you for your help!!

  • Jan

You can take a look at how Sage triggers JS events by examining the router code, which is fairly straightforward: https://github.com/roots/sage/blob/master/resources/assets/scripts/util/Router.js

As for troubleshooting, I would look at the following things:

  • What is the behavior if you target a specific element, rather than document?
  • What is the behavior if you try this with a native event (I. E. click)?
  • What is the behavior if you try this using native JavaScript event handling instead of jquery?
  • What is the behavior if you put the event setup and trigger in your custom route’s init?

Hi @alwaysblank,

Thank you for your feedback. I looked at the code, but I am not really sure, how I can bind a function to an event registered in another route. I am still stuck at that point and I cannot seem to get my head around it… :frowning:

Regarding the questions:

  1. E.g. with “body” it is the same result. The second console.log is not firing.
  2. Click and other events work fine (e.g. jQuery(‘body’).on(‘click tap’,function(){console.log(‘Click works’);}); works fine)
  3. Native JavaScript event handling. I used this structure (I followed https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events):
    In my common.js file I have this in the init method (I registered mbEvent globally):
    mbEvent = new Event(‘myEvent’);
    and in the finalize method of common.js I have this:
    var elem = document.getElementsByTagName(“BODY”)[0];
    elem.dispatchEvent(mbEvent);

In my custom route, I have this in my init method:
var elem = document.getElementsByTagName(“BODY”)[0];
elem.addEventListener(‘myEvent’, function () {
console.log(‘My event is being fired’);
}, false);

Unfortunately this also results in the fact that the console.log inside the event is not being fired.

  1. If event setup and trigger are setup in the same route (common.js or my custom route file) it works fine.

I hope this helps. I think I tried all kinds of combinations already, but I must be missing something …

Hey @MB-Jan,

I tested the setup you describe on a new copy of Sage, and it’s working on my end. Any chance you could share the full contents of common.js and your custom route?

Here’s how I tested this. In home.js:

export default {
  init() {
    // JavaScript to be fired on the home page
    console.log("My custom route init method works fine");

    $(document).on("myEvent", function() {
      console.log("MyEvent Test works fine");
    });
  },
  finalize() {
    // JavaScript to be fired on the home page, after the init JS
  },
};

In common.js:

export default {
  init() {
    // JavaScript to be fired on all pages
  },
  finalize() {
    // JavaScript to be fired on all pages, after page specific JS is fired
    $(document).trigger("myEvent");
  },
};

And the result in the console:

image

PS - there are docs on the routing system here if you want to learn more about it: Sage 9.x: Compiling Assets | Roots Documentation

2 Likes

Hi @mmirus,

I really appreciate you taking the time. I figured it out with your help. Once you told me that it is working on your end, I started over with a new theme installation. I then only had the common.js file + my new route file and it worked as well.
I then went back to my original project and realized that my setup was a bit different. The trigger was in the finalize method as mentioned earlier, but not in the common.js file, but in another custom route file.
Technically this should still work, as the events were bound to document on init, but it just did not work until I did changed the trigger code in the finalize method to this:

finalize() {
  console.log("MyEvent Test works fine");
  jQuery(window).on('load',function(){
    $(document).trigger("myEvent");
  }
},

This works now in any finalize method of any route (common.js and custom files) and is good enough for me, because I want to fire my event any ways after everything else is done loading.

If this is not good practice, please let me know.

@Everyone who helped: Thank you!!!

1 Like

Hey @MB-Jan - glad you got it working! My guess is that your event was being triggered before you were listening for it because the custom route where you were calling $(document).trigger("myEvent") was being run before the route where you were calling $(document).on('myEvent', ...).

From the docs:

The order of execution for routes is:

  1. The init scripts in the common route (after the browser’s DOM load event)
  2. For each route matching the loaded page (e.g., home), the init scripts and then the finalize scripts
  3. The finalize scripts in the common route

So, the common route’s finalize hook (#3) will always run after all custom routes (which is why my test worked), but custom routes (#2) are evaluated in an unspecified order–in whatever order they come back from document.body.className.

I think that because you’re now using the window’s load event, you’re effectively bypassing the timing of the routes because all of the routes run when the DOM is loaded, which happens before the window is fully loaded. That’s why it’s working for you now.

One cautionary note: the problem with using the window’s load event is that the browser waits for all assets (images, etc.) to load before that event is fired. If an image or a script loads slowly, then your event is delayed, and you wouldn’t want that in some use cases. For example, if your menu requires JavaScript to function and you’re running that JavaScript on the window’s load event, you wouldn’t want your menu to be broken for a couple of seconds while a slow asset loads. You can decide if that’s a concern in your specific use case.

If you are worried about that, one alternative is that you could move your .trigger() to the common route’s finalize method without the jQuery(window).on('load', ...) code.

1 Like

Hey @mmirus,

I really appreciate your detailed feedback.

My guess is that your event was being triggered before you were listening for it because the custom route where you were calling $(document).trigger(“myEvent”) was being run before the route where you were calling $(document).on(‘myEvent’, …).

I thought so, too. But I am listening to it in the init method of the other route file. This is the confusing part for me. Out of curiosity I also tried changing the route order and even the name in the main.js file to see if that makes any different, but it does not.

For the functionality I need, it is (luckily) okay that I wait for the load event. The trigger is not necessarily needed on all pages, that is why I wanted to keep it outside of the common.js file.

If you have any other idea to make it work with two custom routes, please let me know. But you already helped a lot. Thank you very much!!

Hey @MB-Jan,

To clarify this:

The order of execution for routes is:

  1. The init scripts in the common route (after the browser’s DOM load event)
  2. For each route matching the loaded page (e.g., home), the init scripts and then the finalize scripts
  3. The finalize scripts in the common route

When the custom routes run in step #2, it doesn’t run all of the init scripts from the routes and then all of the finalize scripts from the routes, it runs init and finalize from route 1, then init and finalize from route 2, and so on. In other words, putting something in finalize doesn’t mean that it will run after the init scripts from all custom routes, only that it will run after the init scripts of the route in which you’ve placed it.

When you say you tried changing the route order, I’m assuming you meant the order in which they are listed in the routes declaration in main.js?

/** Populate Router instance with DOM routes */
const routes = new Router({
  // All pages
  common,
  // Home page
  home,
  // About Us page, note the change from about-us to aboutUs.
  aboutUs,
});

E.g., to try to change the order, you might have moved aboutUs before home.

If that’s what you meant, unfortunately that doesn’t affect the order in which the routes are run.

To find and run the custom routes, the Router.loadEvents() method gets document.body.className, which contains a list of the class names on the current page’s body element and then goes through that list one class at a time. This is why your routes have to be named after a body class name. For each class, if there is a matching route, it runs the init scripts and then the finalize scripts, and then it moves on to the next class name. Because of this, the order in which the routes are run is determined by the order of the class names in document.body.className. I don’t know what, if anything, determines that order.

I’m not sure there’s a way to get it working reliably with two custom routes because of that ordering issue, but if you wanted to avoid using the window’s load event, I would move it to the common route and put it in a conditional that checks if the current page’s body has the class name you are currently using for the custom route where you are attaching the event listener. Note that if you did it that way you’d want to check the actual class name, not the camel case version of the name you use for the route name (e.g., you would check for “front-page” and not “frontPage”).

Hey @mmirus,

Thank you again for your reply. You are absolutely right. I misunderstood the loading process. I thought each route’s init method gets executed and then each routes finalize method. Thank you for clearing that up.
I have a question regarding performance that is somehow related to this. I tried to use separate routes, because I import modules in them that are not required on every page. Is it correct that it will help performance by doing this separation different route files or would there be no difference, if I import the module in the common js file, but have the code execution inside an if statement?

I am all new to JavaScript routes, so please excuse me, if this question does make sense.
This question is not urgent.

Thank you very much!!

Hey @MB-Jan,

I don’t think it should matter either way. In terms of download size, unless you change your configuration, all of your theme JavaScript will be merged into one file when you run the build process, so it’s all being downloaded regardless of routes, imports, and what page is being viewed. That means that putting it in the common route inside of a conditional check has the same effect as putting it in a more specific route.

Generally, I would still put things in more specific routes where possible because it saves you that conditional check and keeps your code organized more cleanly. That said, in this case there’s a functional reason for putting it in common, so I wouldn’t feel like you’re doing something dirty.

Hey @mmirus,

Thank you again for your post That clears up a lot of questions I had.
I will need to look into other ways to incorporate scripts that can safely be loaded deferred.
I guess that will be my task for the weekend :slightly_smiling_face:

Thank you again, I really appreciate it!

1 Like