Categories
Vue Tips

Vue Tips — Transitions, Routes, and Data

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 create reusable transitions and making requests in a Vue app.

Encapsulated Transition Component

We can encapsulate transitions into its own component so that we can reuse it anywhere.

For instance, we can create a transition component that has the transition component and then reuse it as follows:

styles.css :

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}

index.js :

Vue.component("foo-transition", {
  template: `
  <transition name="fade">
    <slot></slot>
  </transition>
  `
});

new Vue({
  el: "#app",
  data: {
    show: true
  }
});

index.html :

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <link type="text/css" rel="stylesheet" href='styles.css'></link>
  </head>
  <body>
    <div id="app">
      <button v-on:click="show = !show">
        Toggle transition
      </button>
      <foo-transition>
        <p v-if='show'>foo</p>
      </foo-transition>
      <foo-transition>
        <p v-if='show'>bar</p>
      </foo-transition>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we have the reusable foo-transition component that has a transition component with a slot inside so we can toggle on and off what we want within the foo-transition component with the transition effects applied to it.

Then in index.html , we reused foo-transition by writing:

<foo-transition>
  <p v-if='show'>foo</p>
</foo-transition>
<foo-transition>
  <p v-if='show'>bar</p>
</foo-transition>

Then when we toggle show between true and false , we’ll see both p elements have the fade transition effect applied to it.

Also, we can also use v-bind='$attrs' to bind attributes from parent to child and v-on='$listeners' to pass event listeners from parent to child.

For instance, we can change the example above as follows:

styles.css :

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}

.foo {
  color: green;
}

index.js :

Vue.component("foo-transition", {
  template: `
  <transition name="fade" v-bind="$attrs" v-on="$listeners">
    <slot></slot>
  </transition>
  `
});

new Vue({
  el: "#app",
  data: {
    show: true
  }
});

index.html :

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <link type="text/css" rel="stylesheet" href='styles.css'></link>
  </head>
  <body>
    <div id="app">
      <button @click="show = !show">
        Toggle transition
      </button>
      <foo-transition class="foo">
        <p v-if='show'>foo</p>
      </foo-transition>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we have v-bind=”$attrs” in transitions . So we’ll get the foo class applied to it if we set the class to foo in foo-transition .

Use Axios for Data Fetching

Vue doesn’t come with its own HTTP client, so to make HTTP requests, we have to add our own. Axios is a good choice since it’s available via CDN and as a Node package.

Also, it’s promise-based so that we won’t have to deal with nested callbacks.

For instance, we can use it as follows:

index.js :

new Vue({
  el: "#app",
  data: {
    info: {}
  },
  async mounted() {
    const { data } = await axios.get(
      "https://api.coindesk.com/v1/bpi/currentprice.json"
    );
    this.info = data;
  }
});

index.html :

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  </head>
  <body>
    <div id="app">
      <p v-if="info.bpi">
        {{info.bpi['USD'].code}} - {{info.bpi['USD'].rate}}
      </p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we used the Axios library by including it from CDN. Then we get the data in the mounted hook. Since axios.get returns a promise, we can use the async and await syntax to make the request.

An alternative would be the Fetch API, which is similar to Axios, but we don’t have to add a library to use it.

Use vue-router to Handle Client-Side Routing

Vue Router is one of the most commonly used router for Vue apps. It lets us map URLs to components and also get route and query parameters and pass them into our components.

It also supports nested routes and transitions.

A simple example would be the following:

index.js :

const Foo = { template: "<div>foo</div>" };
const Bar = { template: "<div>bar</div>" };

const routes = [
  { path: "/foo", component: Foo },
  { path: "/bar", component: Bar }
];

const router = new VueRouter({
  routes
});

new Vue({
  el: "#app",
  router
});

index.html :

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <div id="app">
      <router-link to="/foo">Foo</router-link>
      <router-link to="/bar">Bar</router-link>
      <router-view></router-view>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we added the Vue Router and then add the router-view to display the route content, which are the components we mapped to.

Then we have the router-link that we can click on to navigate between the routes.

Conclusion

We can create reusable transition components by adding slots inside transition components. To make HTTP requests, we can use the Axios HTTP client to make send requests easy. Finally, we can use Vue Router to map URLs to our components.

Categories
Vue Tips

Vue Tips — Cleaning up Props, Computed Properties, and Watchers

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 ways to make developing Vue apps even easier, including reducing props accepted by components, and removing confusion between computed properties and watchers.

Cleaning up Props

We should reduce the number of props that a component takes. It takes components harder to work with and makes the code longer.

We can do this in several ways. The best way is to pass them in as the properties of objects of one prop instead of multiple props. For instance, we can write the following code to do that:

