Upcoming Header Changes - Preparing Themes and Plugins

We’ve recently been working on updating Discourse’s header from the legacy ‘widget’ rendering system to modern Glimmer components. This change is now available in Discourse core behind the experimental glimmer header groups site setting.

:timer_clock: Approximate Timeline

(very rough estimates - subject to change in either direction)

Q1 2024:

  • :white_check_mark: core implementation finished & enabled on Meta

  • :white_check_mark: upgrade advice published; console deprecation messages enabled

  • :white_check_mark: work begins to update all official and third-party plugins/themes

Q2 2024:

  • :white_check_mark: start enabling new header implementation by default

  • :arrows_clockwise: official and third-party themes/plugins are ready for the upgrade

  • :soon: Deprecation messages start triggering an admin warning banner for any remaining issues

  • Possible announcements topic for site admins (depending on expected impact)

Q3 2024:

  • Final removal of feature flag and legacy code

:eyes: What Does it Mean for Me?

If your plugin or theme uses any ‘widget’ APIs to customize the header, those will need to be updated for compatibility with the new header.

:person_tipping_hand: How Do I Try the New Header?

In the latest version of Discourse, the new header is automatically enabled when all your themes/plugins are compatible.

If your themes/plugins are not compatible, then the legacy header will still be used, and a warning will be printed to the console alongside the existing deprecation messages. In the near future, this console warning will be upgraded to a warning banner in the UI.

In the unlikely event that this automatic system does not work as expected, you can temporarily override this ‘automatic feature flag’ via the glimmer header mode site setting. If you do that, please let us know the reason in this topic.

:technologist: Do I Need to Update My Plugin/Theme?

To determine whether your customization needs to be updated, check if it uses decorateWidget, changeWidgetSetting, reopenWidget or attachWidgetAction on any of these widgets:

  • header
  • site-header
  • header-contents
  • header-buttons
  • user-status-bubble
  • sidebar-toggle
  • header-icons
  • header-topic-info
  • header-notifications
  • home-logo

or uses one of these plugin API methods:

  • addToHeaderIcons
  • addHeaderPanel

All of these things will now cause deprecation messages to be printed to the console. Deprecation IDs are:

  • discourse.add-header-panel
  • discourse.header-widget-overrides

:twisted_rightwards_arrows: What Are the Replacements?

Each theme/plugin is different, but here is some guidance for the most common use cases:

addToHeaderIcons

The addToHeaderIcons plugin API has been deprecated in favor of the new headerIcons API. It exists to allow adding, removing, or re-ordering of icons in the header. It requires a Component to be passed.

The component can be passed as so:

Before After
api.addToHeaderIcons(“widget-foo”) api.headerIcons.add(“foo”, FooIcon)
api.decorateWidget(“header-icons:before”, () => return helper.h(“div”, “widget-foo”)) api.headerIcons.add(“foo”, FooIcon, { before: “search” })
api.decorateWidget(“header-icons:after”, () => return helper.h(“div”, “widget-foo”)) api.headerIcons.add(“foo”, FooComponent, { after: “search” })

This example uses Ember’s Template Tag Format (gjs) to define a component inline and pass it to the headerButtons.add API:

// .../discourse/api-initializers/add-my-button.gjs

import DButton from "discourse/components/d-button";
import { apiInitializer } from "discourse/lib/api";

export default apiInitializer("1.0", (api) => {
  api.headerIcons.add("some-unique-name", <template>
    <li><DButton class="icon btn-flat" @href="/u" @icon="address-book" /></li>
  </template>);
});

Or for a dropdown, you could use <DMenu instead of <DButton:

import DButton from "discourse/components/d-button";
import { apiInitializer } from "discourse/lib/api";
import DMenu from "float-kit/components/d-menu";

export default apiInitializer("1.0", (api) => {
  api.headerIcons.add("some-unique-name", <template>
    <li>
      <DMenu class="icon btn-flat" @icon="address-book">
        <DButton @translatedLabel="User 1" @href="/u/user1" />
        <DButton @translatedLabel="User 2" @href="/u/user2" />
        <DButton @translatedLabel="User 3" @href="/u/user3" />
      </DMenu>
    </li>
  </template>);
});

Example upgrade commits:

decorateWidget("header-buttons:*")

The header-buttons widget has been deprecated and we have introduced a headerButtons plugin API. It exists to allow adding, removing, or re-ordering of buttons in the header. It requires a Component to be passed.

