각 푸터 로드(또는 페이지 로드) 시마다 발사하는 방법

푸터에 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", () => {
  // some code
});

어떤 커스텀 HTML을 대상으로 할지 지정해야 합니다. 대상에 따라 다른 이벤트가 발생하기 때문입니다.

이 경우 푸터(footer)를 대상으로 해야 합니다. 아래 코드를 시도해보시고 작동하는지 확인해 주시겠습니까?

api.onAppEvent("inserted-custom-html:footer", () => {
  // some code
});

하! 그게 문제였구나! (물론, 그들에게 푸터에서 하려는 작업은 애초에 할 가치가 없다고 설득한 이후의 일이긴 했지만). 이 문제를 해결할 수 있어서 정말 기쁘고 도움을 주셔서 감사해요. 이쪽 분야가 점점 이해가 되기 시작하네요. (그리고 이제 그들은 아예 푸터 자체를 삭제할 것 같아요.)

아하! 왜 "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', () => {

자바스크립트와 엠퍼(Ember)는 저에게 가장 이해하기 어려운 부분인데… 아마 1980년대 중반에 리스프(Lisp)를 배웠을 때 이후로 그런 것 같습니다. 그래서 아직도 무엇이 “당연한” 것인지 판단을 잘 못 하겠어요.

아닙니다. 해당 이벤트는 custom-html ember 컴포넌트가 렌더링될 때만 발생합니다.

그렇다면, 이 ember 컴포넌트를 어디에서 사용하며 어떤 역할을 하는 것일까요?

우리는 주로 메인 애플리케이션 템플릿에서 두 개의 테마 필드를 렌더링하는 데 이것을 사용합니다.

테마 after_header 탭

테마 푸터 탭

다른 곳에서도 이것을 사용하지만, 이 맥락에서는 중요하지 않습니다.

푸터에도 triggerAppEvent="true"가 있다는 점에 주목하세요.

그래서 이렇게 사용할 수 있습니다.

api.onAppEvent("inserted-custom-html:footer", () => {
  // some code
});

after_header 탭에서는 같은 작업을 수행할 수 없습니다. Discourse는 해당 탭에 대해 이벤트를 발생시키지 않기 때문입니다. 따라서 이것은 작동하지 않습니다.

api.onAppEvent("inserted-custom-html:top", () => {
  // some code
});

그렇다면 왜 일부 테마 필드를 렌더링하기 위해 그 ember 컴포넌트를 사용할까요?

간단한 답은, 그것이 렌더링 시점에 대해 훨씬 더 많은 제어 권한을 우리에게 제공해 주기 때문입니다.

이것의 두 가지 예를 들어보겠습니다… 토픽 목록과 관리자 페이지입니다.

무한 스크롤을 통해 아직 로드될 토픽이 남아 있는 동안 토픽 목록에서 푸터를 렌더링하고 싶지 않습니다.

또한 after_header 탭에 추가된 배너나 사이트 브랜딩 마크업도 관리자 페이지에서 렌더링하고 싶지 않습니다.

따라서 custom-html ember 컴포넌트를 사용하면 이러한 사항에 대해 더 세밀한 제어가 가능합니다.

자, 다시 질문으로 돌아가 보겠습니다. 푸터가 렌더링될 때 수행해야 할 작업이 있다면, 이것이 필요한 래퍼입니다.

api.onAppEvent("inserted-custom-html:footer", () => {
  // some code
});

해당 API 메서드는 모든 앱 이벤트에 대해 작동합니다. 따라서那里的 예제가 실제로는 완전하지 않더라도, 그것은 단지 예제일 뿐입니다. 해당 메서드에서 사용할 수 있는 다른 AppEvents가 많이 있습니다. 예를 들어, 여기에서 확인할 수 있습니다.

Code search results · GitHub

여기서 몇 가지 발생되는 이벤트의 이름을 볼 수 있습니다.

this.appEvents.trigger("EVENT_NAME")

그리고 어떻게 그것들에 후크를 걸어 다른 변경 사항을 만드는지

this.appEvents.on("EVENT_NAME")

Discourse 코어가 발생시키는 모든 이벤트에 테마에서 후크를 걸 수 있습니다. 따라서 컴포저가 열리기 바로 전에 일부 코드를 실행하고 싶다면.

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

테마에 다음과 같은 것을 추가할 수 있습니다.

