Vue - Render an element out of string - vue.js

I would like to create a vue element from a string from my database.
In this case, it should be a message with a smiley emoji.
I actually save it like: Some text with Emoji: :santa::skin-tone-3:, and replace all valid string between '::' with the <Emoji emoji=':santa::skin-tone-3:' :size='16' />
<template>
<span class=message v-html=convertedMessage></div>
</template>
<script>
import { Emoji } from 'emoji-mart-vue'
export default {
components: {
Emoji
},
computed:{
convertedMessage(){
return "Some text with Emoji: "+"<Emoji emoji=':santa::skin-tone-3:' :size='16' />"
}
}
}
</script>
But instead of the rendered element which should be something like:
<span data-v-7f853594="" style="display: inline-block; width: 32px; height: 32px; background-image: url("https://unpkg.com/emoji-datasource-apple#4.0.4/img/apple/sheets/64.png"); background-size: 5200%; background-position: 15.6863% 41.1765%;"></span>
I only get:
<emoji emoji=":santa::skin-tone-3:" :size="16"></emoji>
What is the best possibility to render this Element like intended?

Here are some much easier ways to do what you generally want. If you give more specifics, your right direction may be a strategy pattern before one of these solutions, but one of these solutions is probably what you want:
1) Vue lets you dynamically define components right out of the box, so this single line:
<component v-for="(component, index) in components" :key="'component'+index" :is="component.name" v-bind="component.props" />
...would draw a bunch of components in an array of objects like this (for example): {name: 'myComponentName', props: {foo: 1, bar: 'baz'}}.
2) Vue lets you inject HTML into components by simply adding v-html="variable"
For example, here is a component that creates dynamic SVG icons, where the contents of the SVG is dynamically injected from JavaScript variables...
<template>
<svg xmlns="http://www.w3.org/2000/svg"
:width="width"
:height="height"
viewBox="0 0 18 18"
:aria-labelledby="name"
role="presentation"
>
<title :id="name" lang="en">{{name}} icon</title>
<g :fill="color" v-html="path">
</g>
</svg>
</template>
<script>
import icons from '../common/icons'
export default {
props: {
name: {
type: String,
default: 'box'
},
width: {
type: [Number, String],
default: 18
},
height: {
type: [Number, String],
default: 18
},
color: {
type: String,
default: 'currentColor'
}
},
data () {
return {
path: icons[this.name]
}
},
created () {
console.log(icons)
}
}
</script>
<style scoped>
svg {
display: inline-block;
vertical-align: baseline;
margin-bottom: -2px;
}
</style>
3) Vue lets you dynamically define your component template through this.$options.template:
export default {
props: ['name', 'props'],
template: '',
created(){
this.$options.template = `<component :is="name" ${props.join(' ')} ></component>`
},
}
4) Vue lets you define a render function, so proxy components or other advanced shenanigans are trivial:
Vue.component('component-proxy', {
props: {
name: {
type: String,
required: true
},
props: {
type: Object,
default: () => {}
}
},
render(h) {
// Note the h function can render anything, like h('div') works too.
// the JS object that follows can contain anything like on, class, or more elements
return h(this.name, {
attrs: this.props
});
}
});
A smart genius wrote a jsbin for this here: http://jsbin.com/fifatod/5/edit?html,js,output
5) Vue allows you to create components with Vue.extend or even passing in raw JavaScript objects into a page or apps components section, like this, which creates a component named "foo" from a simple string for the template and an array for props, you could also extend the data, created, on, etc. the same way using the JS object alone:
new Vue({
el: '#app',
data: {
foo: 'bar',
props: {a: 'a', b: 'b'}
},
components: {
foo: {
template: '<p>{{ a }} {{ b }}</p>',
props: ['a', 'b']
}
}
})

What i figured out now:
convertedMessage(){
let el = Vue.compile("<Emoji emoji=':santa::skin-tone-3:' :size='16' />")
el = new Vue({
components: {
Emoji
},
render: el.render,
staticRenderFns: el.staticRenderFns
}).$mount()
return "Some text with Emoji: "+el.$el.innerHTML
}
Maybe there is still a better solution to handle this?