Before After
api.decorateWidget(“header-buttons:before”) api.headerButtons(“button-name”, ButtonComponent, { before: “auth” })
api.decorateWidget(“header-buttons:after”) api.headerButtons(“button-name”, ButtonComponent, { after: “auth” })

home-logo

We have introduced a home-logo plugin outlet in replacement of home-logo:before or home-logo:after widget decorations. You can utilize the automatic __before and __after naming in your connector file to specify where your custom content should be placed.

More details on before/after connector file naming can be found here.

Before After
api.decorateWidget(“home-logo:before”) Move content to /connectors/home-logo__before
api.decorateWidget(“header-buttons:after”) Move content to /connectors/home-logo__after)

:sos: What about other customizations?

If your customization cannot be achieved using CSS, PluginOutlets, or the new APIs we’ve introduced, please let us know by creating a new dev topic to discuss.

:sparkles: How do I update a theme/plugin to support both old and new header?

All the new APIs and plugin outlets listed in this document are supported on both the new and the old header. So you only need to make one update to your theme/plugin now, and users will be ready for the switch.

25 Likes

But how do I define a FooIcon?

In a plugin I tried creating /assets/javascripts/discourse/components/server-link.js (like for other components that I use in an hbs file)

import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";

export default Component.extend({
// does something have to go here?
});

And a assets/javascripts/discourse/templates/components/server-link.hbs with
this is a link (I figure I can make it be a link if I get this “Hello, world” working)

The above example has const IconWithDropdown = ... but where would that go? I tried putting it in an initializer (where the api.decorateWidget had been) but it doesn’t look like valid javascript to me or ember, best I can tell.

Before I had a headerlinks array and would

        headerLinks.push(
          h(
            `li.headerLink.no-servers`,
            h("a", anchorAttributes, I18n.t("pfaffmanager.no_servers_title"))
          )
        );

to add the links I wanted. I think if I can get

      api.headerIcons.add("foo", ServerLink, { before: "search" });

to work then I can just put that in the loop that built that array.

1 Like

OMG. So glimmer components go in assets/javascripts/discourse/component and embrer components go in assets/javascripts/discourse/components?!?!

I now have a server-link.gjs

import Component from "@ember/component";
export default class ServerLink extends Component {
  // Required argument for the URL
  url = null;
  // Optional argument for the link text
  text = 'asdf';
  click() {
    console.log('ServerLink clicked!',this);

  }
  // Template for the component
  <template>
    {{log "my template" this}}
    LINK!
    <a href={{this.url}}>{{this.text}}</a>
  </template>
}

and in my initializer this:

      api.headerIcons.add("foo", ServerLink, { param: "url, yo", before: "search" });

Now I have something in the header.

But how do I pass stuff to ServerLink? I need to call it several times with different URLs and different text-to-click. I can’t seem to see the stuff in that {} in the component.

And you wan’t really put javascript in before the <template>, as my console.log("") won’t parse!

I also tried doing:

      const x = new ServerLink({
        url: "mylink",
        text: "my-text",
        name: 'Bob',
        message: 'Generated from JavaScript',
      });

and then passing x instead of ServerLink, but still no joy.

2 Likes

Do you mean you want multiple buttons in the headers with different icons/text/URLs or the same button, but depending on the context, the text/URL can change?

Yes, you are in a class—you would declare variables, functions, or templates there!

Yes. The links change for different users. The old code linked through an array of servers and pushed them into this array:

            headerLinks.push(
              h(
                `li.headerLink${deviceClass}${newClass}`,
                h("a", anchorAttributes, linkText)
              )
            );

And then did this:

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

So then I had up to 3 links that got added to the header, each linking to a separate server URL.

Aha. Now I get it.

No don’t worry - the convention is still /components/ :sweat_smile:

(Technically, you can define and pass around gjs components however you like, so you can pick whatever directory name you like. But we’re sticking to /components/.)

Yeah that’s a fair question - I’ll work on writing up some ‘from scratch’ docs on how to add icons to the header so we have a better reference point.

In the meantime though, you might like to take a look at the discourse-icon-header-links update for inspiration. The cool thing about using GJS is that you can define components anywhere, and they get access to variables in the local scope.

So, if you rename your initializer to be .gjs, you can do stuff like

servers.forEach((server) => {
  api.headerIcons.add(`server-${server.id}`, <template>
    <li><DButton @translatedLabel={{server.name}} @icon="server" /></li>
  </template>);
});

