How to replace a string with a component (vue) - javascript

I have strings that contains ### and I am replacing with array values. Now I want to use them with a component, I created the component and it works but I don't know how to use it in the strings. I don't want to wrap them manually because I don't know how the strings will be, it can have several ###. If it has 2 ###, options will have 2 subArrays.
What is the better way to do it?
Code: https://jsfiddle.net/tsobh4nu/
Vue.component('opt', {
template: `<label>
<span class="bold" v-for="(str,idx) in options">
{{str + " / "}}
</span>
</label>`,
props:{options: Array}
})
new Vue({
el: '#app',
data: {
str: "I have ### and you have a ###.",
options: [
['AAA', 'BBB', 'CCC'],
['XXX', 'YYY', 'ZZZ']
]
},
computed:{
replacedStr(){
let newStr = this.str;
this.options.forEach(option=>{
newStr = newStr.replace('###',this.concatenateOptions(option));
})
return newStr;
}
},
methods: {
concatenateOptions(strArr) {
let separator = "";
let strOptions = "";
strArr.forEach(word => {
strOptions += separator + word;
separator = " / ";
});
return strOptions;
}
}
})
.bold {
font-weight: bold
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<p>I want something like this, but using components: </p>
{{replacedStr}}
<br>
<hr>
My Components:<br>
<opt :options="options[0]"></opt>
<br>
<opt :options="options[1]"></opt>
</div>
Many thanks.

This is more general, but I hope it will help someone. Add a dynamic component in your template: <component v-bind:is="processedHtml"></component>.
Then add a computed method:
computed: {
processedHtml () {
let html = this.html.replace('[Placeholder]', '<my-component></my-component>');
return {
template: '<div>' + html + '</div>'
}
}
}
Where <my-component> is your custom component and the this.html is your HTML content that contains the placeholder [Placeholder].
It is important to return an element that has one root node. That's why the return is wrapped with <div>.
Read more advanced tutorial about this issue here in my blog. For example, to pass props to <my-component>

I just experienced the same issue. I had an element that needed to display the current count of an item. The current count came from the store and was constantly changing. I used v-text. I know this is pretty situation specific, but hopefully it helps someone down the line.
<P id="results_count" v-text="current_count"></P>
and in the data portion of the component I had a property named current_count that was updated via methods.

Related

Vue.js v-for not rendering component

I have the following issue, for some reason, the v-for will not render at all. Please find the fiddle here https://jsfiddle.net/tadeyemi/k6s4gv85/ I have absolutely no idea why it isn't working. Someone care to shed some light?
<div id="app">
<h1>Finds</h1>
<div>
<input ref="option">
</div>
<button v-if #click="addFind">
New Find
</button>
<p v-for="(option,idx) in options.slice(1)">
<span #click="removeOption(idx+1)">Option{{idx+1}}: {{option}}</span>
</p>
</div>
and the JavaScript as follows:
new Vue({
el: '#app',
data: {
options: [],
count:0
},
methods: {
addFind: function () {
var msg = this.$refs.option.value;
console.log(this.options);
if( msg.trim() != "" ){
this.count++;
var i = this.count;
this.options[i]= this.$refs.option.value.trim();
}
},
removeOption:function(index){
this.options.splice(index,1);
this.count--;
}
}
});
There are some issues with your code, but the most prominent is that you break some reactivity rules explained here: https://v2.vuejs.org/v2/guide/reactivity.html#For-Arrays
Vue cannot detect the following changes to an array:
When you directly set an item with the index, e.g.
vm.items[indexOfItem] = newValue When you modify the length of the
array, e.g. vm.items.length = newLength
Basically: this.options.push(msg.trim()); would work, while this.options[i]= this.$refs.option.value.trim(); won't
I edited the fiddle a little to make it work: https://jsfiddle.net/63jyw7gz/

Add span-tags around the text(string), that included in an object

Hi I'm trying to change the background color of the text that is included an the object. I'm trying it in vuejs.
I've text(string) and some data(object).
My requirement is, if the word is included in the object. Then I need to change the background color of that particular word.
From the response I'll get the text like this:
text: "Pavan's\nPavan\nKumar Dasireddy is\ntrying to\nadd\nthe span tag\nto the text\n That is\n in an Object.".
Calling the nb2html(this.theText) function in the <span id="test" v-html="nb2html(this.text)"> </span> for replacing the \n with <br> tag.
After Replacing with <br> I can see the UI as below:
So, when the page loaded the included text should be highlighted with some background color.
sanbox link: Sandbox link
Eg: In the below code, Kumar is included in the object, so I need to change the background color for Kumar only.
I tried mapping the object and checking the text with myObject.includes(${searchText}).
If includes then tried to replace the text with the <span style="background-color: yellow">${searchText}</span>.
But span tag not adding to the text.Here is my code sample:
<template>
<span>
<el-row type="flex">
<el-col>
<el-card>
<span id="test" v-html="nb2html(this.text)">
</span>
</el-card>
</el-col>
</el-row>
</span>
</template>
<script>
export default {
name: 'samplefile',
data() {
return {
theText: '',
text: "Pavan's\nPavan\nKumar Dasireddy is\ntrying to\nadd\nthe span tag\nto the text\n That is\n in an Object.",
myObject: {
name: 'Kumar',
trying: 'add',
isGood: 'the text which is on tag form of',
mobile: '9874563210',
concept: 'trying',
user:'Pavan',
}
};
},
watch: {
document: 'caluclateBoundingBoxes',
selectedText: 'hideContextMenu',
},
mounted() {
this.caluclateBoundingBoxes();
},
methods() {
nb2html(text) {
const mainText = text.replace(/\n/g, '<br>');
this.theText = mainText;
Object.keys(this.myObject).map((key, index) => {
const searchText = `${this.myObject[key]}`;
const n = this.theText.includes(`${searchText}`);
console.log(`index:${index}`, `${key}: ${searchText}`, n, 'position:', this.theText.indexOf(`${searchText}`), 'length:', searchText.length);
const repText = `<span style="background-color: yellow">${searchText}</span>`;
this.theText = this.theText.replace(/${searchText}/i, repText);
return this.theText;
});
return this.theText;
},
caluclateBoundingBoxes() {
if (this.myObject) {
this.myObject = JSON.parse(JSON.stringify(this.myObject));
console.log('the text is:', this.theText);
console.log(this.myObject);
}
},
}
}
</script>
Please suggest me the possible way to achieve this. Thanks!!
Adding this as a second argument to map should fix this:
Object.keys(this.myObject).map((key, index) => {
...
}, this)
There is a scope issue where the this in map declaration is not scoped to the Vue component.
I don't use vue.js here and perhaps you have different logic, but hopefully this plain javascript snippet could help:
var myText = "Pavan's Pavan Kumar Dasireddy is trying to add the span tag to the text that is in an Object."
var myObject = {
name: 'Kumar',
trying: 'add',
isGood: 'the text which is on tag form of',
mobile: '9874563210',
concept: 'trying',
user:'Pavan'
}
var result = myText.split(' ').map(function(word) {
if (Object.values(myObject).includes(word)) {
return '<span class="yellow">' + word + '</span>'
} else {
return word
}
})
var e = document.getElementById('result')
e.innerHTML = result.join(' ')
.yellow {
background-color: yellow;
}
<div id="result"></div>
If the above snippet is unavailable check this codepen.

Feed Outside JS object into Vue.js component

In my JS I have list of products.
var productData = [{
title: 'Bike',
price: 300
},{
title: 'Guitar',
price: 199
}]
This data is gathered from remote Json in a Async call. So I have to do this in JS.
Eventually, I come to a situation, that I need to display this product. But here are 2 things.
1) In HTML I am outside of my main Vue instance scope. It is so because there is 3rd party API that interacts with those products, apart other things... So This array is not bound to Vue instance in any way.
2) This product list is dynamically created, on the fly. In order to display it, I have to do a good old html string concatenation and then apply it.
Main problem:
Now whenever I need to display a product - I have a Vue.js Component for it. That has a template, and all data goes really nicely. I obviously want to reuse this same template.
So right now I have something like this:
//Vue.js Component, that I want to use, and feed my product to:
Vue.component('product', {
props: ['product'],
template: `
<div class="prod">
<h3>{{product.title}}</h3>
<p>{{product.price}} €</p>
</div>
`, data() {
return {
}
}
});
Next in JS, I have this code:
var prodList = getProductsAsync("3rd party service URL and parameters");
var prodHtml = '';
for(var i = 0; i < prodList.length; i++){
prodHtml += `
<div class="prod">
<h3>${prodList[i].title}</h3>
<p>${prodList[i].price} €</p>
</div>
`;
}
Here I have 2 templates (one as JS html, and another in Vue.js component), that is redundant.
I need to have 1 way to maintain this template. At the moment template is simple, but more functionality will shortly come.
Idealy I would like my JS object to use my Vue.js template. As a result it would also behave as Vue.js component too.
So in my JS I would like to have it like this (But this obviously does not work):
var prodList = getProductsAsync("3rd party service URL and parameters");
var prodHtml = '';
for(var i = 0; i < prodList.length; i++){
prodHtml += ` <product :product='prodList[i]'></product>`; // attempt to use Vue.js component 'on the fly' but this does not work!
}
Any suggestions?
It doesn't work, because there is some order in which you have to create component, html, and Vue instance.
// just use all your code together
// first, prepare the component
Vue.component('product', {
props: ['product'],
template: `
<div class="prod">
<h3>{{product.title}}</h3>
<p>{{product.price}} €</p>
</div>
`
})
// and get products
var prodList = getProductsAsync('/someMountpoint')
// then generate html with unknown tags "product"
var prodHtml = '<div id="productList">'
for(let i = 0; i < prodList.length; i++) {
prodHtml += `<product :product='prodList[i]'></product>`
}
prodHtml += '</div>'
// append this html to DOM
$('#someWhere').html(prodHtml)
// and now create new Vue instance to replace
// these unknown product tags with real tags
new Vue({
el: '#productList'
})