Here's how I went about when I needed to do something similar.
I rendered the component, say, <my-component> normally, but since I only needed rendered HTML, I wrapped it inside a <div class="hidden"> like so:
<div class="hidden">
<my-component />
</div>
With CSS:
.hidden {
display: none;
}
This way, I can refer to the element through $refs or you could get the element from the DOM using document.querySelector() while keeping it invisible to the end users.
So in the above example, to get the rendered HTML, You'd only need to do this:
let el = document.querySelector('.hidden');
let renderedHTMLString = el.children[0].outerHTML;
This way, you get the rendered HTML, without any overhead costs that's associated with Vue.compile or any other plugin. Render it normally. Hide it. Access it's outerHTML.

v-html only render plain HTML, see https://v2.vuejs.org/v2/guide/syntax.html#Raw-HTML
In your case you should probably take a look at render functions and JSX. I'm not an expert but it seems that some people are acheiving what you want with the dangerouslySetInnerHTML JSX function. Take a look at this thread : How do I convert a string to jsx?
I know sometimes we have no choice but if you can I think the best solution could be to avoid generating the template from the backend as it breaks separation of concern (and also, probably, security issues).

Related

Set content of vue 3 single file components' style tag

I need to present some random/dynamic html data in my project that might contain style tags, by default vue.js doesn't allow style tags in the template to avoid messing styles that is a very good option.
To make my project work I tried some runtime-component projects on github but they doesn't accept css styles in the way I need, so I came up with the solution of creating my own runtime component with accepting css styles data.
What I want in code is sth like:
<template>
<div v-html="template"></div>
</template>
<script>
export default {
name: "DynamicTemplate",
props: {template: String, style: String}
}
</script>
<style scoped>
// style data goes here
</style>
Any alternative working solution is welcome too :)
I tried v-html and v-text attributes on the component style tag (without any results) and also using css #import statement with base64 encoded css codes (got some errors like Cannot read properties of undefined), but none of them worked :(
You can encapsulate the data inside the style tags inside the component tag provided by vue in this manner ->
<component :is="'style'"> .test-class { color: red; }....Your style data </component>
An example of the same can be found in this project I created
https://codesandbox.io/s/interesting-dewdney-id9erd?file=/src/components/HelloWorld.vue
Edit 1 =>
After reading the comment as I think the CSS scope compilation is done during the build process not during runtime thus having css at runtime won't scope it an alternate solution for this can be embedding your HTML code inside an iframe and passing your code to the iframe using the srcdoc attribute.An example of the same is given below
https://codesandbox.io/s/currying-cherry-zzv3wk?file=/src/components/HelloWorld.vue
<template>
<iframe style="width: 100vw; height: 80vh" :srcdoc="htmlCssCode"></iframe>
</template>
<script>
export default {
data() {
return {
htmlCssCode: `Your Html code here`
<html>
Here is an example of passing that can pass dynamic HTML data in your project with an example using style tags
<template>
<div>
<div v-html="template" :style="style"></div>
</div>
</template>
<script>
export default {
name: 'DynamicTemplate',
props: {
template: {
type: String,
required: true
},
style: {
type: Object,
required: false,
default: () => {}
}
}
}
</script>
Here is the component being used.
<DynamicTemplate
:template="<p>This is some dynamic HTML content</p>"
:style="{ color: 'red', font-size: '14px' }">
</DynamicTemplate>
Here is an example of passing that can pass dynamic HTML data in your project with an example of passing classes dynamically
<template>
<div>
<div v-html="template" :class="classes"></div>
</div>
</template>
<script>
export default {
name: 'DynamicTemplate',
props: {
template: {
type: String,
required: true
},
classes: {
type: Object,
required: false,
default: () => {}
}
}
}
</script>
Here is the component being used.
<DynamicTemplate
:template="template"
:classes="classes"
/>

Dynamic components based on route params in vue-router

Is it possible in Vue to dynamically use components based on the value of a route parameter?
Here's an example of what I'm trying to do:
path: '/model/:modelName',
components: {
default: ModelDefinition.models.find((model) => model.key === $route.params.modelName),
},
The problem is: $route is not available here. Neither is modelName.
What might be nice if default could also be a function (similar to props):
path: '/model/:modelName',
components: {
default: (modelName) => (ModelDefinition.models.find((model) => model.key === $route.params.modelName)),
},
This function actually gets called to my surprise, but the argument seems to be some sort of function, and certainly not the modelName I'm hoping for.
The reason I need this is: there's actually a lot more configuration for this route, specifying lots of components and values for various things. If I have to specify that for every model, it's going to be long and repetitive, and would have to be expanded for every model we add to our system.
I'm not sure you have to "do magic" with vue-router - why don't you just handle the parameter inside your component (or create a container-component, and handle the parameter there)?
The snippet below shows that you can use external data to control what component should be displayed. If you pass your model parameter down, then any existing (and registered) component-name can be displayed.
const CompA = {
template: `<div class="border-blue">COMP-A</div>`
}
const CompB = {
template: `<div class="border-red">COMP-B</div>`
}
new Vue({
el: "#app",
components: {
CompA,
CompB
},
data: {
selected: ''
}
})
[class^="border-"] {
padding: 10px;
}
.border-blue {
border: 1px solid blue;
}
.border-red {
border: 1px solid red;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<div id="app">
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>CompA</option>
<option>CompB</option>
</select>
<br />
<span>Selected: {{ selected }}</span>
<br />
<br /> Here's a variable component - based on external data:
<component :is="selected"></component>
</div>
In components declaration, this is not available and hence you can not access the props and other instance data.
What you can do is create a computed property that returns a component. Like this:
<template>
<component :is="componentInstance" />
</template>
<script>
export default {
props: {
componentName: String,
},
computed: {
componentInstance() {
return () => import(`./modules/${this.componentName}/template.vue`);
},
},
};
</script>

:hover color in vue components from props

Some of my single-file components need to take hover color from props.
My solution is that i set css variables in the following way (the main part is in the mounted(){...})
<template>
<div class="btnWrapper" ref="btnWrapper">...</div>
</template>
...
...
props() {
color1: {type: String, default: 'blue'},
},
mounted () {
this.$refs.btnWrapper.style.setProperty('--wrapHoverColor', this.color1)
}
...
...
<style scoped>
.btnWrapper {
--wrapHoverColor: pink;
}
.btnWrapper:hover {
background-color: var(--wrapHoverColor) !important;
}
</style>
This solution seems kind of woowoo.
But maybe there is no better way with pseudo elements, which are hard to control from js.
Do you guys ever take pseudo element's properties from props in vue components?
You have two different ways to do this.
1 - CSS Variables
As you already know, you can create CSS variables from what you want to port from JS to CSS and put them to your root element :style attr on your components created hooks, and then use them inside your CSS codes with var(--x).
<template>
<button :style="style"> Button </button>
</template>
<script>
export default {
props: ['color', 'hovercolor'],
data() {
return {
style: {
'--color': this.color,
'--hovercolor': this.hovercolor,
},
};
}
}
</script>
<style scoped>
button {
background: var(--color);
}
button:hover {
background: var(--hovercolor);
}
</style>
2 - Vue Component Style
vue-component-style is a tiny (~1kb gzipped) mixin to do this internally. When you active that mixin, you can write your entire style section inside of your component object with full access to the component context.
<template>
<button class="$style.button"> Button </button>
</template>
<script>
export default {
props: ['color', 'hovercolor'],
style({ className }) {
return [
className('button', {
background: this.color,
'&:hover': {
background: this.hovercolor,
},
});
];
}
}
</script>

Use component based translations in child components in vue-i18n

I'm using vue-i18n to translate messages in my vue app. I have some global translations that are added in new VueI18n(...) as well as some component based translations in a component named c-parent. The component contains child components named c-child. Now, I would like to use the component based translations of c-parent also in c-child.
I made a small example in this fiddle: https://jsfiddle.net/d80o7mpL/
The problem is in the last line of the output: The message in c-child is not translated using the component based translations of c-parent.
Since global translations are "inherited" by all components, I would expect the same for component based translations (in their respective component subtree). Is there a way to achieve this in vue-i18n?
Well, you need to pass the text to child component using props.
Global translations are "inherited" by all components. But you're using local translation in child.
const globalMessages = {
en: { global: { title: 'Vue i18n: usage of component based translations' } }
}
const componentLocalMessages = {
en: { local: {
title: "I\'m a translated title",
text: "I\'m a translated text"
}}
}
Vue.component('c-parent', {
i18n: {
messages: componentLocalMessages
},
template: `
<div>
<div>c-parent component based translation: {{ $t('local.title') }}</div>
<c-child :text="$t('local.title')"></c-child>
</div>
`
})
Vue.component('c-child', {
props: ['text'],
template: `
<div>c-child translation: {{ text }}</div>
`
})
Vue.component('app', {
template: '<c-parent />'
})
const i18n = new VueI18n({
locale: 'en',
messages: globalMessages
})
new Vue({
i18n,
el: "#app",
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
}
h5 {
margin: 1em 0 .5em 0;
}
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vue-i18n"></script>
<div id="app">
<h2>{{ $t('global.title') }}</h2>
We define two Vue components: <code><c-child/></code> contained in <code><c-parent/></code>.
<code><c-parent/></code> defines some component based translations. We would like to use the
parent's translations in the child but it does not work.
<h5>Example:</h5>
<app />
</div>
What I'm doing is using i18n.mergeLocaleMessage in router.ts to merge a particular .i18n.json translation file (by setting a meta.i18n property) for each route:
const router = new Router({
[...]
{
path: '/settings',
name: 'settings',
component: () => import('./views/Settings.vue'),
meta: {
i18n: require('./views/Settings.i18n.json'),
},
},
[...]
});
router.beforeEach((to, from, next) => {
// load view-scoped translations?
if (!!to.meta.i18n) {
Object.keys(to.meta.i18n).forEach((lang) => i18n.mergeLocaleMessage(lang, to.meta.i18n[lang]));
}
next();
});
With Settings.i18n.json being like:
{
"en":
{
"Key": "Key"
},
"es":
{
"Key": "Clave"
}
}
That way, all child components will use the same translation file.
In case you can't use vue-router, maybe you can do it in the parent component's mounted() hook (haven't tried that)
I had the same situation with i18n.
Let's say we have a "card" object prop which it includes the needed language ( was my case) that we'll use in a CardModal.vue component which will be the parent.
So what i did was get the needed locale json file ( based on the prop language) and adding those messages within the card prop.
So in the parent component we'll have:
<template>
<div id="card-modal">
<h1> {{ card.locales.title }} </h1>
<ChildComponent card="card" />
</div>
</template>
<script>
export default {
name: 'CardModal',
props: {
card: {
type: Object,
required: true,
}
}
data() {
return {
locale: this.card.language, //'en' or 'es'
i18n: {
en: require('#/locales/en'),
es: require('#/locales/es'),
},
}
},
created() {
this.card.locales = this.i18n[this.locale].card_modal
}
}
</script>
Notice that we are not relying in the plugin function anymore ( $t() ) and we are only changing the locale in the current component. I did it in this way cause i didn't want to use the "i18n" tag in each child component and wanted to keep all the locales messages in one single json file per language. I was already using the card prop in all child components so that's why i added the locales to that object.
If you need a way to change the locale using a select tag in the component, we can use a watcher for the locale data property like the docs shows

Understanding export statement in Vue

I am coming from React and Vue frankly seems to be way different to me even from Javascript prospective.
From the boiler plate code (HelloWorld.vue), Say we have the following code snippet
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br />
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
>vue-cli documentation</a
>.
</p>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
</style>
Here, I am unable to comprehend how does our export default knows What we need to export
Since the question was marked closed due to duplicate, Sharing the difference between the both
Consider, In case of react (or take it as any function) if we created a function
const someFunc = () => (
<div>
<h1> Test Component </h1>
</div>
)
export default someFunc
Here i am exporting my somefunc which I can import and use it with whatever way I want in other component
import Whatever from "./location"
But in the above Vue snippet, I cannot comprehend the significance of
export default {
name: "HelloWorld",
props: {
msg: String
}
};
What is the significance of exporting this object?
Also, where would If suppose I need to will I write my functions, class?
Everything inside the curly braces on export default {} including methods and data will be exported, and can be imported using import like in your example on React
import Whatever from "./location"
Just to answer your question
Also, where would If suppose I need to will I write my functions, class?
Methods and other data can be included inside the curly braces on export default {}. For example,
<script>
export default {
name: "HelloWorld",
props: {
message: {
type: String
}
},
data() {
return {
// put any data here, e.g.,
isVueDeveloper: true
}
},
methods: {
// put any methods here, e.g.,
firstMethod() {
return;
}
}
}
</script>