Categories
Vue

Getting Started with Form Validation with Vuelidate

Vue.js doesn’t come with any form validation capabilities by default.

Therefore, we need to add our own form validation library or with our own code.

In this article, we’ll look at how to get started with adding form validation with Vuelidate.

Getting started

We can install the package by running:

npm install vuelidate --save

Then in main.js , we add:

import Vue from "vue";
import App from "./App.vue";
import Vuelidate from "vuelidate";
Vue.use(Vuelidate);

Vue.config.productionTip = false;

new Vue({
  render: (h) => h(App)
}).$mount("#app");

to add the Vuelidate plugin.

Alternatively, we can also write:

import { validationMixin } from 'vuelidate'

var Component = Vue.extend({
  mixins: [validationMixin],
  validations: { ... }
})

to add the validationMixin from Vuelidate globally.

We can also require the packages with:

const { validationMixin, default: Vuelidate } = require('vuelidate')
const { required, minLength } = require('vuelidate/lib/validators')

Now we can use validation with our code.

For example, we can write:

<template>
  <div id="app">
    <div :class="{ 'form-group--error': $v.name.$error }">
      <label>Name</label>
      <input v-model.trim="$v.name.$model">
      <div v-if="!$v.name.required">Field is required</div>
      <div
        v-if="!$v.name.minLength"
      >Name must have at least {{$v.name.$params.minLength.min}} letters.</div>
    </div>

<div :class="{ 'error': $v.age.$error }">
      <label>Age</label>
      <input v-model.trim.lazy="$v.age.$model">
      <div
        class="error"
        v-if="!$v.age.between"
      >Must be between {{$v.age.$params.between.min}} and {{$v.age.$params.between.max}}</div>
    </div>
  </div>
</template>

<script>
import { required, minLength, between } from "vuelidate/lib/validators";

export default {
  name: "App",
  data() {
    return {
      name: "",
      age: 0
    };
  },
  validations: {
    name: {
      required,
      minLength: minLength(4)
    },
    age: {
      between: between(20, 30)
    }
  }
};
</script>

to add our validation with Vuelidate.

We access everything we need with the $v variable since we registered the plugin.

Then the errors can be accessed with the $v.age and $v.name properties.

between has the between validation state.

If it’s truthy, then it’s valid.

Likewise, we have the required and minLength properties with those validation states.

We added the validations with the name and age properties to set the validation rules.

To make the validation work, we’ve to set the v-model directive to the correct value.

$v.name.$model is the model for the name state.

And $v.age.model is the model for the age state.

The $error property has the errors.

Without v-model

We can use Vuelidate without setting v-model to the values provided by Vuelidate.

To do that, we add extra methods to set the values:

<template>
  <div id="app">
    <div :class="{ 'form-group--error': $v.name.$error }">
      <label>Name</label>
      <input v-model.trim="name" @input="setName($event.target.value)">
      <div v-if="!$v.name.required">Field is required</div>
      <div
        v-if="!$v.name.minLength"
      >Name must have at least {{$v.name.$params.minLength.min}} letters.</div>
    </div>

<div :class="{ 'error': $v.age.$error }">
      <label>Age</label>
      <input v-model.trim="age" @input="setAge($event.target.value)">
      <div
        class="error"
        v-if="!$v.age.between"
      >Must be between {{$v.age.$params.between.min}} and {{$v.age.$params.between.max}}</div>
    </div>
  </div>
</template>

<script>
import { required, minLength, between } from "vuelidate/lib/validators";

export default {
  name: "App",
  data() {
    return {
      name: "",
      age: 0
    };
  },
  validations: {
    name: {
      required,
      minLength: minLength(4)
    },
    age: {
      between: between(20, 30)
    }
  },
  methods: {
    setName(value) {
      this.name = value;
      this.$v.name.$touch();
    },
    setAge(value) {
      this.age = value;
      this.$v.age.$touch();
    }
  }
};
</script>

We set the v-model to the name and age states.

And we added the setName and setAge methods to set those values to the name and age states.

And we call $touch to trigger the validation.

Conclusion

We can add basic form validation capabilities to a Vue app with Vuelidate.

Categories
Vue

How to render an array of data in Vue

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to render lists with thev-for directive.

Using v-for to Map Array to Elements

We can use the v-for directive to render a list of items from an array.

For example, we can use it as follows:

src/index.js :

