The Ultimate Responsive Vuetify Data Iterator
Tags: vue, vuetify, webdev, tutorial
Published: 2020-04-15 16:09:02

---
title: The Ultimate Responsive Vuetify Data Iterator
published: true
description: 
tags: vue, vuetify, webdev, tutorial
---

![Vuetify Data Iterator](https://dev-to-uploads.s3.amazonaws.com/i/myptc0yousgb6yf6ki1y.png)

Design Goals
---

- **Responsive columns per row** 
   - 1 card per row on phones (xs)
   - 2 cards per row on tablets (sm)
   - 3 cards per row on laptops (md)
   - 4 cards per row on desktops (lg)
   - 6 cards per row on wide (xl)
- **Maximize rows per page, no scrolling** 
- **Maximize items per page, no gaps**
- **Dynamic pagination**

***

What makes this the Ultimate Responsive Data Iterator?
---

Without a doubt, creating a data iterator in Vuetify is NBD. *However*, creating a data iterator that meets our specific design goals is more challenging. Consider this SO post 💡...

{% stackoverflow 61124031 %}

There are several unknowns here. The columns in each row wrap responsively based on [breakpoints](https://vuetifyjs.com/en/customization/breakpoints/#breakpoints), so it's difficult to determine the ideal number of items per page. It's also difficult to know the ideal number of columns per row. If we set a specific number of items per page, there may be gaps on the last row depending on the current viewport.

This is a common issue with responsive grids like Bootstrap and Vuetify's. This is because we iterate (repeat) the columns (items) in a single `row` element.

```
<v-row>
  <v-col v-for="n in items">..</v-col>
</v-row>
```

But the "visual" rows that are rendered wrap according to the responsive breakpoints we have set. Therefore, the challenge is to to sync-up the "wrapping" of the repeating data items with the current viewport width (breakpoint). 

***

Vuetify's Data Iterator Component
---

Since I don't want to reinvent the wheel, I'm using the [data iterator](https://vuetifyjs.com/en/components/data-iterators/#data-iterators). What's cool is that you can customize it simply using a template. You can use whatever markup you want for each item (rows, list items, card, etc..). This solution makes *full* use of the Vuetify 2.x Data Iterator using customizations on the header, footer and default slot templates.

***

The Solution
---

Controlling the rows-per-page and items-per-row with responsive items is a little math challenge🤓. What we do know is:

- the total number of data items
- the current responsive breakpoint
- there are 12 column units per row (based on Vuetify's grid)

Using Vue computed values, determine the number of pages (for pagination), optimal rows per page (based on viewport height), items per row (based on how many cols you want on each breakpoint), and finally use this to calculate the items per page (ipp)...

```
   computed: {
      numberOfPages () {
        return Math.ceil(this.beers.length / this.ipp)
      },
      rowsPerPage () {
        return this.rpp
      },
      itemsPerRow () {
        switch (this.$vuetify.breakpoint.name) {
          case 'xs': return 1
          case 'sm': return 2
          case 'md': return 3
          case 'lg': return 4
          case 'xl': return 6
        }
      },
      ipp () {
         return Math.ceil(this.rowsPerPage * this.itemsPerRow)
      },
   },

```

The number of pages, and items-per-row is calculated **as viewport width changes**. This meets our responsive design goal of cards per row, while also preventing gaps (or missing items) in the last row.

```

   methods: {
        ...
        calcRowsPerPage () {
            let container = document.getElementById('container')
            let minItemHeight = 170
            if (container) {
                let containerHeight = parseInt(container.clientHeight, 0)
                this.rpp = Math.floor(containerHeight/minItemHeight)
            }
            else {
                this.rpp = 4
            }
        },
    },
    created() {
        // re-calc on screen resize
        window.addEventListener('resize', () => {
            this.calcRowsPerPage()
        })
   },

```

The responsive behavior is achieved using the slot:default template. Notice the `cols` prop is determined by dividing 12 (the number of columns in Vuetify's grid) by the computed `itemsPerRow` value. As you can see above, `itemsPerRow` is based on the Vuetify breakpoints.

```
   <template v-slot:default="props">
        <v-row class="fill-height overflow-auto" id="container">
          <v-col
            v-for="(item,idx) in props.items"
            :key="item.name"
            :cols="(12/itemsPerRow)"
            class="py-2"
          > 
            <v-card class="card fill-height">
              <v-card-title>
                  <span class="font-weight-light text-truncate">
                    <span v-text="item.id"></span> {{ item.name }} 
                  </span>
              </v-card-title>
              <v-divider></v-divider>
              <v-card-text>
                <span v-html="item.tagline" class="mr-2"></span>
                <v-chip v-text="item.first_brewed"></v-chip>
              </v-card-text>    
            </v-card>
          </v-col>
        </v-row>
    </template>
```

6 items on wide...
![XL](https://dev-to-uploads.s3.amazonaws.com/i/6cft4kynnamtidd0ifx2.png)

4 items on large...
![LG](https://dev-to-uploads.s3.amazonaws.com/i/i6yuyofamyagztq4kzvi.png)

3 items on medium..
![MD](https://dev-to-uploads.s3.amazonaws.com/i/3klbhekjo0xnc7lbq3jw.png)

Stacking with 1 item per row on mobile...
![XS](https://dev-to-uploads.s3.amazonaws.com/i/wh7w8xbi4kn5ad0yt6g3.png)

**And...**, on all these breakpoints the pagination is updated! This happens because the reactive props are synced...

```
    <v-data-iterator
         :items="items"
         :items-per-page.sync="ipp"
         :page.sync="page"
         hide-default-footer
    >...
```

Rows-per-page is dynamic too!
---

The visual rows are also maximized to fill the viewport *height*. For example, 3 rows on short screens, 5 rows on tall screens.

Short screens..
![short](https://dev-to-uploads.s3.amazonaws.com/i/1mu2y1txc4h1ceyszp8s.png)

Or, tall screens..
![tall](https://dev-to-uploads.s3.amazonaws.com/i/o48722j6kmhp81zqojfz.png)

The end result is a responsive grid with dynamic paging that maximizes viewport height and width. **No scrolling baby!** 😎

[Demo](https://codeply.com/v/A4uOblnFzf) and [Full Source](https://codeply.com/p/A4uOblnFzf)

***