components/Button.vue :

<template>
  <button :style="styles">Button</button>
</template>

<script>
export default {
  name: "Button",
  props: {
    styles: Object
  }
};
</script>

App.vue :

<template>
  <div id="app">
    <Button :styles="{color: 'white', backgroundColor: 'black', outline: 'none'}"/>
  </div>
</template>

<script>
import Button from "./components/Button";

export default {
  name: "App",
  components: {
    Button
  }
};
</script>

In the code above, we passed in an object as the value of the style prop. It’s much more compact than passing in multiple props for each style.

Another example would be pass in multiple attributes with the v-bind directive:

components/Button.vue :

<template>
  <button v-bind="attrs">Button</button>
</template>

<script>
export default {
  name: "Button",
  props: {
    attrs: Object
  }
};
</script>

App.vue :

<template>
  <div id="app">
    <Button :attrs="{autofocus: 'true', name  : 'foo', type: 'button', value: 'foo'}"/>
  </div>
</template>

<script>
import Button from "./components/Button";

export default {
  name: "App",
  components: {
    Button
  }
};
</script>

In the code above, we pass in the attrs prop into the Button component, and then we used v-bind=”attrs” to apply them as attributes for out button element.

We should see the attributes applied when we inspect the button in the developer console.

Don’t Confuse Computed Properties and Watchers

Computed properties should be used as much as possible for derived properties unless we need to watch something and create side effects from them.

A computed property lets us derive new data from existing data. For instance, if we have firstName and lastName and we want to create a property fullName from firstName and lastName , we create a computed property as follows:

<template>
  <div id="app">{{fullName}}</div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      firstName: "Jane",
      lastName: "Smith"
    };
  },
  computed: {
    fullName() {
      return `${this.firstName} ${this.lastName}`;
    }
  }
};
</script>

In the code above, we have the computed property, which has the fullName method that returns `${this.firstName} ${this.lastName}` .

Then we reference it as a property like what we have in data in the template with {{fullName}} .

Computed properties are reactive. A new value will be returned if either firstName or lastName change. Therefore, it’ll always be up to date.

To do this with watchers, we have to write the following:

<template>
  <div id="app">{{fullName}}</div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      firstName: "Jane",
      lastName: "Smith",
      fullName: ""
    };
  },
  watch: {
    firstName: {
      immediate: true,
      handler() {
        this.fullName = `${this.firstName} ${this.lastName}`;
      }
    },
    lastName: {
      immediate: true,
      handler() {
        this.fullName = `${this.firstName} ${this.lastName}`;
      }
    }
  }
};
</script>

As we can see, the code above is much more complex, and we have to remember to set immediate to true , so that we can watch the initial value change in addition to the subsequent ones. In the handler functions, we have to set this.fullName each time.

It’s a much more complex and error-prone way to do the same thing as a computed property.

Computed properties are pure functions, whereas watcher handlers commit side effects, which also make them harder to test.

Where watchers are useful is for creating side effects. As we can see from the code above, it’s used for creating side effects by setting this.fullName as a combination of this.firstName and this.lastName .

For instance, we can use it as follows:

<template>
  <div id="app">
    <input v-model="name">
    <p>{{info.age}}</p>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      name: "",
      info: {}
    };
  },
  watch: {
    async name(val) {
      const response = await fetch(`https://api.agify.io?name=${val}`);
      this.info = await response.json();
    }
  }
};
</script>

In the code above, we watch for the value of the name field and then calls the Agify API to get some data from the name.

Then we set the value of the response to this.info . We can’t do this with computed properties since we don’t want to return a promise in computed properties.

Conclusion

We should clean up our props by passing them in as objects instead of multiple props.

Computed properties are good for deriving data from existing data, while watchers are good for committing side effects like when we need to run asynchronous code.

Categories
Vue

Vue Tips — Slots, Vue Router, and Mutations

Vue.js is a popular framework for creating front end web apps.

In this article, we’ll look at some tips for writing better Vue.js apps.

How to Pass Down Slots Inside Wrapper Component

To pass down slots in a wrapper component, we can loop through all the slots and pass them down to the child.

For instance, we can write:

<wrapper>
  <parent-table v-bind="$attrs" v-on="$listeners">

    <slot v-for="slot in Object.keys($slots)" :name="slot" :slot="slot"/>

    <template v-for="slot in Object.keys($scopedSlots)" :slot="slot" slot-scope="scope">
      <slot :name="slot" v-bind="scope"/>
    </template>

  </parent-table>
</wrapper>

We get the slots with the $slots variable.

Then we use Object.keys to get names of the slots so that we can loop through all of them and pass the name down.

Likewise, we can loop through the scoped slots with the $scopedSlots variables.

We get the keys the same way and loop through them with v-for the same way.

With Vue 2.6, the v-slot= directive is introduced to let is pass the slots down.

For instance, we can write:

<wrapper>
  <parent-table v-bind="$attrs" v-on="$listeners">
    <template v-for="(_, slot) of $scopedSlots" v-slot:[slot]="scope">
      <slot :name="slot" v-bind="scope"/>
    </template>
  </parent-table>
</wrapper>

We loop through the slot with v-for .

We get the scoped slots with the $scopedSlots variable.

slot is the slot name again.

This time, we pass it to the v-slot directive as a modifier to pass down the named slot.

scope has the scope from the scoped slot.

We use v-bind to get the scope.

And we

Alternatively, we can use render function to pass down the slots.

For instance, we can write:

render(h) {
  const children = Object.keys(this.$slots)
    .map(slot => h('template', { slot }, this.$slots[slot]))

  return h('wrapper', [
    h('parent-table', {
      attrs: this.$attrs,
      on: this.$listeners,
      scopedSlots: this.$scopedSlots,
    }, children)
  ])
}

We get the slots with the this.$slots property.

We call Object.keys to get the slot names.

And we call map on it to map the slot names to the template components.

And we pass in the slots name and the scope down,.

Then we return a wrapper component with the parent-table component with the listeners, attributes, and scoped slots and children as the children.

Get Query Parameters from a URL in Vue.js

We can get the query parameters from a URL in Vue with the this.$route.query property.

To get the query parameter foo=bar , we write:

this.$route.query.foo

and we get 'bar' as the value.

This is available assuming we’re using Vue Router in our Vue app.

If we haven’t added it, we can write:

index.html

<script src="https://unpkg.com/vue-router"></script>

index.js

const router = new VueRouter({
  mode: 'history',
  routes: [
    {
      path: '/page',
      name: 'page',
      component: PageComponent
    }
  ]
});

const vm = new Vue({
  router,
  el: '#app',
  mounted() {
    const q = this.$route.query.q;
    console.log(q)
  },
});

to get it.

We create the VueRouter instance and pass it into the object we passed into the Vue constructor.

routes has the routes.

Passing Multiple Parameters to a Mutation with Vuex

To pass multiple parameters to action with Vuex, we can pass in an object as the payload.

For instance, we can create our mutation by writing:

mutations: {
  setToken(state, { token, expiration }) {
    localStorage.setItem('token', token);
    localStorage.setItem('expiration', expiration);
  }
}

We have an object as the second parameter.

It has the token and expiration properties.

Then we can invoke the mutation by writing:

store.commit('setToken', {
  token,
  expiration,
});

We invoke the setToken mutation with the token and expiration properties in an object as the 2nd argument.

Reload Route with Vue Router

To reload a route with Vue Route, we can call the this.$router.go() method.

If it has no arguments, then it’ll reload the current route.

We can also add an unique value for the key prop to the router view:

<router-view :key="$route.fullPath"></router-view>

This way, it’ll notice when the path changes and it’ll trigger a reload of the component with new data.

Conclusion

We can reload our route with this.$router.go() .

There are many ways to pass a scope down to a child.

We can get query parameters in our component if we use Vue Router.

To pass in multiple pieces of data into a mutation, we can pass in an object with all the data we want into the mutation.

Categories
Vue

Vee-Validate — Required Fields and Built-in Rules

Form validation is a feature that’s not built into Vue.js.

However, we still need this feature very much.

In this article, we’ll look at how to define required fields and use built-in validation rules.

Required Fields

A field is required if we must have it present.

Values that are empty or falsy can’t be accepted.

Therefore, undefined , null , empty strings and empty arrays can’t be accepted.

0 is accepted as a valid value for the required fields.

false is also accepted by Vee-Validate as a valid value.

Optional Fields

Optional fields are validated when they’re empty.

Create a Required Rule

We can create a required rule with the computesRequired property.

We set it to true for it to be required.

For instance, we can write:

extend('required', {
  validate () {
    // ...
  },
  computesRequired: true
});

In the validate method, we have to return an object with the required and valid properties.

For example, we can write:

extend('required', {
  validate (value) {
    return {
      required: true,
      valid: !['', null, undefined].includes(value)
    };
  },
  computesRequired: true
});

Built-in Rules

Vee-Validate comes with come validation rules.

However, they aren’t installed by default.

To use them, we have to import them:

import { extend } from 'vee-validate';
import { required, email } from 'vee-validate/dist/rules';

