[Vuemastery] Beautify with Vuetify - part 2

lesson7

lesson 8

lesson 9

Forms: Validation

Form Validation

One of the key features of a good form is the ability to validate your user’s input. After all, this helps to ensure that they are providing the required information in the correct format. And while all form inputs should use HTML attributes as a starting point for validation, there typically comes a point where you need custom rules along with custom validation methods. This is where Vuetify is here to help.

Rules

By wrapping our form in a v-form component, this enables us to use the rules prop on any Vuetify input component. The rules prop takes an array of functions that allow us to define when the value being passed in is valid or not.

All form input components in Vuetify have a rules prop that takes an array of functions that allow you to specify the conditions for whether the value is valid or not. Each function inside of the array is its own individual rule. As a result, if any of the functions inside the array return false or a String, which is typically the error message you want to display to the user, then the input is considered invalid.

In our Vuetify Dashboard’s Signup page, we have some form fields that are required in order for the user to create their account:

  1. Agree to terms and conditions
  2. Email

Creating validation for agree to terms and conditions

When creating a rule in the rules prop array, each rule is an anonymous function that provides the value of the input.

value => {
  // Check if the value has at least 1 character
  if (value.length > 0) {
    // Since this is true, we can return true
    return true
  } else {
    // If it is false, then display the returned String
    return 'Error message to display to user'
  }
}

However, it is typically written in a shorthand version:

value => value > 0 || 'Error message to display to user'

Since the “Agree to terms and conditions” is required, we need to attach a v-model to our checkbox and add a rules prop that checks whether it is true or not.

src/views/Signup.vue

<template>
  <v-container>
    <v-row>
      <v-col>
        <h1>Signup</h1>
        <v-form>
          <!-- Other form elements hidden for brevity -->
          ...
          <v-checkbox
            label="Agree to terms & conditions"
            v-model="agreeToTerms"
            :rules="agreeToTermsRules"
            required
          ></v-checkbox>
          <v-btn type="submit" color="primary">Submit</v-btn>
        </v-form>
      </v-col>
    </v-row>
  </v-container>
</template>
<script>
export default {
  data: () => ({
    agreeToTerms: false,
    agreeToTermsRules: [
      value =>
        !!value ||
        'You must agree to the terms and conditions to sign up for an account.'
    ],
    // Other data hidden for brevity
    ...
  })
}
</script>

And with that, we have error validation on the “Agree to terms and conditions” checkbox!

Creating validation for our email input

For our email input validation, it is a little bit more complicated. While there are many ways to validate for proper email inputs, we will use the following criteria:

  • Email is required
  • Email should have a username of at least 1 character
  • Email should contain an @ symbol
  • Email should contain a domain name with at least one character, a period, and at least two letter extension

When creating an array of rules for an input, each rule will be show to the user in order. As a result, it’s important to consider the proper order that users should see error messages.

src/views/Signup.vue

<template>
  <v-container>
    <v-row>
      <v-col>
        <h1>Signup</h1>
        <v-form>
          <v-text-field
            label="Email"
            type="email"
            v-model="email"
            :rules="emailRules"
          ></v-text-field>
          <!-- Other form elements hidden for brevity -->
          ...
        </v-form>
      </v-col>
    </v-row>
  </v-container>
</template>
<script>
export default {
  data: () => ({
    // Other data hidden for brevity...
    email: '',
    emailRules: [
      value => value.indexOf('@') !== 0 || 'Email should have a username.',
      value => value.includes('@') || 'Email should include an @ symbol.',
      value =>
        value.indexOf('.') - value.indexOf('@') > 1 ||
        'Email should contain a valid domain.',
      value => value.includes('.') || 'Email should include a period symbol.',
      value =>
        value.indexOf('.') <= value.length - 3 ||
        'Email should contain a valid domain extension.'
    ]
  })
}
</script>

How to Reset Forms

There are two types of resets when it comes to forms:

  • reset() - Resetting only the validation of the form
  • resetValidation() - Resetting the entirety of the form (including the form input values)

However, we need to be able to refer to the form that we want to trigger it on. In order to do this, we assign our v-form element with a ref prop that is specific to Vue and allows you to reference a specific HTML element. So in our form example, let’s give our form a ref of loginForm .

