VueJS2 v-html with filter - vuejs2

How to display raw html with filter?
I have something like this:
K.json = function( json ) {
if( typeof json!='string' ) json = JSON.stringify( json, null, 2 );
json = json.replace( /</g, '<' ).replace( />/g, '>' ); // replace(/&/g, '&')
var pattern = /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g;
var html = json.replace( pattern, function( match ) {
var cls = 'number';
var suffix = '';
if( /^"/.test( match ) ) {
if( /:$/.test( match ) ) {
cls = 'key';
match = match.slice( 0, -1 );
suffix = ':'
} else {
cls = 'string';
}
} else if( /true|false/.test( match ) ) {
cls = 'boolean';
} else if( /null/.test( match ) ) {
cls = 'null';
}
return '<span class="' + cls + '">' + match + '</span>' + suffix;
} );
return html;
};
Vue.filter( 'json', K.json );
And use them something like this:
<div v-html="thecolumn | json"></div>
It shows a warning and displayed incorrectly:
vue.js:523 [Vue warn]: Property or method "json" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
(found in root instance)
I also tried solution from forum: https://laracasts.com/discuss/channels/vue/use-a-filter-custom-filter-in-v-html-property?page=1
<p v-html="this.$options.filters.json(description)"></p>
It shows error:
[Vue warn]: Error when rendering root instance:
vue.js:3063 Uncaught TypeError: Cannot read property 'filters' of undefined
at eval (eval at makeFunction (vue.js:8260), <anonymous>:2:2975)
at Proxy.renderList (vue.js:3158)
at Proxy.eval (eval at makeFunction (vue.js:8260), <anonymous>:2:2169)
at Vue$3.Vue._render (vue.js:3054)
at Vue$3.<anonymous> (vue.js:2430)
at Watcher.get (vue.js:1661)
at new Watcher (vue.js:1653)
at Vue$3.Vue._mount (vue.js:2429)
at Vue$3.$mount (vue.js:6000)
at Vue$3.$mount (vue.js:8327)
What's the correct way to do this on VueJS2?

For completeness, in Vue 2 you have some options, like:
v-html="$options.filters.FILTERNAME(args)" or
:inner-html.prop="args | FILTERNAME" or
v-html="METHODNAME(args)", if you create a method.
For Vue 3, filters are no longer supported. So the only options are method (or a computed prop).
More info in the demos below.
Vue 2:
function json(text) {
// do your magic
return text.toUpperCase(); // just for demo
}
Vue.filter('json', function (value) {
return json(value);
})
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
},
methods: {
jsonMethod(v) {
return json(v); // create a "proxy" to the outer function
}
}
})
<script src="https://unpkg.com/vue#2"></script>
<div id="app">
<p v-html="$options.filters.json(message)"></p>
<p :inner-html.prop="message | json"></p>
<p v-html="jsonMethod(message)"></p>
</div>
Vue 3:
In Vue.js 3 filters are no longer supported. Therefore the only options left is to use a method or a computed property:
function json(text) {
// do your magic
return text.toUpperCase(); // just for demo
}
const { createApp } = Vue
createApp({
data() {
return {
message: 'Hello Vue.js!'
}
},
methods: {
jsonMethod(v) {
return json(v); // create a "proxy" to the outer function
}
},
computed: {
transformedMessage() {
return json(this.message)
}
}
}).mount('#app')
<script src="https://unpkg.com/vue#3/dist/vue.global.js"></script>
<div id="app">
<p v-html="jsonMethod(message)"></p>
<p v-html="transformedMessage"></p>
</div>

For me, as #acdcjunior pointed out, this worked:
<p v-html="$options.filters.json(description)"></p>
Besides that, I had two filters to apply, and it also worked:
<p v-html="$options.filters.filter1($options.filters.filter2(description))"></p>