new Vue({
  el: "#app",
  data: {
    persons: [{ name: "Joe" }, { name: "Jane" }, { name: "Mary" }]
  }
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>

  <body>
    <div id="app">
      <div v-for="person in persons">
        {{person.name}}
      </div>
    </div>
    <script src="src/index.js"></script>
  </body>
</html>

In the code above, we have the v-for directive, and then we write person in persons to loop through the data.persons array.

Then we get the name property from each entry and display it.

person is an alias for the array element being iterated on.

Then we get:

Joe

Jane

Mary

All the properties of the array entry is available during iteration.

We can also add an optional second argument to access the index of the array. For example, we can write the following:

src/index.js :

new Vue({
  el: "#app",
  data: {
    persons: [{ name: "Joe" }, { name: "Jane" }, { name: "Mary" }]
  }
});

index.html ;

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>

  <body>
    <div id="app">
      <div v-for="(person, index) in persons">
        {{index}} - {{person.name}}
      </div>
    </div>
    <script src="src/index.js"></script>
  </body>
</html>

Then we get:

0 - Joe

1 - Jane

2 - Mary

We can also use of instead of in to match the for...of loop syntax in JavaScript:

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>

  <body>
    <div id="app">
      <div v-for="person of persons">
        {{person.name}}
      </div>
    </div>
    <script src="src/index.js"></script>
  </body>
</html>

Photo by Jens Kreuter on Unsplash

v-for with an Object

We can also use v-for to loop through the values of an object.

So if we write the following:

src/index.js :

new Vue({
  el: "#app",
  data: {
    book: {
      title: "JavaScript Book",
      author: "John Smith",
      publishedYear: 2019
    }
  }
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>

  <body>
    <div id="app">
      <div v-for="value in book">
        {{value}}
      </div>
    </div>
    <script src="src/index.js"></script>
  </body>
</html>

And we get:

JavaScript Book

John Smith

2019

We can loop through the keys of the object by providing a second argument to the expression as follows:

src/index.js :

new Vue({
  el: "#app",
  data: {
    book: {
      title: "JavaScript Book",
      author: "John Smith",
      publishedYear: 2019
    }
  }
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>

  <body>
    <div id="app">
      <div v-for="(value, key) in book">
        {{key}} - {{value}}
      </div>
    </div>
    <script src="src/index.js"></script>
  </body>
</html>

Then we get:

title - JavaScript Book

author - John Smith

publishedYear - 2019

Also, we can provide a third argument for the index of the object entry.

To do this, we can write:

src/index.js:

new Vue({
  el: "#app",
  data: {
    book: {
      title: "JavaScript Book",
      author: "John Smith",
      publishedYear: 2019
    }
  }
});

index.html :

`<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>

  <body>
    <div id="app">
      <div v-for="(value, key, index) in book">
        {{index}}: {{key}} - {{value}}
      </div>
    </div>
    <script src="src/index.js"></script>
  </body>
</html>

Then we get:

0: title - JavaScript Book

1: author - John Smith

2: publishedYear - 2019

The order of iteration over an object is based on the enumeration order of Object.keys() , which isn’t guaranteed to be consistent across JavaScript engine implementations.

Maintaining State

v-for by default updates the items in place instead of moving the DOM elements to match the order of the items to make sure it reflects what should be rendered at a particular index.

Therefore, it’s only suitable when the list doesn’t rely on child component state or temporary DOM state like input values.

To make sure Vue knows which elements are unique, we should provide a key attribute via the v-bind:key directive.

For example, we can write:

src/index.js :

new Vue({
  el: "#app",
  data: {
    persons: [{ name: "Joe" }, { name: "Jane" }, { name: "Mary" }]
  }
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>

  <body>
    <div id="app">
      <div v-for="person in persons" v-bind:key="person.name">
        {{person.name}}
      </div>
    </div>
    <script src="src/index.js"></script>
  </body>
</html>

We have to use primitive values as keys.

The key attribute is a generic mechanism for Vue to identify nodes.

Conclusion

We can use v-for to iterate through entries of an array or entries of an object and render the entries.

To use it to render arrays, we can either use in or of . Also, we can access the index of an array entry with v-for .

To iterate through objects, we can do the same thing, except that the first argument is the value of a property, the second argument is the key and the third is the index.

Only the first argument is required in each case.

The key attribute can be set so the Vue can identify unique nodes. We can set it to a primitive value with v-bind:key .

Categories
Vuetify

Vuetify — Virtual Scroller and Click Outside

Vuetify is a popular UI framework for Vue apps.

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

Virtual Scroller

The v-virtual-scroll component lets us display a virtual, infinite list.

It supports dynamic height and scrolling vertically.

To use it, we can write:

<template>
  <v-card class="mx-auto" max-width="400">
    <v-virtual-scroll :items="items" :item-height="50" height="300">
      <template v-slot="{ item }">
        <v-list-item>
          <v-list-item-avatar>{{ item.initials }}</v-list-item-avatar>

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

          <v-list-item-action>
            <v-btn depressed small>
              View User
              <v-icon color="orange darken-4" right>mdi-open-in-new</v-icon>
            </v-btn>
          </v-list-item-action>
        </v-list-item>
      </template>
    </v-virtual-scroll>
  </v-card>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    colors: ["#2196F3", "#90CAF9", "#64B5F6"],
    names: ["Oliver", "Jake", "Noah", "James", "Jack"],
    surnames: ["Smith", "Anderson", "Clark", "Wright", "Mitchell"],
  }),
  computed: {
    items() {
      const namesLength = this.names.length;
      const surnamesLength = this.surnames.length;
      const colorsLength = this.colors.length;

      return Array.from({ length: 10000 }, (k, v) => {
        const name = this.names[this.genRandomIndex(namesLength)];
        const surname = this.surnames[this.genRandomIndex(surnamesLength)];

return {
          color: this.colors[this.genRandomIndex(colorsLength)],
          fullName: `${name} ${surname}`,
          initials: `${name[0]}${surname[0]}`,
        };
      });
    },
  },
  methods: {
    genRandomIndex(length) {
      return Math.ceil(Math.random() * (length - 1));
    },
  },
};
</script>

We create an array of random names with the items computed property.

Then in the template, we use the v-virtual-scroll component to render the items with the items prop.

The item slot prop has the array entry with the item.

Click Outside

The v-click-outside directive calls a function when we click on something outside the target element.

This is used with components like v-menu and v-dialog .

For example, we can use it by writing:

<template>
  <v-list>
    <v-list-item v-click-outside="onClickOutsideStandard" @click="models.base = true">
      <v-list-item-title>Default Click Outside</v-list-item-title>

      <v-list-item-action>
        <v-icon :color="models.base ? 'green' : 'red'">mdi-record</v-icon>
      </v-list-item-action>
    </v-list-item>
    <v-list-item
      v-click-outside="{
        handler: onClickOutsideWithConditional,
        closeConditional,
      }"
      @click="models.conditional = true"
    >
      <v-list-item-title>Close Conditional</v-list-item-title>
      <v-list-item-action>
        <v-icon :color="models.conditional ? 'green' : 'red'">mdi-record</v-icon>
      </v-list-item-action>
    </v-list-item>
  </v-list>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    models: {
      base: false,
      conditional: false,
    },
  }),

  methods: {
    onClickOutsideStandard() {
      this.models.base = false;
    },
    onClickOutsideWithConditional() {
      this.models.conditional = false;
    },
    closeConditional(e) {
      return this.models.conditional;
    },
  },
};
</script>

We have the v-click-outside directive on the v-list-item to ley us toggle the dot’s color when we click outside the list item.

The closeConditional has the callback to run when we click outside the component.

Conclusion

We can add a virtual scroller and a directive to detect clicks outside an element with Vuetify.

Categories
Vuetify

Vuetify — Treeview Search and Open

Vuetify is a popular UI framework for Vue apps.

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

Open-All

Treeview nodes can be pre-opened on page load.

To do that, we can add the open-all prop to the v-treeview component:

<template>
  <v-treeview open-all :items="items"></v-treeview>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    items: [
      {
        id: 1,
        name: "Root",
        children: [
          { id: 2, name: "Child 1" },
          { id: 3, name: "Child 2" },
          {
            id: 4,
            name: "Child 3",
            children: [
              { id: 5, name: "Grandchild 1" },
              { id: 6, name: "Grandchild 2" },
            ],
          },
        ],
      },
    ],
  }),
};
</script>

It’ll expand all the nodes when we load the page.

Treeview Slots

Treeviews have various slots.

The prepend slot lets us add content to the left of the node.

The label has the label for the node.

And the append slot lets us add content to the right of the label.

For example, we can write:

<template>
  <v-treeview v-model="tree" :open="open" :items="items" activatable item-key="name" open-on-click>
    <template v-slot:prepend="{ item, open }">
      <v-icon v-if="item.children">{{ open ? 'mdi-folder-open' : 'mdi-folder' }}</v-icon>
      <v-icon v-else>{{ item.id }}</v-icon>
    </template>
  </v-treeview>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    tree: undefined,
    open: ["Root"],
    items: [
      {
        id: 1,
        name: "Root",
        children: [
          { id: 2, name: "Child 1" },
          { id: 3, name: "Child 2" },
          {
            id: 4,
            name: "Child 3",
            children: [
              { id: 5, name: "Grandchild 1" },
              { id: 6, name: "Grandchild 2" },
            ],
          },
        ],
      },
    ],
  }),
};
</script>

to add a prepend slot so that we can show the nodes our way.

The slot has the item property with the node object.

And the open property is a boolean to indicate whether the node is open or not.

We can use them to display our nodes differently.

Searching Nodes

We can easily filter our treeview with the search prop.

The filtering can be customized.

For example, we can write:

<template>
  <v-card class="mx-auto" max-width="500">
    <v-sheet class="pa-4 primary lighten-2">
      <v-text-field
        v-model="search"
        label="Search"
        dark
        flat
        solo-inverted
        hide-details
        clearable
        clear-icon="mdi-close-circle-outline"
      ></v-text-field>
      <v-checkbox v-model="caseSensitive" dark hide-details label="Case sensitive search"></v-checkbox>
    </v-sheet>
    <v-card-text>
      <v-treeview :items="items" :search="search" :filter="filter" :open.sync="open"></v-treeview>
    </v-card-text>
  </v-card>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    open: [1, 2],
    search: null,
    caseSensitive: false,
    items: [
      {
        id: 1,
        name: "Root",
        children: [
          { id: 2, name: "Child 1" },
          { id: 3, name: "Child 2" },
          {
            id: 4,
            name: "Child 3",
            children: [
              { id: 5, name: "Grandchild 1" },
              { id: 6, name: "Grandchild 2" },
            ],
          },
        ],
      },
    ],
  }),
  computed: {
    filter() {
      return this.caseSensitive
        ? (item, search, textKey) => item[textKey].includes(search)
        : undefined;
    },
  },
};
</script>

We have the v-text-field to let us enter our search keyword.

The search state is passed into the search prop of the v-treeview to let us filter the items.

Also, we customize the search with the filter prop.

It returns a function that lets us return the condition that we want to match with the search.

Conclusion

We can add search to treeviews and also open all the nodes when the page loads.

Categories
Vuetify

Vuetify — Scrolling and Breakpoints

Vuetify is a popular UI framework for Vue apps.

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

Watching Bound Element

We can watch any element’s scrolling, including the element that v-scroll is applied to.

To do that, we add the self modifier to the v-scroll directive.

For instance, we can write:

<template>
  <v-card v-scroll.self="onScroll" class="overflow-y-auto" max-height="400">
    <v-banner class="justify-center headline font-weight-light" sticky>
      Scroll Me - Method invoked
      <span class="font-weight-bold" v-text="scrollInvoked"></span>
      times
    </v-banner>

    <v-card-text>
      <div
        v-for="n in 12"
        :key="n"
        class="mb-4"
      >Lorem ipsum dolor sit amet consectetur adipisicing elit.</div>
    </v-card-text>
  </v-card>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    scrollInvoked: 0,
  }),

  methods: {
    onScroll() {
      this.scrollInvoked++;
    },
  },
};
</script>

We have the v-scroll directive with the self modifier to let us watch the scrolling of itself.

Whenever the card is scrolled, the onScroll method is run.

Touch Support

The v-touch directive lets us capture swipe gestures and apply directional callbacks.

For example, we can write:

`<template>
  <v-row
    v-touch="{
      left: () => swipe('Left'),
      right: () => swipe('Right'),
      up: () => swipe('Up'),
      down: () => swipe('Down')
    }"
    align="center"
    class="grey lighten-2"
    justify="center"
    style="height: 500px"
  >
    <v-subheader>Swipe Direction</v-subheader>
    {{ swipeDirection }}
  </v-row>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    swipeDirection: "None",
  }),

  methods: {
    swipe(direction) {
      this.swipeDirection = direction;
    },
  },
};
</script>
`

We have the v-touch directive with the value being an object with the left , right , up and down properties that have functions that run when the swipe are in those directions.

Breakpoints

We can get the breakpoints from the app with the this.$vuetify.breakpoint property.

For instance, we can write:

`<template>
  <v-row align="center" justify="center">
    <v-col cols="12">{{ imageHeight }}</v-col>
  </v-row>
</template>
<script>
export default {
  name: "HelloWorld",
  mounted() {
    console.log(this.$vuetify.breakpoint);
  },

  computed: {
    imageHeight() {
      switch (this.$vuetify.breakpoint.name) {
        case "xs":
          return "220px";
        case "sm":
          return "400px";
        case "md":
          return "500px";
        case "lg":
          return "600px";
        case "xl":
          return "800px";
      }
    },
  },
};
</script>
`

to create an imageHeight computed property which is derived from the breakpoint.

We get the breakpoint name with the name property.

And we display that in the template.

We can define our own breakpoint values.

For example, we can write:

import Vue from 'vue';
import Vuetify from 'vuetify/lib';
Vue.use(Vuetify);

export default new Vuetify({
  breakpoint: {
    thresholds: {
      xs: 340,
      sm: 540,
      md: 800,
      lg: 1280,
    },
    scrollBarWidth: 24,
  },
});

in src/plugins/vuetify.js .

We defined our breakpoints with the breakpoint property.

The keys are the name and the numbers are the min-width of the breakpoint in pixels.

Conclusion

We can watch the scrolling of a bound element and define and use breakpoints with Vuetify.