Once we assign the ref , it can now be programmatically referred to using the $refs object, which contains any ref s that have been defined. As a result, we can now create two buttons:

  • One to reset the validation on our form
  • Another to reset the entirety of our form

src/views/Signup.vue

<template>
  <v-container>
    <v-row>
      <v-col>
        <h1>Signup</h1>
        <v-form ref="signUpForm">
          <!-- Form input elements hidden for brevity -->
          ...
          <v-btn type="submit" color="primary" class="mr-4">
            Submit
          </v-btn>
          <v-btn color="warning" class="mr-4" @click="resetValidation">
            Reset Validation
          </v-btn>
          <v-btn color="error" @click="resetForm">Reset</v-btn>
        </v-form>
      </v-col>
    </v-row>
  </v-container>
</template>
<script>
export default {
  data: () => ({
    // Data store hidden for brevity
    ...
  }),
  methods: {
    resetForm() {
      this.$refs.signUpForm.reset()
    },
    resetValidation() {
      this.$refs.signUpForm.resetValidation()
    }
  }
}
</script>

Validate

When it comes to checking whether or not our form is valid, this is typically done at the time of submission. However, there will be times when you need to programmatically check whether or not a form is valid in order to perform other actions (i.e., trigger notifications, disable buttons, etc.). Vuetify makes this easy for us by providing us two ways for checking:

  • Every v-form has a value prop which tracks whether or not it is valid
  • And it has a validate function to trigger a manual check on whether the form is valid or not.

For our Signup form, let’s go ahead and disable our Submit button until the form is valid.

src/views/Signup.vue

<template>
  <v-container>
    <v-row>
      <v-col>
        <h1>Signup</h1>
        <v-form ref="signUpForm" v-model="formValidity">
          <!-- Other form input elements hidden for brevity -->
          ...
          <v-btn
            type="submit"
            color="primary"
            class="mr-4"
            :disabled="!formValidity"
          >
            Submit
          </v-btn>
          ...
        </v-form>
      </v-col>
    </v-row>
  </v-container>
</template>
<script>
export default {
  data: () => ({
    // Other data hidden for brevity
    ...
    formValidity: false
  }),
  methods: {
    // Methods hidden for brevity
    ...
  }
}
</script>

Finally, let’s add a button for the user to manully validate the form. Just like the reset and resetValidation function, it can be triggered by referencing the form via its ref property.

src/views/Signup.vue

<template>
  <v-container>
    <v-row>
      <v-col>
        <h1>Signup</h1>
        <v-form ref="signUpForm" v-model="formValidity">
          <!-- Other form elements hidden for brevity -->
          ...
          <v-btn color="success" class="mr-4" @click="validateForm">
            Validate Form
          </v-btn>
          <v-btn color="warning" class="mr-4" @click="resetValidation">
            Reset Validation
          </v-btn>
          <v-btn color="error" @click="resetForm">Reset</v-btn>
        </v-form>
      </v-col>
    </v-row>
  </v-container>
</template>
<script>
export default {
  data: () => ({
    // Data store hidden for brevity
    ...
  }),
  methods: {
    resetForm() {
      this.$refs.signUpForm.reset()
    },
    resetValidation() {
      this.$refs.signUpForm.resetValidation()
    },
    validateForm() {
      this.$refs.signUpForm.validate()
    }
  }
}
</script>

And with that, our Signup form is complete with form validation!

Final Thoughts

Congratulations, you now possess all of the fundamental knowledge you need to succesfully build forms with custom validations.

For those who want to use other validation libraries like Vuelidate and Vee-validate, you can find examples for how to do so in the:

See you in the next lesson!

Directives

Introduction

One of the great things about Vue.js is that it provides us with directives such as:

  • v-if / v-show: for toggling the display of an element
  • v-on: for listening to events on an element

And while these built-in directives cover many common use cases, the Vuetify team has included some useful directives in order to enhance your development workflow.

In this lesson, we will be learning about:

  1. What Vuetify directives are
  2. Exploring the v-intersect in depth

Let’s get started!

Vuetify Directives

To get started using Vuetify directives, there’s no better place to start than the docs. On the docs home page, you can find the Directives dropdown which contains a comprehensive list of all of the current directives in the latest version of Vuetify.

For our first directive, let’s take a look at the resizing directive:

The resize directive allows you to detect when the window resizes so that you can respond accordingly. And using it is as simple as applying v-resize to an element with the callback function defined as the value.