api.onAppEvent("composer:will-open", () => {
  console.log("this fires right before the composer opens");
});

그럼에도 불구하고, 저는 당신과 동의합니다. API 파일에서 해당 메서드에 대해 다른 예제를 사용해야 합니다. 그 예제를 개선하기 위해 메모를 해두었습니다.

정말 훌륭한 답변이고, 너무 명확해서 나조차도 이해할 수 있었습니다.

이런 "어떤 것을 실행시키는가"에 대한 문제는 꽤 자주 발생합니다. 사람들이 여기서 발견할 수도 있지만, 마지막 게시글은 독립적인 주제로 다루어져도 좋을 것 같고, 테마 및/또는 플러그인 가이드에 추가하거나 링크를 걸어도 좋을 것 같습니다.

그리고 이제 "테마"는 대부분 "ember"을, "플러그인"은 대부분 "rails"을 의미하므로, 이 부분들을 조금 리팩토링해 보는 것이 합리적일 수 있습니다. 처음 시작할 무렵에는 플러그인이 필요했던 Ember 관련 작업들이 지금은 테마 컴포넌트로도 수행할 수 있게 된 것 같죠? 그리고 이제는 Ember 관련 작업을 플러그인 또는 테마 컴포넌트에서 수행할 수 있습니다.

Hey @Johani, 이 “트리거를 발생시키거나 트리거를 받는” 문제의 또 다른 버전은 플러그인에서 Custom Header Links의 변형 버전을 사용하고 있다는 점입니다. 이 링크는 플러그인이 추가하는 별도 모델에서 생성된 항목(“서버”)으로 연결됩니다. server가 생성되면, 헤더 링크를 다시 빌드하여 가장 최근에 생성된 두 개의 서버로 연결되도록 하고 싶습니다. 현재 이를 초기화자(initializer)에서 수행하고 있으며, 서버가 추가된 후 업데이트되려면 페이지를 다시 불러와야 한다는 점을 제외하면 잘 작동합니다.

트리거를 추가하고 관찰하는 방법을 알려주셨기 때문에, 스스로 해결할 수 있을 것 같다고 생각했지만, 작업을 수행하는 페이지는 discourse-subscriptions에 있습니다. 아마 제가 원하는 것은 discourse-subscriptions에 PR을 제출하여 구매가 완료된 후에

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

를 추가하는 것일 것입니다(구매는 그룹에 추가를 트리거하고, 이는 서버 생성과 사용자 그룹 제거를 트리거합니다). 또는, 구매가 완료된 후에나 “구매 완료” 모달의 “확인” 버튼을 클릭했을 때 단순히 리로드를 트리거할 수 있다면 그것도 괜찮을 텐데, 그것도 어떻게 해야 할지 모르겠습니다(시도해본 것들은 페이지가 계속 무한히 리로드되는 결과를 낳았거든요…)

그래서 아마 여기에서:

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

로딩이 false로 설정된 후에

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

를 추가하고, 그런 다음 초기화자에

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

를 추가하여 헤더 조작을 수행할 수 있을 것입니다.

그렇게 한 후에는 다른 방식으로 처리해야 할 것 같습니다. 왜냐하면

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

이 코드는 header-buttons:before에 링크를 대체하는 것이 아니라 추가하기 때문에, 이벤트가 발생할 때마다 링크가 계속 늘어나지 않을까 걱정되기 때문입니다.

이번에도 정말 큰 도움이 되었습니다! 제가 해야 했던 것은 게시글 스트림이 업데이트될 때 스크립트를 로드하는 것이었습니다. 제가 찾은 모든 예제는 this를 통해 appEvent에 접근할 수 있는 컴포넌트 안에 있었습니다. 마침 이 방법을 찾아서 이제 apiInitializer에서 다음과 같이 사용할 수 있게 되었습니다.

  api.onAppEvent('post-stream:refresh', args => {
   // do some stuff!!!
  });

이제 각 게시글 배치(일괄)가 로드될 때 광고도 함께 로드됩니다. 이미 게시글에 :heart:을 눌렀기 때문에, 더 길고 구체적인 감사 인사를 남기고 싶었습니다. :wink:

이것은 제가 가장 좋아하는 주제 중 하나예요 :heart: 가끔씩 여기로 돌아와요 :sweat_smile: 정말 감사합니다! :hugs: