Как запускать при каждой загрузке футера (или страницы?)

Я пытаюсь загрузить JavaScript-форму в футере.

В хедере у меня вот что:

<script type="text/discourse-plugin" version="0.8">
  let loadScript = require("discourse/lib/load-script").default;

  api.onPageChange(() => {
    loadScript("//js.hsforms.net/forms/current.js").then(() => {
      console.log("doing the thing");
      hbspt.forms.create({
        portalId: "229276",
        formId: "a86ca9cc",
        submitButtonClass: "button orange-button hubspot-button",
        target: ".subscription-form"
      });
    });
  });
</script>

А в футере вот это:

<div class="subscription-form clearfix">
  <h5>Sign Up for Our Blog</h5>
</div>

При первой загрузке страницы всё работает отлично. На последующих страницах (большинство из них, по крайней мере) форма не загружается, и появляется ошибка:

Couldn't find target container .subscription-form for HubSpot Form a86ca9cc. Not rendering form onto the page

Кажется, футер загружается после того, как срабатывает скрипт?

Мне каким-то образом нужно отложить выполнение скрипта до момента загрузки футера.

В plugin-api.js сказано:

//  Listen for a triggered `AppEvent` from Discourse.

api.onAppEvent("inserted-custom-html", () => {
  console.log("a custom footer was rendered");
});

Возможно, мне просто нужно узнать, какое событие AppEvent следует отслеживать?

Немного информации об этом здесь.

Javascripts targeting the footer doesn't work after page transitions - #4 by Johani

Когда вы используете это

api.onAppEvent("inserted-custom-html", () => {
  // какой-то код
});

вы должны указать, какой именно пользовательский HTML вы хотите таргетировать — в зависимости от этого срабатывают разные события.

В данном случае вы хотите таргетировать футер. Попробуйте это и посмотрите, сработает ли для вас?

api.onAppEvent("inserted-custom-html:footer", () => {
  // какой-то код
});

Ха! Сработало! (Конечно, это произошло после того, как я убедил их, что то, что они пытались сделать в футере, изначально не стоило делать). Я в полном восторге, что наконец разобрался в этом, и очень ценю вашу помощь; я постепенно начинаю понимать, как всё это работает. (И, думаю, теперь они просто удалят футер целиком.)

Ага! Не знаю, почему я не подумал поискать по слову «footer»! :man_shrugging:

Так что inserted-custom-html относится к чему угодно в темах? И тогда я могу добавить :footer или :header, или, возможно, :head_tag? В этом и есть магия? Это только у меня так, или было бы полезнее, если бы

https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/app/lib/plugin-api.js#L516