<template>
	<div v-resize="onResize">
		<!-- content -->
	</div>
</template>
<script>
export default {
	methods: {
		onResize() {
			console.log('This window size is changing!')
		}
	}
}
</script>

Another fun directive is the ripple directive, which allows you to add a ripple animation to an HTML element in order to make it easier for users to detect when they interact with an element (i.e., clicking).

Using the ripple directive is even simpler since all you have to do it apply if on the desired HTML element.

<template>
	<div v-ripple>
		<p>HTML element with v-ripple applied</p>
	</div>
</template>

The Loading Problem

One scenario that is often overlooked in the user experience is the loading state. Whether it’s communicating to users that something is happening rather than simply displaying a blank canvas, or whether it is delaying a component from loading because it hasn’t come into the viewport yet, it’s a critical part of most application’s user experience.

Oftentimes, we send more information than necessary to users.

As a result, users have larger download size which slows down performance and creates a sub-optimal user experience. Wouldn’t it be great if we could detect when a user has hit a certain point on the web page (i.e., below the fold) and then trigger additional content to load?

To help you with this, Vuetify 2.1+ introduced the [v-intersect directive](https://vuetifyjs.com/en/directives/intersect).

The purpose v-intersect directive is to provide an easy way to detect whether elements are visible within the user’s viewport. This allows us to do things like delay loading of elements which improves the performance of our app since users are only downloading what they need.

In our Vuetify Dashboard app, let’s apply this to our Dashboard page. Assuming you’ve fetched the latest from the Lesson-8-BEGIN branch, you’ll notice that I’ve added more content to the page in order to give the page a longer scroll range.

:page_facing_up: src/views/Dashboard.vue

<template>
  <v-container>
    <!-- More content above -->
		<v-row>
	    <v-col cols="12" md="8">
	      <EmployeesTable :employees="employees" @select-employee="setEmployee" />
	    </v-col>
	    <v-col cols="12" md="4">
	      <EventTimeline :timeline="timeline" />
	    </v-col>
	  </v-row>
	
	  <v-row id="below-the-fold">
	    <v-col cols="12" md="8">
	      <EmployeesTable :employees="employees" @select-employee="setEmployee" />
	    </v-col>
	    <v-col cols="12" md="4">
	      <EventTimeline :timeline="timeline" />
	    </v-col>
	  </v-row>
	
	  <v-row id="more-content">
	    <v-col>
	      <v-skeleton-loader
	        ref="skeleton"
	        type="table"
	        class="mx-auto"
	      ></v-skeleton-loader>
	    </v-col>
	  </v-row>
  </v-container>
</template>

You might also notice a new component that is being used at the very bottom:

This is the new skeleton loader component which Vuetify introduced in v2.1+. This allows you to scaffold a contextual loading state for users, which is much more useful to users than a generic spinner.

However, we only want this to be visible once the user scrolls past the #below-the-fold element. So, let’s start by configuring our skeleton loader to only appear based on a reactive data property: loadNewContent .

:page_facing_up: src/views/Dashboard.vue

<template>
  <v-container>
    <h1>Dashboard</h1>

		<!-- Hide content for easier readability -->

    <v-row id="below-the-fold">
      <v-col cols="12" md="8">
        <EmployeesTable :employees="employees" @select-employee="setEmployee" />
      </v-col>
      <v-col cols="12" md="4">
        <EventTimeline :timeline="timeline" />
      </v-col>
    </v-row>

    <v-row v-if="loadNewContent" id="more-content">
      <v-col>
        <v-skeleton-loader
          ref="skeleton"
          type="table"
          class="mx-auto"
        ></v-skeleton-loader>
      </v-col>
    </v-row>

    <!-- Hide snackbar for easier readability -->
  </v-container>
</template>
<script>
export default {
	data() {
		return {
			loadNewContent: false,
			...
		}
	},
	...
}
</script>

Now we need to dynamically toggle the skeleton loader based on whether the #below-the-fold element is intersected on the page. As mentioned before, we will use the v-intersect directive to help us with this. To get started, we will apply the v-intersect directive on our element and pass it a method named showMoreContent .

:page_facing_up: src/views/Dashboard.vue

<template>
  <v-container>
    <h1>Dashboard</h1>

		<!-- Hide content for easier readability -->

    <v-row id="below-the-fold" v-intersect="showMoreContent>
      <v-col cols="12" md="8">
        <EmployeesTable :employees="employees" @select-employee="setEmployee" />
      </v-col>
      <v-col cols="12" md="4">
        <EventTimeline :timeline="timeline" />
      </v-col>
    </v-row>

    <v-row v-if="loadNewContent" id="more-content">
      <v-col>
        <v-skeleton-loader
          ref="skeleton"
          type="table"
          class="mx-auto"
        ></v-skeleton-loader>
      </v-col>
    </v-row>

    <!-- Hide snackbar for easier readability -->
  </v-container>
</template>
<script>
export default {
	data() {
		return {
			loadNewContent: false,
			...
		}
	},
	methods: {
		showMoreContent(entries) { },
		...
	},
	...
}
</script>

You might have noticed that I’m including an entries argument to the function. This is because underneath the hood, v-intersect utilizes the standard Intersection Observer API, which provides an array of elements being observed as one of the default parameters. And for each observer entry, it has a property called isIntersecting which returns a boolean based on whether the observed entry is in currently intersecting or not.

As a result, making our skeleton loader component load dynamically is as simple as reassigning the loadNewContent data property to the isIntersecting property for our entry.

:page_facing_up: src/views/Dashboard.vue

<template>
  <v-container>
    <h1>Dashboard</h1>

		<!-- Hide content for easier readability -->

    <v-row id="below-the-fold" v-intersect="showMoreContent>
      <v-col cols="12" md="8">
        <EmployeesTable :employees="employees" @select-employee="setEmployee" />
      </v-col>
      <v-col cols="12" md="4">
        <EventTimeline :timeline="timeline" />
      </v-col>
    </v-row>

    <v-row v-if="loadNewContent" id="more-content">
      <v-col>
        <v-skeleton-loader
          ref="skeleton"
          type="table"
          class="mx-auto"
        ></v-skeleton-loader>
      </v-col>
    </v-row>

    <!-- Hide snackbar for easier readability -->
  </v-container>
</template>
<script>
export default {
	data() {
		return {
			loadNewContent: false,
			...
		}
	},
	methods: {
		showMoreContent(entries) {
			this.loadNewContent = entries[0].isIntersecting
	  },
		...
	},
	...
}
</script>

And with that, our skeleton element component only loads once the user goes past the #below-the-fold row! :tada:

To learn about other useful components related to the Intersection API, be sure to check out the v-lazy and v-img component!

As a small word of caution, you will need a polyfill for IE11 and Safari. For more details and how it all works under hood, be sure to check out the browser compatibility table in the official Intersection Observer API docs.

Other Directives to Know About

Before we wrap up, there are a few more directives that you should know about:

  • Mutation observer - Uses the Mutation Observer API to detect when elements are updated on the page. Particularly useful if you need to detect change on elements that are not within Vue’s control
  • Scrolling - For detecting when the window or a specific element is being scrolled on
  • Touch support - For detecting swipe gestures

Conclusion

In this lesson, we learned about various directives in the Vuetify library, while also taking a closer look at the v-intersect directive. For more information on how to use each directive, be sure to check out the docs. In addition, don’t forget that this is not a fixed list! The team is always looking for ways to improve the developer experience and will be updating these as time goes on.

Themes

Introduction

While component libraries can make our lives much easier, one common downside is that every website you build will look the same. As a result, it’s critical for us to be able to customize the theme so we can utilize the power of component libraries while creating unique designs.

In this lesson, we will be learning about:

  • Basics behind Vuetify themes
  • How to toggle between light and dark themes
  • And how to customize theme variables

The Basic of Themes

For most websites, they often display using a light theme:

However, as many developers know, being able to choose your theme can greatly impact the readability and overall experience. For example, many developers prefer writing code in the dark theme.

When working with themes in Vuetify, as you might expect, there is no better place to get started than the Vuetify theme docs. And for those who are interested in paid themes, be sure to check out their Premium themes marketplace!

Toggling Light and Dark Themes

In our Vuetify Dashboard app, let’s start by configuring our Vuetify theme to use the dark theme. To do this, we configure out Vuetify configuration object with the theme property and pass it a key value pair of: dark: true .

vuetify.js

import Vue from 'vue'
import Vuetify from 'vuetify/lib'

Vue.use(Vuetify)

export default new Vuetify({
  icons: {
    iconfont: 'mdi'
  },
  theme: {
    dark: true
  }
})

While this is a fairly straightforward change, it would be more helpful to our users if we gave them the ability to choose which theme they preferred. As a result, let’s go ahead and add a button in the navigation bar that allows them to do that:

App.vue

<template>
  <v-app>
    <v-app-bar app color="primary" dark>
      <v-toolbar-title>Vuetify Dashboard</v-toolbar-title>
      <v-spacer></v-spacer>
      <v-btn
        v-for="link in links"
        :key="`${link.label}-header-link`"
        text
        rounded
        :to="link.url"
      >
        {{ link.label }}
      </v-btn>
      <v-btn @click="toggleTheme" text rounded>
        Toggle Theme
      </v-btn>
    </v-app-bar>
    ...
  </v-app>
</template>

Now we need to create our toggleTheme method that will allow users to update the theme. And to do this, we refer to our global $vuetify object and toggle the property theme.dark which takes a Boolean value.

App.vue

<script>
export default {
  name: 'App',
  data() {
    return {
      ...
    }
  },
  methods: {
    toggleTheme() {
      this.$vuetify.theme.dark = !this.$vuetify.theme.dark
    }
  }
}
</script>

And with that, your users can now toggle the theme!

Applying Custom Colors to a Theme

In order to apply custom color palettes to our theme, we can configure this globally by passing defining the themes property in the theme property:

vuetify.js

import Vue from 'vue'
import Vuetify from 'vuetify/lib'

Vue.use(Vuetify)

export default new Vuetify({
  icons: {
    iconfont: 'mdi'
  },
  theme: {
    themes: {
      light: {
        primary: '#41B883'
      },
      dark: {
        primary: '#34495E'
      }
    }
  }
})

For a list of all the various color properties in a theme, you can find them in the Theme: Customizing section of the docs.

However, one thing you may have noticed is that our links in the dark theme are hard to read. One way we can fix this is to define the colors for anchor in our theme configuration.

vuetify.js

import Vue from 'vue'
import Vuetify from 'vuetify/lib'

Vue.use(Vuetify)

export default new Vuetify({
  icons: {
    iconfont: 'mdi'
  },
  theme: {
    themes: {
      light: {
        primary: '#41B883'
      },
      dark: {
        primary: '#34495E',
        anchor: '#fff'
      }
    }
  }
})

However, for the purposes of demonstrating how you can programmatically and dynamically set the colors of themes. Let’s do this inside of our toggleTheme method.

App.vue

<script>
export default {
  name: 'App',
  data() {
    return {
      ...
    }
  },
  methods: {
    toggleTheme() {
      this.$vuetify.theme.themes.dark.anchor = '#41B883'
      this.$vuetify.theme.dark = !this.$vuetify.theme.dark
    }
  }
}
</script>

Just like before, we can refer to the global $vuetify object in order to dynamically change the value of the theme color. And with just one line of code, our links look great in the dark theme now!

Defining Custom Typography

While the Roboto font family looks great, it is common for designers to want custom typography for their website. And while it is not as easy as defining properties inside the Vuetify configuration object, we can still accomplish it with a couple of configurations.

To start, let’s swap out the Google Font import for Roboto in index.html for another font pair: Fira Sans and Merriweather.

public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <title>vuetify-dashboard</title>
    <link href="https://fonts.googleapis.com/css?family=Fira+Sans:700|Merriweather&display=swap" rel="stylesheet">
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"
    />
  </head>
  <body>
    <noscript>
      <strong
        >We're sorry but vuetify-dashboard doesn't work properly without
        JavaScript enabled. Please enable it to continue.</strong
      >
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

Now that we have our our new font families imported, we need to access Vuetify’s Sass variables. To do this, we start by creating our own scss/variables.scss file in our src directory.

src / scss/variables.scss

$body-font-family: 'Fira Sans';
$heading-font-family: 'Merriweather';

Once we restart our local server, Vuetify will rebuild the styles according to these new variables. And for those wondering where the variable names are coming from, they were derived from the [variables.scss file in the Vuetify source code](https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/styles/settings/_variables.scss).

For more information, you can learn more about how Sass variables work on their docs.

Conclusion

And with that, you now know the basics of configuring your own theme with Vuetify. You are now ready to leverage the power of Vuetify’s component library while creating unique designs!

1 симпатия