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.

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.

Для справки: у нас появилась новая система для рендеринга компонентов Ember внутри виджетов. Подробности можно найти в сообщении коммита здесь:

Чтобы найти примеры использования, выполните поиск по RenderGlimmer в основной кодовой базе.

Это отлично, @David! … но почему у меня могут возникать трудности с импортом:

import { hbs } from "ember-cli-htmlbars";
Ошибка: Не удалось найти модуль `ember-cli-htmlbars`

при попытке использовать это в компоненте темы?

(я использую последнюю версию tests-passed)

Ах… вполне возможно, что инлайн-компиляция пока не работает в компонентах тем. В плагинах это должно работать (начиная с этой недели). Посмотрю, что можно сделать, чтобы добавить поддержку компонентов тем :eyes:

Это должно заставить всё работать:

Постараюсь объединить это на следующей неделе

Отличный прогресс. У меня есть для этого применение, так что жду с нетерпением :+1:t2:

Это уже объединено — дайте знать, как у вас получится, @merefield

Бинго! Компонент теперь отображается в моем виджете на основе TC, и данные корректно заполнены — отлично! :rocket:

Спасибо за невероятную оперативность, несмотря на британский банковский выходной!

Я знаю, что виджеты обрабатывают события клика, но что делать, если мой компонент имеет событие, которое запускает действие? Где можно обработать это действие внутри виджета?

Ой, Дэвид, только что увидел пример, изучаю его!

Да, это, вероятно, лучший справочный материал. Насколько я знаю, в ядре мы ещё не передавали действия «по-настоящему», так что дайте знать, если возникнут какие-либо проблемы.

Посмотрев на этот тест ещё раз, я подозреваю, что функции actionForComponentToTrigger() понадобится @bind над ней, чтобы убедиться, что this работает внутри неё корректно.

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, управление перестает обновлять выбранные теги в своём интерфейсе (хотя моя пользовательская переменная содержит правильные значения).

А если я не включаю собственное действие для onChange, интерфейс компонента снова работает, но я не могу наполнить свой набор данных. :thinking:

Я попытался добавить this.scheduleRerender() в функцию onChangeUpdateTagSet, но она показывает undefined — очевидно, она не в её области видимости?

Я добавил событие клика к виджету, и тогда он обновляется, показывая правильные данные.

Однако похоже, что связь работает только в одну сторону, поэтому я не могу использовать компонент для обновления внешних данных, а могу только добавлять новые данные в компонент.

Есть ли способ сделать связь двусторонней?

Я предполагаю, что вам потребуется использовать widget ‘state’, а не обращаться к this.chosen — экземпляры виджетов существуют недолго и являются «бессостоянными». При каждом повторном рендеринге вы получаете новый экземпляр, что означает, что this.chosen будет сброшен.

this.state не определен внутри функции change, так что это немного усложняет задачу?

(Я могу передать его, но это перезапишет данные, поступающие из компонента, которые сообщают, что было выбрано

  onChangeUpdateTagSet: this.onChangeUpdateTagSet(this.state),

)

Можете ли вы поделиться большим фрагментом кода виджета? Насколько я помню, для работы состояния вам нужно определить buildKey и метод defaultState().

Я посмотрю, смогу ли добавить пример такого подхода в тесты MountWidget :eyes: