Categories
Vue 3

Getting Started with Form Validation in a Vue 3 App with Vee-Validate 4

Form validation is an important part of any app.

In this article, we’ll look at how to use Vee-Validate 4 in our Vue 3 app for form validation.

Getting Started with Script Tag

We can get started with the script tag.

We use the v-form component as the form container.

The v-field component is our input field.

error-message component is our error message.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vee-validate@next"></script>
  </head>
  <body>
    <div id="app">
      <v-form @submit="onSubmit">
        <v-field
          name="name"
          as="input"
          type="text"
          placeholder="What's your name?"
          :rules="isRequired"
        ></v-field>
        <br />
        <error-message name="name"></error-message>
        <br />
        <button>Submit</button>
      </v-form>
    </div>
    <script>
      const app = Vue.createApp({
        components: {
          VForm: VeeValidate.Form,
          VField: VeeValidate.Field,
          ErrorMessage: VeeValidate.ErrorMessage
        },
        methods: {
          isRequired(value) {
            if (!value) {
              return "this field is required";
            }
            return true;
          },
          onSubmit(values) {
            alert(JSON.stringify(values, null, 2));
          }
        }
      });
      app.mount("#app");
    </script>
  </body>
</html>

First, we add the script tag with:

<script src="https://unpkg.com/vee-validate@next"></script>

Then we register the VForm , VField , and ErrorMessage components so we can use them in our template.

We have the isRequired method which we use for the rule for the form by passing it into the rules prop.

It returns true if the form field is valid.

Otherwise, it returns the error message we want to display.

value has the value that we entered.

The v-field component has the as prop which is set to input to render it as an input box.

The error-message has the name which would match the value of the name attribute of the field that we’re validating.

Getting Started with NPM Package

We can install the Vee-Validate NPM package by running:

yarn add vee-validate@next

or

npm i vee-validate@next --save

Then we can use it by writing:

<template>
  <Form v-slot="{ errors }">
    <Field name="field" as="input" :rules="isRequired" />
    <br />
    <span>{{ errors.field }}</span>
  </Form>
</template>

<script>
import { Field, Form } from "vee-validate";

export default {
  components: {
    Field,
    Form,
  },
  methods: {
    isRequired(value) {
      return value ? true : "This field is required";
    },
  },
};
</script>

We register the Field and Form components so we can use them in our template.

Form is the form wrapper. It has the errors property from the slot props with the error message.

The Field component is like the v-field component we have in the previous example.

errors.field has the validation form validation error message of the 'field' field.

isRequired function has the same validation logic as the previous example.

It returns true if value is valid and an error validation message otherwise.

Conclusion

We can add simple form validation into our Vue 3 app with Vee-Validate 4.

Categories
Vue 3

Vue 3 — Mixins

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to create mixins to make our code reusable.

Mixins

Mixins are a flexible way to let us add reusable functionalities to Vue components.

A mixin object can have any component options.

When a component uses a mixin, the mixin will be merged into the component’s own options.

For example, we can create a mixin and use it by writing:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <p>app</p>
    </div> <script>
      const mixin = {
        created() {
          this.foo();
        },
        methods: {
          foo() {
            console.log("foo");
          }
        }
      }; const app = Vue.createApp({
        mixins: [mixin]
      }); app.mount("#app");
    </script>
  </body>
</html>

We called Vue.createApp with an object that has the mixins property.

The value is our mixin mixin.

It’s just an object that has the component properties in the places we expect.

They’ll be merged into the component automatically.

Therefore, the this.foo method will be called and we’ll see the console log run.

Option Merging

The options will be merged using the appropriate strategies.

The component’s data take priority over the mixin in case there are any conflicts.

Hook functions with the same name are merged into an arraty so that all of them will be called.

So if we have:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <p>app</p>
    </div> <script>
      const mixin = {
        created() {
          console.log("mixin hook");
        }
      }; const app = Vue.createApp({
        mixins: [mixin],
        created() {
          console.log("app hook");
        }
      }); app.mount("#app");
    </script>
  </body>
</html>

Then we have the 'mixin hook' and 'app hook' logged in that order.

Options that expect object values like methods , components , and directives will be merged into the same object.

The component’s option will take priority when there’re conflicting keys.

So if we have:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <p>app</p>
    </div> <script>
      const mixin = {
        methods: {
          foo() {
            console.log("foo");
          },
          conflicting() {
            console.log("mixin conflicting");
          }
        }
      }; const app = Vue.createApp({
        mixins: [mixin],
        methods: {
          baz() {
            console.log("bar");
          },
          conflicting() {
            console.log("app conflicting");
          }
        }
      }); const vm = app.mount("#app");
      vm.conflicting();
      vm.foo();
      vm.baz();
    </script>
  </body>
