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

Right, after a lot of headaches I’ve got it working - thanks for the help! :tada:

The two other problems I hit which could possibly do with some re-engineering in the widget code:

  • When setting dirtyKeys, it only re-renders the first widget with that buildKey. I’ve got around that by adding the post-id to the post-avatar’s buildKey so that every buildKey is unique
  • Setting the dirtyKey doesn’t work if the widget you want to re-render is inside another widget. In my case the post-avatar is inside a post. I think this is because of the shadowTree stuff.
    It would be really nice if the widget code could work out that the widget is deep in the tree and re-render its parents. I’ve got around this by setting the post widget as dirty as well.

That’s because keys are supposed to be unique! So adding the post id to the avatar is correct in this case. I might want to raise an error in development mode if two elements use the same keys. If you want to commit the post avatar key to master that would be fine.

That’s exactly what it is. By default I think a widget is supposed to re-render its parents, but the shadowTree is a performance optimization that avoids too much re-rendering and requires you to be more explicit.

Just in case anyone is trying to track down how widgets can use handlebars templates, see the commit message here (can’t find it documented anywhere on meta):

https://github.com/discourse/discourse/commit/dffb1fc4ee8a4d58f48145decb1590a304e8cf7d

Cómo crear una barra de navegación: usando un widget.

como esto

image

El siguiente código en la instrucción ya no funciona. El renderizado funciona, pero el estado no se incrementa al hacer clic:

<script type="text/javascript">
        const { createWidget } = require('discourse/widgets/widget');

        createWidget('increment-button', {
            tagName: 'button',

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

            html(attrs, state) {
                return `¡Haz clic en mí! ${state.clicks} clics`;
            },

            click() {
                this.state.clicks++;
            }
        });
</script>


<script type='text/x-handlebars' data-template-name='/connectors/above-footer/increment-button'>
    {{mount-widget widget="increment-button" }}
</script>

Eso es extraño, para mí cuando intento usar ese componente no se renderiza en absoluto porque le falta una key. He actualizado la documentación anterior para agregar un método buildKey y ahora me funciona. Pruébalo.

Gracias @eviltrout, ¡logré que funcionara! Muy agradecido.

¿Cómo puedo renderizar HTML que proviene de un argumento dentro del widget? Actualmente, está escapando el HTML y mostrándolo tal cual.

Además, el siguiente ejemplo no funciona:

Parece que la variable user es nula. ¿Debo hacer algo para pasar user al HBS desde donde se monta el widget?

Encontré la respuesta a esto:

Respuesta:

import { createWidget } from 'discourse/widgets/widget';
import RawHtml from "discourse/widgets/raw-html";

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

  html() {
    return new RawHtml({ html: `<div>${this.siteSettings.user_rank_alert_message}</div>` });
  }
});

Pero aún no sé cómo obtener datos de usuario en un archivo hbs de conector donde los datos no se pasan desde el outlet.

échale un vistazo a las llaves triples, bro :smiley:

¡No tan rápido :wink: :warning: :

En lugar de usar llaves triples, puedes utilizar un helper:

{{html-safe result}}

Hola @eviltrout,

Noté que en la OP de este tema dices:

Pero más abajo en este tema, mencionas:

¿Estarías dispuesto a corregir la OP?

Gracias, he realizado esa edición.

@eviltrout ¿No debería ser action="deleteThing"?

Depende de tu widget. En el ejemplo de @eviltrout, my-widget espera recibir una acción llamada “deleteThing”. Diferentes widgets usarán nombres diferentes para sus acciones (y de hecho, es posible que llamen a su acción “action”).