How to provide dynamic className to element in React Class render method

I have a ReactClass with name Alert. Its render method returns a div with class alert alert-success or alert alert-error according to the type passed while creating element. I just want to know how to add class based on the type of alert element.
Here is my attempt:
var Alert = ReactClass({
render: function() {
return <div className="alert {this.props.type}">{this.props.message}</div>
}
});
var successAlert = React.createElement(Alert, {
type: 'alert-success'
message: 'Information saved successfully!!'
});
When JSX Template is compiled this.props.type is not converted to the class passed to element. How to achieve this ?
Looks like I have found answer to my question. We can simply do something like this:
var Alert = ReactClass({
render: function() {
return <div className={"alert " + this.props.type}>{this.props.message}</div>
}
});
Just put your classes inside Template evaluators { } in this case. Create your class string based on your props and states.
Hope this is helpful to others.
One way to accomplish this is to have a string which will contain all of your classes and then set it to the Component's className:
var Alert = ReactClass({
var yourClassName = 'alert ';
// Add any additional class names
yourClassName += this.props.type + ' ';
render: function() {
return <div className={yourClassName}>{this.props.message}</div>
}
});
or alternatively you can store your class names in an array and convert it to a class friendly string when you're ready to use it:
var Alert = ReactClass({
var yourClassArray = [];
// Add any additional class names
yourClassArray.push('alert');
yourClassArray.push(this.props.type);
var classString = yourClassArray.join(' ');
render: function() {
return <div className={classString}>{this.props.message}</div>
}
});
Take a look at the classnames package. You can do stuff like this:
className={classNames('alert', `alert-${type}`)}
or
className={classNames({
'alert': true,
'alert-success': success,
'alert-error': error
})
You can use JavaScript template literals
var Alert = ReactClass({
render: function() {
return <div className={`alert ${this.props.type}`}>{this.props.message}</div>
}
});
Your code can be written in following way:
const Alert = ({type, message}) =>
<div className={`alert ${type}`}>{message}</div>
Write in code
className={`form-control-sm d-inline per_player ${"per_player_b_" + index + "_score"}`}
and You will get

mark search string dynamically using angular.js

How can I mark my search pattern dynamically in my html?
Example:
I'm using angular and my html looks like this:
<div>
<input type="text" ng-model="viewmodel.searchString"/>
<!--Moving over all phrases-->
<div ng-repeat="phrase in viewmodel.Phrases">
{{phrase.title}}
</div>
</div>
I want the string matching pattern will be mark on every change in search string.
Can you help me?
Angular UI is a great choice. You can also do it with filter like: http://embed.plnkr.co/XbCsxmfrgmdtOAeBZPUp/preview
The essence is as commented by #Hylianpuffball, dynamically create styled 'span' tags for the matches.
.filter('highlight', function($sce) {
return function(text, phrase) {
if (phrase) text = text.replace(new RegExp('('+phrase+')', 'gi'),
'<span class="highlighted">$1</span>')
return $sce.trustAsHtml(text)
}
})
And use it like:
<li ng-repeat="item in data | filter:search.title"
ng-bind-html="item.title | highlight:search.title">
</li>
Just in case that someone (like me a moment ago) needs this for angular2:
highlight-pipe.ts:
import {Pipe, PipeTransform} from '#angular/core';
#Pipe({name: 'highlightPipe'})
export class HighlightPipe implements PipeTransform{
transform(text:string, filter:string) : any{
if(filter){
text = text.replace(new RegExp('('+filter+')', 'gi'), '<span class="highlighted">$1</span>');
}
return text;
}
}
and use it like this:
at top of file:
import {HighlightPipe} from './highlight-pipe';
in template where 'yourText' is the original text and 'filter' is the part you want to highlight:
<div [innerHTML]="yourText | highlightPipe: filter"/>
in component:
pipes: [HighlightPipe]
EDIT:
I updated it for RC 4
and created a plunker for testing:
http://plnkr.co/edit/SeNsuwFUUqZIHllP9nT0?p=preview
Try Angular UI
They have a highlight directive. You can use it as a reference to make your own (or just use it directly).
Inspired by #tungd's answer but valid for multiple search terms.
.filter('highlight', function($sce) {
return function(text, phrase) {
if (phrase){
phrases = phrase.split(" ");
for(i=0;i<phrases.length;i++)
text = text.replace(new RegExp('('+phrases[i]+')', 'gi'),'~~~~~$1%%%%%')
text = text.replace(new RegExp('('+'~~~~~'+')', 'gi'),'<span class="bold greenTxt">');
text = text.replace(new RegExp('('+'%%%%%'+')', 'gi'),'</span>')
}
return $sce.trustAsHtml(text)
}
});
PS: One can always limit the input to be in non-special chars for this to be 100% bullet-proof.

Categories

Resources