The issue is that your HTML is processed before the filter is added to your Vue instance. Try like this:
JSFiddle
var jsonFormatter = function(json){
if( typeof json!='string' ) json = JSON.stringify( json, null, 2 );
json = json.replace( /</g, '<' ).replace( />/g, '>' ); // replace(/&/g, '&')
var pattern = /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g;
var html = json.replace( pattern, function( match ) {
var cls = 'number';
var suffix = '';
if( /^"/.test( match ) ) {
if( /:$/.test( match ) ) {
cls = 'key';
match = match.slice( 0, -1 );
suffix = ':'
} else {
cls = 'string';
}
} else if( /true|false/.test( match ) ) {
cls = 'boolean';
} else if( /null/.test( match ) ) {
cls = 'null';
}
return '<span class="' + cls + '">' + match + '</span>' + suffix;
} );
return html;
}
new Vue({
el: '#app',
data(){
return {
jsonData: {dog: 'woof', nestedObject: {cat: 'meow'}}
}
},
filters: {
jsonFormatter: jsonFormatter
}
});
//Vue.filter( 'jsonFormatter', jsonFormatter ); // Doesn't work becuase happens after html is processed
<div id="app" class="container">
<h1>
v-html directive
</h1>
<div v-html="this.$options.filters.jsonFormatter(jsonData)"></div>
</div>

If you're just displaying the data then create a method:
json(jsonable) {
return jsonedValue;
}
then in the html
<div v-html="json(mydata)"></div>

Related

Update v-html without misbehaving focus on typing VUE JS

I need help,
Requirement
when the user types in an input box I want to highlight the link with blue color if any
My Research
when I dig into it, I realize that without using a contenteditable div it's not possible to do, also there is no v-model associated with contenteditable div I am manually updating the state.
so far I have this, courtesy- contenteditable div append a html element and v-model it in Vuejs
<div id="app"><div class="flex">
<div class="message" #input="updateHtml" v-html="html" contenteditable="true"></div>
<br>
<div class="message">{{ html }}</div>
</div>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
html: 'some text',
},
methods: {
updateHtml: function(e) {
this.html = e.target.innerHTML;
},
renderHtml: function(){
this.html += '<img src="https://cdn-images-1.medium.com/max/853/1*FH12a2fX61aHOn39pff9vA.jpeg" alt="" width=200px>';
}
}
});</script>
Issue
every time user types something, the focus is misbehaving which is strange to me, I want v-html to update along with user types #keyup,#keydown also have the same behavior.it works ok on #blur #focusout events, but that's not what I want
Appreciate Help.Thanks
I figured it out myself. Posting the answer so that may help other developers. v-HTML doesn't do all the trick. You’ll need to store the cursor position so it can be restored properly each time the content updates as well as parse the content so that it renders as expected. Here is the example
HTML
<p>
An example of live syntax highlighting in a content-editable element. The hard part is storing and restoring selection after changing the DOM to account for highlighting.
<p>
<div contentEditable='true' id='editor'>
Edit text here. Try some words like bold and red
</div>
<p>
Just a demo trivial syntax highlighter, should work with any syntax highlighting you want to implement.
</p>
JS
const editor = document.getElementById('editor');
const selectionOutput = document.getElementById('selection');
function getTextSegments(element) {
const textSegments = [];
Array.from(element.childNodes).forEach((node) => {
switch(node.nodeType) {
case Node.TEXT_NODE:
textSegments.push({text: node.nodeValue, node});
break;
case Node.ELEMENT_NODE:
textSegments.splice(textSegments.length, 0, ...(getTextSegments(node)));
break;
default:
throw new Error(`Unexpected node type: ${node.nodeType}`);
}
});
return textSegments;
}
editor.addEventListener('input', updateEditor);
function updateEditor() {
const sel = window.getSelection();
const textSegments = getTextSegments(editor);
const textContent = textSegments.map(({text}) => text).join('');
let anchorIndex = null;
let focusIndex = null;
let currentIndex = 0;
textSegments.forEach(({text, node}) => {
if (node === sel.anchorNode) {
anchorIndex = currentIndex + sel.anchorOffset;
}
if (node === sel.focusNode) {
focusIndex = currentIndex + sel.focusOffset;
}
currentIndex += text.length;
});
editor.innerHTML = renderText(textContent);
restoreSelection(anchorIndex, focusIndex);
}
function restoreSelection(absoluteAnchorIndex, absoluteFocusIndex) {
const sel = window.getSelection();
const textSegments = getTextSegments(editor);
let anchorNode = editor;
let anchorIndex = 0;
let focusNode = editor;
let focusIndex = 0;
let currentIndex = 0;
textSegments.forEach(({text, node}) => {
const startIndexOfNode = currentIndex;
const endIndexOfNode = startIndexOfNode + text.length;
if (startIndexOfNode <= absoluteAnchorIndex && absoluteAnchorIndex <= endIndexOfNode) {
anchorNode = node;
anchorIndex = absoluteAnchorIndex - startIndexOfNode;
}
if (startIndexOfNode <= absoluteFocusIndex && absoluteFocusIndex <= endIndexOfNode) {
focusNode = node;
focusIndex = absoluteFocusIndex - startIndexOfNode;
}
currentIndex += text.length;
});
sel.setBaseAndExtent(anchorNode,anchorIndex,focusNode,focusIndex);
}
function renderText(text) {
const words = text.split(/(\s+)/);
const output = words.map((word) => {
if (word === 'bold') {
return `<strong>${word}</strong>`;
}
else if (word === 'red') {
return `<span style='color:red'>${word}</span>`;
}
else {
return word;
}
})
return output.join('');
}
updateEditor();
Hope this helps...

