How to parse JSON data into HTML elemnets - javascript

I have a JSON data with HTML.
Like this:
"elements":[
{
"element":".dyno-text",
"value":"This fun here.<br> <button type='button' onclick='changeTheme(this)' data-theme='sketchy' class='theme-link btn btn-light'>Sketchy</button>",
"class": 'text-success'
}
]
How will I parse this JSON data to Bootstrap Layout Design for example: Button will come to real.
Thanks

Uses Vue.component to assembly JSON as one component may be one solution.
But you may need to adjust the HTML template in JSON. Because for supporting some features such as onclick, binding class, it will be one serious headache.
Below is one demo which may provide you some ideas how to reach your goal.
new Vue ({
el:'#app',
data () {
return {
"elements":[
{
"element":"dyno-text",
"value":"This fun here.<br> <button type='button' #click='changeTheme(this)' data-theme='sketchy' class='theme-link btn btn-light'>Sketchy</button>",
"class": 'text-success',
"methods": {
// changed onclick to #click, if you still like to use 'onclick' in the template, you have to define window.changeTheme
changeTheme: function(obj) {console.log('clicked')}
}
}
]
}
},
methods: {
createComponent(element) {
/*window.changeTheme = function () {
console.log('clicked by onclick')
}*/
return Vue.component(element.element, {
template: `<div ref="test">${element.value}</div>`,
mounted: function () {
this.$nextTick(() => {
this.$refs.test.querySelector('button.btn').classList.add(element.class)
// or adjust your template in JSON like `<button :class="classes"/>`, then binds element.class to data property=classes
})
},
methods: element.methods
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div class="container">
<div v-for="(item, index) in elements" :key="index">
<component :is="createComponent(item)"/>
</div>
</div>
</div>

You can do something like this; I changed the element in order to create a known htmlElement, so what you do here is to iterate your array of elements and you insert them inside the body, set the value, and toggle the class.
--Edit--
Cleanner solution thanks to pointing it out supercool
Documentation of classList
let elements=[
{
"element":"div",
"value":"This fun here.<br> <button type='button' onclick='changeTheme(this)' data-theme='sketchy' class='theme-link btn btn-light'>Sketchy</button>",
"class": 'text-success'
}
]
elements.forEach((elemen,i)=>{
let createdElement= document.createElement(elemen.element)
createdElement.innerHTML = elemen.value
createdElement.classList.toggle(elemen.class)
document.body.appendChild(createdElement)
})

Related

How to update a row with contenteditable in Vue?

I'm trying to figure out how to get the current changes in a 'contenteditable' and update it in the row that it was changed.
<tbody>
<!-- Loop through the list get the each data -->
<tr v-for="item in filteredList" :key="item">
<td v-for="field in fields" :key="field">
<p contenteditable="true" >{{ item[field] }}</p>
</td>
<button class="btn btn-info btn-lg" #click="UpdateRow(item)">Update</button>
<button class="btn btn-danger btn-lg" #click="DelteRow(item.id)">Delete</button>
</tr>
</tbody>
Then in the script, I want to essentially update the changes in 'UpdateRow':
setup (props) {
const sort = ref(false)
const updatedList = ref([])
const searchQuery = ref('')
// a function to sort the table
const sortTable = (col) => {
sort.value = true
// Use of _.sortBy() method
updatedList.value = sortBy(props.tableData, col)
}
const sortedList = computed(() => {
if (sort.value) {
return updatedList.value
} else {
return props.tableData
}
})
// Filter Search
const filteredList = computed(() => {
return sortedList.value.filter((product) => {
return (
product.recipient.toLowerCase().indexOf(searchQuery.value.toLowerCase()) != -1
)
})
})
const DelteRow = (rowId) => {
console.log(rowId)
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowId}`, {
method: 'DELETE'
})
.then((response) => {
// Error handeling
if (!response.ok) {
throw new Error('Something went wrong')
} else {
// Alert pop-up
alert('Delete successfull')
console.log(response)
}
})
.then((result) => {
// Do something with the response
if (result === 'fail') {
throw new Error(result.message)
}
})
.catch((err) => {
alert(err)
})
}
const UpdateRow = (rowid) => {
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowid.id}`, {
method: 'PUT',
body: JSON.stringify({
id: rowid.id,
date: rowid.date,
recipient: rowid.recipient,
invoice: rowid.invoice,
total_ex: Number(rowid.total_ex),
total_incl: Number(rowid.total_incl),
duration: rowid.duration
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
})
})
}
return { sortedList, sortTable, searchQuery, filteredList, DelteRow, UpdateRow }
}
The commented lines work when I enter them manually:
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
Each cell has content editable, I'm not sure how to update the changed event
The way these run-time js frontend frameworks work could be summarized as "content is the function of data". What I mean is the html renders the data that you send it. If you want the data to be updated when the user changes it, you need to explicitly tell it to do so. Some frameworks (like react) require you to setup 1-way data binding, so you have to explicitly define the data that is displayed in the template, as well as defining the event. Vue has added some syntactic sugar to abstract this through v-model to achieve 2-way binding. v-model works differently based on whichever input type you chose, since they have slightly different behaviour that needs to be handled differently. If you were to use a text input or a textarea with a v-model="item[field]", then your internal model would get updated and it would work. However, there is no v-model for non-input tags like h1 or p, so you need to setup the interaction in a 1-way databinding setup, meaning you have to define the content/value as well as the event to update the model when the html tag content changes.
have a look at this example:
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1 contenteditable #input="({target})=>msg=target.innerHTML">{{ msg }}</h1>
<h2 contenteditable>{{ msg }}</h2>
<input v-model="msg">
</template>
If you change the h2 content, the model is not updated because vue is not tracking the changes. If you change through input or h1, the changes are tracked, which will also re-render the h2 and update its content.
TL;DR;
use this:
<p
contenteditable="true"
#input="({target})=>item[field]=target.innerHTML"
>{{ item[field] }}</p>

How to Store Data Property Value from a Specific Item in Rendered List in Vue

I'm trying create a follow button on list items in Vue. My strategy is to grab the value of a particular list item property and store it in the data object. Then use this value in a method to add it to an array in my database.
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock">+follow</button>
</div>
I'm not sure how to get the value of result.symbol "into" the button element to set the value symbol in the data object below.
<script>
export default {
data() {
return {
results: [ // this is populated by an api call
{
currency: "USD"
exchangeShortName: "NYSE"
name: "International Game Technology PLC"
stockExchange: "NYSE"
symbol: "IGT"
},
{...},
...
],
symbol: "",
};
},
followStock() {
// add this.symbol to database array
},
},
};
</script>
I'm guessing there might be an easier strategy I'm overlooking as I'm still new to Vue, so any other solution that essentially allows me to fire off the value of result.symbol from any rendered result to my database would be awesome.
You can just pass the result as a parameter to your method.
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock(result)">+follow</button>
</div>
And in your method:
methods: {
followStock(result) {
// do something with result
console.log({result});
let symbol = result.symbol;
},
}
P.S I didn't see you put your followStock() inside a methods object, but I did so in the example. https://v2.vuejs.org/v2/api/#methods
Write directly as a function call.
The vue compiler will turn followStock(result.symbol) into function(event) {followStock(result.symbol)}.
new Vue({
el: '#app',
data() {
return {
results: [
{
name: "International Game Technology PLC",
symbol: "IGT"
},
{
name: "A name",
symbol: "A symbol"
}
]
};
},
methods: {
followStock(symbol) {
console.log(symbol)
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock(result.symbol)">+follow</button>
</div>
</div>
As Nazaire mentioned you can access the results anywhere inside the child elements when using v-for.
(it works like a normal for-loop)
It's not only limited to the corresponding element (the element in which you do v-for)
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock(result.symbol)">+follow</button>
</div>
followStock(symbol){
// you can now add symbol to db
}

How to display values of an associative array with vue.js?

I have an array which is:
And I want to make a foreach loop and list all key's & script_content's to the view.
My vue components mounted method:
mounted() {
this.loading = true;
axios.get('/app/json-ld/json-ld-settings')
.then(res => {
let data = res.data;
console.log(data.scripts);
this.key = data.scripts[0]['key'];
this.scriptContent = data.scripts[0]['script_content'];
})
.catch(error => {
this.loading = false;
this.$notify({
group: 'notify',
type: 'error',
text: 'Something happened! Please refresh the page and try again or contact support!',
});
});
},
component data:
data: () => ({
errors: {},
key: [],
scriptContent: [],
I am able to display the values of the first array, but don't know how to make a foreach loop in an associative array.
HTML:
<div class="py-3 d-flex flex-row justify-content-end align-content-end">
<div class="pr-2">
<h5>Key</h5>
<span>{{key}}</span>
</div>
<div class="pl-2">
<h5>Script content</h5>
<span>{{scriptContent}}</span>
</div>
</div>
The goal is to list all key's and script_content's in a HTML list or a div.
Any help will be appriciated.
You can just use codes below:
data() {
return {
keys: [],
contents: [],
}
}
...
for (let index in data) {
this.keys.push(data[index].key);
this.contents.push(data[index].script_content);
}
...
Then you can use v-for in html codes to use keys and contents.
You should store all scripts into the data, not just data.scripts[0], and then iterate over them in the template using v-for directive. Here is a couple of good examples:
https://v2.vuejs.org/v2/guide/list.html

How to get the component triggered in parent vue?

I am using Laravel 6 (mix) + Vue.js 2.
I am creating a multi-lang dictionary where users can add words and for each word they can add multiple definitions with their translations.
Inside index.blade.php
<div class="modal fade" id="modal-addItem">
<form>
<div class="card" v-for="(value, index) in nbDefinitions">
<div class="card-header">#{{ value }}.</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-3">English</dt>
<dd class="col-sm-9">
<autocomplete data-language="english" :search="searchDefinition" :get-result-value="getDefinitionResultValue" #submit="handleSearchDefinitionSubmit"></autocomplete>
</dd>
<dt class="col-sm-3">French</dt>
<dd class="col-sm-9">
<autocomplete data-language="french" :search="searchDefinition" :get-result-value="getDefinitionResultValue" #submit="handleSearchDefinitionSubmit"></autocomplete>
</dd>
</dl>
</div>
</div>
<div class="row">
<button type="button" class="btn btn-primary" id="addItems-addDefinition" #click="addDefinition">+ Add definition</button>
</div>
</form>
</div>
Inside myCustom.js
new Vue({
el: '#modal-addItem',
data: {
nbDefinitions: 0,
nbSentencesPerDef: [],
translatedDefinitions: []
},
methods: {
addDefinition: function() {
this.nbDefinitions++;
this.nbSentencesPerDef.push(1);
this.translatedDefinitions.push({
english: null,
french: null
});
},
searchDefinition: function (input) {
// How can I know which <autocomplete> is triggered?
return new Promise(resolve => {
if (input.length < 3) { return resolve([]); }
fetch(`/api/v1/definitions?search=${encodeURI(input)}`)
.then(response => response.json())
.then(responseJson => {
resolve(responseJson.definitions);
})
})
},
getDefinitionResultValue: function(result) {
// How can I know which <autocomplete> is triggered?
let definition = result.definition;
let item = result.item.name;
return `${item} - ${definition}`;
},
handleSearchDefinitionSubmit: function(result) {
// How can I know which <autocomplete> is triggered?
console.log(this);
}
}
});
I am using autocomplete which is an external component loaded globally (in Laravel main app.js) https://autocomplete.trevoreyre.com/#/
My question is: How can I know inside the methods “addDefinition”, “searchDefinition” and “handleSearchDefinitionSubmit” what is the child component who was triggered? Because I have 3 autocomplete components inside my Vue object, and this refers to the root (in my case the html modal), so I have no idea which autocomplete child was triggered. Also this.$refs is empty.
Maybe it is an architectural issue, but I don’t have enough experience to know how to get it done.
Component itself doesn't support this directly (by passing itself as a parameter of functions for example). But luckily in case of functions passed as props we can use JS feature called "closure" - instead of just function name, call a function which returns another function. In case of events, we can use the feature of Vue allowing us to access special '$event' value when defining handlers.
Like this:
before: <autocomplete data-language="english" :search="searchDefinition" #submit="handleSearchDefinitionSubmit" />
after: <autocomplete data-language="english" :search="getSearchDefinitionFunc('english')" #submit="handleSearchDefinitionSubmit($event, 'english')" />
...and change your methods like this:
getSearchDefinitionFunc(lang) {
return input => this.searchDefinition(input, lang);
},
searchDefinition: function(input, lang) {
console.debug(`searchDefinition called with "${input}" and "${lang}"`);
if (input.length < 3) {
return Promise.resolve([]);
}
// ...don't create new Promise, just return an existing one
return fetch(`/api/v1/definitions?search=${encodeURI(input)}`)
.then(response => response.json())
.then(responseJson => responseJson.definitions);
},
handleSearchDefinitionSubmit: function(result, lang) {
console.log(`Submit: ${lang}`);
}
I also refactored your code a bit as creating new promise is not necessary.
You can find working example here
Could be refactored even more by defining your languages in component data (['english', 'french']) and generating each row using v-for

How to link words in an HTML string and assign them #click method in VueJS 2?

I have a array of strings in which I want to linkify certain words like "User object", "Promise", etc like this:
var strings = ['This returns a promise containing a User Object that has the id', 'next string']
This needs to be rendered like this
<div class="wrapper">
<div class="item" v-for="str in strings" v-html="str"></div>
</div>
The problem is I want to replace words like "User object", "Promise" and bind them to a #click event that my app can handle.
So if it were rendered like I want it to be, it would be something like this (the same v-for loop above rendered manually)
<div class="wrapper">
<div class="item">This returns a promise containing a User object that has the id</div>
<div class="item">next string</div>
</div>
I tried doing this but it doesn't bind the #click event
methods: {
linkify(str) {
return str.replace(/user object/, 'User object');
}
}
Any ideas?
Here's an example of a component that takes in a string for the full message and a string for the text to replace with a link and renders a span with that message with the link text wrapped in a <a> tag:
Vue.component('linkify', {
template: '#linkify-template',
props: {
value: { type: String },
linkText: { type: String }
},
computed: {
before() {
return this.value.split(this.linkText)[0];
},
after() {
return this.value.split(this.linkText)[1];
}
}
});
new Vue({
el: '#app',
data() {
return {
message: 'This returns a promise containing a User Object that has the id',
}
},
methods: {
foo() {
console.log('clicked')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<script type="text/x-template" id="linkify-template">
<span>
{{ before }}
<a href="#" #click.prevent="$emit('click')">
<code>{{ linkText }}</code>
</a>
{{ after }}
</span>
</script>
<div id="app">
<linkify link-text="User Object" :value="message" #click="foo"></linkify>
</div>
Okay figured it out. If somebody has a better way to do it please answer too!
Vue.component('linkify', {
props: ['value', 'words'],
template: `<span :is="html"></span>`,
data() {
return {
html: Vue.compile('<span>' + this.value.replace(new RegExp('(' + this.words.join('|') + ')', 'g'), `<code>$1</code>`) + '</span>'),
}
}
});
Now all I need to do in the main app is this:
<div class="wrapper">
<div class="item" v-for="str in strings">
<linkify :value="str" :words="['user object', 'promise']" #click="help"></linkify>
</div>
</div>
Unfortunately this only works with full version of Vue (which has the compile function)

Categories

Resources