Re-evaluate widgets in header upon route change without error

I’m trying to override this widget template:

      api.reopenWidget("header-contents", {
        template: hbs`
          {{#if this.site.desktopView}}
            {{#if attrs.sidebarEnabled}}
              {{sidebar-toggle attrs=attrs}}
            {{/if}}
          {{/if}}

          {{home-logo attrs=attrs}}

          {{#if attrs.topic}}
            {{header-topic-info attrs=attrs}}
          {{else if this.siteSettings.bootstrap_mode_enabled}}
            {{#if transformed.showBootstrapMode}}
              {{header-bootstrap-mode attrs=attrs}}
            {{/if}}
          {{/if}}

          <div class="panel clearfix" role="navigation">{{yield}}</div>
        `,
      });

Despite the template being currently identical to the original in discourse/header-contents.js at 4aa81e709ea49e30383a3a3acd33dfedaebfc240 · discourse/discourse · GitHub

It is generating an error:

This is unexpected, especially since I’m not actually “changing anything” (yet)?

I’m able to reproduce this problem even if this is the only modification.

Ultimately the reason I’m trying to change this is so that I can force a re-evaluation of this template even if I’m not moving in or out of a topic route.

Ultimately I want to add logic so that the widget is forced to refresh when moving in and out of category routes because my Category route presentation needs to be different.

So one solution to this might be another way to force the header contents to be refreshed.

In any case, this doesn’t seem to be behaving as I should expect?

We don’t really want to invest much time here - widgets aren’t going to be around for much longer. What customization are you trying to make? Can we help by adding a plugin outlet somewhere (we have the ability to add normal plugin outlets inside widget code now).

Can you share more of your file (e.g. where is hbs being imported from?)

1 Like

Also what version of Discourse is this? site-header.js:337 does not currently reference a ‘header’ variable

1 Like

Understood.

import { hbs } from "ember-cli-htmlbars";

Functionally I’m trying to swap the logo depending on the route. In reality I’m doing this by attaching an image to the home logo on some routes and overlaying the primary logo. I want that logic to be re-evaluated when moving between routes, which currently seems to work moving in and out of the Topic routes I’m guessing because the header-contents widget to which everything is attached has logic evaluating the attrs.topic which obviously changes when that occurs.

My category-logo-widget is attached like so:

      api.decorateWidget("home-logo:after", (helper) => {
        const currentPath = helper.register
          .lookup("service:router")
          .get("_router.currentPath");

        if (
          helper.widget.currentUser &&
          !helper.widget.site.mobileView &&
          currentPath.indexOf("discovery") === -1
        ) {
          return helper.attach("category-logo-widget");
        } 
      });

Which works fine, but is not being re-evaluated between certain route transitions, so isn’t fulfilling the requirement fully dynamically without a page reload.

oooooops, this could be the issue, I had a feeling it might be but my tests were obviously too superficial to rule this out.

I see this commit, I’ll need to update my dev instance … PERF: Memoize element references for `dockCheck` (#21079) · discourse/discourse@db16700 · GitHub

I’ll revert either way, thanks for your time!

This is likely the problem. Widget ‘templates’ are a custom Discourse thing, and are compiled in a totally different way to Ember templates. You’ll need to import hbs like this:

Looks like we have a site-header:force-refresh appEvent which you could trigger in response to some other appEvent or Ember router event:

(example trigger here)

3 Likes

Awesome, thanks David, I’ll fix the dated install and try your recommendations here too. Multiple pile up, thanks for helping clearing up!

3 Likes

So probably a bit gratuitous, but I ended up using your suggestion, with:

      api.onPageChange(url =>  {
        const applicationController = container.lookup(
          "controller:application"
        );
        applicationController.appEvents.trigger(
          "site-header:force-refresh"
        );
      });

that worked great, although it doesn’t quite sync perfectly with the route transition, but that’s ok.

thanks again!

3 Likes

btw, just as a side note here, whilst the widget API is all very bespoke and somewhat fiddly (especially the hyperscript stuff), I must admit it is extremely powerful to attach new behaviour to existing widgets without having to override lots of code.

for example I’ve used this pattern a lot:

api.reopenWidget('discourse-awesome-widget', {

  html(attrs, state) {
    let contents = this._super(...arguments);

    contents.unshift(h("div.my-cool-new-thing", "cool new thing"))

    return contents;
  }
});

this._super could be a lot of code!!!

And it can get a lot cleverer than that such that I’ve come to really appreciate it.

I hope the “override-abiliy” of whatever replaces it will be as equally flexible and powerful.

1 Like

The main solution here is going to be plugin outlets. As always, if you feel you need one that doesn’t exist, feel free to make a PR to core :rocket:

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.