</html>

Then we’ll see:

app conflicting
foo
baz

logged.

The Vue instance’s conflicting method is used over the mixin one.

But foo and baz are incorporated into the Vue instancem since there’re no conflict between them.

Global Mixin

We can create global mixins with the app.mixin method.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <p>app</p>
    </div> <script>
      const app = Vue.createApp({
        foo: "bar"
      }); app.mixin({
        created() {
          const foo = this.$options.foo;
          console.log(foo);
        }
      }); app.mount("#app");
    </script>
  </body>
</html>

We added the foo property to our Vue instance.

This is accessed with the this.$options property in our mixin.

So we’ll see 'bar' logged from our created hook.

Once we apply a mixin globally, then it’ll affect every Vue instance created afterwards.

Conclusion

Mixins are reusable pieces of code that can be incorporated into our Vue components.

Categories
Vue 3

Vue 3 — Dynamic Transitions and State Animations

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at creating dynamic transitions and state change animation.

Dynamic Transitions

We can create dynamic transitions by passing in a dynamic string to the name prop.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
  </head>
  <body>
    <div id="app">
      Fade in
      <input type="range" v-model="fadeDuration" min="0" :max="5000" /> <button @click="show = !show">
        toggle
      </button>
      <transition
        :css="false"
        @before-enter="beforeEnter"
        @enter="enter"
        @leave="leave"
      >
        <p v-if="show">hello</p>
      </transition>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            show: false,
            fadeDuration: 1000
          };
        },
        methods: {
          beforeEnter(el) {
            el.style.opacity = 0;
          },
          enter(el, done) {
            Velocity(
              el,
              { opacity: 1 },
              {
                duration: this.fadeDuration,
                complete: done
              }
            );
          },
          leave(el, done) {
            Velocity(
              el,
              { opacity: 0 },
              {
                duration: this.fadeDuration,
                complete: done
              }
            );
          }
        }
      }); app.mount("#app");
    </script>
  </body>
</html>

We added the Velocity library to add transitions effects of our choice.

The fadeDuration can be adjusted so that we change the length of the enter and leave effects.

We just called Velocity to change the opacity to what we want.

State Transitions

Vue can animate state changes.

They include things like numbers and calculations, colors displayed, SVG nodes, and other properties.

To do that, we can animate state change with watches.

For instance, we can make a simple state transition by writing:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.min.js"></script>
  </head>
  <body>
    <div id="app">
      <input v-model.number="number" type="number" step="20" />
      <p>{{ animatedNumber }}</p>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            number: 0,
            tweenedNumber: 0
          };
        },
        computed: {
          animatedNumber() {
            return this.tweenedNumber.toFixed(0);
          }
        },
        watch: {
          number(newValue) {
            gsap.to(this.$data, { duration: 1.5, tweenedNumber: newValue });
          }
        }
      }); app.mount("#app");
    </script>
  </body>
</html>

When we type in a number, then it’ll be animated.

The animation is don’t in the number watcher.

We call Greensock’s to method to animate towards the final value.

this.$data has the value of the final number.

tweenedNumber is the value displayed as the animation is being done.

animatedNumber takes the tweenedNumber value, return it and display it.

Organizing Transitions into Components

We can organize animations into their own components.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.min.js"></script>
  </head>
  <body>
    <div id="app">
      <input v-model.number="x" type="number" step="20" /> +
      <input v-model.number="y" type="number" step="20" /> = {{ result }}
      <p>
        <animated-integer :value="x"></animated-integer> +
        <animated-integer :value="y"></animated-integer> =
        <animated-integer :value="result"></animated-integer>
      </p>
    </div> <script>
      const app = Vue.createApp({
        data() {
          return {
            x: 400,
            y: 373
          };
        },
        computed: {
          result() {
            return this.x + this.y;
          }
        }
      }); app.component("animated-integer", {
        template: "<span>{{ fullValue }}</span>",
        props: {
          value: {
            type: Number,
            required: true
          }
        },
        data() {
          return {
            tweeningValue: 0
          };
        },
        computed: {
          fullValue() {
            return Math.floor(this.tweeningValue);
          }
        },
        methods: {
          tween(newValue, oldValue) {
            gsap.to(this.$data, {
              duration: 0.5,
              tweeningValue: newValue,
              ease: "sine"
            });
          }
        },
        watch: {
          value(newValue, oldValue) {
            this.tween(newValue, oldValue);
          }
        },
        mounted() {
          this.tween(this.value, 0);
        }
      }); app.mount("#app");
    </script>
  </body>
