Dev
Dev

Reputation: 1223

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.

Upvotes: 1

Views: 774

Answers (4)

hamid niakan
hamid niakan

Reputation: 2851

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>

Upvotes: 1

Majed Badawi
Majed Badawi

Reputation: 28404

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>

Upvotes: 0

Keimeno
Keimeno

Reputation: 2644

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.

Upvotes: 0

mahmed nabil
mahmed nabil

Reputation: 366

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>

Upvotes: 3

Related Questions