Content Component: event for content rendered?

Hi, I’m using Builder.io content in a Vue.js app using Builder’s standard Vue.js sdk. I need to embed this Vue.js app inside an <iframe> tag on an existing webpage developed in PHP technology.

The Vue.js App.vue code:

<script setup>
import { Content, fetchOneEntry, isPreviewing, getBuilderSearchParams } from '@builder.io/sdk-vue';
import { nextTick, onMounted, ref } from 'vue';

const content = ref(null);
const apiKey = '<api key>';
const canShowContent = ref(false);
const model = 'test-section';

onMounted(async () => {
  content.value = await fetchOneEntry({
    model,
    apiKey,
    options: getBuilderSearchParams(
      new URL(location.href).searchParams
    ),
    userAttributes: {
      urlPath: window.location.pathname,
    },
  });
  canShowContent.value = content.value ? true : isPreviewing();
  setTimeout(() => {
    console.log('scrollHeight: ', document.body.scrollHeight)
    window.parent.postMessage({
      msg: 'postScrollHeight',
      scrollHeight: document.body.scrollHeight
    }, '*')
  }, 300)
});
</script>

<template>
    <h1>Builder.io CMS Vue demo</h1>

    <Content
      v-if="canShowContent"
      :model="model"
      :content="content"
      :api-key="apiKey"
    />
    <div v-else>Content not Found</div>
</template>

The PHP container page:

<!DOCTYPE html>
<html lang="nl">

  <head>
    <script type="text/javascript">
      window.addEventListener("message", (e) => {
        const data = e.data;
        if (data.msg == 'postScrollHeight') {
          console.log(`postScrollHeight ${data.scrollHeight} received`)
          document.getElementById("ifrmBuilder").height = data.scrollHeight;
        }
      });
    </script>

    <style>
      iframe {
        display: block;
        background: #FFF;
        margin: 0;
        border: none;         /* Reset default border */
        width: 100%;
        overflow: hidden;
      }
    </style>
  </head>

  <body id="body">
    <div id="php">
      <div class="container-fluid pt-md-2">
        <iframe id="ifrmBuilder" src="http://localhost:5173" scrolling="no"></iframe>
      </div>  
    </div>
  </body>
</html>

When the Builder content is rendered inside the Vue.js app, I need to determine the scroll height of the Builder content from the Vue app’s body tag to set the height of the iframe. Next, I’m using window.postMessage() to notify the iframe’s parent (the PHP container page) to set the height of the iframe to the scrollHeight of the Builder content.

However, the Builder content first needs to be rendered completely before you can even determine the scrollHeight.

The purpose of this: the Builder content needs to be seamlessly embedded inside (existing) PHP pages (without a visible iframe or scroll bars), using a Micro Frontend architecture. This allows you to leverage all the features of Builder content in a Vue.js app, such as your own custom Vue components, something that was not possible on a first attempt (trying to embed Builder content directly inside a PHP page).

So to integrate this seamlessly I wonder if there is a content loaded or content rendered event available in the Content component that fires when all content has been rendered in the browser DOM? As a temporary workaround, I’m now using a setTimeout() with a delay of 300ms to send the scrollHeight to the parent. Btw, using Vue’s nextTick() after the await fetchOneEntry() and setting the content.value didn’t work out either - the nextTick() fires too early.

Thanks in advance for any useful pointers!

Hello @wdbacker,

While there isn’t a built-in “content rendered” event in the Content component in Builder’s SDK, you can possibly manage this with a combination of Vue lifecycle hooks and JavaScript events.

  1. Use ref for Content: You can use Vue’s ref to watch when the content is fully loaded.
  2. Vue watch hook: Consider using Vue’s watch or watchEffect to monitor the changes in your content and trigger an event when the content is populated.
  3. Custom Event on Content Load: You can emit a custom event after the content is successfully fetched and rendered in your Vue component.
<script setup>
import { Content, fetchOneEntry, isPreviewing, getBuilderSearchParams } from '@builder.io/sdk-vue';
import { onMounted, ref, watch, nextTick } from 'vue';

// Reactive references for content and rendering flags
const content = ref(null);
const apiKey = '<api key>';
const canShowContent = ref(false);
const model = 'test-section';

// Fetch content on component mount
onMounted(async () => {
  content.value = await fetchOneEntry({
    model,
    apiKey,
    options: getBuilderSearchParams(
      new URL(location.href).searchParams
    ),
    userAttributes: {
      urlPath: window.location.pathname,
    },
  });

  // Set the rendering flag based on content availability
  canShowContent.value = content.value ? true : isPreviewing();
});

// Watch for changes to the `canShowContent` and trigger logic once it's true
watch(canShowContent, async (newValue) => {
  if (newValue) {
    // Ensure DOM changes are applied using nextTick
    await nextTick();
    adjustIframeSize();
  }
});

// Function to adjust the iFrame size by sending the scrollHeight
function adjustIframeSize() {
  const height = document.body.scrollHeight;
  window.parent.postMessage({
    msg: 'postScrollHeight',
    scrollHeight: height
  }, '*');
}
</script>

<template>
  <h1>Builder.io CMS Vue demo</h1>

  <Content
    v-if="canShowContent"
    :model="model"
    :content="content"
    :api-key="apiKey"
  />
  <div v-else>Content not Found</div>
</template>

You can try the above approach and let us know how that works for you!

Best regards,

Hi @manish-sharma

Thanks for the suggestions! I tried them out in my current setup (e.g. the watch method with the await nextTick()) but even after waiting on nextTick, the content is not rendered yet: I get a very small scrollHeight (without the Builder content rendered). I even tried with 10x await nextTick(), but still no correct scrollHeight. The only solution currently where I get the correct scrollHeight is with a setTimeout() delay.

I also notice warnings in the browser console suggesting that the content inside Builder’s Content component is rendering asynchronously: I see <AsyncComponentWrapper> and <DynamicRenderer> components being used behind the scenes by Builder. So I’m afraid that the nextTick comes too early.

Is there a possibility in the current implementation of the Content component to receive an event when all async rendering is done?

Kind regards,

Hi @manish-sharma, I found a usable workaround for displaying Builder CMS content in a container page using an iframe at full (scroll)height: I outlined the steps in Using Builder.io CMS content in a Micro Frontend setup.

1 Like