input file component is not updating, VueJS

I have some code where I update multiple files using a package.
Add / Remove seems to work if I console.log, but if I do a POST request, on server I get all files, even if I delete them.
Example: I add 3 files, I delete 2 of them and I do a POST, on server I get 3 files. (But on console.log it shows me that I have only 1 which is correct).
Also, I find this article , but I am not sure what to do in my case.
This is a short version of my code.
<div id="upload-files-on-update">
<file-upload
:multiple="true"
v-model="certifications"
input-id="certifications"
name="certifications[]"
#input-filter="inputFilter"
ref="upload">
<span class="button">Select files</span>
</file-upload>
</div>
new Vue({
el: '#upload-files-on-update',
data: function () {
return {
certifications: [],
}
},
components: {
FileUpload: VueUploadComponent
},
methods: {
updateFiles(){
let formData = new FormData();
this.certifications.forEach((file, index) => {
if (!file.status && file.blob) {
formData.append("certifications[]",
{
types: this.accept,
certifications_ids: this.certifications_ids,
}
);
this.loadingButton = true;
}
});
axios
.post("<?php echo $link;?>", formData, {
headers: {
"Content-Type": "multipart/form-data"
},
params:{
types: this.accept,
certifications_ids: this.certifications_ids,
}
})
},
inputFilter(newFile, oldFile, prevent) {
if (newFile && !oldFile) {
if (/(\/|^)(Thumbs\.db|desktop\.ini|\..+)$/.test(newFile.name)) {
return prevent()
}
if (/\.(php5?|html?|jsx?)$/i.test(newFile.name)) {
return prevent()
}
}
if (newFile && (!oldFile || newFile.file !== oldFile.file)) {
newFile.blob = ''
let URL = window.URL || window.webkitURL
if (URL && URL.createObjectURL) {
newFile.blob = URL.createObjectURL(newFile.file)
}
newFile.pending = true;
newFile.thumb = ''
if (newFile.blob && newFile.type.substr(0, 6) === 'image/') {
newFile.thumb = newFile.blob
}
}
},
// Remove file from table
removeFile(index) {
this.certifications.splice(index, 1);
},
}
});
I found a solution for this problem.
//I catch ajax request and I make sure that is the request that I want it
var self = this;
$.ajaxSetup({
beforeSend: function (xhr,settings) {
if(settings.type != 'POST'){
return ;
}
if(settings.data.get('controller') != 'wcfm-memberships-registration'){
return ;
}
// Here I set file input as an empty array
settings.data.set('certifications[]',[]);
// Here I add my new files from a VueJS array
self.certifications.forEach((file, index) => {
settings.data.append("certifications[]", file.file);
});
}
});
});