Or you can define a component earlier in the same file, and use it like

class ServerButton extends Component {
  get icon(){
    // some logic to decide the icon
  }
  <template>
    <li><DButton @translatedLabel={{@server.name}} @icon={{this.icon}} /></li>
  </template>
}

...

servers.forEach((server) => {
  api.headerIcons.add(`server-${server.id}`, <template>
    <ServerButton @server={{server}} />
  </template>);
});

Or you could move the iteration inside the template (useful if the list of servers is a TrackedArray which may change at runtime!)

api.headerIcons.add("server-buttons", <template>  
  {{#each servers as |server|}}
    <ServerButton @server={{server}} />
  {{/each}}
</template>);
7 Likes

While we work on some more detailed example code

Oh. Hooray. I thought I tried it in components and it didn’t work.

Thanks! I think I can make one of these things work. The link to the header links is a big help. I’m pretty sure that when I wrote my code I had enough sense to look at that very component to figure it out then.

4 Likes

Seeing a glimmer of hope!

Hey @david and @Arkshine ! I did it!

Whaaaaaaaaaaaaaaaaaaat? Just rename it? That’s bananapants. But sure enough. I did it, and now

          servers.filter(Boolean).map((server) => {
            const linkHref = `/pfaffmanager/servers/${server.id}`;
            const linkTitle = `click to configure server ${server.id}`;
            let host = String(server.hostname);
            const linkText = host.replace(
              /www.|community.|forums?.|talk.|discourse./,
              ""
            );
            const serverLink = <template>
              <li class="headerLink">
                <a class="btn-flat" href={{linkHref}} title={{linkTitle}}>
                  {{host}}
                </a>
              </li>
            </template>;
            const beforeIcon = ["chat", "search", "hamburger", "user-menu"];
            api.headerIcons.add(host, serverLink, { before: beforeIcon });
          });

And it’s doing what it did before, which is what I wanted!

Yeah, Now that sounds very cool, and even more of what I want, but those things don’t change that much, so I’m going to call it a day on this. If they change a hostname, they’ll have to reload the page to get the link to change.

I presume you’ll delete my extra cruft here when you update the stuff above (or I might not have been so chatty in a documentation topic. . .)

5 Likes

I’ve updated the OP with some fully-fledged gjs examples, and included a link to the upstream Ember documentation. How’s that look to you @pfaffman? Anything else you think would be worth adding?

1 Like

It’s better since it has a working example. But I do understand correctly that there are ember components and glimmer components? And if that’s right, you should say that a glimmer component is required, I think?

And maybe link to the glimmer docs about how those work?

It seems like you can have inline components as in your example, and another kind where you assign it to something like a variable in the same file or put it in another file that you put in the components directory and then include? I think all of that may be too much for this topic, but I’d love a dedicated topic about that.

2 Likes

They’re totally interchangeable - you can use a Classic Ember Component, or a Glimmer Component. And for either of those, you can choose to author them using the old-style .js/.hbs format, or the new-style .gjs format.

I’ll see if I can work in some links to the Ember documentation :+1:

3 Likes

:mega: Today we merged this change, which will automatically enable the new header implementation for sites with compatible themes/plugins.

If your themes/plugins are not compatible, then the legacy header will still be used, and a warning will be printed to the console alongside the existing deprecation messages. In the near future, this console warning will be upgraded to a warning banner in the UI.

In the unlikely event that this automatic change causes issues, you can temporarily override this ‘automatic feature flag’ via the glimmer header mode site setting. If you do that, please let us know the reason in this topic.

2 Likes

I wasn’t looking to make any changes but the depreciation notices tell me otherwise,

So there’s a choice and perhaps an easy way to just keep the status quo?

or

What would I be missing to elect to try and maintain an old header, I don’t understand what the new one means, I see group settings, custom tailoring to different groups is intriguing, but what can be made custom?

This is what I found today,

image

I’m no guru or wiz with these changes, they take time and I don’t do them often enough to really desire to learn the techniques the users here seem to easily comprehension/know

I somewhat begrudge having to do them in the first place, but before I scream like an old man who ran out of pudding I’d like to know, what why and where is this going?

I do this for a living and and I still find this javascript stuff far from easy.

I’m an old man and I feel your pain.

It’s just progress, I’m afraid. This ember upgrade broke a bunch of things and it’s not over yet.

You “asked for it” when you did that customization. I bet in the past five years you’ve gotten a new phone or laptop.

If I were you (and you were like me, without the full time discourse gig), I’d post in marketplace. If I were me, I’d likely not respond for under $300, but there’s a reasonable chance that someone else will for $100 or $200. I’d guess that it won’t break again for another 5 years or more.

I think the hamburger theme selector you can get rid of and use the sidebar.

2 Likes

Nice honest reply, appreciate it but not much to work with, perhaps there’s more to come (I hope)

I didn’t even know it was java we were dealing in here :man_shrugging:

Don’t want anyone taking your pudding either :face_with_hand_over_mouth:

Sure, but what is the desired goal, this software dabbles in so many things I wonder who sees what end?

Is this simply needed from the ember upgrade?

I don’t know why ember was done either but if it works why fix it, I’m sure there’s a long deep explanation that all leads to the future of things but is there not a true vision to be shared?

I visit other forums that use very aged software, personally I see discourse as much better than any of them, but they don’t seem to suffer in comparison, they have the same growth issues, most are personality vs software IMO, too many old timers who lost their pudding, I’m wondering, is there a future IOT that will literally make all those forums obsolete where they won’t work at all and discourse is aware and prepping?

There’s more of that honesty you provide :grin: true enough, and I was more eager to learn, more ambitious, felt the worthwhile more, been beat up run over and left for dead since then

ok you’re on, I’ll take that bet and as you’ve already lost you help me out with this, we’ll be friends.

Then you’d of likely quit a long time ago, the beat up run over and left for dead was a bit of a euphemistic description, its just me left at the wheel, I guess anyone else is in some panel somewhere trying to fix the hyper-drive, IDK as I don’t often communicate with others, we have no funding, we (FULL30) were de-platformed from social media and discourse as well, I wonder how many other paying customers discourse willingly cut loose or how many others were deemed so offensive in their beliefs discourse publicly put money out against them?

Yet while I speak the truth I don’t take offense, live and let live, I know there’s a future coming, what I don’t know is why I’m still here and still trying, but I am, so, I’ll keep trying, like AA, just for today :hugs:

But it was all the rage when I employed it :expressionless:

The sidebar (here) can be closed with a hamburger menu, there’s not much difference in function, it opens and closes a navigation window, but mine can’t easily be saved?

Yes, I’d love and prefer to pay someone to clean up my customized code and make things work nice, and I’d happily pay, I enjoy employing others, sharing the wealth, when I grow up I want to be a philanthropist, but today I need a philanthropist :innocent: and again appreciate any help others can offer.

The other way to play it is to ask your community for help, stop doing whatever the customization was, start a new topic that shares your code am ask for help. I’ve gotten lots of help in such matters recently.

2 Likes

Unfortunately not. The ability to stick to the ‘old header’ is just a temporary thing during the transition period. Soon, the new header will be the only option.

Yup! We’re always happy to help with questions in dev. Plus, sharing the code and the solutions publicly creates a useful resource for others.

4 Likes

phew, my community is more in tune to other issues

Surely I could share here but then it goes the opposite, what coder finds interest in helping us?

The irony, coding may well be the firearms of the future, may well cause far more death and destruction as well, I digress

Very well, that means what exactly for me, create one user group, public and not logged in perhaps?

These group settings, I perceive they are based on trust levels vs actual different groups, like a hunting group and a fishing group?

First I need to understand the goal, and where my efforts as a lone ranger will effect the most bang for my buck, saving time and saving the custom feel for my forum.


I don’t wish to derail anyone’s thread, if its deemed this should be its own thread I’m fine with it

image

but how will there ever be a true cohesive relationship when people feel the need to remove something that offends them?

It takes patience to understand others, the link removed, it showed a missing logo after but not while posting, another header issue to discuss?

It was a post on my forum written by a man I believe well into his 80’s, I could ask for sure but he refuses to talk to me, do I berate him,ban or shun him?

No, why, because there’s a better way but it means putting up with others and how they think, I find good people in bad areas, good people who look bad, and just the opposite in both.

Exactly, I just found the errors, wish to address them but don’t understand the root cause other than the future is moving on, we need a new header, ok fine, whats the correct course to set my sites on, simple tuning, a full course correction?

Are we discussing only needing these three areas worked on?

image

I have mixed component usage, started with none then learned they can be beneficial, I never went full component and have a mixed bag.

Here’s my theme for what its worth without the components
discourse-full30-ii.zip (10.1 KB)
I can post those too, some, the modals, are already not working as of recent

image