Categories
Vuetify

Vuetify — Data Iterators

Vuetify is a popular UI framework for Vue apps.

In this article, we’ll look at how to work with the Vuetify framework.

Data Iterator — Expanding Items

We can expand and collapse items with the v-data-iterator component.

For example, we can write:

<template>
  <v-container fluid>
    <v-data-iterator
      :items="items"
      item-key="name"
      :items-per-page="4"
      :single-expand="expand"
      hide-default-footer
    >
      <template v-slot:default="{ items, isExpanded, expand }">
        <v-row>
          <v-col v-for="item in items" :key="item.name" cols="12" sm="6" md="4" lg="3">
            <v-card>
              <v-card-title>
                <h4>{{ item.name }}</h4>
              </v-card-title>
              <v-switch
                :input-value="isExpanded(item)"
                :label="isExpanded(item) ? 'Expanded' : 'Closed'"
                class="pl-4 mt-0"
                @change="(v) => expand(item, v)"
              ></v-switch>
              <v-divider></v-divider>
              <v-list v-if="isExpanded(item)" dense>
                <v-list-item>
                  <v-list-item-content>Calories:</v-list-item-content>
                  <v-list-item-content class="align-end">{{ item.calories }}</v-list-item-content>
                </v-list-item>
                <v-list-item>
                  <v-list-item-content>Fat:</v-list-item-content>
                  <v-list-item-content class="align-end">{{ item.fat }}</v-list-item-content>
                </v-list-item>
              </v-list>
            </v-card>
          </v-col>
        </v-row>
      </template>
    </v-data-iterator>
  </v-container>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    itemsPerPage: 4,
    items: [
      {
        name: "Yogurt",
        calories: 159,
        fat: 6.0,
      },
      {
        name: "Ice cream sandwich",
        calories: 237,
        fat: 9.0,
      },
      {
        name: "Donuts",
        calories: 262,
        fat: 16.0,
      },
      {
        name: "Cupcake",
        calories: 305,
        fat: 3.7,
      },
    ],
  }),
};
</script>

We get the isExpanded function from the default slot to style our entries differently if it’s expanded or not.

The expand function from the slot lets us expand or collapse the entry if we click on the v-switch .

Filter Items

We can order, filter, and paginate items.

For example, we can write:

<template>
  <v-container fluid>
    <v-data-iterator
      :items="items"
      :items-per-page.sync="itemsPerPage"
      :page="page"
      :search="search"
      :sort-by="sortBy.toLowerCase()"
      :sort-desc="sortDesc"
      hide-default-footer
    >
      <template v-slot:header>
        <v-toolbar dark color="blue darken-3" class="mb-1">
          <v-text-field
            v-model="search"
            clearable
            flat
            solo-inverted
            hide-details
            prepend-inner-icon="search"
            label="Search"
          ></v-text-field>
          <template v-if="$vuetify.breakpoint.mdAndUp">
            <v-spacer></v-spacer>
            <v-select
              v-model="sortBy"
              flat
              solo-inverted
              hide-details
              :items="keys"
              prepend-inner-icon="search"
              label="Sort by"
            ></v-select>
            <v-spacer></v-spacer>
            <v-btn-toggle v-model="sortDesc" mandatory>
              <v-btn large depressed color="blue" :value="false">
                <v-icon>mdi-arrow-up</v-icon>
              </v-btn>
              <v-btn large depressed color="blue" :value="true">
                <v-icon>mdi-arrow-down</v-icon>
              </v-btn>
            </v-btn-toggle>
          </template>
        </v-toolbar>
      </template>

      <template v-slot:default="props">
        <v-row>
          <v-col v-for="item in props.items" :key="item.name" cols="12" sm="6" md="4" lg="3">
            <v-card>
              <v-card-title class="subheading font-weight-bold">{{ item.name }}</v-card-title>

              <v-divider></v-divider>

              <v-list dense>
                <v-list-item v-for="(key, index) in filteredKeys" :key="index">
                  <v-list-item-content :class="{ 'blue--text': sortBy === key }">{{ key }}:</v-list-item-content>
                  <v-list-item-content
                    class="align-end"
                    :class="{ 'blue--text': sortBy === key }"
                  >{{ item[key.toLowerCase()] }}</v-list-item-content>
                </v-list-item>
              </v-list>
            </v-card>
          </v-col>
        </v-row>
      </template>

      <template v-slot:footer>
        <v-row class="mt-2" align="center" justify="center">
          <span class="grey--text">Items per page</span>
          <v-menu offset-y>
            <template v-slot:activator="{ on, attrs }">
              <v-btn dark text color="primary" class="ml-2" v-bind="attrs" v-on="on">
                {{ itemsPerPage }}
                <v-icon>mdi-chevron-down</v-icon>
              </v-btn>
            </template>
            <v-list>
              <v-list-item
                v-for="(number, index) in itemsPerPageArray"
                :key="index"
                @click="updateItemsPerPage(number)"
              >
                <v-list-item-title>{{ number }}</v-list-item-title>
              </v-list-item>
            </v-list>
          </v-menu>

          <v-spacer></v-spacer>

          <span class="mr-4 grey--text">Page {{ page }} of {{ numberOfPages }}</span>
          <v-btn fab dark color="blue darken-3" class="mr-1" @click="formerPage">
            <v-icon>mdi-chevron-left</v-icon>
          </v-btn>
          <v-btn fab dark color="blue darken-3" class="ml-1" @click="nextPage">
            <v-icon>mdi-chevron-right</v-icon>
          </v-btn>
        </v-row>
      </template>
    </v-data-iterator>
  </v-container>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    itemsPerPageArray: [1, 2],
    search: "",
    filter: {},
    sortDesc: false,
    page: 1,
    itemsPerPage: 1,
    sortBy: "name",
    keys: ["Name", "Calories", "Fat"],
    items: [
      {
        name: "Yogurt",
        calories: 159,
        fat: 6.0,
      },
      {
        name: "Ice cream sandwich",
        calories: 237,
        fat: 9.0,
      },
      {
        name: "Donuts",
        calories: 262,
        fat: 16.0,
      },
      {
        name: "Cupcake",
        calories: 305,
        fat: 3.7,
      },
    ],
  }),
  computed: {
    numberOfPages() {
      return Math.ceil(this.items.length / this.itemsPerPage);
    },
    filteredKeys() {
      return this.keys.filter((key) => key !== `Name`);
    },
  },
  methods: {
    nextPage() {
      if (this.page + 1 <= this.numberOfPages) this.page += 1;
    },
    formerPage() {
      if (this.page - 1 >= 1) this.page -= 1;
    },
    updateItemsPerPage(number) {
      this.itemsPerPage = number;
    },
  },
};
</script>

We have the text field to search for items in the header slot.

The search state will be set if we type something into the text field in the header.

The v-btn-toggle component lets us toggle the sorting between forward and reverse order.

When we click on the buttons, we toggle the sortDesc state.

Also, we have a dropdown that sets the sortBy state to let us sort items by the given key automatically.

All the states are used as prop values in the v-data-iterator component.

Then all the sorting, pagination, and search logic will be taken care of by the v-data-iterator .

We also have a v-select to let us change the number of items per page with the updateItemsPerPage method.

The content is populated in the default slot.

The props.items property has the items that we want to render into cards.

Conclusion

We can add sorting, pagination, and searching with the v-data-iterator component with a few props.

Categories
Vuetify

Vuetify — Time Picker Customization

Vuetify is a popular UI framework for Vue apps.

In this article, we’ll look at how to work with the Vuetify framework.

Time Picker Width

We can set the width of a time picker with the width prop:

<template>  
  <v-row justify="space-around">  
    <v-time-picker v-model="time" type="month" width="290" class="ml-4"></v-time-picker>  
  </v-row>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({  
    time: "11:15",  
  }),  
};  
</script>

The width is in pixels.

Also, we can add the full-width prop to make the time picker full width:

<template>  
  <v-row justify="space-around">  
    <v-time-picker v-model="time" :landscape="$vuetify.breakpoint.mdAndUp" full-width type="month"></v-time-picker>  
  </v-row>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({  
    time: "11:15",  
  }),  
};  
</script>

We can set it to landscape mode at the breakpoint we want so that the header will be displayed as the sidebar.

Time Picker’s Elevation

A time picker can have a shadow below it.

We can add the flat prop to remove the shadow:

<template>  
  <v-row justify="space-around">  
    <v-time-picker v-model="time" flat></v-time-picker>  
  </v-row>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({  
    time: "11:15",  
  }),  
};  
</script>

Or we can add the elevation prop to add our shadow:

<template>  
  <v-row justify="space-around">  
    <v-time-picker v-model="time" elevation="15"></v-time-picker>  
  </v-row>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({  
    time: "11:15",  
  }),  
};  
</script>

