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

ナビゲーションバーの作成方法:ウィジェットを使用

このように

image

以下の指示にあるコードがもう機能しなくなっています。レンダリングは動作しますが、クリック時に状態が増加しません。

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

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

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

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

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


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

奇妙ですね。私の方では、そのコンポーネントを試すと key が不足しているため、全くレンダリングされません。上記のドキュメントを更新して buildKey メソッドを追加したところ、私の環境では動作するようになりました。ぜひ試してみてください。

@eviltrout さん、ありがとうございます!無事に動作しました。大変助かりました。

引数から来る HTML をウィジェットにレンダリングするにはどうすればよいですか?現在、HTML がエスケープされ、そのまま表示されてしまいます。

また、以下の例は動作しません:

user 変数が null のようです。ウィジェットがマウントされている hbs から user を渡すために何か特別な処理が必要でしょうか?

これの答えが見つかりました:

答え:

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

ただし、Outlet からデータが渡されていないコネクタの hbs でユーザーデータを取得する方法については、まだわかりません。

三重の中括弧を見てくれ、ブロ :smiley:

急がずにお待ちください :wink: :warning: :

代わりに三重の中括弧の代わりに使用できるヘルパーがあります:

{{html-safe result}}

@eviltrout さん、こんにちは。

このトピックの OP で以下のように書かれていることに気づきました。

しかし、このトピックのさらに下の方では、以下のようにおっしゃっています。

OP を修正していただけませんか?

ありがとうございます、その編集を行いました。

@eviltrout これは action="deleteThing" ではないでしょうか?

それはあなたのウィジェットによります。@eviltrout の例では、my-widget は「deleteThing」という名前のアクションが渡されることを期待しています。ウィジェットによってアクションに使用される名前は異なります(実際、アクションを「action」と呼ぶことも可能です)。