Как вручную добавить содержимое в pluginOutlet после AJAX-запроса? И как перерендерить или обновить pluginOutlet?

Привет, :wave:

Я новичок в темизации Discourse и Ember.js, и в настоящее время работаю над темой, которая включает элементы навигации с основного сайта.

JSON с элементами навигации поступает через AJAX-запрос, и мне нужно отобразить его в двух местах.

Первое место — header-buttons:before. Мне удалось это сделать с помощью api.decorateWidget и вызова событий приложения site-header:force-refresh.

ajax( settings.site_navigation_links_endpoint ).then((result) => {

	if (!result.length) {
		return;
	}

	result.map((customHeaderLinksArray) => {

		const anchorAttributes = {
			title  : customHeaderLinksArray.title,
			href   : customHeaderLinksArray.url,
			target : "self"
		};

		headerLinks.push(
			h(
				`li.custom-header-nav-item`,
				h( "a.custom-header-nav-item-link", anchorAttributes, customHeaderLinksArray.title )
			)
		);

	});

	headerNav = h("ul.custom-header-nav", headerLinks);

	if( settings.site_navigation_position == "inline" ) {
		api.decorateWidget("header-buttons:before", (helper) => {
			return headerNav;
		});
	}

	appEvents.trigger("site-header:force-refresh");

	// Для отображения во втором месте (см. ниже)
	// ...
	// ...
}

Второе место — через pluginOutlet, below-site-header.
Я полагаю, что я не могу сделать то же самое, что и выше, используя api.decorateWidget, потому что это pluginOutlet, а не виджет(?).

Мои вопросы:

  1. Как вручную вставить контент из AJAX-запроса в pluginOutlet?
  2. Также я хотел бы узнать, как компилировать виртуальное DOM-дерево на лету после моего AJAX-вызова? Например, переменную выше, headerNav, я хотел бы получить в виде скомпилированной HTML-разметки. Не уверен, какую библиотеку или функцию использовать.
  3. Если возможно, как также перерендерить pluginOutlet? Аналогично appEvents.trigger("site-header:force-refresh");

Заранее спасибо! :man_bowing:

Верно. Есть два способа решить эту задачу…

  1. Вы можете сделать вашу кастомизацию виджетом, а затем подключить этот виджет в pluginOutlet следующим образом:

    {{mount-widget widget="widget-name"}}
    

    Вы можете увидеть пример создания виджета в Discourse, например, логотип на главной странице: discourse/app/assets/javascripts/discourse/app/widgets/home-logo.js at 2dbcea9eeeb816dda347027497b3a49634ef851f · discourse/discourse · GitHub

    В теме вы добавите свой файл widget-name.js в директорию javascripts/discourse/widgets.

    Подробнее о виджетах читайте здесь: A tour of how the Widget (Virtual DOM) code in Discourse works, но обратите внимание, что мы постепенно отказываемся от виджетов, поэтому любые знания, полученные в этом процессе, не будут полезны в долгосрочной перспективе.

  2. Оставьте ваш декоратор виджета без изменений и создайте отдельный компонент Ember, который выполнит то, что вам нужно, в pluginOutlet. Мы отказываемся от виджетов в пользу компонентов Ember (на которых построена большая часть Discourse).

    Этот процесс выглядит следующим образом:

    1. Создайте файлы JS и HBS (шаблон) компонента в директории javascripts/discourse/components вашей темы.
    • component-name.js

    • component-name.hbs

    1. Создайте компонент Ember… к сожалению, этот шаг по сути означает «изучить Ember» (Руководство по Ember), но, думаю, это даст вам общее представление для начала:
    • В component-name.js:
    import Component from "@glimmer/component";
    import { tracked } from "@glimmer/tracking";
    import { action } from "@ember/object";
    
    const endpoint = settings.site_navigation_links_endpoint;
    
    export default class ComponentName extends Component {
      @tracked navLinks = null;
    
      @action
      async fetchNavLinks() {
       try {
          const response = await fetch(endpoint);
          const data = await response.json(); // предполагается, что это JSON
          this.navLinks = data;
        } catch (error) {
          console.error("Ошибка:", error);
        }
      }
    }
    
    • В component-name.hbs:
    <div {{did-insert this.fetchNavLinks}}>
     {{#each this.navLinks as |link|}}
       <a href={{link.anchor}}>{{link.title}}</a>
     {{/each}}
    </div>
    

    Это вызовет действие fetchNavLinks всякий раз, когда компонент вставляется (в данном случае, когда вы посещаете сайт Discourse и приложение рендерится). Всякий раз, когда navLinks обновляется, содержимое компонента также обновляется, так как это отслеживаемое свойство.

    Если вы хотите обновлять ссылки чаще, чем при рендеринге компонента, вам нужно добавить в JS дополнительную логику… например, проверку того, соответствует ли текущий маршрут (страница) определенным условиям.

    1. Этот компонент будет добавлен в pluginOutlet путем добавления его в коннектор в javascripts/discourse/connectors/below-site-header/my-component-connector.hbs:
    <ComponentName />
    

Большое спасибо, Крис! Это очень помогло.

Я выбрал второй подход с использованием компонента, так как виджеты постепенно выводятся из употребления. Кроме небольшой опечатки в вызове функции — в файле .hbs должно быть {{did-insert this.fetchNavLinks}} — всё работает!

Отлично, что у нас есть did-insert, это большое облегчение! Я теперь завершил задачу. :man_bowing:

ах, рад, что вы заметили, я исправил свой пост выше