The value is between 0 to 24.

AM/PM Switch in Title

We can add the ampm-in-title prop to add the AM and PM text into the header.

For example, we can write:

<template>  
  <v-row justify="space-around">  
    <v-time-picker v-model="time" ampm-in-title></v-time-picker>  
  </v-row>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({  
    time: "11:15",  
  }),  
};  
</script>

This way, we can choose an AM or PM time.

No Title

We can remove the title bar with the no-title prop:

<template>  
  <v-row justify="space-around">  
    <v-time-picker v-model="time" no-title></v-time-picker>  
  </v-row>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({  
    time: "11:15",  
  }),  
};  
</script>

Time Picker With Seconds

A time picker can let us choose the seconds.

To do that, we add the use-seconds prop:

<template>  
  <v-row justify="space-around">  
    <v-time-picker v-model="picker" use-seconds></v-time-picker>  
  </v-row>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({  
    time: "11:15",  
  }),  
};  
</script>

Now we can pick the seconds.

Scrollable Time Picker

We can make the time values scrollable with the scrollable prop:

<template>  
  <v-row justify="space-around">  
    <v-time-picker v-model="picker" scrollable></v-time-picker>  
  </v-row>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({  
    time: "11:15",  
  }),  
};  
</script>

Now we pick our time values by scrolling with the scroll wheel.

Range

The time value range can be restricted with the min and max props:

<template>  
  <v-row justify="space-around" align="center">  
    <v-col style="width: 290px; flex: 0 1 auto;">  
      <h2>Start:</h2>  
      <v-time-picker v-model="start" :max="end"></v-time-picker>  
    </v-col>  
    <v-col style="width: 290px; flex: 0 1 auto;">  
      <h2>End:</h2>  
      <v-time-picker v-model="end" :min="start"></v-time-picker>  
    </v-col>  
  </v-row>  
</template>  
<script>  
export default {  
  name: "HelloWorld",  
  data: () => ({  
    start: null,  
    end: null,  
  }),  
};  
</script>

We have the max prop to set the maximum time we can choose.

And the min prop sets a minimum time restriction.

Conclusion

We can customize time pickers in many ways.

Categories
Vuetify

Vuetify — Subheaders

Vuetify is a popular UI framework for Vue apps.

In this article, we’ll look at how to work with the Vuetify framework.

Subheaders

The v-subheader component is used to separate sections of lists.

For example, we can write:

<template>
  <v-col cols="12" sm="6" offset-sm="3">
    <v-card>
      <v-subheader :inset="inset">Subheader</v-subheader>

      <v-list>
        <template v-for="(item, index) in items">
          <v-list-item v-if="item.action" :key="item.title">
            <v-list-item-action>
              <v-icon>{{ item.action }}</v-icon>
            </v-list-item-action>

            <v-list-item-content>
              <v-list-item-title>{{ item.title }}</v-list-item-title>
            </v-list-item-content>
          </v-list-item>

          <v-divider v-else-if="item.divider" :key="index" :inset="inset"></v-divider>
        </template>
      </v-list>
    </v-card>
  </v-col>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    inset: true,
    items: [
      {
        action: "label",
        title: "List item 1",
      },
      {
        divider: true,
      },
      {
        action: "label",
        title: "List item 2",
      },
      {
        divider: true,
      },
      {
        action: "label",
        title: "List item 3",
      },
    ],
  }),
};
</script>

We have the v-subheader component on top of a list.

It’ll be aligned to the text of the list with the inset prop.

Grid Subheaders

We can add subheaders above a grid.

For example, we can write:

<template>
  <v-row>
    <v-col cols="12" sm="6" offset-sm="3">
      <v-card>
        <v-subheader>May</v-subheader>
        <v-container fluid>
          <v-row>
            <v-col v-for="i in 6" :key="i" cols="4">
              <img
               :src="`https://randomuser.me/api/portraits/men/${i}.jpg`"
                class="image"
                height="100%"
                width="100%"
              />
            </v-col>
          </v-row>
        </v-container>
      </v-card>
    </v-col>
  </v-row>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({}),
};
</script>

We have the v-subheader on top of a grid.

Menu Subheaders

Subheaders also fit automatically in menus.

For example, we can write:

