Vuejs - list - How to pass click functions as string - vue.js

I have an array of objects, each with a click property (a string) that is passed to a click-event handler. I can print the .click property to the console, but it is not recognized as Vue data. I tried to eval(todo.click), but it didn't work.
html:
<div id="app">
<h2>Todos:</h2>
<ol>
<li v-for="todo in todos">
<label #click="clickMethod(todo)">{{todo.text}}</label>
</li>
</ol>
<br>
<div v-if="infoVisible">infoVisible</div>
<div v-if="tresVisible">tresVisible</div>
</div>
and my js:
new Vue({
el: "#app",
data: {
infoVisible:false,
tresVisible:true,
todos: [
{ text: "Learn JavaScript", done: false, click:'infoVisible=!infoVisible' },
{ text: "Learn Vue", done: false, click:'infoVisible=!infoVisible' },
{ text: "Play around in JSFiddle", done: true , click:'infoVisible=!infoVisible'},
{ text: "Build something awesome", done: true , click:'tresVisible=!tresVisible'}
]
},
methods: {
clickMethod(todo){
console.log(todo.click)
todo.click()
}
}
})
Fiddle

Instead of using strings as functions (which would require eval()), you could define function expressions:
new Vue({
el: "#app",
data: (vm) => ({
infoVisible: false,
tresVisible: true,
todos: [
{ ..., click() { vm.infoVisible = !vm.infoVisible } },
{ ..., click() { vm.infoVisible = !vm.infoVisible } },
{ ..., click() { vm.infoVisible = !vm.infoVisible } },
{ ..., click() { vm.tresVisible = !vm.tresVisible } },
]
}),
methods: {
clickMethod(todo){
todo.click()
}
}
})
Steps:
In todos[], change the type of .click properties from strings to function expressions:
//click: 'infoVisible = !infoVisible' // from strings
click() { infoVisible = !infoVisible } // to function expressions (to be updated in step 3)
In the function body, a reference to the Vue instance is required so that click() can change the data properties (i.e., infoVisible and tresVisible). Update the Vue declaration's data property to be a function that takes an argument (the argument will be the Vue instance itself):
data: (vm) => ({/* ... */})
Update click() to use that argument to reference the target data properties:
click() { vm.infoVisible = !vm.infoVisible }
^^^ ^^^
updated fiddle

eval(todo.click) will work but you need to add "this." to all of the todo properties in the click attributes so they have the right context, that is the context of the Vue instance.
new Vue({
el: "#app",
data: {
infoVisible:false,
tresVisible:true,
todos: [
{ text: "Learn JavaScript", done: false, click:'this.infoVisible=!this.infoVisible' },
{ text: "Learn Vue", done: false, click:'this.infoVisible=!this.infoVisible' },
{ text: "Play around in JSFiddle", done: true , click:'this.infoVisible=!this.infoVisible'},
{ text: "Build something awesome", done: true , click:'this.tresVisible=!this.tresVisible'},
]
},
methods: {
clickMethod(todo){
eval(todo.click)
}
}
})

Related

Vue.js access variable from method

