3

I am developing a dynamic dashboard in Vue 3 where the user can specify simple XML-like templates for the parts that they want to render.

I would like to have the nodes in the XML that match the name of specific Vue components rendered as Vue components, while the other nodes should be rendered as normal HTML.

For example, an XML template may look like this:

<Dashboard>
  <Elements>
    <div class="title">Switches</div>
    <Switches device="kitchen-bulb" />
  </Elements>
</Dashboard>

The <div> inside of <Elements> should be rendered as normal HTML (and I have achieved this already by both leveraging <slot> and passing the node content to v-html on the <Elements> component), while <Switches>, which matches the name of a specific imported Vue component, should be rendered as a Vue component - in this case with a device property passed from the template.

The code right now looks like this:

/// index.js
import Switch from './Switch'

export default {
    Switch,
}

/// Elements.vue
<template>
  <div class="elements">
    <div class="container" v-html="content" />
  </div>
</template>

<script>
import elements from './index'

export default {
  name: "Elements",
  props: {
    content: {
      type: String,
    },
  },

  mounted() {
    const content = new DOMParser()
        .parseFromString(`<div>${this.content}</div>`, 'text/html')
        .childNodes[0]

    Object.entries(elements).forEach(([name, element]) => {
      this.$options.components[name] = element

      const nodes = content.getElementsByTagName(name);
      [...nodes].forEach((node) => {
         const renderedComponent = magicStringToRenderedHTMLStringOrDOM(element, node)
         node.parentElement.replaceChild(renderedComponent, node)
      })
    })

    this.content = content.childNodes[0].innerHTML
  },
}
</script>

content is the node content of <Elements> passed to the component by its parent.

What I need is something to fill the magicStringToRenderedHTMLStringOrDOM call that, given the Vue component definition, the node representation and the properties, renders Vue component that I can simply replace in content and render with v-html.

I know that Vue 2 used to provide something similar with Vue.compile. The only Vue 3 function I have found that apparently does something similar is createRenderer, but I haven't found many examples of how to use it.

1 Answer 1

1

I have solved the problem by resorting to the native render function, and mounting an app on the fly for each tag that matches a dynamic component on the index.

Better than reinventing the wheel with compiling and rendering the template from scratch :)

/// Content of index.js
import Switch from './Switch'

export default {
    Switch,
}

/// Content of Elements.vue
<template>
  <div class="elements">
    <div class="container" ref="container" />
  </div>
</template>

<script>
import elements from './index'

export default {
  name: "Elements",
  props: {
    content: {
      /**
       * Example content property:
       * <div>
       *   <h1>Switches</h1>
       *   <Switches />
       * </div>
       */
      type: String,
    },
  },

  mounted() {
      this.$refs.container.innerHTML = this.content

      // Iterate over all the dynamic components on index.js
      Object.entries(elements).forEach(([name, element]) => {
        // Dynamically load each of them
        this.$options.components[name] = element;

        // Get all the tags on the content DOM that match
        // a certain dynamic component
        [...this.$refs.container.getElementsByTagName(name)].forEach((component) => {
          // Replace each of them with a new Vue app
          // that renders the component and mounts it
          // on the DOM node
          createApp({
            render() { return h(element) }
          }).mount(component)
        })
      })
  }
}
</script>
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.