setting single element to hidden in vue js when mapped - javascript

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

Related

KnockoutJS - How to hide certain elements inside foreach using Observable Arrays?

I have a list of WebsiteOwners. I'm trying to build a UI which will display more information about the owners when I click on them.
this.toExpand = ko.observableArray(); //initialize an observable array
this.invertExpand = ko.observable("");
this.invertExpand = function (index) {
if (self.invertExpand[index] == false) {
self.invertExpand[index] = true;
alert(self.invertExpand[index]); //testing whether the value changed
}
else {
self.invertExpand[index] = false;
alert(self.invertExpand[index]); //testing whether the value changed
}
};
Here's the HTML code :
<div data-bind="foreach: WebsiteOwners">
<div>
<button data-bind="click: $root.invertExpand.bind(this,$index())" class="label label-default">>Click to Expand</button>
</div>
<div data-bind="visible: $root.toExpand()[$index]">
Primary Owner: <span data-bind="text:primaryOwner"></span>
Website Name : <span data-bind="text:websiteName"></span>
//...additional information
</div>
</div>
You can store one of your WebsiteOwner items directly in your observable. No need to use an index.
Don't forget you read an observable by calling it without arguments (e.g. self.invertExpand()) and you write to it by calling with a value (e.g. self.invertExpand(true))
I've included 3 examples in this answer:
One that allows only a single detail to be opened using knockout
One that allows all details to be opened and closed independently using knockout
One that does not use knockout but uses plain HTML instead 🙂
1. Accordion
Here's an example for a list that supports a single expanded element:
const websiteOwners = [
{ name: "Jane", role: "Admin" },
{ name: "Sarah", role: "Employee" },
{ name: "Hank", role: "Employee" }
];
const selectedOwner = ko.observable(null);
const isSelected = owner => selectedOwner() === owner;
const toggleSelect = owner => {
selectedOwner(
isSelected(owner) ? null : owner
);
}
ko.applyBindings({ websiteOwners, isSelected, toggleSelect });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul data-bind="foreach: { data: websiteOwners, as: 'owner' }">
<li>
<span data-bind="text: name"></span>
<button data-bind="
click: toggleSelect,
text: isSelected(owner) ? 'collapse' : 'expand'"></button>
<div data-bind="
visible: isSelected(owner),
text: role"></div>
</li>
</ul>
2. Independent
If you want each of them to be able to expand/collapse independently, I suggest adding that state to an owner viewmodel:
const websiteOwners = [
{ name: "Jane", role: "Admin" },
{ name: "Sarah", role: "Employee" },
{ name: "Hank", role: "Employee" }
];
const OwnerVM = owner => ({
...owner,
isSelected: ko.observable(null),
toggleSelect: self => self.isSelected(!self.isSelected())
});
ko.applyBindings({ websiteOwners: websiteOwners.map(OwnerVM) });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul data-bind="foreach: websiteOwners">
<li>
<span data-bind="text: name"></span>
<button data-bind="
click: toggleSelect,
text: isSelected() ? 'collapse' : 'expand'"></button>
<div data-bind="
visible: isSelected,
text: role"></div>
</li>
</ul>
3. Using <details>
This one leverages the power of the <details> element. It's probably more accessible and by far easier to implement!
const websiteOwners = [
{ name: "Jane", role: "Admin" },
{ name: "Sarah", role: "Employee" },
{ name: "Hank", role: "Employee" }
];
ko.applyBindings({ websiteOwners });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul data-bind="foreach: websiteOwners">
<li>
<details>
<summary data-bind="text: name"></summary>
<div data-bind="text: role"></div>
</details>
</li>
</ul>

Vue.js - Nested v-for Loops