I try to fetch stocks data from an API. This data should be used to create a chart.js graph.
how do I access in vue.js data to generate a chart.js line chart from the methods http(axios) call?
Is it possible to access the data directly in the mounted component or should I define a const in the section and create the variables there?
<template>
<select v-model="selected">
<option v-for="option in options" :value="option.value">
{{ option.text }}
</option>
</select>
<div>Selected: {{ selected }}</div>
<div>
<canvas id="myChart" height="200" width="650"></canvas>
</div>
<script>
export default {
mounted() {
const ctx = document.getElementById("myChart");
const myChart = new Chart(ctx, {
type: "line",
data: {
labels: [prices[0].date],
datasets: [
{
label: 'Dataset msft',
data: prices[0].price
},
{
label: 'Dataset google',
data: prices[1].price
},
],
},
});
},
data() {
return {
selected: "",
prices: [],
options: [
{ text: "msft", value: "msft" },
{ text: "GOOGL", value: "GOOGL" },
],
};
},
watch: {
selected: function () {
this.getPrice();
},
},
methods: {
getPrice: function () {
var this_ = this;
axios
.get(
"https://site/...."
)
.then((response) => {
// JSON responses are automatically parsed.
this_.prices = response.data;
})
},
},
};
</script>
Yes, you can access variables in data() from mounted().
You need to prepend variables with this. when using the Options API
ex: this.prices[0].price
As you are putting watcher on selected but I did not see any changes in the selected variable in your code. As per my understanding you are making an API call to get the graph data based on the selected option.
If Yes, Instead of generating a chart in mounted you can generate it inside your getPrice() method itself based on the response. It should be :
methods: {
getPrice: function () {
var this_ = this;
axios
.get(
"https://site/...."
)
.then((response) => {
this.generateChart(response.data);
})
},
generateChart(prices) {
const ctx = document.getElementById("myChart");
const myChart = new Chart(ctx, {
type: "line",
data: {
labels: [prices[0].date],
datasets: [
{
label: 'Dataset msft',
data: prices[0].price
},
{
label: 'Dataset google',
data: prices[1].price
}
]
}
});
}
}
Here, a very basic example:
<script>
export default {
async mounted() {
await this.$nextTick();
const ctx = document.getElementById("myChart");
this.chart = new Chart(ctx, {
type: "line",
data: {
labels: [],
datasets: [],
},
});
},
data() {
return {
selected: "",
chart: null,
options: [
{ text: "msft", value: "msft" },
{ text: "GOOGL", value: "GOOGL" },
],
};
},
watch: {
selected: function () {
this.getPrice();
},
},
methods: {
async getPrice() {
let { data } = await axios.get("https://site/....");
this.chart.data.datasets = [{ label: "dummy data" , data: [2, 3, 4]}];
this.chart.data.label = [1, 2, 3];
this.chart.update(); //very important, always update it
},
},
};
</script>
You create a property called chart and save your chart to it.
Then, after you fetch your data, you can access your chart with this.chart and then you set your datasets and labels. Whenever you make an change to the chart, use this.chart.update() to update it on the browser.
If you execute this code, you should see some dummy data in the chart

Intersection Observer doesn't observe new elements

