I'm looking to generate popover highlight tags on data that I've gotten from a query. Currently, I've tried finding substrings wrapped in curly braces within the text, then replacing them with components, but I can't figure out how to get Vue to render & mount the new components
Example text:
Lorem {ipsum} dolor sit amet, consectetur {adipiscing} elit, sed do eiusmod tempor incididunt
I've created a fairly simple regex to find and grab text wrapped in curly braces:
\{(.[^{}]*)\}
And this is the code I've tried so far:
<template>
<span v-html="paragraphText"></span>
</template>
<script lang="ts">
import {MyComponent} from '#components';
export default defineComponent({
data () {
return {
paragraphText: '',
}
},
created () {
const re = /\{(.[^{}]*)\}/g;
let newText = `${this.text}`;
let match: RegExpExecArray;
do {
match = re.exec(newText);
if (match) {
newText = newText.replace(
match[0],
`<MyComponent ref='${match[1]}' style='font-weight:bold;'>${match[1]}</MyComponent>`
);
}
} while (match);
this.paragraphText = newText;
},
components: {
MyComponent,
}
})
</script>
I'm well aware that v-html
won't render components to avoid XSS attacks, but this is mostly just to show what I'm trying to achieve.
My reasoning behind having a component behind each one, is that I want the component to make a web request on hover, to grab some additional information about the word being highlighted.
I'm looking for the cleanest and/or most efficient solution possible - it doesn't have to be Options API
CodePudding user response:
Not sure if this is clean but it should do what you require - sorry, composition API, but it's so simple its trivial to convert to Options API
const { createApp, ref, computed } = Vue;
createApp({
setup() {
const data = ref("Lorem {ipsum} dolor sit amet, consectetur {adipiscing} elit, sed do eiusmod tempor incididunt");
const magic = computed(() => data.value.split('}').map(v => v.split('{')));
return { data, magic };
}
}).mount('#app');
.this.would.be.your.component.rather.than.a.span {
background-color :#ffdddd;
}
<script src="https://unpkg.com/vue@next"></script>
<div id="app">
<span v-for="([text, tag], i) in magic" :key="i">
<span v-if="text.length">{{ text }}</span>
<span v-if="tag?.length" :title="`this is tag: ${tag}`">{{ tag }}</span>
</span>
</div>
CodePudding user response:
v-html interprets text as raw html, without doing any data binding, meaning it doesn't create any components. Maybe you want dynamic components instead?
On the topic at hand, if you want to highlight some matched text, I did it this way by using a mark html tag
const highlightText = (word: string, search: RegExp): string => {
return word.replace(search, function (matchedText) {
return '<mark >' matchedText '</mark>';
});
};
And in template, I used v-html on object's field.
<span v-if="isHighlighted" v-html="object.text"></span>
<span v-else>{{ object.text }}</span>
Now, in your case, you could go over your text, create a new object for each word with a boolean flag that indicates whether the word should be highlighted or not, and based on that iterate through that list with v-for directive, if it should be highlighted, use MyComponent otherwise use a span.
<div v-for="(word, index) in words" :key="index">
<MyComponent v-if="word.isHighlighted" :data="word.text" />
<span v-else>{{ word.text }}</span>
</div>
Resources: