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

**URL:** https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347
**Category:** Development
**Created:** [February 29, 2016, 8:18pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347 "2016-02-29T20:18:39Z")
**Posts on this page:** 20
**Page:** 3

<div class="post-metadata">

### Author: ![eviltrout](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/eviltrout/32/5275_2.png) [@eviltrout](https://meta.discourse.org/u/eviltrout)
#### Post date: [May 13, 2016, 2:37pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/41 "2016-05-13T14:37:38Z")

</div>

Maybe start a new topic explaining exactly what you are looking to do, since I think changing the handlebars code is off topic for this virtual dom topic.

---

<div class="post-metadata">

### Author: ![stevenpslade](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/stevenpslade/32/53550_2.png) [@stevenpslade](https://meta.discourse.org/u/stevenpslade)
#### Post date: [June 9, 2016, 8:30pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/42 "2016-06-09T20:30:55Z")

</div>

Were you able to do this but with an array of multiple links? I’m trying and failing.

---

<div class="post-metadata">

### Author: ![tgxworld](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/tgxworld/32/106117_2.png) [@tgxworld](https://meta.discourse.org/u/tgxworld)
#### Post date: [June 13, 2016, 10:00pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/43 "2016-06-13T22:00:27Z")

</div>

What code did you add? 🙂

---

<div class="post-metadata">

### Author: ![stevenpslade](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/stevenpslade/32/53550_2.png) [@stevenpslade](https://meta.discourse.org/u/stevenpslade)
#### Post date: [June 13, 2016, 10:14pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/44 "2016-06-13T22:14:18Z")

</div>

