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

Como criar uma barra de navegação? Usando um widget.

Assim:

image

O seguinte código nas instruções não funciona mais. A renderização funciona, mas o estado não está sendo incrementado nos cliques:

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

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

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

            html(attrs, state) {
                return `Clique em mim! ${state.clicks} cliques`;
            },

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


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

Estranho, para mim, quando tento usar esse componente, ele não é renderizado de forma alguma porque falta uma key. Atualizei a documentação acima para adicionar um método buildKey e está funcionando para mim. Tenta isso.

Obrigado @eviltrout, consegui fazer isso funcionar! Muito obrigado.

Como posso renderizar HTML vindo de um argumento dentro do widget? Atualmente, ele está escapando o HTML e o renderizando como está.

Além disso, o seguinte exemplo não está funcionando:

Parece que a variável user é nula. Preciso fazer algo para passar o user para o HBS de onde o widget é montado?

Encontrei a resposta para esta questão:

Resposta:

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>` });
  }
});

Mas ainda não sei como obter dados do usuário em um connector HBS onde os dados não são passados a partir da saída.

confere as chaves triplas, mano :smiley:

Calma aí :wink: :warning: :

Em vez de chaves triplas, temos um helper que você pode usar:

{{html-safe result}}

Ei @eviltrout,

Notei que na postagem original (OP) deste tópico você diz:

Mas mais abaixo neste tópico, você menciona:

Você estaria disposto a corrigir a postagem original?

Obrigado, fiz essa edição.

@eviltrout Isso não deveria ser action="deleteThing"?

Depende do seu widget. No exemplo de @eviltrout, o my-widget espera receber uma ação chamada “deleteThing”. Widgets diferentes usarão nomes diferentes para suas ações (e, de fato, é possível que eles chamem sua ação de “action”).