Reputation: 3240
I am working on a vue.js project(version 3). I come to a situation where I want to use the rendered HTML view of a component to my current component's method.
I created the following SVG component in my Vue project.
CircleWithTextSvg.vue
<template>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" width="20" height="20">
<g id="UrTavla">
<circle cx="10" cy="10" r="10" stroke="gray" stroke-width="1" :fill="fill" />
<text x="50%" y="50%" text-anchor="middle" stroke="black" stroke-width="1px" dy=".3em">{{text}}</text>
</g>
</svg>
</template>
<script>
export default {
props: {
text: {
type: String,
default: "1"
},
fill: {
type: String,
default: "white"
}
}
}
</script>
This component basically renders a circle with text inside. If I use this component in my main component's template section like the following then it works fine(obviously)
MainComponent.vue
<template>
<circle-with-text-svg />
</template>
But I want to send this SVG component rendered output as an option to the third party.
Real Use Case:- Actually, I created this separate component to show as a marker on my leaflet map. The problem is now I want to use this SVG component inside my MainComponent's method so I can use it as an option to L.divIcon
When I try the following
export default {
methods: {
generateMap(element) {
// ...
let icon = L.divIcon({
html: <circle-with-text-svg
fill="'#D3D5FF'"
text="1" />,
iconSize: [10, 10]
});
// ...
}
}
}
Then it gives the errors
Support for the experimental syntax 'JSX isn't currently enabled
In the react, we can simply use the component inside the template of another component normally. but how can we achieve this in vue.js
By looking at the error, it seems like JSX experimental is not enabled.
Can someone address me how can I achieve this?
Upvotes: 5
Views: 13074
Reputation: 37883
Ok, so in comments I recommended the answer for question How to get the compiled html content of a component in vuejs which is actually implemented for Vue 2
I was curious if this works in Vue 3 and you can see the result below. Here are changes required:
new Vue
with createApp
and using global h() instead the one passed into render()
$mount()
function of main Vue instance without the argument. That worked because Vue created DOM element to mount to in memory. This is not the case in Vue 3 - you need to provide the element yourselfapp.component()
are not accessible in the tempApp
used to render HTML. All components in use must be registered in appropriate instance - see the migration guide// We use component options object with string template
// In the proper Vue app (with Webpack and Vue SFC) this whole block can be replaced with "import CircleWithTextSvg from CircleWithTextSvg.vue"
const CircleWithTextSvg = {
name: 'CircleWithTextSvg',
props: {
text: {
type: String,
default: "1"
},
fill: {
type: String,
default: "white"
}
},
template: `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" width="20" height="20">
<g id="UrTavla">
<circle cx="10" cy="10" r="10" stroke="gray" stroke-width="1" :fill="fill" />
<text x="50%" y="50%" text-anchor="middle" stroke="black" stroke-width="1px" dy=".3em">{{text}}</text>
</g>
</svg>
`
}
const app = Vue.createApp({
mounted() {
console.log(this.getIconHtml('Hello!'))
},
methods: {
getIconHtml(text, type) {
const tempApp = Vue.createApp({
render() {
return Vue.h(CircleWithTextSvg, {
text,
type
})
}
})
// in Vue 3 we need real element to mount to unlike in Vue 2 where mount() could be called without argument...
const el = document.createElement('div');
const mountedApp = tempApp.mount(el)
return mountedApp.$el.outerHTML
}
}
})
app.mount('#app')
<script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
<div id='app'>
</div>
Note 1: Code above is intended to run directly in the browser where import
is not available. For that reason we use Vue global build and accessing Vue global API's using for example Vue.createApp
or Vue.h
. In regular Vue app you need to import those functions as import { createApp, h } from 'Vue'
Note 2: Arguably if the HTML fragmets used with Leaflet components are so simple as your CircleWithTextSvg
component, much more simple and performant would be to define them not as Vue components but as template literals
Upvotes: 8
Reputation: 2316
I tried the above solution but it did not budge for me, the $el.outerHTML
did not feel right and kept throwing null and undefined because of the life cycle in the rest of my app.
In vue3 I did this. it still does not feel right. But getting there :)
const getIconHtml = (item, element) => render(h(CircleWithTextSvg, {
onSomeEmit: (ev) => {
},
text: item, //props
}), element //element to render to
)
In my problem I had to return a template for a 3rd party js(visjs) library
so how i used it was something like this.
import {render, h} from 'vue';
const itemTemplate = async (item, element) => render(h(bar, {
onDelete: (ev) => {
vis.removeItem(item.id)
},
item: item,
}), element
)
//note this template key does not refer to vuejs template
template: (item, element) => {
if (!item) return;
return itemTemplate(item, element);
}
Things to consider using this approach.
This technically creates a new vue instance, so any additional apps, props, components, directives you want to use, you might need to manually register to this component.
some globals might work, but I had issues getting directives to work because of some deep
issue,
Upvotes: 0