HTML entities in Vue filters - vuejs2

I'm new to Vue and currently going through a tutorial.
I came across the following:
Vue.filter('currency', function (value) {
return '$' + value.toFixed(2);
});
I thought I'd change the $ sign to a British pound sign. Knowing that, just entering the pound sign is not guaranteed to display properly everywhere, I used the html entity instead £
But it seems that Vue renders the & as & giving an end result of £ which displays as £ and not as £
How do I get round this?
If it helps, here is the html that uses the filter:
<span>{{service.price | currency}}</span>

The string returned by the filter will be treated as plain-text, so if you include &, <, etc. they'll be escaped to entities.
Rather than trying to use entity escaping inside the filter you can use JavaScript string escaping. e.g.
Vue.filter('currency', function (value) {
return '\u00a3' + value.toFixed(2);
});
Note that '\u00a3' will create a single character. You won't see £ in the DOM but that won't be a problem. It dodges any problems with character encoding in the source file but it is effectively equivalent to writing '£'.

for the £ it should work
Vue.filter('currency', function (value) {
return '£' + value.toFixed(2);
});
because vue.js should handle the converting by itself.
EDIT:
If you wanna use other special signs, you can use those 2 methods i found
Vue.filter('currency', function(value) {
let sign = '£';
let parser = new DOMParser();
let dom = parser.parseFromString('<!doctype html><body>' + sign, 'text/html');
return dom.body.textContent + value.toFixed(2)
})
new Vue({
el: "#app",
methods: {
domParser() {
let sign = '£';
let parser = new DOMParser();
let dom = parser.parseFromString('<!doctype html><body>' + sign, 'text/html');
return dom.body.textContent;
},
innerHTML() {
let sign = '£';
var textArea = document.createElement('textarea');
textArea.innerHTML = sign;
return textArea.value;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
{{ 25 | currency}}
<br><br> {{ domParser() }}
<br><br> {{ innerHTML() }}
</div>

Related

How to handle event on html render by computed function in vue js?

I'm using vue 2. I have a text get from api.
"Hello everyone! My name is [input]. I'm [input] year old".
Now, I have to replace the [input] with an html input and handle the onKeyUp for this input.
What I have to do?
I used computed render html, but it not work with v-on:xxx.
content.replaceAll('[answer]', '<input type="text" class="input_answer" v-on:click="handleInput()"/>')
Thanks!
After spending an hour and so on this requirement, I came up with the solution.
Here you go (I added all the descriptive comments/steps in the below code snippet itself) :
// Template coming from API
var textFromAPI = "<p>Hello everyone! My name is [input]. I'm [input] year old</p>";
// getting the array of input tags. So that we can loop and create the proper input element.
const matched = textFromAPI.match(/(input)/g);
// Iterating over an array of matched substrings and creating a HTML element along with the required attributes and events.
matched.forEach((el, index) => {
textFromAPI = textFromAPI.replace('[input]', `<input type="text" id="${index + 1}" v-model="inputValue[${index}]" v-on:keyup="getValue"/>`);
})
// Here, we are compiling the whole string so that it will behave in a Vue way.
var res = Vue.compile(textFromAPI)
var app = new Vue({
el: '#app',
data: {
compiled: null,
inputValue: []
},
render: res.render,
staticRenderFns: res.staticRenderFns,
mounted() {
setTimeout(() => {
this.compiled = res;
})
},
methods: {
getValue() {
// Here you will get the updated values of the inputs.
console.log(this.inputValue);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
</div>
Thanks # Rohit Jíndal, but!
When I use vue2 and it doesn't work. And there is an error:
TypeError: Cannot read properties of undefined (reading 'string')
I build this to a component and use anywhere in my project.
<render-html :text="question.quest_content" #handleAnswer="handleAnswer"></render-html>
I used and it's work.
this.$options.staticRenderFns = string.staticRenderFns;
return string.render.bind(this)(h)
Thanks so much!

Vue JS Filter for Title Case

I am absolutely new to Vue JS so please forgive me if my question sounds dumb.
I am learning to create string filters in Vue JS. I was able to convert my string to uppercase using following code.
var app=new Vue({
el:'#app',
data:{
message:'hello world',
},
filters:{
uppercase(value){
return value.toUpperCase();
},
}
})
Now I am trying to make a filter to convert my message to Title Case. eg. hello world to Hello World
I have tried many things like:
filters:{
upper(value){
value.toLowerCase().split(' ');
return value.charAt(0).toUpperCase()+value.slice(1);
}
}
But I am unable to create a filter correctly. How can we use a for loop inside Vue JS? Or is there any other way to accomplish the Title Case?
filters: {
titleize(value){
return value.replace(/(?:^|\s|-)\S/g, x => x.toUpperCase());
}
}
Filters/ Mappers, can be simple JS methods who receives some param, apply operation on it and returns a value.
I believe you really don't need a JS method for this,
Try to apply the text-transform CSS style to HTML.
eg: ( text-transform: capitalize );
h1 { text-transform: capitalize; }
<h1>hello world </h1>
Use JS approach when absolutely necessary.
If the value is in uppercase, you can use it this way:
Vue.filter("TitleCase", value => {
return value.toLowerCase().replace(/(?:^|\s|-)\S/g, x => x.toUpperCase());
});
Thanks #skirtle. I used the below code.
filters:{
upper(str){
str = str.toLowerCase().split(' ');
let final = [ ];
for(let word of str){
final.push(word.charAt(0).toUpperCase()+ word.slice(1));
}
return final.join(' ')
}
}
This worked like a charm. :)

How to preserve \n on front-end when fetching data from firebase database?

I'm trying to fetch the data from Firebase Realtime database with \n preserved on front-end.
With css property: "white-space: pre-line", I'm able to send \n to firebase database, but when I'm trying to fetch the data on front-end using Vue.js it's not maintaining the \n.
Firebase database is an array so, I tried to convert the array to the string and it converts "\n" to the white space. I didn't find any online help how to preserve \n while displaying data on front-end
My firebase database:
answers
0: "some data"
1: "some data. more data.\nsomething"
I'm using for loop to display each answer to new line. But splits each key-value to new line not data with "\n".
Any help or guidance is appreciated.
You could replace the \n with <br/> and display your it as html.
let str = "some data. more data.\nsomething";
str = str.replace('\n', '<br/>');
// "some data. more data.<br/>something"
And then display it using the v-html directive.
<span v-html="str"></span>
This will display your content as html.
However : dynamically rendering arbitrary HTML on your website can be very dangerous because it can easily lead to XSS vulnerabilities. Only use HTML interpolation on trusted content and never on user-provided content.
See here :
https://v2.vuejs.org/v2/guide/syntax.html#Raw-HTML
Example :
Vue.config.devtools = false;
Vue.config.productionTip = false;
var app = new Vue({
el: '#app',
data: {
answers: ["some data", "some data. more data.\nsomething"]
},
computed: {
transformed_answers() {
return this.answers.map(a => a.replace('\n', '<br/>'));
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="answer in transformed_answers">
<p v-html="answer"></p>
</div>
</div>

Display a list in leaflet popup

I want to display an unorderd list or table in a leaflet popup.
The number of items and their content are different and depend on the type of element which was clicked.
So ideally the popup content should be created on the click event.
I tried to build the list inside the bindPopup function, but it's not working.
L.marker([mapElement.y * -1, mapElement.x], {
uniqueID: mapElement.elementID,
mapIconWidth: mapElement.width,
icon: new mapIcon({
iconUrl: icon.mapIcon.imageData,
iconSize: [elementSize, elementSize]
})
})
.addTo(markers)
.bindPopup(mapElement.element.nbr + ' ' + mapElement.element.name + "<br/<ul> <li v-for='state in mapElement.element.states'>{{ state.definition.stateTypeTitle }}</li> </ul>");
That's the output:
Any ideas would be great!
Thanks!
Edited code (get this error message: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.):
LoadMarker(mapID) {
console.log("Load Markers");
map.removeLayer(markers);
markers.clearLayers();
fetch(this.$apiUrl + "/mapelements/" + mapID)
.then(response => response.json())
.then(received => {
let mapElements = received;
let mapZoom = map.getZoom();
mapElements.forEach(function(mapElement) {
let elementSize = (mapElement.width / 8) * mapZoom;
let popup = new Vue({
template:
mapElement.element.nbr +
" " +
mapElement.element.name +
"<br/<ul> <li v-for='state in mapElement.element.states'>{{ state.definition.stateTypeTitle }}</li> </ul>",
data: {
mapElement
}
}).$mount().$el;
let icon = mapIconSchemas.find(
schema => schema.mapIconSchemaID == mapElement.mapIconSchemaID
);
if (icon != null) {
L.marker([mapElement.y * -1, mapElement.x], {
uniqueID: mapElement.elementID,
mapIconWidth: mapElement.width,
icon: new mapIcon({
iconUrl: icon.mapIcon.imageData,
iconSize: [elementSize, elementSize]
})
})
.addTo(markers)
.bindPopup(popup);
}
});
});
map.addLayer(markers);
},
You can not use Vue templating syntax in the HTML String for the popup. But as can be seen from the docs the .bindPopup method can also accept HTML element. So your way to go would be like this:
first create the popup element:
let popup = new Vue({
template: mapElement.element.nbr + ' ' + mapElement.element.name + "<br/<ul> <li v-for='state in mapElement.element.states'>{{ state.definition.stateTypeTitle }}</li> </ul>",
data: {
mapElement
}
}).$mount().$el
and then use it in the .bindPopup method:
/*...*/
.bindPopup(popup)
There is a solution, if you want to use the vue templating engine to fill the popup content.
I explained it for this question.
You create a component with the content you want to display in the popup, but you hide it :
<my-popup-content v-show=False ref='foo'><my-popup-content>
Then you can access the generated html of that component in your code like this :
const template = this.$refs.foo.$el.innerHTML
and use it to fill your popup.
The big advantage of that method is that you can generate the popup content with all the vue functionalities (v-if, v-bind, whatever) and you don't need messy string concatenations anymore.
import Component from './Component.vue'
import router from './router'
import store from './store'
bindPopup(() => new Vue({
// router,
// store,
render: h => h(Component)
}).$mount().$el)
perfect

What is the mechanism of calling a function from a template in Vue js

I am trying to learn Vue.js. I am following a tutorial on this site https://scrimba.com/p/pZ45Hz/c7anmTk. From here I am not getting something clear.
Here is the code below and my confusion as well :
<div id="app">
<wizard :name="harry" :cast="oculus_reparo" ></wizard>
<wizard :name="ron" :cast="wingardium_leviosa"></wizard>
<wizard :name="hermione" :cast="alohomora" ></wizard>
</div>
// emojify returns the corresponding emoji image
function emojify(name) {
var out = `<img src="emojis/` + name + `.png">`
return out
}
// cast returns a spell (function) that decorates the wizard
function cast(emoji) {
var magic = emojify("magic")
return function (wizard) {
return wizard + " " + magic + " " + emoji + " " + magic
}
}
Vue.component("wizard", {
props: ["name", "cast"],
template: `<p v-html="cast(name)"></p>`
})
var app = new Vue({
el: "#app",
data: {
harry : emojify("harry" ),
ron : emojify("ron" ),
hermione : emojify("hermione")
},
methods: {
// oculus_reparo returns a spell (function) that repairs glasses
oculus_reparo: cast(emojify("oculus-reparo")),
// wingardium_leviosa returns a spell (function) that levitates an object
wingardium_leviosa: cast(emojify("wingardium-leviosa")),
// alohomora returns a spell (function) that unlocks a door
alohomora: cast(emojify("alohomora"))
}
})
So far what I have got is that, I have created a component named wizard which takes two properties - name and cast. name is getting the value from data, and so far I understand that cast is calling the method with a parameter.
So both of them should return their specific image. My first confusion: Where does wizard come from and how is it showing the data.name image? If it is because of the method call in the template then why does emoji return another image?
I think the example is unnecessarily complex for the ideas you're looking to learn.
wizard is being globally registered with Vue by Vue.component("wizard", ...). When Vue interprets each wizard call in the template it will replace it with <p v-html="cast(name)"></p> which is set in the wizard component definition. Here name gets mapped to the property that is set via :name=. v-html is just saying to render as html the return value of cast(name), here cast is the function property that is passed to the component and not the cast function locally defined. Everything after that happens as you would expect where emojify returns a template literal that is passed to cast, that then returns a function, which combines the emoji and other properties.