A tour of how the Widget (Virtual DOM) code in Discourse works

I can see the plugin-outlets in the header are gone. Any tips on how to add stuff to the header now?

The header uses the same apis as the new post stream, so you can just use decorateWidget to add code before or after any widget in the header for your needs.

It seems that this.queueRerender() is now this.scheduleRerender() ?


Sorry, my mistake. Within a widget you call queueRerender() which triggers a scheduleRerender() on the ember component that embedded it.

So yes, you should be using queueRerender

edit: No, this post was wrong and I was right before. scheduleRerender is what you call within a widget to rerender. It calls queueRerender on the component.


I want to createWidget that has the attrs of an exisitng widget (specfically the topic-map widget’s attrs). When creating a widget, how do you pass it the attrs of another widget to use?

You can pass anything as attrs to a widget when you attach it using this.attach('widget-name', attrs).

It looks like topic-map is attached with the attrs from the post it’s inside.


Right, that’s what I thought.

attrs.topicViews is inside the widget topic-map, which is inside the post widget (attached through a few other widgets of course). However, in the post widget, attrs.topicViews comes up undefined.

I just realized that a topic with one post will not show the topic views in the attrs. I am able to see it now on a topic with multiple posts.

Yes the topic map is only shown if certain criteria is met:


Otherwise we don’t bother to include that information because it’s not displayed.


Is is possible to render a component inside a created widget?

:confused: hm, maybe it’s my own confusion, but I’ve found the opposite is true.

Within the message-list widget I’ve created for Quick Messages, if I replace this.scheduleRerender() with this.queueRerender() it doesn’t work. It tells me this.queueRerender() is not a function:

Likewise, if I use this.scheduleRerender() in the site-header component I get an error.

Using this.scheduleRerender() in widgets and this.queueRerender() in components works fine however.


Yes but it’s slow so you should avoid doing it whenever possible. Create a decorator, then use connect on it.

Oops, not sure if I wrote that before :coffee: but I did get it backwards. I’ll edit that post. It’s scheduleRerender within a widget. It calls queueRerender on the component that embedded it.


@eviltrout I’m adding a link in the hamburger menu but I’m not sure if this is the right way. Also, do you think we can expose this in the pluginAPI?

api.decorateWidget("hamburger-menu:generalLinks", _ => {
   return { route: 'birthdays', label: 'birthdays.title' };

That is the correct way. What do you mean in the plugin api? It looks like you are already calling api.decorateWidget. Unless you mean a shortcut for adding links like api.addHamburgerGeneral or something perhaps better named?

1 Like

Yea I was thinking of addGeneralLink like addPosterIcon since adding a link to the hamburger menu should be pretty common for plugin developers.


I’m getting this error:

Uncaught Error: Assertion Failed: You specified the templateName discourse/components/topic-notifications-button for <Ember.View:ember1314>, but it did not exist.

I tested with admin/templates/modal/admin_delete_flag and it did indeed work.

Sure, go ahead and add it!


Here’s another one for you, Robin. Mounting a widget in raw.hbs.

I’m attempting to use mount-widget in through a plugin-outlet (until the topic-list is ported over to the widget structure) here:


It fails and I’m unable to find an error anywhere. It’s as if it never tries to mount it.

You can’t embed a widget in a raw template. But don’t worry, we aren’t going to convert the topic list over. The gains over raw rendering for that part are negligible.


That’s a relief. I’ve been working on a few things that this would impact and I was worried I’d end up repeating everything.

So if I can’t mount a widget (and I assume I can’t assign actions or helpers as well), is best practice to go this route:

import TopicListItemView from `discourse/views/topic-list-item`;

TopicListItemView.reopen(click: function(e){
   // my stuff