> [@tgxworld](#):
>
> api.decorateWidget(“hamburger-menu:generalLinks”, \_ =\> {  
> return { route: ‘birthdays’, label: ‘birthdays.title’ };  
> });

I was trying:

```plaintext
api.decorateWidget("hamburger-menu:generalLinks", _ => {
   return [{ route: 'birthdays', label: 'birthdays.title' }, {route: 'another', label: 'another.title' }];
 });

```

---

<div class="post-metadata">

### Author: ![angus](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/angus/32/341715_2.png) [@angus](https://meta.discourse.org/u/angus)
#### Post date: [June 25, 2016, 9:34pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/45 "2016-06-25T21:34:25Z")

</div>

@eviltrout I may be missing something here, but it seems that it’s not possible for a widget to retain a state if it’s re-attached as part of a re-render.?

In other words, if you have a series of nested widgets (i.e. widgets attached to widgets), whenever one of the widgets triggers a re-render, the whole chain is re-rendered and any state in the child widgets is lost as they are newly re-attached. Is that correct?

For example when [widgetizing Babble](https://github.com/gdpelican/babble/pull/172), I tried to set a default view state for the [menu widget](https://github.com/gdpelican/babble/pull/172/files#diff-d07e8aa4f6a331b4fc1baf50a572d9a5R27) and then toggle it when the menu was toggled between a single chat stream and the list of chat streams, but found that the state would always remain at its `defaultState`. I surmised that the state was being reset when the menu widget was re-attached on a re-render.

I then tried to stop the ‘propagation’ of the re-render trigger, but wasn’t able to. I ended up using widget actions to send an updated state to the ultimate parent widget (i.e. the header) and retained the state in the header. [See here](https://github.com/gdpelican/babble/pull/172/files#diff-07023adacd31a9f2a582548d1c6fbcbbR103).

If my assumptions are correct, it would be useful to have a way to stop the propagation of the widget re-render trigger, or a way to ‘turn off’ the re-render triggers for certain widgets.

For example this would be useful for the [`search-term`](https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/widgets/search-menu-controls.js.es6#L5) widget, which is currently triggering the entire header to be re-rendered on every keyup in the search input. It would also be useful in various ways for Babble.

---

<div class="post-metadata">

### Author: ![eviltrout](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/eviltrout/32/5275_2.png) [@eviltrout](https://meta.discourse.org/u/eviltrout)
#### Post date: [June 27, 2016, 2:34pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/46 "2016-06-27T14:34:51Z")

</div>

You are definitely missing something. The whole point of state is that it should remain across re-renders of the same widget!

One thing to check is that you have a `buildKey` method for every widget that has `state`. The key needs to be the same in order for the virtual dom to re-assign the state the next time it renders.

In the first releases of the virtual DOM code I didn’t enforce this, but I encountered enough bugs that I added a warning. Were you not seeing that warning in your development console?

---

<div class="post-metadata">

### Author: ![angus](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/angus/32/341715_2.png) [@angus](https://meta.discourse.org/u/angus)
#### Post date: [June 27, 2016, 9:17pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/47 "2016-06-27T21:17:22Z")

</div>

Ah I see. I didn’t read [this post](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/18) carefully 😊

> [@eviltrout](#):
>
> Were you not seeing that warning in your development console?

Nope, I don’t think so. I’ll double check…

---

<div class="post-metadata">

### Author: ![SHIV\_GARG](https://avatars.discourse-cdn.com/v4/letter/s/8e7dd6/32.png) [@SHIV\_GARG](https://meta.discourse.org/u/SHIV_GARG)
#### Post date: [October 9, 2016, 4:03am UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/48 "2016-10-09T04:03:29Z")

</div>

Can i add an ember component inside an widget. I want to a add login with google directly in place of log\_in and sign\_up buttons in header. So i was trying to directly call login-buttons component to be rendered over there.

---

<div class="post-metadata">

### Author: ![eviltrout](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/eviltrout/32/5275_2.png) [@eviltrout](https://meta.discourse.org/u/eviltrout)
#### Post date: [October 17, 2016, 2:06pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/49 "2016-10-17T14:06:57Z")

</div>

You can do it, but there is a performance penalty from jumping back and forth between widgets and ember components.

See the [connect](https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/widgets/decorator-helper.js.es6#L104) method.

---

<div class="post-metadata">

### Author: ![mrded](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/mrded/32/62438_2.png) [@mrded](https://meta.discourse.org/u/mrded)
#### Post date: [November 14, 2016, 5:24pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/50 "2016-11-14T17:24:49Z")

</div>

I understand that you are trying to use `virtual-dom` over normal templates by performance reason, but it makes writing and maintaining code much more complicated. Didn’t you think to use something more readable, for example JSX?

---

<div class="post-metadata">

### Author: ![eviltrout](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/eviltrout/32/5275_2.png) [@eviltrout](https://meta.discourse.org/u/eviltrout)
#### Post date: [November 14, 2016, 5:39pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/51 "2016-11-14T17:39:03Z")

</div>

Yes of course I thought of it, but it would have been significantly more work to integrate a JSX → virtual-dom compiler.

In the future we plan on using Glimmer 2’s handlebars compiler. It exposes an AST that we can use to build widgets, and then people will be able to use handlebars like for regular ember and for our topic lists.

---

<div class="post-metadata">

### Author: ![mrded](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/mrded/32/62438_2.png) [@mrded](https://meta.discourse.org/u/mrded)
#### Post date: [November 15, 2016, 9:36am UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/52 "2016-11-15T09:36:32Z")

</div>

If you do so, will you still support all “deprecated” compilers? I didn’t really understand yet how do you update Discourse, and how should I rely on using core functionality.

---

<div class="post-metadata">

### Author: ![eviltrout](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/eviltrout/32/5275_2.png) [@eviltrout](https://meta.discourse.org/u/eviltrout)
#### Post date: [November 15, 2016, 3:39pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/53 "2016-11-15T15:39:31Z")

</div>

I’m not sure what you mean by deprecated compilers?

---

<div class="post-metadata">

### Author: ![mrded](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/mrded/32/62438_2.png) [@mrded](https://meta.discourse.org/u/mrded)
#### Post date: [November 15, 2016, 3:49pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/54 "2016-11-15T15:49:07Z")

</div>

If you change to `Glimmer 2`, will you still support `virtual-dom` and old handlebars?

---

<div class="post-metadata">

### Author: ![eviltrout](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/eviltrout/32/5275_2.png) [@eviltrout](https://meta.discourse.org/u/eviltrout)
#### Post date: [November 15, 2016, 3:52pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/55 "2016-11-15T15:52:26Z")

</div>

Yes, Glimmer 2 uses the handlebars syntax so the “old handlebars” will just work.

The virtual-dom code is currently not compiled, so Glimmer 2 will expose an AST that I will convert to virtual-dom, so old code will continue to work.

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [September 4, 2017, 2:44pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/56 "2017-09-04T14:44:52Z")

</div>

I’m using appEvents to trigger widget re-rendering after message-bus notifications. This is working ok, but I can’t find any way to “de-register” an appEvent listener once a widget is no longer required.

I setup the listener in the widget’s `defaultState()` function, in the same way as is done [in core widgets](https://github.com/discourse/discourse/blob/60b7453f3f1c6ead58409d9e056a2745dab37469/app/assets/javascripts/discourse/widgets/search-menu-controls.js.es6#L10-L16):

[https://github.com/davidtaylorhq/discourse-whos-online/blob/master/assets/javascripts/discourse/initializers/start-whos-online.js.es6#L85-L92](https://github.com/davidtaylorhq/discourse-whos-online/blob/master/assets/javascripts/discourse/initializers/start-whos-online.js.es6#L85-L92)

Ideally then I want to be able to call

```plaintext
appEvents.off("whosonline:changed")

```

when the widget is no longer needed. Otherwise the array of listeners keeps growing and eventually explodes

[https://meta.discourse.org/t/whos-online-plugin/52345/90?u=david\_taylor](https://meta.discourse.org/t/whos-online-plugin/52345/90)

I was hoping to use the widget `destroy` function, but it doesn’t look like it’s actually wired up to anything. The default implementation is a console message, and I don’t ever see that in the console.

[https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/widgets/widget.js.es6#L176-L178](https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/widgets/widget.js.es6#L176-L178)

@eviltrout is there some way to run logic when a widget is thrown away? Or is there another way I should be approaching this problem?

---

<div class="post-metadata">

### Author: ![eviltrout](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/eviltrout/32/5275_2.png) [@eviltrout](https://meta.discourse.org/u/eviltrout)
#### Post date: [September 4, 2017, 3:12pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/57 "2017-09-04T15:12:16Z")

</div>

Thanks for pointing out that the core widget is watching for an app event in `defaultState` - that is actually incorrect as it is never unloaded (cc @tgxworld) . In the case of search that might not be a problem since the widget is probably never unmounted but it’s not correct. (I’ve added it to my list to improve.)

The correct way to delegate events to widgets is by using `dispatch()`. For example:

[https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/components/site-header.js.es6#L63](https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/components/site-header.js.es6#L63)

In that case, every time the `notifications:changed` event is fired, it will call `notificationsChanged()` in the `user-notifications` widget. After the method is called, it will automatically queue a rerender.

The event will be automatically removed when the widget is unmounted.

---

<div class="post-metadata">

### Author: ![eviltrout](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/eviltrout/32/5275_2.png) [@eviltrout](https://meta.discourse.org/u/eviltrout)
#### Post date: [September 4, 2017, 3:50pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/58 "2017-09-04T15:50:15Z")

</div>

Update, it was definitely a problem, I noticed after using an autocomplete that the event was firing 20+ times! Fixed here:

[https://github.com/discourse/discourse/commit/be1cce503c3576426ac9759d88fdcab4c0b87be2](https://github.com/discourse/discourse/commit/be1cce503c3576426ac9759d88fdcab4c0b87be2)

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [September 4, 2017, 4:27pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/59 "2017-09-04T16:27:53Z")

</div>

Thanks - that’ll teach me to copy stuff from core 😆

So in terms of using `dispatch` from my plugin, I have modified the parent component that contains the `post-avatar` widget, to add the dispatch line.

```plaintext
   api.modifyClass('component:scrolling-post-stream', {
     didInsertElement() {
       this._super();
       this.dispatch('whosonline:changed', 'post-avatar');
     }
   });

```

I discovered that the second argument to `dispatch` is not the name of the widget, it is in fact the `buildKey`… This presents a problem for me. I need the event to be distributed to every instance of `post-avatar`, but obviously each user’s avatar is presented differently, so assigning the same `buildKey` to all of them makes very weird things happen.

> [@eviltrout](#):
>
> Whenever a widget has state it needs a key so that the virtual dom can apply the state to the same element the next time it is repainted.

Is implementing something like this the right thing to be do? Register/deregister for the `appEvent` in the parent component, set `dirtyKeys` to the `buildKeys` that require re-rendering, then queue a re-render of the whole post stream widget?

> <https://github.com/discourse/discourse/blob/1a02f5154fc1d479524e32928f421ebd19b46f6b/app/assets/javascripts/discourse/components/scrolling-post-stream.js.es6#L244-L256>

Is there any way for me to set a wildcard `dirtyKey`, something like `post-avatar-*`?

---

<div class="post-metadata">

### Author: ![eviltrout](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/eviltrout/32/5275_2.png) [@eviltrout](https://meta.discourse.org/u/eviltrout)
#### Post date: [September 4, 2017, 5:08pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/60 "2017-09-04T17:08:21Z")

</div>

Unfortunately you cannot refresh all widgets whose keys match a certain wildcard. You can only refresh all widgets `*` or do the trick that the scrolling post stream does there with the dirtyKeys if you know the `ids` of things you are changing.

Your solution of the `appEvent` in the parent component with the keys should work. I think a cleaner solution would involve re-engineering Discourse’s widget code a bit to add wildcards, or refreshing a widget by name instead of key.

[Previous page](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347.md?page=2)

[Next page](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347.md?page=4)