Change value after confirmation in input VUE-JS

I have a table with an input column. This input column will have value if it has been saved in ddbb before.
If this value changes, handled with an event '#blur', I show a modal to confirm the change.
My problem is that if you want to keep the old value it will always change...
I tried to change this value with javascript, but it doesn't work... Any suggestions?
This is my code:
<b-tbody>
<b-tr
v-for="(driver, index) in drivers"
:key="driver.clientId">
<b-td
data-label="Client"
:title="driver.clientName">{{ driver.clientName }}</b-td>
<b-td data-label="Numerator">
<b-input
:id="'numeratorDriver_'+index"
class="driver-numerator text-right"
type="text"
:value="(driver.driverNumerator === undefined) ? 0 : $utils.formatNumber(driver.driverNumerator)"
#blur="calculatePricingDriver($event, index)" /></b-td>
<b-td
class="text-right"
data-label="Pricing"
:title="driver.pricingDriver"><span>{{ driver.pricingDriver }}</span></b-td>
</b-tr>
</b-tbody>
<script>
function calculatePricingCustomDriver (event, index) {
let lastValue = this.drivers[index].driverNumerator
if (this.$store.getters.price !== undefined) {
let title = 'Modify numerator'
let message = 'If you modify numerator driver, the calculated pricing will be deleted'
this.$bvModal.msgBoxConfirm(message, {
title: title,
size: 'sm',
buttonSize: 'sm',
okTitle: 'Yes',
okVariant: 'primary',
cancelTitle: 'No',
cancelVariant: 'primary',
hideHeaderClose: false,
centered: true
})
.then(confirmed => {
if (confirmed) {
this.$http.get('delete-price', { params: { id: this.$store.getters.id } })
.then((response) => {
if (response.status === this.$constants.RESPONSE_STATUS_OK) {
this.price = ''
let newNumerator = event.target.value
this.drivers[index].driverNumerator = Number(newNumerator)
let sumTotal = _.sumBy(this.drivers, 'driverNumerator')
for (let i = 0; i < this.drivers.length; i++) {
this.drivers[i].pricingDriver = (this.drivers[i].driverNumerator / sumTotal).toFixed(2)
}
} else {
this.drivers[index].driverNumerator = lastValue
// this is that I want change because it doesn't work fine
document.getElementById('numeratorDriver_' + index).value = lastValue
}
})
} else {
this.drivers[index].driverNumerator = lastValue
document.getElementById('numeratorDriver_' + index).value = lastValue
}
})
.catch(() => {
/* Reset the value in case of an error */
this.$utils.showModalError()
})
} else {
let newNumerator = event.target.value
this.drivers[index].driverNumerator = Number(newNumerator)
let sumTotal = _.sumBy(this.drivers, 'driverNumerator')
for (let i = 0; i < this.drivers.length; i++) {
this.drivers[i].pricingDriver = (this.drivers[i].driverNumerator / sumTotal).toFixed(2)
}
}
}
</script>
how is calculatePricingCustomDriver being loaded into your Vue component? For it to be called like that from #blur you would need to define it as a method:
<template>
<!-- your table-->
</template>
<script>
export default {
name: "MyComponent",
methods : {
calculatePricingCustomDriver () {
// your code
}
}
}
</script>
Or it could be installed as a global mixin

Memory Leak vue axios