I need to observe elements in list and mark them as read when they appear in the user's viewport. But when I add new elements in the beginning of the array (unshift), observer doesn't work :(
I use Vue, but I know that the problem does not correlate with it.
Here is observer method, which don't fire for new elements:
onElementObserved(entries) {
entries.forEach(({ target, isIntersecting}) => {
if (!isIntersecting) {
return;
}
this.observer.unobserve(target);
setTimeout(() => {
const i = target.getAttribute("data-index");
this.todos[i].seen = true;
}, 1000)
});
}
codepen
The v-for items have no key specified, so Vue tracks each list element by index. When a new item is unshifted into the list, the new element will have an index of 0, which already exists in the list, so the existing element is simply patched in place. Since no new element is created, the Intersection Observer is not triggered.
To resolve the issue, set a unique key per item in v-for. For example, you could add an id property to each array element, and then bind that id as the key for <todo>:
let nextId = 0;👈
const TodoList = Vue.extend({
template: `
<div>
<ul class="TodoList">
<todo
v-for="(todo, i) in todos"
:todo="todo"
:observer="observer"
:index="i"
:key="todo.id"👈
></todo>
</ul>
<button #click="pushNewTodo()">PUSH NEW</button>
</div>
`,
data() {
return {
todos: [ 👇
{ id: nextId++, seen: false, text: "Add app skeleton" },
{ id: nextId++, seen: false, text: "Add to-do component" },
{ id: nextId++, seen: false, text: "Add to-do list component" },
{ id: nextId++, seen: false, text: "Style the components" },
{ id: nextId++, seen: false, text: "Add the IntersectionObserver" },
{ id: nextId++, seen: false, text: "Mark to-do's as seen" }
],
};
},
methods: {
pushNewTodo() { 👇
this.todos.unshift({ id: nextId++, seen: false, text: "Add app skeleton BLAH BLAH BLAH" })
},
}
})
updated codepen

How to override default values of a vuejs component?

I've implemented a VUEJS plugin (via CDN) and it works, but I cannot change the default props.. any idea how to change the defaults?
In the code below you can see my attempt to accomplish this
html:
<tags-dic element-id="tags" v-model="selectedTags" :typeahead="true"></tags-input>
<script src="https://cdn.jsdelivr.net/npm/#voerro/vue-tagsinput#1.11.2/dist/voerro-vue-tagsinput.js"></script>
vue/javascript:
<script type="text/javascript">
var app = new Vue({
el: '#searchapp',
components: { "tags-dic": VoerroTagsInput },
data: {
query: "",
typeahead: true,
placeholder: 'Add a new tag',
limit: 1,
onlyExistingTags: true,
existingTags: [
'DNA',
'RNA',
'Protein',
],
selectedTags: [],
},
methods: {
submit: function() {
console.log("clicked");
if (this.query) {
console.log("OK");
}
}
}
})
</script>
Thanks
You can supply your own property values in the template. (See the docs for details.)
<tags-dic
element-id="tags"
v-model="selectedTags"
:typeahead="true"
:existing-tags="existingTags"
:placeholder="placeholder"
/>
and so on

How to trigger a Vue method by name for each element in a v-for loop?

I'm having troubles triggering a separate Vue instance method by name for each element in a v-for loop on click.
Each action corresponds to a method, but it's not triggered. What am I doing wrong?
Code:
<v-btn v-for="btn in windowControlButtons" :key="btn.id"
#click="btn.action"
>
<v-icon size="20px">{{btn.icon}}</v-icon>
</v-btn>
...
window: remote.getCurrentWindow(),
windowControlButtons: [
{
icon: 'remove',
action: minimizeWindow()
},
{
icon: 'crop_square',
action: maximizeWindow()
},
{
icon: 'close',
action: closeWindow()
}
]
...
methods: {
minimizeWindow() {
this.window.minimize()
},
maximizeWindow() {
this.window.maximize()
},
closeWindow() {
this.window.close()
}
}
UPDATE
I can trigger some code directly in the data(), e.g.:
...
{
icon: 'remove',
action: () => {remote.getCurrentWindow().minimize()}
},
But what if a method wasn't as short?
How do I trigger a method already specified in methods: { }?
btn.action is a string, thus you can't execute it.
Every Vue instance/component method is accessible as a property in the vm.$options.methods object.
I suggest creating another method, say handleClick, to simplify your method calling depending on the button, and invoke the best suitable method from this.$options.methods as shown below.
new Vue({
el: '#app',
data: {
windowControlButtons: [
{id: 1, icon: 'remove', action: 'minimizeWindow'},
{id: 2, icon: 'crop_square', action: 'maximizeWindow'},
{id: 3, icon: 'close', action: 'closeWindow'}
]
},
methods: {
handleClick(button) {
if (this.$options.methods[button.action]) { // guard to prevent runtime errors
this.$options.methods[button.action]();
}
},
minimizeWindow() {
console.log('minimizeWindow');
},
maximizeWindow() {
console.log('maximizeWindow');
},
closeWindow() {
console.log('closeWindow');
}
}
})
<script src="https://unpkg.com/vue#2.5.15/dist/vue.min.js"></script>
<div id="app">
<button v-for="btn in windowControlButtons" :key="btn.id" #click="handleClick(btn)">
<span>{{btn.icon}}</span>
</button>
</div>

VueJS Virtual field on array item

I am building something with VueJS and I have some problem to select an item in a list:
Let's imagine the following VueJS component:
new Vue({
el: '#app',
data: {
list: [
{
id: 1,
title: 'My first Item',
selected: false
},
{
id: 2,
title: 'My second Item',
selected: false
}
]
}
})
With the selected property, I can apply a class or not to the item:
<div id="app">
<ul>
<li #click="item.selected = !item.selected" :class="item.selected ? 'active' : ''" v-for="item in list">{{ item.title }}</li>
</ul>
</div>
But now, let's imagine that I grab my data from an API, I still want to be able to select the items:
new Vue({
el: '#app',
data: {
list: []
},
created: function () {
// Let's imagine that this is an Ajax Call to a webservice
this.$set('list', [
{
id: 1,
title: 'My first Item'
},
{
id: 2,
title: 'My second Item'
}
])
}
})
Now, my html can't work anymore because the data has not a selected property.
So how could I do such a thing?
Here are two JsFiddle that explain the problem:
The working one
The non working one
Please read docs on vue lifecycle :
id prefer you set the list to be a computed property that always checks for returned items : ie ,
new Vue({
el: '#app',
data: {
list: []
},
computed: {
list (){
// Let's imagine that this is an Ajax Call to a webservice
let returned = [
{
id: 1,
title: 'My first Item'
},
{
id: 2,
title: 'My second Item'
}
]
return returned
}
}
})