<template>
  <v-row>
    <v-col cols="12" sm="6" offset-sm="3">
      <v-card>
        <v-toolbar color="teal" dark>
          <v-app-bar-nav-icon></v-app-bar-nav-icon>

           <v-toolbar-title>Manage</v-toolbar-title>

          <v-spacer></v-spacer>

          <v-btn icon>
            <v-icon>mdi-dots-vertical</v-icon>
          </v-btn>
        </v-toolbar>

        <v-list>
          <template v-for="(item, index) in items">
            <v-list-item v-if="item.action" :key="item.title">
              <v-list-item-action>
                <v-icon>{{ item.action }}</v-icon>
              </v-list-item-action>

              <v-list-item-content>
                <v-list-item-title>{{ item.title }}</v-list-item-title>
              </v-list-item-content>
            </v-list-item>

            <v-divider v-else-if="item.divider" :key="index"></v-divider>

            <v-subheader v-else-if="item.header" :key="item.header">{{ item.header }}</v-subheader>
          </template>
        </v-list>
      </v-card>
    </v-col>
  </v-row>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    items: [
      {
        action: "move_to_inbox",
        title: "Inbox",
      },
      {
        action: "send",
        title: "Sent",
      },
      { divider: true },
      { header: "Labels" },
      {
        action: "label",
        title: "Family",
      },
    ],
  }),
};
</script>

We created a v-list with the items.

The v-subheader is one of the types of items that we’re looping through within the v-list component.

The subheader will be aligned with the menu text without any extra styles.

Conclusion

Subheaders are useful for titles for lists.

Categories
Vuetify

Vuetify — Steppers Customization

Vuetify is a popular UI framework for Vue apps.

In this article, we’ll look at how to work with the Vuetify framework.

Vertical Steppers

We can make the stepper display vertically.

For example, we can write:

<template>
  <v-stepper v-model="step" vertical>
    <v-stepper-step :complete="step > 1" step="1">
      Select an app
      <small>Summarize if needed</small>
    </v-stepper-step>

    <v-stepper-content step="1">
      <v-card color="grey lighten-1" class="mb-12" height="200px"></v-card>
      <v-btn color="primary" @click="step = 2">Continue</v-btn>
      <v-btn text>Cancel</v-btn>
    </v-stepper-content>

    <v-stepper-step :complete="step > 2" step="2">Configure keywords</v-stepper-step>

    <v-stepper-content step="2">
      <v-card color="grey lighten-1" class="mb-12" height="200px"></v-card>
      <v-btn color="primary" @click="step = 3">Continue</v-btn>
      <v-btn text>Cancel</v-btn>
    </v-stepper-content>

    <v-stepper-step :complete="step > 3" step="3">Select ad unit</v-stepper-step>

    <v-stepper-content step="3">
      <v-card color="grey lighten-1" class="mb-12" height="200px"></v-card>
      <v-btn color="primary" @click="step = 4">Continue</v-btn>
      <v-btn text>Cancel</v-btn>
    </v-stepper-content>
  </v-stepper>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    step: 1,
  }),
};
</script>

The v-stepper-step is added above the v-stepper-content to display the step title above the content.

Linear Steppers

We can make a linear stepper to move users through each step sequentially:

<template>
  <div>
    <v-stepper>
      <v-stepper-header>
        <v-stepper-step step="1" complete>Select keywords</v-stepper-step>

        <v-divider></v-divider>

        <v-stepper-step step="2" complete>Create an ad group</v-stepper-step>

        <v-divider></v-divider>

        <v-stepper-step step="3">Create an ad</v-stepper-step>
      </v-stepper-header>
    </v-stepper>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({}),
};
</script>

We add the complete prop so that users can’t go back to that step.

Non-Linear Steppers

To make steps editable, we can add the editable prop:

<template>
  <div>
    <v-stepper>
      <v-stepper-header>
        <v-stepper-step step="1" editable>Select keywords</v-stepper-step>

        <v-divider></v-divider>

        <v-stepper-step step="2" editable>Create an ad group</v-stepper-step>

        <v-divider></v-divider>

        <v-stepper-step step="3">Create an ad</v-stepper-step>
      </v-stepper-header>
    </v-stepper>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({}),
};
</script>

The editable prop lets users click on the step.

Alternate Labels

We can place text below the step icon with the alt-labels prop:

<template>
  <div>
    <v-stepper alt-labels>
      <v-stepper-header>
        <v-stepper-step step="1">Select keywords</v-stepper-step>

        <v-divider></v-divider>

        <v-stepper-step step="2">Create an ad group</v-stepper-step>

        <v-divider></v-divider>

        <v-stepper-step step="3">Create an ad</v-stepper-step>
      </v-stepper-header>
    </v-stepper>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({}),
};
</script>