extend('email', email);
extend('required', {
  ...required,
  message: 'please fill in'
});

We have the message property which overrides the existing one.

Install All Rules

We can install all rules by importing everything from the vee-validate/dist/rules module.

For example, we can write:

import { extend } from 'vee-validate';
import * as rules from 'vee-validate/dist/rules';

Object.keys(rules).forEach(rule => {
  extend(rule, rules[rule]);
});

The rules are in the rules object properties, so we just loop through them and call extend on them to import them.

Rules

There are many rules available. The following can be imported and use:

  • alpha — alphabetic characters
  • alpha_dash — alphabetic characters with dashes or underscores
  • alpha_num — alphanumeric characters
  • alpha_spaces — alphabets and spaces
  • between — minimum and maximum values
  • confirmed — check that 2 fields are the same
  • digits — a specified number of digits
  • dimensions — dimensions of images
  • email — emails
  • excluded — check if something isn’t in the list
  • ext — checks file extensions
  • image — image mime type
  • integer — integers
  • is — match the given value
  • is_not — doesn’t match the given value
  • length — length of the value
  • max — check if something doesn’t exceed the maximum length
  • max_value — check if something doesn’t exceed the maximum value
  • mimes — check if something has one of the listed mime types
  • min — less than the specified length
  • min_value — check if something isn’t less than the given value
  • numeric — numeric characters
  • oneOf — check if the inputted value is one of the listed ones
  • regex — check if a value matches a given regex
  • required — check if something is entered
  • required_if — required if something is entered in another field
  • size — check if a file doesn’t exceed a given size in kilobytes

Some of them take arguments.

digits can be used as follows:

<ValidationProvider rules="digits:10" v-slot="{ errors }">
  <input v-model="value" type="text">
  <span>{{ errors[0] }}</span>
</ValidationProvider>

where 10 is the number of digits allowed

dimensions checks the dimensions of the image file selected:

<ValidationProvider rules="dimensions:220,230" v-slot="{ errors, validate }">
  <input type="file" @change="validate">
  <span>{{ errors[0] }}</span>
</ValidationProvider>

220 is the width and 230 is the height.

excluded checks if something isn’t in the list:

<ValidationProvider rules="excluded:1,2" name="number" v-slot="{ errors }">
  <select v-model="value">
    <option value="1">one</option>
    <option value="2">two</option>
    <option value="3">three</option>
  </select>
  <span>{{ errors[0] }}</span>
</ValidationProvider>

1 and 2 are arguments of excluded , so they’re invalid.

ext checks if a selected file has the given extension specified:

<ValidationProvider rules="ext:jpg,png" v-slot="{ errors, validate }">
  <input type="file" @change="validate">
  <span>{{ errors[0] }}</span>
</ValidationProvider>

So jpg and png are valid file extensions.

is_not takes an argument to exclude.

For example, we can write:

<ValidationProvider rules="is_not:foo" v-slot="{ errors }">
  <input type="text" v-model="value">
  <span>{{ errors[0] }}</span>
</ValidationProvider>

foo is the value that’s not valid when entered.

length means that the string must be a string or array value that must have the given length.

Therefore, if we have:

<ValidationProvider rules="length:5" v-slot="{ errors }">
  <input type="text" v-model="value">
  <span>{{ errors[0] }}</span>
</ValidationProvider>

Then the inputted value must have length 5.

Conclusion

There are many built-in rules we can use for validation.

Inputted values including files can be validated.

We can also define the required fields.

Categories
Vue

Vee-Validate — Localization, and Input Component

Form validation is a feature that’s not built into Vue.js. However, we still need this feature very much. In this article, we’ll look at how to add localization features with Vee-Validate. We also look at how to extract the input component code.

Lazily Import Locales

We can use the import function to import a locale file from Vee-Validate.

For example, we can write:

import { localize } from 'vee-validate';

import(`vee-validate/dist/locale/fr.json`).then(locale => {
  localize(code, locale);
});

Using Other i18n Libraries

Vee-Validate integrates with other i18n libraries. For instance, we can use the vue-i18n library with Vee-Validate.

We can write:

import Vue from "vue";
import App from "./App.vue";
import { ValidationProvider, extend } from "vee-validate";
import { required } from "vee-validate/dist/rules";
import en from "vee-validate/dist/locale/en.json";
import VueI18n from "vue-i18n";

Vue.use(VueI18n);

const i18n = new VueI18n({
  locale: "en",
  messages: {
    en: {
      validation: en.messages
    }
  }
});

extend("required", {
  ...required,
  message: (_, values) => i18n.t("validation.required", values)
});

Vue.component("ValidationProvider", ValidationProvider);

Vue.config.productionTip = false;

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

We imported the messages from Vee-Validate.

Then we set them as the property of messages.en.validation .

Then we use i18n.t to translate the message.

Then we can write:

<template>
  <div id="app">
    <ValidationProvider name="name" rules="required" v-slot="{ errors }">
      <input v-model="name" type="text" placeholder="name">
      <span>{{ errors[0] }}</span>
    </ValidationProvider>

<input type="submit" value="submit">
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      name: ""
    };
  }
};
</script>

to display the message.

Model-less Validation

We can validate inputted values without models.

For example, we can write:

main.js

import Vue from "vue";
import App from "./App.vue";
import { ValidationProvider, extend } from "vee-validate";
import { required, image } from "vee-validate/dist/rules";

extend("required", required);
extend("image", image);
Vue.component("ValidationProvider", ValidationProvider);

Vue.config.productionTip = false;

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

App.vue

<template>
  <div id="app">
    <ValidationProvider rules="required|image" name="file" v-slot="{ validate, errors }">
      <input type="file" @change="validate">
      <p>{{ errors[0] }}</p>
    </ValidationProvider>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

We imported the required and image rules in main.js .

Then we used ValidationProvider in the template.

We called the built-in validate function to validate the file type when it’s changed.

We also have the name prop on the ValidationProvider .

Then we’ll see an error message if it’s not a valid image file.

Custom Component File Validation

Any component that emits an input event can be validated by Vee-Validate.

The value is emitted with the input event.

Value Synchronization

We can synchronize the value manually between what’s entered and what Vee-Validate sees with the syncValue method.

For example, we can write:

this.$refs.provider.syncValue(newValue);

to set the value of the validation provider with newValue .

Dynamic Rules

Rules can be applied dynamically.

We can put an expression in the rules prop.

For example, we can write:

<template>
  <div id="app">
    <button @click="required = !required">{{required ? 'disable':'enable'}} required</button>
    <br>
    <ValidationProvider
      :rules="`${required ? 'required' : ''}`"
      name="value"
      v-slot="{ validate, errors }"
    >
      <input v-model="value" type="text">
      <p>{{ errors[0] }}</p>
    </ValidationProvider>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      value: "",
      required: true
    };
  }
};
</script>

Then when required is false , no validation will be done.

Otherwise, validation will be done.

Extracting Input Fields

We can extract input fields into their own component.

This way, we don’t have to repeatedly reference ValidationProvider .

For example, we can write:

main.js

import Vue from "vue";
import App from "./App.vue";
import { ValidationProvider, ValidationObserver, extend } from "vee-validate";
import { required, image } from "vee-validate/dist/rules";

extend("required", required);
extend("image", image);
Vue.component("ValidationProvider", ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);

Vue.config.productionTip = false;

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

TextInput.vue

<template>
  <ValidationProvider tag="div" :rules="rules" :name="name" :vid="vid" v-slot="{ errors }">
    <input :type="type" v-model="currentValue">
    <span>{{ errors[0] }}</span>
  </ValidationProvider>
</template>

<script>
import { ValidationProvider } from "vee-validate";

export default {
  name: "TextInput",
  components: {
    ValidationProvider
  },
  props: {
    value: {
      type: String,
      default: ""
    },
    rules: {
      type: [String, Object],
      default: ""
    },
    name: {
      type: String,
      default: ""
    },
    vid: {
      type: String,
      default: undefined
    },
    type: {
      type: String,
      default: "text"
    }
  },
  data: () => ({
    currentValue: ""
  }),
  watch: {
    currentValue(val) {
      this.$emit("input", val);
    }
  }
};
</script>

The TextInput component is the component with the form field logic.

It takes various props that are used to populate fields with the data we want.

The value entered with the input event in the currentValue watcher.

It also takes the value prop.

Therefore, it can bind to v-model .

App.vue

<template>
  <div id="app">
    <ValidationObserver v-slot="{ handleSubmit }">
      <form @submit.prevent="handleSubmit(onSubmit)">
        <TextInput v-model="firstName" name="First Name" rules="required"/>

        <TextInput v-model="lastName" name="Last Name" rules="required"/>

        <input type="submit" value="submit">
      </form>
    </ValidationObserver>
  </div>
</template>

<script>
import TextInput from "./components/TextInput";

export default {
  name: "App",
  components: {
    TextInput
  },
  data() {
    return {
      firstName: "",
      lastName: ""
    };
  },
  methods: {
    onSubmit() {
      alert("success");
    }
  }
};
</script>

Then we can use it in the form element like any other input element.

Conclusion

We can add localization and extract form fields into a component.