I'm developing a Vue app and I have a list of movies like this:
movies [
{name: 'spider-man', id: 3},
{name: 'thor'},
{name: 'x-men', id: 7}
]
and this is my template
<ul>
<li v-for="movie in movies" :key="movie.name">{{movie.name}}</li>
</ul>
I only wanna show the movies who has an ID, in this case, thor shouldn't be shown. I've tried with v-if but I got this error: "The 'movies' variable inside 'v-for' directive should be replaced with a computed property that returns filtered array instead. You should not mix 'v-for' with 'v-if'".
Thanks, hope you can help me!
Computed properties to the rescue:
data: () => ({
movies: [
{name: 'spider-man', id: 3},
{name: 'thor'},
{name: 'x-men', id: 7}
]
})
...
computed: {
moviesWithId() {
return this.movies.filter(movie => movie.id)
}
}
...
In your template
<ul>
<li v-for="movie in moviesWithId" :key="movie.name">{{movie.name}}</li>
</ul>
Or you may add condition to render to your li tags like v-show="movie.id"
Or filter your dataset like :
<li v-for="movie in movies.filter(movie => movie.id)" :key="movie.name">{{movie.name}}</li>
Related
kinda new to vue, I have mapped out some data from my initial data object in vue.js I am trying to hide and show only the items within that iteration of the mapping when the user selects the heading. I am using the isHidden prop in vue to hide and show my list items but when selecting any heading it shows all the tags instead of those associated with that specific header.
anyone know the proper way to do this? should I use some index or id from e.target? or should I give each list item a 'hidden' property and change it that way somehow?
here's my list that I mapped out
<div v-for="item in list">
<h4 v-on:click="viewItemsinCat()">{{item.category}}</h4>
<ul>
<li v-if="!isHidden" v-for="item in item.food">
{{item}}
</li>
</ul>
</div>
then I have my data like so:
data: {
list: [{
category: 'Baked goods',
food: ['bread', 'cookie', 'butter', 'powder']
}, {
category: 'meats',
food: ['chicken', 'turkey', 'beef']
}, {
category: 'fruits',
food: ['bannana', 'apple', 'pineapple']
}, {
category: 'canned goods',
food: ['tomatoes', 'green beans', 'corn']
}, {
category: 'veggies',
food: ['broccoli', 'celery', 'lettuce']
}, {
category: 'pantry',
food: ['broom', 'mop', 'dried beans']
}, ],
isHidden: true,
}
then I have my method to alter isHidden
viewItemsinCat: function(){
this.isHidden = false
},
Add a selected property to contain the currently selected item when the user clicks:
data() {
return {
selected: null,
list: [...]
}
}
<div v-for="item in list">
<h4 v-on:click="selected=item.category">{{item.category}}</h4>
<ul v-if="selected==item.category">
<li v-for="food in item.food">
{{ food }}
</li>
</ul>
</div>
Here's a demo
I have an object that holds both known and unknown values. Quantity is unknown, Type is known. I want to present the user inputs for every Type and the user enters quantity. How do I model this in Vue?
data() {
return {
message: "",
order: {
orderDetails: [], // end result, after user made choices: [{ quantity: 1, productType: "A"}, {quantity: 1 , productType :"B"}] ,
},
productType: ["A", "B", "C", "D"],
};
},
and
<ul>
<li v-for="(item, index) in productType" :key="index">
<input type="number" v-model="?order.orderDetails[index].quantity?" /> {{ item }}
</li>
</ul>
and the desired output is kinda like this
<ul>
<li><input type="number"> A </li>
<li><input type="number"> B </li>
<li><input type="number"> C </li>
<li><input type="number"> D </li>
</ul>
obviously this is not working cos the vmodel object doesn't exist. I don't think that initialising the model with null quantities is the correct approach as the Types will be coming from an API call and will not be hardcoded. Is a computed property the solution?
It is best to structure your data model to reflect the view. If you keep the product types and the quantities separate, then you're going to have a difficult time trying to pair them together in the view because the data will be all over the place.
You want to have a list of inputs for each type, so your data model can be an array of objects where each object has a productType property and a quantity property. Initially each quantity will be 0.
data() {
return {
orderDetails: [
{ productType: 'A', quantity: 0 },
{ productType: 'B', quantity: 0 },
{ productType: 'C', quantity: 0 },
{ productType: 'D', quantity: 0 },
]
}
}
But let's say you don't know what the types will be ahead of time (perhaps you fetch the types from some API), so you can't structure the data like that straight away in the code like I have shown. So then all you have to do is dynamically construct the orderDetails array once you have the array of types:
data() {
return {
orderDetails: []
}
},
async created() {
// Fetch the types from some hypothetical API
const types = await fetchTypes()
// Populate the orderDetails array from the types
this.orderDetails = types.map(type => ({
productType: type,
quantity: 0,
}))
}
Now it's just a 1-to-1 mapping between the data and the view!
<ul>
<li v-for="(item, index) in orderDetails" :key="index">
<input type="number" v-model="item.quantity">
{{ item.productType }}
</li>
</ul>
Your view (template) should be very simple with no complex data processing; it should be just a simple function of the data.
I have:
Vue app data:
data: function() {
return {
items: [
{id: 1, text: 'one', other_data: {}},
{id: 2, text: 'two', other_data: {}},
{id: 3, text: 'three', other_data: {}}
]
}
}
Template:
<div v-for="item in items" id="my_items">
<span>{{ item.text }}</span>
</div>
And i need to access from one item by external JS code like next:
let item_node = document.getElementById('my_items').children[1]; // get 2nd child node of #my_items
item_node.__vuedata__ // must be a 2nd item from items in Vue data. {id: 2, text: 'two'...
How to do like this?
Vue recommends to use ref over ID and classes for DOM references. We use ref as a replacement for id. Since it is used on top of the v-for directive, all child elements are now referenced as an array of the ref. So every span will now be hello[0] - hello[n] The mounted code outputs the item.text for first child as it uses 0.
I added a click listener, every time you click the element, the value of item is passed to the method. Here you can extract all values and do whatever manipulation you require. Hope this is what you are looking for.
<template>
<div id="app">
<div v-for="item in items" :key="item.id" ref='hello' #click="logItem(item)">
<span>{{ item.text }}</span>
</div>
</div>
</template>
<script>
export default {
name: "App",
data: function() {
return {
items: [
{id: 1, text: 'one', other_data: {}},
{id: 2, text: 'two', other_data: {}},
{id: 3, text: 'three', other_data: {}}
]
}
},
methods: {
logItem: (item) => console.log(item)
},
mounted: function() {
console.log(this.$refs.hello[0].innerText)
}
};
</script>
I have an huge array organized sort of like this:
[{ name: 'name1',
nodes: []},
{ name: 'name2',
nodes: [
{ name: 'name21',
nodes: [
{ name: 'name211',
nodes: []},
{ name: 'name212',
nodes: []}]
}]
},
{ name: 'name3',
nodes: [...] },
{...}
]
and it goes on...
I tried to use something like this:
<script type='text/ng-template', id='categoryTree'>
<p ng-if='!node.nodes'> {{node.name}} </p>
<details ng-if='node.nodes'>
<summary><b> {{node.name}}</b></summary>
<ul>
<span ng-repeat="node in node.nodes" ng-include="'categoryTree'"></span>
</details>
</script>
<div>
<ul>
<span ng-repeat="node in objArray" ng-include="'categoryTree'"></span>
</div>
This gives me what I want in terms of showing all the nested array in a tree format. The problem is that it seems to be caught in an infinite loop for when I look at the Task Manager, the RAM used starts increasing and only stops when Chrome crashes.
Does anybody know how could I get around with that? Or even if I have a better way to do this tree view?
Angular does not really handle recursive directives - by default
But there's a solution:
https://github.com/marklagendijk/angular-recursion
What you need actually is to temporarily remove the nested element when rendering the parent.
I have a JSON that looks like this:
{
list: [
{ name: 'AAA',
id: 1,
age: 34
},
{ name: 'BBB',
id: 2,
age: 24
}
]
}
And a template like this:
<ul>
{{#each list}}
<li onclick="someFunc({{this}})">{{name}} ({{age}}) </li>
{{/each}}
</ul>
Basically I just want to pass the current object , to a function that does something with it.
Now if I try it, then the generated HTML just has
... onclick="someFunc( [object Object] )" ...
whereas I'd like it to be like this:
... onclick="someFunc( {name: 'AAA', id: 1, age: 34} )" ...
How can I fix this?
Posting for future references:
Got my answer from here:
Handlebars.js parse object instead of [Object object]
Turns out Handlebar does a toString on the data before pasting into the template. All I had to do was to register a helper method that converts it back to json.
Handlebars.registerHelper('json', function(context) {
return JSON.stringify(context);
});
<li onclick="someFunc({{json this}})">{{name}} ({{age}}) </li>