I have a Vue 3.0 app. In this app, I have the following:
export default {
data() {
return {
selected: null,
departments: [
{
name: 'Sports',
items: [
{ id:'1', label: 'Football', value:'football' },
{ id:'2', label: 'Basketball', value:'basketball' },
]
},
{
name: 'Automotive',
versions: [
{ id:'3', label: 'Oil', value:'oil' },
{ id:'4', label: 'Mud Flaps', value:'mud-flaps' },
]
}
]
};
}
};
I need to render these items in a dropdown list. Notably, the items need to be rendered by sections. In an attempt to do this, I'm using Bootstrap's dropdown headers. My challenge is, the HTML needs to be rendered in a way that I can't see how to do with Vue. I need to render my HTML like this:
<ul class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<li><h6 class="dropdown-header">Sports</h6></li>
<li><a class="dropdown-item" href="#">Football</a></li>
<li><a class="dropdown-item" href="#">Basketball</a></li>
<li><h6 class="dropdown-header">Automotive</h6></li>
<li><a class="dropdown-item" href="#">Oil</a></li>
<li><a class="dropdown-item" href="#">Mud Flaps</a></li>
</ul>
Based on this need, I would have to have a nested for loop. In psuedocode, it would be like this:
foreach department in departments
<li><h6 class="dropdown-header">{{ department.name }}</h6></li>
foreach item in department.items
<li><a class="dropdown-item" href="#">{{ item.label }} </a></li>
end foreach
end foreach
I know I could do this with something like Razer. However, I don't see anyway to do this in Vue. Yet, I have to believe I'm overlooking something as rendering a hierarchy is a common need. How do you render a hierachy in Vue without changing the data structure?
Thank you.
just use nested v-for like this:
<template v-for="(department , i) in departments">
<li><h6 class="dropdown-header" :key="`head-${i}`">{{ department.name }}</h6></li>
<template v-for="(item , j) in department.items">
<li><a class="dropdown-item" href="#" :key="`sub-${i}${j}`">{{ item.label }} </a></li>
</template>
</template>
you can have nested v-for with the help of template and also make sure that the keys binding to the elements are unique (this has to be unique so you don't get weird behavior from vue).
run the code below and check the result (click on full screen to see the full list rendered):
// you can ignore this line
Vue.config.productionTip = false;
new Vue({
el: '#app',
data: {
departments: [{
name: 'Sports',
items: [{
id: '1',
label: 'Football',
value: 'football'
},
{
id: '2',
label: 'Basketball',
value: 'basketball'
},
]
},
{
name: 'Automotive',
items: [{
id: '3',
label: 'Oil',
value: 'oil'
},
{
id: '4',
label: 'Mud Flaps',
value: 'mud-flaps'
},
]
}
],
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<template v-for="({name, items}, i) in departments">
<li :key="`header-${i}`">
<h6 class="dropdown-header">{{ name }}</h6>
</li>
<li v-for="{label, id} in items" :key="`link-${id}`">
<a class="dropdown-item" href="#">{{ label }}</a>
</li>
</template>
</ul>
</div>
One approach to this is to use a computed method. I've created a jsfiddle here.
computed: {
departmentItems() {
const items = [];
this.departments.forEach(department => {
items.push({
label: department.name,
class: 'dropdown-header',
});
department.items.forEach(item => {
items.push({
label: item.label,
class: 'dropdown-item'
})
})
})
return items;
}
}
The important part here is, that we have a common model for both dropdown-header and dropdown-item.
You structure your data in a computed property, and then use v-for to iterate over the list. Here is an example:
new Vue({
el: "#app",
data() {
return {
selected: null,
departments: [
{
name: 'Sports',
items: [
{ id:'1', label: 'Football', value:'football' },
{ id:'2', label: 'Basketball', value:'basketball' },
]
},
{
name: 'Automotive',
items: [
{ id:'3', label: 'Oil', value:'oil' },
{ id:'4', label: 'Mud Flaps', value:'mud-flaps' },
]
}
]
}
},
computed: {
departmentsItem: function() {
return this.departments.reduce((acc,department) =>
[
...acc,
{ type: "department", name: department.name },
...department.items.map(item => ({ type: "item", name: item.label }))
]
, []);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<li v-for="(departmentItem,i) in departmentsItem" :key="i">
<h6
v-if="departmentItem.type==='department'" class="dropdown-header"
>{{departmentItem.name}}</h6>
<a
v-else-if="departmentItem.type==='item'" class="dropdown-item" href="#"
>{{departmentItem.name}}</a>
</li>
</ul>
</div>

Only show elements who has some propertie Vue JS

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>

Why does't this toggle and filter not work in Vue JS?

I have created a v-for directive and I am now trying to add a dropdown filter in order to filter the results displayed. However, it's just not working. I have followed every step carefully as this is based on a Treehouse tutorial, but for some reason when changing the dropdown nothing displays. Seems the name property value is not being set to the object. Every time a new option is chosen it should fire the function filterList.
const clubs = [
{
name: 'Tigers',
location: 'Manchester',
members: '22',
registered: 'No',
pitch: 'Grass'
},
{
name: 'Dolphins',
location: 'Miami',
members: '19',
registered: 'Yes',
pitch: 'Grass'
},
{
name: 'Bleu Sox',
location: 'Paris',
members: '13',
registered: 'Yes',
pitch: 'Astroturf'
}
];
const app = new Vue({
el: '#app',
data: {
clubs,
name: ''
},
methods: {
toggleDetails: function(club) {
this.$set(club, 'showDetails', !club.showDetails)
},
filterList: function() {
this.name = event.target.value;
console.log(this.name);
}
}
});
My HTML is as follows;
<div id="app">
<select v-on:change="filterList">
<option v-for="club in clubs">{{club.name}}</option>
</select>
<ul>
<li v-show="name === club.name" v-for="club in clubs" v-on:click="toggleDetails(club)">
<h1>{{club.name}}</h1>
<div v-show="club.showDetails">
<p>{{club.location}}</p>
<p>{{club.members}}</p>
</div>
</li>
</ul>
</div>
You don't need to access DOM events for this - Vue is reactive and will update name when this is changed:
<select v-model="name">
<option v-for="club in clubs">{{club.name}}</option>
</select>

How can I use ng-repeat to iterate through arrays associated using dynamic keys

I am trying to use ng-repeat to iterate through an array of objects and use each objects ID to look up the data binded to a checklist model.
I have the following javascript object in a project I'm working on:
{
diagnosis: {
mainfractures: [
{
id: "metacarpal",
textinput_id: "metacarpal_text",
title: "5th Metacarpal",
},
{
id: "proximal_phalanx",
textinput_id: "proximal_phalanx_text",
title: "Proximal Phalanx",
},
{
id: "middle_phalanx",
textinput_id: "middle_phalanx_text",
title: "Middle Phalanx",
},
{
id: "distal_phalanx",
textinput_id: "distal_phalanx_text",
title: "Distal Phalanx",
},
{
id: "scaphoid_fracture",
textinput_id: "scaphoid_fracture_text",
title: "Scaphoid Fracture",
}
]
}}
Here is what I have for my checklist model. As the user selects a checkbox, a value is binded to the array associated with that fracture.
$scope.checklists = {
"diagnosis": {
metacarpal: [],
proximal_phalanx: [],
middle_phalanx: [],
distal_phalanx: [],
scaphoid_fracture: []
}
}
Checklist Image Example
Once a users makes a selection similar to the image above the the checklist model for metacarpal should look like this: metacarpal: ["head"]
What I'm trying to do is list each of the users selection in bulletpoint via fracture.id. I'm trying to accomplish it with this piece of code but it's only listed the fracture title so far. is it a problem with trying to interpolate fracture.id into ng-repeat?
<div ng-repeat="fracture in diagnosis.mainfractures">
<div > <!--ng-if="checklists['diagnosis'][fracture.id] > 0"-->
<h4>{{ fracture.title }}</h4>
<div class="row">
<ul type="disc">
<li ng-repeat="selection in checklists['diagnosis'][fracture.id]">
• {{ capitalize(selection) }}
</li>
</ul>
</div>
</div>
</div>
Based on your supplied code, I'd have to say your issue is actually due to JS syntax errors. You're missing commas after each object item and there is a random double quote here scaphoid_fracture"[].
$scope.checklists = {
"diagnosis": {
metacarpal: []
proximal_phalanx: []
middle_phalanx: []
distal_phalanx: []
scaphoid_fracture"[]
}
}
Here is a fully working jsfiddle
Adjusted the code a bit:
capitalize ->
uppercase(https://docs.angularjs.org/api/ng/filter/uppercase)
some syntax errors
But seems that access by dynamic object key inside ng-repeat works pretty correct
https://jsbin.com/rodecosoyo/1/edit?html,js,output
Angular Application
var app = angular.module('arrayid', []);
app.controller('arrayContainerCtrl', function($scope) {
$scope.diagnosis = {
mainfractures: [{
id: "metacarpal",
textinput_id: "metacarpal_text",
title: "5th Metacarpal",
}, {
id: "proximal_phalanx",
textinput_id: "proximal_phalanx_text",
title: "Proximal Phalanx",
}, {
id: "middle_phalanx",
textinput_id: "middle_phalanx_text",
title: "Middle Phalanx",
}, {
id: "distal_phalanx",
textinput_id: "distal_phalanx_text",
title: "Distal Phalanx",
}, {
id: "scaphoid_fracture",
textinput_id: "scaphoid_fracture_text",
title: "Scaphoid Fracture",
}]
};
$scope.checklists = {
"diagnosis": {
metacarpal: ['1', '2'],
proximal_phalanx: ['2', '3'],
middle_phalanx: ['3'],
distal_phalanx: ['4'],
scaphoid_fracture: ['5']
}
};
});
Markup:
<body ng-app='arrayid' ng-controller='arrayContainerCtrl'>
<div ng-repeat="fracture in diagnosis.mainfractures">
<h4>{{ fracture.title }}</h4>
<div class="row">
<ul type="disc">
<li ng-repeat="selection in checklists['diagnosis'][fracture.id]">
• {{ selection | uppercase }}
</li>
</ul>
</div>
</div>
</body>

Categories

Resources