Multi-line Error State

We can add an error state with the rules prop:

<template>
  <div>
    <v-stepper alt-labels>
      <v-stepper-header>
        <v-stepper-step step="1">Select keywords</v-stepper-step>

        <v-divider></v-divider>

        <v-stepper-step step="2" :rules="[() => false]">
          Create an ad group
          <small>Alert message</small>
        </v-stepper-step>

        <v-divider></v-divider>

        <v-stepper-step step="3">Create an ad</v-stepper-step>
      </v-stepper-header>
    </v-stepper>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({}),
};
</script>

We have the rules prop that has an array of functions that returns false to display an error state.

The step text will be red.

Conclusion

We can configure our stepper to do with rules and various props.

Categories
Vuetify

Vuetify — Star Ratings

Vuetify is a popular UI framework for Vue apps.

In this article, we’ll look at how to work with the Vuetify framework.

Ratings

The v-rating component lets us add a star rating input to our app.

To use it, we can write:

<template>
  <div>
    <v-rating v-model="rating" background-color="purple lighten-3" color="purple" small></v-rating>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    rating: 4,
  }),
};
</script>

The v-model has the rating value.

background-color has the fill color.

color has the outline color.

small makes the stars extra small.

We can also set the size to large , x-large , and the size prop.

The size prop lets us set the stars to any size value in pixels.

Custom Length

We can change the number of stars to display.

To do that, we use the length prop:

<template>
  <div>
    <v-rating v-model="rating" background-color="purple lighten-3" color="purple" :length="10"></v-rating>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    rating: 4,
  }),
};
</script>

We set length to 10, so we see 10 stars displayed.

Incremented

We can add full icons, half icons, or empty icons.

For example, we can write:

<template>
  <div>
    <v-rating v-model="rating" background-color="purple lighten-3" color="purple" half-increments></v-rating>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    rating: 4.5,
  }),
};
</script>

The half-increments prop lets us add half stars.

So when we set rating to 4.5, we’ll see 4.5 stars.

Slots

We can populate the star slots to customize the stars as we wish.

For example, we can write:

<template>
  <div class="text-center">
    <v-rating v-model="rating">
      <template v-slot:item="props">
        <v-icon
          :color="props.isFilled ? genColor(props.index) : 'grey lighten-1'"
          large
          @click="props.click"
        >{{ props.isFilled ? 'mdi-star-circle' : 'mdi-circle-outline' }}</v-icon>
      </template>
    </v-rating>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    colors: ["green", "purple", "orange", "indigo", "red"],
    rating: 4.5,
  }),
  methods: {
    genColor(i) {
      return this.colors[i];
    },
  },
};
</script>

We have the isFilled property from the slot props to determine whether the star should be filled or not.

The click method can be passed into the @click directive as the click handler.

This way, when we click it, we’ll see the value displayed with the stars.

We have different colors from different stars by returning the color name from the genColor method.

Card Ratings

We can put a v-rating component on a card.

For example, we can write:

<template>
  <v-card class="mx-auto elevation-20" color="purple" dark style="max-width: 400px;">
    <v-row justify="space-between">
      <v-col cols="8">
        <v-card-title>
          <div>
            <div class="headline">Song</div>
          </div>
        </v-card-title>
      </v-col>
      <v-col cols="4">
        <v-img
          class="shrink ma-2"
          contain
          height="125px"
          src="https://randomuser.me/api/portraits/women/68.jpg"
          style="flex-basis: 125px"
        ></v-img>
      </v-col>
    </v-row>
    <v-divider dark></v-divider>
    <v-card-actions class="pa-4">
      Rate this album
      <v-spacer></v-spacer>
      <span class="grey--text text--lighten-2 caption mr-2">({{ rating }})</span>
      <v-rating
        v-model="rating"
        background-color="white"
        color="yellow accent-4"
        dense
        half-increments
        hover
        size="18"
      ></v-rating>
    </v-card-actions>
  </v-card>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    rating: 4.5,
  }),
  methods: {
    genColor(i) {
      return this.colors[i];
    },
  },
};
</script>

We put the v-rating component in the v-card-actions to make it show at the bottom of the card.

Conclusion

We can add the v-rating component to let us add a star rating input to our app.