How to make nested infinite v-for in vue? - javascript

this is what i have tried
Html
<ul class="tree">
<li>
<span>First Node</span>
<ul>
<li v-for="(tree, i) in trees" :key="i">
<span v-text="tree.name"></span>
<ul v-if="tree.childrens">
<li v-for="(child, j) in tree.childrens" :key="j">
<span v-text="child.name"></span>
</li>
</ul>
</li>
</ul>
</li>
</ul>
Javascript
I wanna loop every children without adding a new HTML element, but I have no idea how to do that.
export default {
data() {
return {
trees: [
{ name: 'Node A' },
{ name: 'Node B', childrens: [
{ name: 'Node B1' },
{ name: 'Node B2', childrens: [
{ name: 'Node B2.1' },
{ name: 'Node B2.2' },
] },
] },
],
};
},
};
Display
Anyone, thanks for your help

You can achieve this requirement by using Circular References Between Components concept of Vue. Where instead of creating a separate element for each children you can recursively call the child component.
Live Demo :
Vue.component('child', {
props: ['children'],
template: `<ul>
<li v-for="(child, index) in children" :key="index">
<parent v-if="child.childrens" :trees="[child]"></parent>
<span v-else>{{ child.name }}</span>
</li>
</ul>`
});
Vue.component('parent', {
props: ['trees'],
template: `<ul>
<li v-for="(tree, i) in trees" :key="i">
<span v-text="tree.name"></span>
<child v-if="tree.childrens" :children="tree.childrens"></child>
</li>
</ul>`
});
var app = new Vue({
el: '#app',
data: {
trees: [
{ name: 'Node A' },
{ name: 'Node B', childrens: [
{ name: 'Node B1' },
{ name: 'Node B2', childrens: [
{ name: 'Node B2.1' },
{ name: 'Node B2.2' },
] },
] },
]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<parent :trees="trees"></parent>
</div>

Related

Filter nested v-for list

<div>
<q-card
v-for="box in boxes"
:key="box.id">
<q-item>
<q-item-section>
<span> {{ box.name }} </span>
</q-item-section>
</q-item>
<q-list>
<q-item
v-for="tool in box.tools"
:key="tool.id"
clickable
<q-item-section>
<span> {{ tool.name }} </span>
</q-item-section>
</q-item>
</q-list>
</q-card>
</div>
Form input filter value
inputFilterValue = "box A"
Filter boxes
Edited with return.
computed: {
boxes(){
return boxes.filter(box => {
return box.name.toLowerCase().match(inputFilterValue.toLowerCase())
});
}
}
This works
How to filter too nested v-for box-tools list?
EDITED:
CODEPEN: https://codepen.io/ijose/pen/NWyoRMX
You can use JavaScript filter() along with some() method. some() method checks if any of the elements in an array pass the function.
Demo :
new Vue({
el: '#app',
data: {
value: null,
boxes: [],
dataObj: [{
id: 1,
name: 'Box A',
tools: [{
id: 1,
name: 'Tool A'
}, {
id: 2,
name: 'Tool D2'
}]
}, {
id: 2,
name: 'Box B',
tools: [{
id: 1,
name: 'Tool B'
}]
}, {
id: 3,
name: 'Box C',
tools: [{
id: 1,
name: 'Tool C'
}]
}]
},
mounted() {
this.boxes = structuredClone(this.dataObj);
},
methods: {
inputFilterValue(filterField) {
if (filterField === 'box') {
this.boxes = this.dataObj.filter(box => box.name.toLowerCase().match(this.value.toLowerCase()))
} else {
const filteredToolsArr = this.dataObj.map(box => {
const filteredTool = box.tools.filter((
{ name }) => name.toLowerCase().match(this.value.toLowerCase()));
return { ...box, tools: filteredTool }
})
this.boxes = filteredToolsArr.filter(obj => obj.tools.length);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
Filter : <input type="text" v-model="value" #keyup="inputFilterValue('tool')"/>
<ul v-for="box in boxes" :key="box.id">
<li>{{ box.name }}</li>
<ul v-for="tool in box.tools" :key="tool.id">
<li>{{ tool.name }}</li>
</ul>
</ul>
</div>
In the filter callback of boxes add a condition if the tool name matches the inputFilterValue
computed: {
boxes(){
return boxes.filter(box => {
return box.name.toLowerCase().match(inputFilterValue.toLowerCase()) ||
box.tools.filter(tool=>tool.name.toLowerCase().match(inputFilterValue.toLowerCase())
});
}
}

Loop throuth a filtered list in vue.js with parameter

How to loop in vue.js through a filtered list with an input parameter given?
Pls. take into consideration that filtering like <li v-for="x in todos_filtered" v-if="x.person == 'Mike'"> should be avoided with many records.
Here the code which returns an empty list instead of 2 elements.
<html>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14/dist/vue.js"></script>
<body>
<div id="app">
<ul>
<li v-for="x in todos_filtered('Mike')">
{{ x.text }}
</li>
</ul>
</div>
<script>
myObject = new Vue({
el: '#app',
data: {
todos: [
{ person: 'Mike', text: 'Learn JavaScript' },
{ person: 'Luke', text: 'Learn Vue.js' },
{ person: 'Mike', text: 'Build Something Awesome' }
]
},
computed: {
todos_filtered(person) {
return this.todos.filter((x) => x.person === person);
}
},
})
</script>
</body>
</html>
The computed property should return a function that take the person as parameter:
computed: {
todos_filtered() {
return (person)=> this.todos.filter((x) => x.person === person);
}
},

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>

Generate a list with a head based on object property in array

In Vue, i'm trying to generate a list based on a property in an object.
I have an array coming from the vuex store that looks like this:
const array = [
{
name: "British title string"
nationality: "British"
},
{
name: "Another title"
nationality: "American"
},
{
name: "Name"
nationality: "Dutch"
},
{
name: "Another american item"
nationality: "American"
},
];
What i want to have is the output like this using v-for:
<h2>British</h2>
<ul>
<li>British title string</li>
</ul>
<h2>American</h2>
<ul>
<li>Another title</li>
<li>Another american item</li>
</ul>
<h2>Dutch</h2>
<ul>
<li>Name</li>
</ul>
I have already sorted the array by the nationality property using lodash _.sortBy and it's given me an array sorted by nationality but i want to add a H2 element with the value of nationality.
If you have multiple items with same nationality and you want to group them up, use _.groupBy() before _.sortBy():
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
template: '#appTemplate',
data: () => ({
rawData: [{
name: "British title string",
nationality: "British"
},
{
name: "Another title",
nationality: "American"
},
{
name: "Name",
nationality: "Dutch"
},
{
name: "Another american item",
nationality: "American"
}
]
}),
computed: {
groupedItems() {
return _.sortBy(
_.map(
_.groupBy(this.rawData, 'nationality'),
items => ({
items,
nationality: items[0].nationality
})
),
['nationality']
);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<script type="text/template" id="appTemplate">
<div>
<template v-for="group in groupedItems">
<h2 v-text="group.nationality" />
<ul>
<li v-for="(item, index) in group.items" :key="index" v-text="item.name" />
</ul>
</template>
</div>
</script>
<div id="app"></div>
For convenience and data readability, I mapped first item's nationality as the group's nationality (and named the grouped items items), but you could have used item 0's nationality in the template directly, instead.
To demonstrate, here's how that would have looked like:
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
template: '#appTemplate',
data: () => ({
rawData: [{
name: "British title string",
nationality: "British"
},
{
name: "Another title",
nationality: "American"
},
{
name: "Name",
nationality: "Dutch"
},
{
name: "Another american item",
nationality: "American"
}
]
}),
computed: {
groupedItems() {
return _.sortBy(
_.groupBy(this.rawData, 'nationality'),
['0.nationality']
);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<script type="text/template" id="appTemplate">
<div>
<template v-for="grouped in groupedItems">
<h2 v-text="grouped[0].nationality" />
<ul>
<li v-for="(item, index) in grouped" :key="index" v-text="item.name" />
</ul>
</template>
</div>
</script>
<div id="app"></div>
Note both of the above examples output the required markup. If you want each item to have its own wrapper, replace <template> with an html tag and add keys. i.e:
<article v-for="(grouped, key) in groupedItems" :key="key">
You can do this, considering that the array is ordered:
const array = [
{
name: "Another title",
nationality: "American"
},
{
name: "Another american item",
nationality: "American"
},
{
name: "British title string",
nationality: "British"
},
{
name: "Name",
nationality: "Dutch"
},
];
const arrayAux = [];
var j = 0;
for (var i = 0; i < array.length;) {
console.log(array[i].name);
arrayAux.push({
nationality: array[i].nationality,
names: [array[i].name]
});
i++;
while ( i < array.length && array[i].nationality === array[i - 1].nationality) {
arrayAux[j].names.push(array[i].name);
i++;
}
j++;
}
console.log(arrayAux);
In the html:
<div v-for="item in arrayAux">
<h2>{{ item.nationality }}</h2>
<ul>
<li v-for="n in item.names">
{{ n.name }}
</li>
</ul>
</div>

Knockout "with" binding

I am trying to display descendant elements in array using "with" binding.
But it displays only last items in "exercises" and I want to see all of them. How is it possible to fix this?
And after that, how can I make each item in array editable?
My ViewModel:
function AppViewModel() {
var self = this;
self.workouts = ko.observableArray([
{name: "Workout1", exercises:{
name: "Exercise1.1",
name: "Exercise1.2",
name: "Exercise1.3"
}},
{name: "Workout2", exercises:{
name: "Exercise2.1",
name: "Exercise2.2",
name: "Exercise2.3"
}},
{name: "Workout3", exercises:{
name: "Exercise3.1",
name: "Exercise3.2",
name: "Exercise3.3"
}},
{name: "Workout4", exercises:{
name: "Exercise3.1",
name: "Exercise3.2",
name: "Exercise3.3"
}},
]);
self.removeWorkout = function() {
self.workouts.remove(this);
};
}
ko.applyBindings(new AppViewModel());
The View:
<div class="content">
<ul data-bind="foreach: workouts">
<li>
<span data-bind="text: name"> </span>
Remove
<ul data-bind="with: exercises">
<li data-bind="text: name"></li>
</ul>
</li>
</ul>
</div>
Here's this code at jsfiddle:
http://jsfiddle.net/9TrbE/
Thanks!
The exercises property you declared as an object should be an array.
self.workouts = ko.observableArray([
{name: "Workout1", exercises:[
{ name: "Exercise1.1" },
{ name: "Exercise1.2" },
{ name: "Exercise1.3" }
]},
]);
So you can use this view :
<div class="content">
<ul data-bind="foreach: workouts">
<li>
<span data-bind="text: name"> </span>
Remove
<ul data-bind="foreach: exercises">
<li data-bind="text: name"></li>
</ul>
</li>
</ul>
</div>
Declaring :
var exercises = {
name: "Exercise1.1",
name: "Exercise1.2",
name: "Exercise1.3"
};
Is like doing that :
var exercises = {
name: "Exercise1.1",
};
exercises.name: "Exercise1.2";
exercises.name: "Exercise1.3";
Get it with this
Here's this code at jsfiddle
self.workouts = ko.observableArray([
{name: "Workout1", exercises:[
{ name: "Exercise1.1" },
{ name: "Exercise1.2" },
{ name: "Exercise1.3" }
]},
]);
`http://jsfiddle.net/9TrbE/8/

Categories

Resources