I am building User Interface with Vue - created with #vue/cli 4.1.2.
Very Simple with not many components. Menu on the left side and results (in a table on the right side). Http requests are made with axios, and the results (responses) are approximately 1000 rows * 6 cols (cca 200KB sized objects). I am using Vuex and Router. However, the HEAP size keeps growing with every request I made. As if all the data downloaded(and much more) never gets released from memory. From initial 18MB heap size increases to cca100 MB with app 10 requests.
I tried to isolate the problem with turning off store, changing the template part (in fact, not showing anything at all but the number of records) but have no idea what is causing this. Tried to make values of gridData and gridColumns null before each reuest, but nothing helps. It seems that all the data get stored in memory and never release. Did try to analyze with google dev tools, but could not resolved that.
The component that is responsible for performing/and displaying requests is as follows:
<template>
<main>
<form id="search">Search <input
name="query"
v-model="searchQuery"
/></form>
<pre>cParamsRecord: {{ cParamsRecord }}</pre>
<pre>cActiveGrid: {{ cActiveGrid }}</pre>
<pre>cActiveRSet: {{ cActiveRSet }}</pre>
<div id="tbl">
<thead>
<tr>
<th
v-for="(col, index) in gridColumns"
:key="index"
>
{{ col }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(entry, index) in gridData"
:key="index"
#click="setActiveRow(entry)"
v-bind:class="{ active: entry == cActiveRow }"
>
<td
v-for="(col, index) in gridColumns"
:key="index"
>
{{ entry[col] }}
</td>
</tr>
</tbody>
<span>Num of records here: {{ propertyComputed }} </span>
</div>
</main>
</template>
<script>
import { rSetParams } from "#/playground/mockData.js";
import api_service from "#/services/api_service";
import * as types from "#/store/types.js";
export default {
name: "pGrid1",
components: {},
data () {
return {
searchQuery: "",
gridData: null,
gridColumns: null,
rSetParams: rSetParams,
property: "Blank"
};
},
computed: {
cActiveRSet: {
get () {
return this.$store.getters[types.ACTIVE_R_SET];
},
set (value) {
this.$store.commit(types.ACTIVE_R_SET, value);
}
},
cActiveGrid: {
get () {
return this.$store.getters[types.ACTIVE_GRID];
},
set (value) {
this.$store.commit(types.ACTIVE_GRID, value);
}
},
cRSetParams: {
get () {
return this.$store.getters[types.R_SET_PARAMS];
},
set (value) {
this.$store.commit(types.R_SET_PARAMS, value);
}
},
cActiveRow: {
get () {
return this.$store.getters[types.ACTIVE_ROW];
},
set (value) {
this.$store.commit(types.ACTIVE_ROW, value);
}
},
cParamsRecord: {
get () {
return this.$store.getters[types.PARAMS_RECORD];
},
set (value) {
this.$store.commit(types.PARAMS_RECORD, value);
}
},
},
methods: {
//function that makes http read http request and stores data locally into
//component data (this.gridData and this.gridColumns
async getRData () {
this.gridData = null
this.gridColumns = null
console.log(
"starting getRData for grid: ",
this.cActiveGrid,
" and routine set: " + this.cActiveRSet
);
if (this.cActiveRSet && this.cActiveGrid) {
var queryData;
try {
this.$store.commit(types.IS_LOADING, true);
const routine = this.cActiveRSet + "_" + this.cActiveGrid + "r";
console.log("routine: ", routine);
const r1 = await api_service.getRData(routine);
//const result = await r1
queryData = r1;
this.gridData = queryData.data;
console.log(this.gridData);
this.gridColumns = this.getTblHeadersFromResults(this.gridData);
//store data into the right vuex variable (in this case R1_DATA)
if (this.cActiveGrid == 1) {
this.$store.commit(types.R1_DATA, queryData.data);
} else if (this.cActiveGrid == 2) {
this.$store.commit(types.R2_DATA, queryData.data);
} else if (this.cActiveGrid == 3) {
this.$store.commit(types.R3_DATA, queryData.data);
}
this.$store.commit(types.IS_LOADING, false);
return queryData;
} catch (e) {
this.$store.commit(types.IS_LOADING, false);
console.error(e);
}
} else {
console.log(
"async getRData not run because there's no cActiveRSet or no cActiveGrid"
);
}
},
//function for defining active row in a table - - when we click on the row, row object get stored into cActiveRow
setActiveRow (row) {
console.log("in setActiveRow and row is: ", row);
this.$store.commit(types.ACTIVE_ROW, row);
this.createParamsRecordForNextGrid();
this.getRDataForNextGrid();
},
//creating tblHeaders object from first line of results
getTblHeadersFromResults (res) {
var tblHeadersRaw = Object.keys(res[0]);
return tblHeadersRaw;
},
//combining values from our record(clicked) - iecActiveRow with rParams to get appropriate record for using it when making http request
createParamsRecordForNextGrid () {
console.log("starting func createParamsRecord");
var nextGrid = this.cActiveGrid + 1;
var rR = this.cActiveRSet + "_" + nextGrid.toString() + "r"; //creating the name of our routine here
//const rR = "r_00305"
console.log("rR: ", rR);
//console.log("rSetParams: " + JSON.stringify(rSetParams))
var rParams = this.cRSetParams.filter(r => r.i9_routine_name == rR); //onyl get the params for our routine from i9 (rSetParams)
console.log("rParams: ", rParams);
// eslint-disable-next-line no-unused-vars
var paramsRecord = {};
console.log(this.cActiveRow);
var cActiveRowObj = this.cActiveRow;
for (var key in cActiveRowObj) {
//console.log(key)
for (var i = 0; i < rParams.length; i++) {
var rParamsObj = rParams[i];
//console.log("rParamsObj.i9_header_db: ", rParamsObj.i9_header_db, " ************** key from cActiveRow: ", key)
if ("p_" + key == rParamsObj.i9_header_db) {
//console.log("matching key with rParamsObj: ", key, rParamsObj.i9_header_db)
//console.log("value: ", cActiveRowObj[key])
paramsRecord[rParamsObj.i9_header_db] = cActiveRowObj[key];
}
}
}
this.$store.commit(types.PARAMS_RECORD, paramsRecord);
return paramsRecord;
}
//create types String - based on cActiveGrid we create types string so that we can store table data into the right variable in this.$store
},
watch: {
cActiveRSet: {
// eslint-disable-next-line no-unused-vars
handler () {
this.getRData();
},
immediate: true
}
}
};
</script>
After

I cannot integrate js query file in vue

i would like to use a js query code that I found on codepen in my vue project, but im not sure How to integrate it .
I simply pasted the file in my created() of my vue component but it doesn seem to work out.. the code is supposing to run a visual animation when typing chinese caractere in the input form.
this is the codepen : https://codepen.io/simeydotme/pen/CFcke
var $input = $('input');
$input.on({
"focus": function(e) {
$(".input-container").addClass("active");
},
"blur": function(e) {
$(".input-container").removeClass("active");
}
});
var previous = null;
setInterval( function() {
if( previous !== $input.val()
|| "" === $input.val()) {
getGoodCharacters( $input );
previous = $input.val();
}
}, 500);
function getGoodCharacters( $this ) {
var output = $this.val().trim();
var letters = output.split("");
var url = "https://service.goodcharacters.com/images/han/$$$.gif";
$(".error-container, .help").removeClass("show");
$(".output-container").empty();
for( letter in letters ) {
var img = letters[letter] + "";
var newurl = url.replace("$$$",img);
loadCharacter( newurl , img );
}
}
function loadCharacter( url , letter ) {
var img = new Image();
var $output = $(".output-container");
var $a = $("<a/>");
var l = $("input").val().length;
var cwidth = "120px";
if( l > 7 ) { cwidth = "70px"; }
else if( l > 6 ) { cwidth = "90px"; }
else if( l > 5 ) { cwidth = "100px"; }
$(img).load(function(){
$a.attr({
"href": url,
"title": "Good Character Chinese Symbol: "+ letter + ""
}).css("width", cwidth ).append( $(this) ).appendTo($output);
$(".help").addClass("show");
}).attr({
src: url
}).error(function(){
$(".error-container").addClass("show");
});
}
var $try = $(".tryme a").on("click", function(e) {
e.preventDefault();
$input.val( $(this).text() );
});
I also imported the jquery module in my componant
import $ from "jquery";
here is the html that i added in the template
<div class="container input-container">
<input type="text" id="input" placeholder="中文" value="中文"/>
</div>
Thanks!