вместо этого содержало

   api.onAppEvent('inserted-custom-html:footer', () => {

JavaScript и Ember — самые сложные для меня вещи, которые я пытаюсь понять, наверное, со времён изучения Lisp в середине 1980-х, поэтому я всё ещё не до конца понимаю, что считается «очевидным».

Не совсем. Это событие срабатывает только при рендеринге ember-компонента custom-html.

Итак, где мы используем этот ember-компонент и что он делает?

Мы в основном используем его в главном шаблоне приложения для рендеринга двух полей темы.

Вкладка темы after_header

Вкладка темы footer

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

Заметьте, что у футера также есть атрибут triggerAppEvent="true".

Именно поэтому вы можете использовать:

api.onAppEvent("inserted-custom-html:footer", () => {
  // какой-то код
});

Вы не можете сделать то же самое с вкладкой after_header. Discourse не генерирует событие для неё. Поэтому это не сработает:

api.onAppEvent("inserted-custom-html:top", () => {
  // какой-то код
});

Теперь, почему мы используем этот ember-компонент для рендеринга некоторых полей темы?

Простой ответ: это даёт нам гораздо больше контроля над тем, когда он рендерится.

Два примера этого… Список тем и страница администратора.

Мы не хотим рендерить футер в списке тем, пока ещё есть темы для загрузки через бесконечную прокрутку.

Также мы не хотим рендерить любые баннеры или разметку брендинга сайта на странице администратора, добавленные во вкладке after_header.

Использование ember-компонента custom-html даёт нам более тонкий контроль над такими вещами.

Теперь вернёмся к вашему вопросу. Если вам нужно выполнить какие-то действия при рендеринге футера, вам нужен этот обёртка:

api.onAppEvent("inserted-custom-html:footer", () => {
  // какой-то код
});

Этот метод API работает для всех событий приложения. Поэтому, хотя пример в нём не совсем полный, это всего лишь пример. Существует множество других AppEvents, которые вы можете использовать в этом методе. Например, вы можете посмотреть здесь:

чтобы увидеть названия нескольких срабатывающих событий.

this.appEvents.trigger("EVENT_NAME")

и как мы подключаемся к ним, чтобы вносить другие изменения:

this.appEvents.on("EVENT_NAME")

Вы можете подключиться к любому событию, которое генерирует ядро Discourse, в своей теме. Так что, если я хочу выполнить какой-то код прямо перед открытием редактора (composer),

discourse/app/assets/javascripts/discourse/app/components/composer-editor.js at 1c38b4abf1fab8d67aaaa4b9f2810add64b709c4 · discourse/discourse · GitHub,

я могу добавить что-то вроде этого в свою тему:

api.onAppEvent("composer:will-open", () => {
  console.log("это срабатывает прямо перед открытием редактора");
});

Тем не менее, я согласен с вами. Нам следует использовать другой пример для этого метода в файле API. Я сделал заметку, чтобы улучшить этот пример.

Это отличный ответ, и он настолько понятен, что даже я смог его осмыслить.

Проблема «как заставить что-то сработать» возникает довольно часто. Возможно, люди найдут её здесь, но ваш последний пост вполне мог бы стать отдельной темой и, возможно, быть добавлен или связан в руководствах по темам и/или плагинам.

А теперь, когда под «темой» в основном подразумевается «ember», а под «плагином» — «rails», имеет смысл подумать о некотором рефакторинге этих разделов. Кажется, когда всё только начиналось, было много функционала Ember, который требовал плагина, но теперь это можно реализовать в компоненте темы, верно? А сейчас вы можете реализовать функционал Ember как в плагине, так и в компоненте темы.

Привет, @Johani, вот ещё один вариант проблемы «как вызвать триггер или быть вызванным»: я использую версию плагина Custom Header Links в своём плагине. Он создаёт ссылки на некоторые элементы («серверы»), которые создаются в отдельной модели, добавляемой моим плагином. Когда создаётся server, я хочу перестроить ссылки в заголовке, чтобы они вели на два последних созданных сервера. Сейчас я делаю это в инициализаторе, и всё работает, но для обновления после добавления сервера необходимо перезагрузить страницу.

Ты рассказывал, как добавлять и отслеживать триггеры, поэтому я подумал, что смогу разобраться, но страница, которая выполняет эту работу, находится в discourse-subscriptions. Возможно, мне стоит отправить PR в discourse-subscriptions, чтобы добавить

 this.appEvents.trigger("purchase-complete")

после завершения покупки (при этом покупка вызывает добавление в группу, что, в свою очередь, вызывает создание сервера и удаление пользователя из группы). Или, если бы я мог просто вызвать перезагрузку после завершения покупки или после нажатия кнопки «OK» в модальном окне «Покупка завершена», это тоже подошло бы, но я не знаю, как это сделать (то, что я пробовал, приводило к бесконечной перезагрузке страницы…)

Так, возможно, здесь:

https://github.com/discourse/discourse-subscriptions/blob/main/assets/javascripts/discourse/controllers/s-show.js.es6#L71-L81

я хочу добавить

 this.appEvents.trigger("successful-transaction")

после установки loading в false, а затем в своём инициализаторе добавить

 this.appEvents.on("successful-transaction")

для манипуляции с заголовком?

Думаю, что после этого мне придётся сделать что-то другое, потому что я боюсь, что

      api.decorateWidget("header-buttons:before", (helper) => {
        return helper.h("ul.pfaffmanager-header-links", headerLinks);
      });

добавляет элементы в header-buttons:before, а не заменяет их, поэтому при каждом срабатывании я буду получать всё больше ссылок?

Это снова мне помогло! Мне нужно было загрузить скрипт при обновлении потока постов. Все примеры, которые я нашёл, были в компонентах, где appEvent был доступен через this. Наконец я нашёл это, и теперь в apiInitializer я могу сделать так:

  api.onAppEvent('post-stream:refresh', args => {
   // сделать что-то!!!
  });

И теперь эти объявления загружаются при загрузке каждой партии постов. Я уже поставил лайк твоему посту, поэтому решил написать более развёрнутую благодарность. :wink:

Это одна из моих любимых тем :heart: Я периодически сюда возвращаюсь :sweat_smile: Я очень благодарен за это! :hugs: