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
}
}
})
Related
Not sure why the data is showing up as undefined. Is the data not formatted correctly? I've tried several ways to adjust it and nest it differently but nothing is working out so far.
<template>
<main>
<AboutMeComponent v-for="i in about" :key="i.posts.id" :title="i.posts.title" :body="i.posts.body" :skills="i.posts.skills"
:img="i.images.img" />
</main>
</template>
<script>
import AboutMeComponent from '../components/AboutMeComponent.vue';
export default {
data() {
return {
about: {
posts: [
{ id: 1, title: "About Me", body: `Body 1` },
{ id: 2, title: "Skills" },
{ id: 3, title: "Hobbies", body: "Body 3" },
],
images: [
{ img: 'figma.png'},
{ img: 'figma.png'},
{ img: 'figma.png'},
]
}
}
},
components: {
AboutMeComponent
}
}
</script>
I think you are need to set the v-for loop on the about.posts, not on the about. Also, if the img is used for the post, I would suggest you keep them with the post, instead of separated.
So your data will be:
data() {
return {
about: {
posts: [
{ id: 1, title: 'About Me', body: 'Body 1', img: 'figma.png' },
{ id: 2, title: 'Skills', body: 'Body 2', img: 'figma.png' },
{ id: 3, title: 'Hobbies', body: 'Body 3', img: 'figma.png' },
],
}
}
},
And now you can loop over about.posts:
<AboutMeComponent v-for="post in about.posts" :key="post.id" :title="post.title" :body="post.body" :img="post.img" />
I left skills out, as they are not in your data.
Just a small suggestion: using variable names as i makes your code harder to understand. If you had used about in about, you would have felt that this was not what you wanted, because you wanted something to do with a post in posts.
Hope this helps.
Few Observations :
As each img in the images array corresponds to each object in the posts array. Merge that into a single one and then use v-for loop for about.posts.
As few properties are missing. Use Optional chaining (?.) operator so that you can read the value of a property located deep within a chain of connected objects without having to check that each reference in the chain is valid.
Live Demo :
Vue.component('aboutmecomponent', {
props: ['key', 'title', 'body', 'skills', 'img'],
template: '<h3>{{ title }}</h3>'
});
var app = new Vue({
el: '#app',
data: {
about: {
posts: [
{ id: 1, title: "About Me", body: "Body 1" },
{ id: 2, title: "Skills" },
{ id: 3, title: "Hobbies", body: "Body 3" },
],
images: [
{ img: 'figma.png'},
{ img: 'figma.png'},
{ img: 'figma.png'},
]
}
},
mounted() {
this.about.posts = this.about.posts.map((obj, index) => {
const newObj = {
...obj,
...this.about.images[index] }
return newObj;
});
delete this.about.images;
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<AboutMeComponent
v-for="item in about.posts"
:key="item?.id"
:title="item?.title"
:body="item?.body"
:skills="item?.skills"
:img="item?.img" >
</AboutMeComponent>
</div>
I am trying to learn vue.js and have came across this line of code.
Vue.component('todo-item', {
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
})
var app7 = new Vue({
el: '#app-7',
data: {
groceryList: [
{ id: 0, text: 'Vegetables' },
{ id: 1, text: 'Cheese' },
{ id: 2, text: 'Whatever else humans are supposed to eat' }
]
}
})
I could not figure out what does todo.text here refer to, since I do not see a todo object with text as one of its objects. Sorry for the noob question! Thanks!
So I'm still learning Vue.js and I got my list working well and I have one question. I will explain what I'm trying to do below as best as possible and I wanted to see if someone could help me with my issue.
So here is the component that I have on the HTML side:
<favorites-edit-component
v-for="(favorite, index) in favorites"
v-bind:index="index"
v-bind:name="favorite.name"
v-bind:key="favorite.id"
v-on:remove="favorites.splice(index, 1)"
></favorites-edit-component>
Here is the vue.js portion that I have:
Vue.component('favorites-edit-component', {
template: `
<div class="column is-half">
<button class="button is-fullwidth is-danger is-outlined mb-0">
<span>{{ name }}</span>
<span class="icon is-small favorite-delete" v-on:click="$emit('remove')">
<i class="fas fa-times"></i>
</span>
</button>
</div>
`,
props: ['name'],
});
new Vue({
el: '#favorites-modal-edit',
data: {
new_favorite_input: '',
favorites: [
{
id: 1,
name: 'Horse',
url: 'www.example.com',
},
{
id: 2,
name: 'Sheep',
url: 'www.example2.com',
},
{
id: 3,
name: 'Octopus',
url: 'www.example2.com',
},
{
id: 4,
name: 'Deer',
url: 'www.example2.com',
},
{
id: 5,
name: 'Hamster',
url: 'www.example2.com',
},
],
next_favorite_id: 6,
},
methods: {
add_new_favorite: function() {
this.favorites.push({
id: this.next_favorite_id++,
name: this.new_favorite_input
})
this.new_favorite_input = ''
},
get_favorite_menu_items: function() {
wp.api.loadPromise.done(function () {
const menus = wp.api.collections.Posts.extend({
url: wpApiSettings.root + 'menus/v1/locations/favorites_launcher',
})
const Menus = new menus();
Menus.fetch().then(posts => {
console.log(posts.items);
return posts.items;
});
})
}
}
});
So as you can see, I have the data: { favorites: [{}] } called inside the vue app and I get this console.log:
Now I built a method called get_favorite_menu_item and this is the return posts.items output inside console.log:
Problem: I don't want to have a manual array of items, I want to be able to pull in the method output and structure that - How would I take a approach on pulling the items?
Could I call something like this:
favorites: this.get_favorite_menu_items?
Here is a JFiddle with all the items: https://jsfiddle.net/5opygkxw/
All help will be appreciated on how to pull in the data.
First I will init favorites to empty array.
then on get_favorite_menu_items() after I will init data from post.item to favorites.
on created() hooks i will call get_favorite_menu_items() to fetch the data when the view is created.
new Vue({
el: '#favorites-modal-edit',
data: {
new_favorite_input: '',
favorites: [],
next_favorite_id: 6,
},
methods: {
add_new_favorite: function() {
this.favorites.push({
id: this.next_favorite_id++,
name: this.new_favorite_input
})
this.new_favorite_input = ''
},
get_favorite_menu_items: function() {
wp.api.loadPromise.done(function () {
const menus = wp.api.collections.Posts.extend({
url: wpApiSettings.root + 'menus/v1/locations/favorites_launcher',
})
const Menus = new menus();
Menus.fetch().then(posts => {
console.log(posts.items);
// need map key also
this.favorites = posts.items;
});
})
}
},
created () {
// fetch the data when the view is created
this.get_favorite_menu_items();
},
});
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
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)
}
}
})