loading...
Codeply

The Ultimate Responsive Vuetify Data Iterator

carolskelly profile image Carol Skelly ใƒป4 min read

Vuetify Data Iterator

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 ๐Ÿ’ก...

We are using Vuetify's v-data-iterator to control pagination on a grid layout. We are setting the iterator's rows-per-page-items to [8, 16, 24, 36]. But this doesn't exactly determine the number of rows per page, but items per page. That is, because it's a grid layout, there may be several itemsโ€ฆ

There are several unknowns here. The columns in each row wrap responsively based on 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. 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

4 items on large...
LG

3 items on medium..
MD

Stacking with 1 item per row on mobile...
XS

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

Or, tall screens..
tall

The end result is a responsive grid with dynamic paging that maximizes viewport height and width. No scrolling baby! ๐Ÿ˜Ž

Demo and Full Source


Discussion

pic
Editor guide