# 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:** 1
**Showing post:** 1

<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: [February 29, 2016, 8:18pm UTC](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347/1 "2016-02-29T20:18:39Z")

</div>

This is out of date. See [Widgets, the Widget API and their roadmap?](https://meta.discourse.org/t/widgets-the-widget-api-and-their-roadmap/292624)

The latest builds of Discourse are much faster at rendering topics thanks to our [re-written post stream](https://eviltrout.com/2016/02/25/fixing-android-performance.html). I’ve written up [our new plugin API](https://meta.discourse.org/t/a-new-versioned-api-for-client-side-plugins/40051) but so far haven’t explained how the code all fits together. The purpose of this topic is to allow Discourse developers to understand how the new code works.

### What’s a Virtual DOM?

A Virtual DOM is a data structure that enables browsers to re-render dynamic content very quickly. The technique was pioneered by [React](https://facebook.github.io/react/) but has since been integrated into many other frameworks.

The basic idea is that updating the HTML content of a browser is slow, so it’s much faster to update a data structure in memory, then figure out what changed (knowng as `diff`ing), so you can instruct the browser to only update exactly what changed.

Discourse uses the [virtual-dom](https://github.com/Matt-Esch/virtual-dom) library to perform all the hard work. However, that library is a bit too low level, so it’s been abstracted in Discourse by a series of classes called `Widget`s.

### Anatomy of a Widget

A `Widget` is a class with a function called `html()` that produces the virtual dom necessary to render itself. Here’s an example of a simple `Widget`:

```javascript
import { createWidget } from 'discourse/widgets/widget';

createWidget('my-widget', {
  tagName: 'div.hello',

  html() {
    return "hello world";
  }
});

```

The above code registers a widget called `my-widget`, which will be rendered in the browser as `<div class='hello'>hello world</div>`. It can be rendered in any of our Handlebars templates using the `mount-widget` helper:

```handlebars
{{mount-widget widget="my-widget"}}

```

If you wanted to return more HTML, you can include the `h` helper and use it to emit more formatting:

```javascript
import { createWidget } from 'discourse/widgets/widget';
import { h } from 'virtual-dom';

createWidget('my-widget', {
  tagName: 'div.hello',

  html() {
    return h('span.greeting', h('b', "hello world"));
  }
});

```

The above would render:

```html
<div class="hello"><span class="greeting"><b>hello world</b></span></div>

```

### Rendering Attributes

So far our widget is 100% static, and in reality Discourse’s code is quite dynamic. When a widget is rendered, it is passed a series of attributes. In our post stream, for example, these attributes are a plain Javascript object with the attributes of a post.

> Aside: you might wonder why we don’t render from our `Post` Ember object itself. This is done for performance reasons. In Ember, calling `get` on a property has overhead that, while small, adds up when you are rendering as fast as possible. In our old code we would be calling `get` repeatedly, frequently on the same property rather than caching it locally. In the new code, we [transform](https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/app/lib/transform-post.js) a `Post` ember model into a plain javascript object that we don’t have to call `get` on to retrieve attributes.

> (I should add that this is one of the uglier aspects of the new post stream code, and I’m not 100% sure we need to do it. It may change.)

You can pass attributes to be rendered by a widget using the `args` property. Let’s say you had a `user` object with `{name: 'Robin'}` in your template. You can pass it to be rendered in the widget like so:

```handlebars
{{mount-widget widget="display-name" args=user}}

```

Then your widget would be passed the `user` object as the first argument to `html()`:

```javascript
import { createWidget } from 'discourse/widgets/widget';
import { h } from 'virtual-dom';

createWidget('display-name', {
  tagName: 'div.name',

  html(attrs) { 
    return h('b', attrs.name);  
  }
});

```

The final HTML would be:

```html
<div class="name"><b>Robin</b></div>

```

Widgets can also render other widgets. Use `attach` to render a widget in an `html` method:

```javascript
html(attrs) {
  return this.attach('another-widget', attrs);
}

```

It’s [widgets all the way down!](https://en.wikipedia.org/wiki/Turtles_all_the_way_down)

### State and Actions

Widgets can also contain `state`. You can think of `state` as data that you want to keep around the next time your widget is rendered. (For example, if you wanted to remember if a post is collapsed or expanded, it would make sense to store that state on the widget itself, and not the the post itself.)

To add state to your widget, create a `defaultState` method that returns an object that will represent its default state the first time the widget is rendered.

Each widget that has state also needs a unique key, so we have a function `buildKey` which returns a string. If you use the widget more than once you need to make this key unique, so you could for example base it on the id of an attribute that is passed in.

Your `state` is basically useless without a form of user interaction, so let’s also implement a `click()` function. `click` is called whenever the user clicks on your widget. Let’s look at it together:

```javascript
import { createWidget } from 'discourse/widgets/widget';

createWidget('increment-button', {
  tagName: 'button',
  buildKey: () => 'increment-button',

  defaultState() {
    return { clicks: 0 };
  },

  html(attrs, state) { 
    return `Click me! ${state.clicks} clicks`;
  },

  click() {
    this.state.clicks++;
  }
});

```

The above code will render a button that, when clicked, will increment a counter.

Widgets can also send actions up. This means that you can call any pre-existing Discourse action. Simply pass it into the widget the same way [you would for an Ember component](https://guides.emberjs.com/v1.12.0/components/sending-actions-from-components-to-your-application/):

```handlebars
{{mount-widget widget="my-widget" deleteThing="deleteThing"}}

```

Then in your `click` handler you can call `sendWidgetAction` to call it:

```javascript
click() {
  this.sendWidgetAction('deleteThing');
}

```

### Bindings and Rerendering Content

If you’ve been working with Ember for a while, you might be wondering how the widgets know when they need to be rerendered since they aren’t using Ember’s bindings or observers system. In fact, this is one of the big trade offs widgets make: in Ember’s view rendering this is handled transparently for you, but with Widgets we assume the widget only needs to be re-rendered when you interact with them.

Widgets are rerendered when:

- A user clicks on them

- After any actions are triggered

- If an action returns a promise, after that promise has resolved

The above cases count for the **vast** majority of the times when Discourse needs to rerender a widget. However, if you have an edge case, a widget can rerender itself by calling `this.scheduleRerender()`.

Render calls are coalesced on the [Ember Run Loop](https://guides.emberjs.com/v1.12.0/understanding-ember/run-loop/), so don’t be afraid to call `this.scheduleRerender()` multiple times in the same event loop – the widget will only render one time for all the changes you’ve queued.

### Where to go from here

If all of the above makes sense, you are probably ready to jump into the codebase! In Discourse, all `Widget`s used to render the post stream are in the [widgets](https://github.com/discourse/discourse/tree/master/app/assets/javascripts/discourse/app/widgets) folder.

There are also [quite a few tests](https://github.com/discourse/discourse/tree/master/app/assets/javascripts/discourse/tests/integration/widgets) that are worth looking through to get an idea of how different parts of the application were implemented as well as how they can be tested.

Of course, I’m also around here on meta for questions, so feel free to ask if something doesn’t make sense. I probably missed something!

---

_[View the full topic](https://meta.discourse.org/t/a-tour-of-how-the-widget-virtual-dom-code-in-discourse-works/40347)._
