Render a component within a Widget. (Using select-kit components within plugin code)

If we successfully define a select-kit component within a plugin we are easily able to use it within a *.hbs template file using its pluginApiIdentifiers however, in order to attach that component within a widget using JS code I am not able to make it work. For instance:

I have:

import DropdownSelectBoxComponent from 'select-kit/components/dropdown-select-box';

export default DropdownSelectBoxComponent.extend({
  pluginApiIdentifiers: ['header-profile-dropdown'],
  ...
  
  autoHighlight() { ... },

  computeContent() {
    ...
    return items;
  },

  mutateValue(id) { ... }
});

Then I can go to any template I want to overwrite and write like:

(taking as an example the discovery template)

...
<div id="list-area">
        {{header-profile-dropdown}}
...

And the snippet above will work flawlessly, BUT how I will include that component into a widget like:

    api.reopenWidget('user-dropdown', {
        html(attrs, state) {
          if (this.site.mobileView) {
            return h('div.profile-dropdown-container', [
              this.attach('header-notifications', attrs),
              --> I NEED ATTACH THE COMPONENT HERE <--
            ]);
          }

          ...
        }
      });

NOTE: much of the code has been skipped to keep the post brief. Feel free to ask for the full code if needed.

PD: I have already tried to describe this issue at How to attach a component to a widget with just markup? but looks like it was a too complicated explanation so I am trying to reduce the information here to make it clear and try to find an answer.

「いいね!」 1

I think it was a matter of ask myself the right question: “How to render a component within a widget?” that’s why I updated the topic title. I have found few results already such as: How to render Component inside Widget? I will take a look and post any useful finding if it is needed.

Sadly enough looks like it is not possible, simply put we need to find a way to create a widget instead to render what we need to and render a widget within a widget, cause using the connector there are a lot of limitations. Check also: Rendering Component in decorativeWidget API

same issue here.
this is really terrible frontend code that uses widgets and ember components in the same page, it’s really hard to revamp and maintain.
can’t understand what’s the logic

An example of this is available in Discourse itself:

https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6#L362

I wasn’t there when widgets were created, but as far as I can tell, it was created for performance reasons at a time where frontend and especially mobile frontend had huge performance issues. And for dev simplicity it was added the possibility to inject components into widgets.

You got to understand that Discourse carries some history and we can’t start from scratch every year.

「いいね!」 6

This topic was automatically closed after 32 hours. New replies are no longer allowed.

参考までに、ウィジェット内でEmberコンポーネントをレンダリングするための新しいシステムがあります。詳細は、ここにあるコミットメッセージで確認できます。

RenderGlimmerをコアコードベースで検索すると、使用例が見つかります。

「いいね!」 5

これは素晴らしいですね、@David!…しかし、なぜインポートに苦労しているのでしょうか:

import { hbs } from "ember-cli-htmlbars";
Error: Could not find module `ember-cli-htmlbars`

これをテーマコンポーネントで使用しようとしている場合?

(最新の tests-passed を使用しています)

「いいね!」 2

ああ…テーマコンポーネントではインラインコンパイルが(まだ)機能しない可能性が非常に高いです。プラグインでは機能するはずです(今週から)。テーマコンポーネントのサポートを追加するためにできることを確認します :eyes:

「いいね!」 2

これで動作するはずです。

来週にはマージできるようにします。

「いいね!」 3

素晴らしい進歩です。これには用途があるので、楽しみにしています :+1:t2:

「いいね!」 2

マージされました。@merefield、お試しになった結果をお知らせください。

「いいね!」 3

ビンゴ!コンポーネントがTCベースのウィジェットに表示されるようになり、データも正しく入力されています。素晴らしいです!:rocket:

英国の祝日を挟んでの素晴らしい対応に感謝します!

ウィジェットはクリックイベントを処理することは知っていますが、コンポーネントにアクションを発火させるイベントがある場合、ウィジェット内でそのアクションをどのように処理できますか?

「いいね!」 2

おっと、デイビッド、例を見たよ、勉強しているんだ!

「いいね!」 1

はい、おそらくそれが最善の参照でしょう。コアではまだ「本物の」アクションを通過していないと思うので、問題が発生した場合は知らせてください。

そのテストをもう一度見ると、this がその中で正しく機能するように、actionForComponentToTrigger() 関数の上に @bind が必要になるのではないかと疑っています。

「いいね!」 2

OK、これはある程度機能します。

    contents.push(
      new RenderGlimmer(
        this,
        "div.tag-chooser-component",
        hbs`<TagChooser @id="list-with-tags"  @tags={{@data.chosen}} @onChange={{action @data.onChangeUpdateTagSet}}/>
        <button onClick={{@data.actionForClick}} label="blah"/>`,
        {
          chosen: this.chosen,
          onChangeUpdateTagSet: this.onChangeUpdateTagSet,
          actionForClick: this.actionForClick
        }
      ),
    );
    return contents;
  },

  @bind
  onChangeUpdateTagSet(chosen) {
    this.chosen.push(chosen.lastObject);
  },

  @bind
  actionForClick () {
    console.log(this.chosen)
  }
});

onChangeUpdateTagSet は変更時に呼び出され、actionForClick はボタンが押されたときに呼び出されます。

しかし、Tag Chooser を常に表示しておくのに苦労しています。

onChange のカスタムアクションを導入するとすぐに、コントロールは選択されたタグを UI で最新の状態に保つことができなくなります(カスタム変数には正しい値が含まれているにもかかわらず)。

そして、onChange のカスタムアクションを含めないと、コンポーネントの UI は再び機能しますが、セットを常に表示しておくことができません。:thinking:

onChangeUpdateTagSet 関数に this.scheduleRerender() を追加しようとしましたが、undefined として表示されています。明らかにスコープ外ですか?

ウィジェットにクリックイベントを追加したところ、更新されて正しいデータが表示されました。

しかし、バインディングは一方向にしか機能していないようです。コンポーネントに新しいデータをプッシュすることしかできず、コンポーネントを使用して外部データを更新することはできません。

バインディングを双方向にする方法はありますか?

this.chosen ではなく widget ‘state’ を使用する必要があると思われます。ウィジェットインスタンスは短命で「ステートレス」です。再レンダリングごとに新しいインスタンスが取得されるため、this.chosen はリセットされます。

「いいね!」 1

this.state が change 関数内で未定義なので、少し厄介ですね。

(渡すことはできますが、選択されたものを伝えるコンポーネントからのデータが上書きされてしまいます。

  onChangeUpdateTagSet: this.onChangeUpdateTagSet(this.state),

「いいね!」 1

ウィジェットコードのさらに詳しい情報を共有していただけますか? IIRC、状態を機能させるには buildKeydefaultState() メソッドを定義する必要があります。

MountWidget のテストにこのような例を追加できるか見てみます :eyes:

「いいね!」 1