</html>

We created the animated-integer component to animate our number.

It still animates the number, but we have a tween method to create the animation so that we can simplify our value watcher method.

fullValue is a computed property to round this.tweeningValue down to the nearest integer.

this.tweeningValue is the values that’ll be displayed during the animation.

So when we type in our numbers, we’ll see all the numbers being animated.

Conclusion

One good use of watchers is to animate state changes.

We made this easy with the Greensock library.

Also, we can change the settings of the transition on the fly.

Categories
Vue 3

Vue 3 — List Transitions

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at creating list transitions.

List Move Transitions

We can add list move transitions.

For instance, we can use the transition-group component to display some effect when we the items change position:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
    <style>
      .list-move {
        transition: transform 0.8s ease;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <button @click="shuffle">shuffle</button>
      <transition-group name="list" tag="div">
        <p v-for="item in items" :key="item">
          {{ item }}
        </p>
      </transition-group>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            items: Array(10)
              .fill()
              .map(() => Math.random()),
          };
        },
        methods: {
          shuffle() {
            this.items = _.shuffle(this.items);
          }
        }
      });
      app.mount("#app");
    </script>
  </body>
</html>

We added the list-move class with our transition effect to display it when the list is shuffled.

The list is shuffled with the Lodash shuffle method.

Staggering List Transitions

We can stagger transitions in a list.

To do that, we use the Greensock library to help us.

We can write;

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.4/gsap.min.js"></script>
  </head>
  <body>
    <div id="app">
      <input v-model="query" />
      <transition-group
        name="fade"
        tag="div"
        :css="false"
        @before-enter="beforeEnter"
        @enter="enter"
        @leave="leave"
      >
        <p
          v-for="(item, index) in computedList"
          :key="item.name"
          :data-index="index"
        >
          {{ item.name }}
        </p>
      </transition-group>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            query: "",
            list: [
              { name: "james" },
              { name: "mary" },
              { name: "alex" },
              { name: "bob" },
              { name: "jane" }
            ]
          };
        },
        computed: {
          computedList() {
            return this.list.filter(item => {
              return item.name.toLowerCase().includes(this.query.toLowerCase());
            });
          }
        },
        methods: {
          beforeEnter(el) {
            el.style.opacity = 0;
            el.style.height = 0;
          },
          enter(el, done) {
            gsap.to(el, {
              opacity: 1,
              height: "1.3em",
              delay: el.dataset.index * 0.55,
              onComplete: done
            });
          },
          leave(el, done) {
            gsap.to(el, {
              opacity: 0,
              height: 0,
              delay: el.dataset.index * 0.45,
              onComplete: done
            });
          }
        }
      });
      app.mount("#app");
    </script>
  </body>
</html>

We included the Greensock library with our app.

In the methods property, we have a few methods.

The beforeEnter method sets the container’s opacity and height to 0.

The enter method has our enter animation effect.

We change the opacity to 1 to make opaque.

height is th height of the container.

delay is the delay of the animation.

onComplete is a function we call to notify Vue that the animation is done.

We do the same thing with the leave transition.

When computedList returns a new value, the animation effects will be applied.

Therefore, when we type in something into the input box, we’ll see the effects applied.

Reusable Transitions

We can make our transitions reusable by moving it into our own component.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.4/gsap.min.js"></script>
  </head>
  <body>
    <div id="app">
      <input v-model="query" />
      <list-transition>
        <p
          v-for="(item, index) in computedList"
          :key="item.name"
          :data-index="index"
        >
          {{ item.name }}
        </p>
      </list-transition>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            query: "",
            list: [
              { name: "james" },
              { name: "mary" },
              { name: "alex" },
              { name: "bob" },
              { name: "jane" }
            ]
          };
        },
        computed: {
          computedList() {
            return this.list.filter(item => {
              return item.name.toLowerCase().includes(this.query.toLowerCase());
            });
          }
        }
      }); 

      app.component("list-transition", {
        template: `
        <transition-group
          name="fade"
          tag="div"
          :css="false"
          @before-enter="beforeEnter"
          @enter="enter"
          @leave="leave"
        >
         <slot></slot>
        </transition-group>
        `,
        methods: {
          beforeEnter(el) {
            el.style.opacity = 0;
            el.style.height = 0;
          },
          enter(el, done) {
            gsap.to(el, {
              opacity: 1,
              height: "1.3em",
              delay: el.dataset.index * 0.55,
              onComplete: done
            });
          },
          leave(el, done) {
            gsap.to(el, {
              opacity: 0,
              height: 0,
              delay: el.dataset.index * 0.45,
              onComplete: done
            });
          }
        }
      }); 

      app.mount("#app");
    </script>
  </body>
