# 如何在主题 HTML 渲染到 DOM 后执行脚本？

**URL:** https://meta.discourse.org/t/how-do-we-fire-scripts-after-topic-html-is-rendered-in-dom/114701
**Category:** Development
**Created:** [2019 年4 月 10 日 21:54 UTC](https://meta.discourse.org/t/how-do-we-fire-scripts-after-topic-html-is-rendered-in-dom/114701 "2019-04-10T21:54:48Z")
**Posts on this page:** 5
**Page:** 1

<div class="post-metadata">

### Author: ![labofoz](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/labofoz/32/135498_2.png) [@labofoz](https://meta.discourse.org/u/labofoz)
#### Post date: [2019 年4 月 10 日 21:54 UTC](https://meta.discourse.org/t/how-do-we-fire-scripts-after-topic-html-is-rendered-in-dom/114701/1 "2019-04-10T21:54:49Z")

</div>

**TL:DR;** Are there [APIs like onPageChange](https://meta.discourse.org/t/how-do-you-force-a-script-to-refire-on-every-page-load-in-discourse/74856/5) or hooks to know when the topic is loaded **and** the HTML is rendered?

* * *

I’m working on an accessibility community, where people can embed games and tools from CodePen. The problem is that the CodePens only fill the 700px content area, which makes the games too small to be playable.

To fix this, I’m working on a component that makes all CodePens widescreen:

 ![image](https://global.discourse-cdn.com/meta/original/3X/c/9/c9e7d65d24513fa596fa38e83c68e25c5ea60ded.png)

I’m using jQuery, which means I have to wait until topic’s HTML actually loads. I can get close with the `onPageChange` api, but the problem is that the topics HTML isn’t actually in the DOM yet.

Are there other apis?

---

<div class="post-metadata">

### Author: ![tshenry](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/tshenry/32/119495_2.png) [@tshenry](https://meta.discourse.org/u/tshenry)
#### Post date: [2019 年4 月 11 日 01:41 UTC](https://meta.discourse.org/t/how-do-we-fire-scripts-after-topic-html-is-rendered-in-dom/114701/2 "2019-04-11T01:41:38Z")

</div>

I think you should be able to use the following:

> <https://github.com/discourse/discourse/blob/7a7c6af21e71a3ff2f743222e1f0f97e44d7df9f/app/assets/javascripts/discourse/lib/plugin-api.js.es6#L184-L198>

You may also want to take a look at @Johani’s iframe lightbox component:

[https://github.com/hnb-ku/discourse-iframe-lightboxes/blob/master/desktop/head\_tag.html](https://github.com/hnb-ku/discourse-iframe-lightboxes/blob/master/desktop/head_tag.html)

---

<div class="post-metadata">

### Author: ![Johani](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/johani/32/176920_2.png) [@Johani](https://meta.discourse.org/u/Johani)
#### Post date: [2019 年4 月 12 日 13:12 UTC](https://meta.discourse.org/t/how-do-we-fire-scripts-after-topic-html-is-rendered-in-dom/114701/3 "2019-04-12T13:12:11Z")

</div>

Like @tshenry pointed out, what you’re looking for is `decorateCooked`

For basic changes, something like this works:

```plaintext
api.decorateCooked($elem => $elem.css("background", "yellow"));

```

If you need something more complicated, you have two choices, you can either use this syntax:

```plaintext
api.decorateCooked($elem => {
  // 1
  console.log("foo");
  // 2
  $elem.css("background", "yellow");
  // 3
  console.log("bar");
});

```

or create your own jQuery plugins like so:

```plaintext
$.fn.doSomething = function() {
  // 1
  console.log("foo");
  // 2
  $(this).css("background", "yellow");
  // 3
  console.log("bar");
  // you have to add this at the end for chainability
  return this;
};

api.decorateCooked($elem => $elem.doSomething());

```

Do note that `decorateCooked` runs every time

1- a post is loaded (yes, that means once for every post in the stream).  
2- the composer preview is updated - this can be very expensive if you don’t watch out.

What that means is that you have to be mindful and only fire scripts on the posts you want to target.

So, if you don’t want the changes to happen in the composer you would add the `onlyStream` option. You would do that with something like this:

```plaintext
$.fn.doSomething = function() {

  // do your work
  
  return this;
};

api.decorateCooked($elem => $elem.doSomething(), { onlyStream: true });

```

This also ensures that your script ignores other places where the post might appear like the user post-stream.

And if you only want to target posts that contain specific elements you can use something like this

```plaintext
$.fn.doSomething = function() {
  const targetElement = $(this).children("[data-theme-test]").length;
  if (!targetElement) return;

  // do your work
  
  return this;
};

api.decorateCooked($elem => $elem.doSomething(), { onlyStream: true });

```

and that would ensure that your script only fires on the posts that contain the element you want to target and also ignore the composer preview.

If you have external scripts you want to load in order to do some work, you can use `loadScript` and you can do that with something like this:

```plaintext
const loadScript = require("discourse/lib/load-script").default;

$.fn.doSomething = function() {
  const targetElement = $(this).children("[data-theme-test]").length;
  if (!targetElement) return;

  loadScript(
    "//cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.1.1/iframeResizer.min.js"
  ).then(() => {
    // do your work
  });

  return this;
};

api.decorateCooked($elem => $elem.doSomething(), { onlyStream: true });

```

Do note that if that’s the case, you will also need to watch out for [CSP issues](https://meta.discourse.org/t/mitigate-xss-attacks-with-content-security-policy/104243)

Finally, if you need to transverse the dom, you might need a little bit of Ember. By default, everything in `cooked` div should be available for jQuery. However, say you want to target one of the parents of cooked, well you can use something like this:

```plaintext
const loadScript = require("discourse/lib/load-script").default;

$.fn.doSomething = function() {
  const targetElement = $(this).children("[data-theme-test]").length;
  if (!targetElement) return;

  Ember.run.scheduleOnce("afterRender", () => {
    loadScript(
      "//cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.1.1/iframeResizer.min.js"
    ).then(() => {
      // do your work
    });
  });

  return this;
};

api.decorateCooked($elem => $elem.doSomething(), { onlyStream: true });

```

and that will allow you to fire a script after everything renders and after you load an external script while ignoring posts you don’t want to target and ignoring the composer preview.

Let me know if you need any clarification.

---

<div class="post-metadata">

### Author: ![labofoz](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/labofoz/32/135498_2.png) [@labofoz](https://meta.discourse.org/u/labofoz)
#### Post date: [2019 年4 月 23 日 17:40 UTC](https://meta.discourse.org/t/how-do-we-fire-scripts-after-topic-html-is-rendered-in-dom/114701/4 "2019-04-23T17:40:49Z")

</div>

Thanks for this response and especially for the extra info on working with loadScript, it was super helpful! I can now get iframes to go widescreen and even fullscreen in the same tab

[https://global.discourse-cdn.com/meta/original/3X/9/d/9df65c23e12a259207db09b4d72a9c0e364e57c9.mp4](https://global.discourse-cdn.com/meta/original/3X/9/d/9df65c23e12a259207db09b4d72a9c0e364e57c9.mp4)

---

<div class="post-metadata">

### Author: ![Arjan](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/arjan/32/242515_2.png) [@Arjan](https://meta.discourse.org/u/Arjan)
#### Post date: [2021 年11 月 27 日 19:30 UTC](https://meta.discourse.org/t/how-do-we-fire-scripts-after-topic-html-is-rendered-in-dom/114701/5 "2021-11-27T19:30:10Z")

</div>

我知道文档会过时，但考虑到本主题的标题，为未来的读者提供一些提示。

如今，`decorateCookedElement` 似乎更受青睐：

> <https://github.com/discourse/discourse/blob/136189508b9a742e6b50e977d3a8677bc462d486/app/assets/javascripts/discourse/app/lib/plugin-api.js#L270-L282>

> <https://github.com/discourse/discourse/pull/9534>
>
> This is a replacement for \`decorateCooked\` which will work without jquery.
> 
> A …backwards compatibility layer is provided for existing plugins/themes which are currently using \`decorateCooked\`
> 
> We should make sure we are 100% happy with the naming here, because it will be very difficult to change in future
