How to fire on every footer load (or page load?)

I’m trying to load a javascript form in a footer.

I have thise in the header:

<script type="text/discourse-plugin" version="0.8">
  let loadScript = require("discourse/lib/load-script").default;

  api.onPageChange(() => {
    loadScript("//js.hsforms.net/forms/current.js").then(() => {
      console.log("doing the thing");
      hbspt.forms.create({
        portalId: "229276",
        formId: "a86ca9cc",
        submitButtonClass: "button orange-button hubspot-button",
        target: ".subscription-form"
      });
    });
  });
</script>

And this in the footer:

<div class="subscription-form clearfix">
  <h5>Sign Up for Our Blog</h5>
</div>

The first time the page loads, it works fine. Subsequent pages (most of them, at least), fail to load the form and are complaining:

Couldn't find target container .subscription-form for HubSpot Form a86ca9cc. Not rendering form onto the page

I think that maybe the footer gets loaded after the script fires?

So I somehow need to delay firing that until the footer loads.

plugin-api.js says:

//  Listen for a triggered `AppEvent` from Discourse.

api.onAppEvent("inserted-custom-html", () => {
  console.log("a custom footer was rendered");
});

Maybe I just need to know what AppEvent to look for?

1 Like

There’s a little bit about this here.

Javascripts targeting the footer doesn't work after page transitions - #4 by Johani

When you use this

api.onAppEvent("inserted-custom-html", () => {
  // some code
});

You have to specify which custom HTML you want to target - different events fire based on that.

In this case, you want to target the footer. Can you try this and see if it works for you?

api.onAppEvent("inserted-custom-html:footer", () => {
  // some code
});
1 Like

Ha! That didit! (Of course, it was after I convinced them that what they were trying to do in the footer wasn’t worth doing in the first place). I’m totally psyched to have gotten this figured out and appreciate your help; I’m slowly getting to where this stuff makes sense. (And I think now, they’ll just delete the footer altogether.)

Aha! Not sure why I didn’t think to search for “footer”! :man_shrugging:

So inserted-custom-html refers to anything in themes? And then I could append :footer or :header or maybe :head_tag? Is that the magic? Is it just me, or would

https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/app/lib/plugin-api.js#L516

be more helpful if it instead said

   api.onAppEvent('inserted-custom-html:footer', () => {

Javascript and Ember are the hardest things for me to figure out since . . . maybe when I learned Lisp in the mid-1980s, so I still can’t quite tell what counts as “obvious.”

1 Like

Not quite. That event only fires when the custom-html ember component is rendered.

So, where do we use that ember component, and what does it do?

We mainly use this in the main application template to render two theme fields.

Theme after_header tab

Theme footer tab

Note that we use it in other places, but they don’t matter in this context.

Notice how the footer also has triggerAppEvent="true"

That’s why you can use

api.onAppEvent("inserted-custom-html:footer", () => {
  // some code
});

You can’t do the same with the after_header tab. Discourse doesn’t fire an event for that. So, this won’t work.

api.onAppEvent("inserted-custom-html:top", () => {
  // some code
});

Now, why do we use that ember component to render some theme fields?

The simple answer is that it gives us a lot more control over when it’s rendered.

Two examples of this… The topic list and the admin page.

We don’t want to render the footer on the topic list while there are still topics to be loaded via infinite scroll.

We also don’t want to render any banners or site branding markup on the admin page added in the after_header tab.

So, using the custom-html ember component gives us more granular control over things like that.

Now, back to your question. If there’s some work you need to do when the footer is rendered, this is the wrapper you need,

api.onAppEvent("inserted-custom-html:footer", () => {
  // some code
});

That API method works for all app events. So, even though the example in there isn’t really complete, it’s just an example. There are plenty of other AppEvents that you can use in that method. For example, you can check here.

Code search results · GitHub

to see the names of a few events that fire.

this.appEvents.trigger("EVENT_NAME")

and how we hook unto them to make other changes

this.appEvents.on("EVENT_NAME")

You can hook unto any event that Discourse core fires in your theme. So, if I want to fire some code right before the composer opens.

discourse/app/assets/javascripts/discourse/app/components/composer-editor.js at 1c38b4abf1fab8d67aaaa4b9f2810add64b709c4 · discourse/discourse · GitHub.

I can add something like this to my theme.

api.onAppEvent("composer:will-open", () => {
  console.log("this fires right before the composer opens");
});

That said, I agree with you. We should use a different example for that method in the API file. I’ve made a note to improve that example.

2 Likes

This is a great response and so very clear that even I was able to understand it.

This “how to get a thing to fire” issue comes up quite a bit. Maybe people will find it here, but just your last post could stand to be its own topic and maybe added or linked over in the theme and/or plugin guides.

And now that “theme” mostly means “ember” and “plugin” mostly means “rails”, it might make sense for someone to think about refactoring those a bit. It seems that when things were just getting started, there was a bunch of Ember stuff that required a plugin that can now be done in a theme component, right? And now you can do the ember stuff in a plugin or a theme component.

1 Like

Hey @Johani , so another version of this “how to trigger or be triggered” issue is that I’m using a version of Custom Header Links in a plugin. It links to some items (“servers”) created in a separate model that my plugin adds. When a server is created, I want to rebuild the header links to link to the two most recently created servers. I’m doing this now in an initializer and it works fine except to get it to update after a server is added you have to reload the page.

You told me how to add and watch triggers, so I thought I might be able to figure it out, but the page that’s doing the work is in discourse-subscriptions. Maybe what I want to do is submit a PR to discourse-subscriptions that adds a

 this.appEvents.trigger("purchase-complete")

after a purchase is complete (and the purchase triggers adding to a group, which triggers creating a server and removing the user from the group). Or, if I could just trigger a reload after the purchase was complete, or when the clicked the “OK” in the “purchase complete” modal, that would be fine too, but I don’t know how to do that either (what I tried just reloaded the page forever…)

SO maybe here:

I want to add a

 this.appEvents.trigger("successful-transaction")

after loading is set false, and then I could have my initializer add an

 this.appEvents.on("successful-transaction")

to do the header manipulation?

I think that once I do that, I’ll need to do something different because I’m afraid that

      api.decorateWidget("header-buttons:before", (helper) => {
        return helper.h("ul.pfaffmanager-header-links", headerLinks);
      });

adds to header-buttons:before rather than replacing them so I’ll get more links every time it fires?

This just helped me again! What I needed to do was to load a script when the post stream was updated. All of the examples that I found were in components that had access to appEvent through this. I finally found this and now in an apiInitializer I can

  api.onAppEvent('post-stream:refresh', args => {
   // do some stuff!!!
  });

And now those ads load when each batch of posts loads. I’ve already :heart:ed your post, so I had to write a more extended thanks. :wink:

2 Likes

This is one of my favorite topic :heart: I come back here time to time :sweat_smile: I really appreciate for this! :hugs:

1 Like