</html>

to move our transition component and hooks to its own component.

We just create a component and add a slot in between the transition-group tags to add the slot for our content.

Now we can use the list-transition component everywhere.

Conclusion

We can add our list transitions effects with the transition-group component.

It takes various directives to let us add hooks to create JavaScript animations.

Categories
Vuetify

Vuetify — Badges and Banners

Vuetify is a popular UI framework for Vue apps.

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

Badges

The v-badge component lets us add an avatar-like icon or text onto the component to highlight information to the user.

They appear as superscripts or subscripts.

For example, we can write:

<template>
  <v-container>
    <v-row class="text-center">
      <v-col col="12">
        <v-toolbar>
          <v-tabs dark background-color="primary" grow>
            <v-tab>
              <v-badge color="pink" dot>One</v-badge>
            </v-tab> <v-tab>
              <v-badge color="green" content="6">Two</v-badge>
            </v-tab> <v-tab>
              <v-badge color="deep-purple accent-4" icon="mdi-vuetify">Three</v-badge>
            </v-tab>
          </v-tabs>
        </v-toolbar>
      </v-col>
    </v-row>
  </v-container>
</template><script>
export default {
  name: "HelloWorld",
  data: () => ({
    alert: false,
  }),
};
</script>

We add badges to tab links with the v-tab component.

The color can be changed with the color prop.

icon lets us change the icon.

Show Badge on Hover

We can make a badge shows on hover with the v-hover component.

For example, we can write:

<template>
  <v-container>
    <v-row class="text-center">
      <v-col col="12">
        <v-badge
          :value="hover"
          color="deep-purple accent-4"
          content="1000"
          left
          transition="slide-x-transition"
        >
          <v-hover v-model="hover">
            <v-icon color="grey lighten-1" large>mdi-account-circle</v-icon>
          </v-hover>
        </v-badge>
      </v-col>
    </v-row>
  </v-container>
</template><script>
export default {
  name: "HelloWorld",
  data: () => ({
    hover: undefined,
  }),
};
</script>

to add a badge that shows on hover.

The hover state is controlled by the hover state.

v-model son the v-hover component sets the hover state.

Dynamic Notifications

We can create dynamic notifications with badges.

The content can be controlled with the content prop.

For example, we can write:

<template>
  <v-container>
    <v-row class="text-center">
      <v-col col="12">
        <div>
          <v-btn class="mx-1" color="primary" @click="messages++">Send Message</v-btn> <v-btn class="mx-1" color="error" @click="messages = 0">Clear Notifications</v-btn>
        </div> <v-badge :content="messages" :value="messages" color="green" overlap>
          <v-icon large>mdi-email</v-icon>
        </v-badge>
      </v-col>
    </v-row>
  </v-container>
</template><script>
export default {
  name: "HelloWorld",
  data: () => ({
    messages: 0,
  }),
};
</script>

We have the Send Message button that increments the message state.

This causes the content to update with the latest message value.

Banners

The v-banner component is used as a message for users with 1 to 2 actions.

It can have a single line or multiple lines.

For example, we can write:

<template>
  <v-container>
    <v-row class="text-center">
      <v-col col="12">
        <v-banner single-line :sticky="sticky">
          Hello world.
          <template v-slot:actions>
            <v-btn text color="deep-purple accent-4">Get Online</v-btn>
          </template>
        </v-banner>
      </v-col>
    </v-row>
  </v-container>
</template><script>
export default {
  name: "HelloWorld",
  data: () => ({
    sticky: true
  }),
};
</script>

We have the v-banner component with the single-line prop to display a single line banner.

The sticky controls whether the banner is sticky or not.

Two-Line Banner

We can add a 2 line banner to store more data.

For example, we can write:

<template>
  <v-container>
    <v-row class="text-center">
      <v-col col="12">
        <v-banner>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent cursus nec sem id malesuada.
          Curabitur lacinia sem et turpis euismod, eget elementum ex pretium.
          <template
            v-slot:actions
          >
            <v-btn text color="primary">Dismiss</v-btn>
            <v-btn text color="primary">Retry</v-btn>
          </template>
        </v-banner>
      </v-col>
    </v-row>
  </v-container>
</template><script>
export default {
  name: "HelloWorld",
  data: () => ({}),
};
</script>

to add a longer message.

The actions slot has the action buttons.

Conclusion

We can add badges and banners to our app with Vuetify.