Categories
Quasar

Developing Vue Apps with the Quasar Library — Breakpoints and Flexbox

Quasar is a popular Vue UI library for developing good looking Vue apps.

In this article, we’ll take a look at how to create Vue apps with the Quasar UI library.

Breakpoints

Quasar uses the following breakpoints:

  • xs — Up to 599px
  • sm — Up to 1023px
  • md — Up to 1439px
  • lg — Up to 1919px
  • xl — Bigger than 1920px

Flexbox

Quasar comes with flexbox helper classes.

We can create rows with the row class and columns with the col class.

For example, we can write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>

  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <div class="row">
        <div class="col">
          .col
        </div>
        <div class="col">
          .col
        </div>
      </div>
    </div>

    <script>
      new Vue({
        el: "#q-app",
        data() {
          return {};
        },
        methods: {}
      });
    </script>
  </body>
</html>

The col class will divide the rows into equal-sized columns.

Setting One Column Width

We can specify the column width of a column.

For instance, we can write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>

  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <div class="row">
        <div class="col">
          .col
        </div>
        <div class="col-6">
          .col-6
        </div>
        <div class="col">
          .col
        </div>
      </div>
    </div>

    <script>
      new Vue({
        el: "#q-app",
        data() {
          return {};
        },
        methods: {}
      });
    </script>
  </body>
</html>

We set the middle column’s class to col-6 to make it wider.

The max-width of a column is 12 units.

Variable Width Content

We can add variable-width content with Quasar.

For instance, we can write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>

  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <div class="row">
        <div class="col-12 col-md-2">
          .col-12 .col-md-2
        </div>
        <div class="col-12 col-md-auto">
          .col-12 .col-md-auto
        </div>
        <div class="col-12 col-md-2">
          .col-12 .col-md-2
        </div>
      </div>
    </div>

    <script>
      new Vue({
        el: "#q-app",
        data() {
          return {};
        },
        methods: {}
      });
    </script>
  </body>
</html>

We have col-md-2 to make the column width 2 when the breakpoint is md .

col-md-auto makes the width automatically sized.

Alignment

We can change the alignment of the elements in the flexbox container with the classes provided by Quasar.

For example, we can write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>

  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <div class="row items-start">
        <div class="col">
          1
        </div>
        <div class="col">
          2
        </div>
        <div class="col">
          3
        </div>
      </div>
    </div>

    <script>
      new Vue({
        el: "#q-app",
        data() {
          return {};
        },
        methods: {}
      });
    </script>
  </body>
</html>

We have the items-start class to put all the column divs at the top of the container.

There’s also the items-center class to make the child elements vertically centered and items-end makes the child elements align to the bottom.

Conclusion

We can use the classes provided by Quasar to position and size our elements.

Categories
Quasar

Getting Started with Developing Vue Apps with the Quasar Library

Quasar is a popular Vue UI library for developing good looking Vue apps.

In this article, we’ll take a look at how to create Vue apps with the Quasar UI library.

Getting Started

The easiest way to get started is with the UMD build of the framework.

We can use that with only a script tag.

To do this, we write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>

  <body>
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>

    <script>
      new Vue({
        el: "#q-app",
        data() {
          return {};
        },
        methods: {}
      });
    </script>
  </body>
</html>

to create an empty Quasar app.

We add the CSS for Google fonts and Quasar in the head tag.

Then we add Vue and Quasar in the body .

The 3rd script tag is the Vue app instance.

Colors

Quasar comes with its own colors.

For example, we can write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>

  <body>
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-btn color="red">button</q-btn>
    </div>

    <script>
      new Vue({
        el: "#q-app",
        data() {
          return {};
        },
        methods: {}
      });
    </script>
  </body>
</html>

We added the q-btn with the color prop set to red to make the background color red.

It comes with many other colors.

Spacing

Quasar has spacing helper classes.

For instance, we can write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>

  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-card class="q-mt-md q-mr-sm">hello world</q-card>
    </div>

    <script>
      new Vue({
        el: "#q-app",
        data() {
          return {};
        },
        methods: {}
      });
    </script>
  </body>
</html>

The q-mt-md and q-mr-sm are the classes for adding the top and right margins respectively.

md is the medium screen size and sm is the small screen breakpoint.

CSS Shadows

We can apply shadows to our components with the provided classes.

For example, we can write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>

  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-card class="shadow-2">hello world</q-card>
    </div>

    <script>
      new Vue({
        el: "#q-app",
        data() {
          return {};
        },
        methods: {}
      });
    </script>
  </body>
</html>

We set the shadow-2 class to add a shadow with a depth of 2 around the card.

Other classes include:

  • no-shadow — remove any shadow
  • inset-shadow — set an inset shadow
  • shadow-N — where N is an integer from 1 to 24.
  • shadow-transition — apply a CSS transition on the shadow

We can also have shadows pointing up with:

  • shadow-up-1 — set a depth of 1
  • shadow-up-2 — set a depth of 2
  • shadow-up-N — where N is an integer from 1 to 24.

They will apply a shadow on the top of the component.

Conclusion

We can get started with Quasar with the UMD module, which can be included with a script tag.

Categories
Vue 3

Form Validation in a Vue 3 App with Vee-Validate 4 — Cross-Field Validation and Array Validations

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.

Cross-Field Validation with defineRules

We can add cross-field validation without the Yup library.

To do this, we write:

<template>
  <div>
    <Form @submit="onSubmit">
      <div>
        <label for="password">Password</label>
        <Field name="password" type="password" rules="required|min:5" />
        <ErrorMessage name="password" />
      </div>

      <div>
        <label for="passwordConfirmation">Confirm Password </label>
        <Field
          name="passwordConfirmation"
          type="password"
          rules="required|confirmed:@password"
        />
        <ErrorMessage name="passwordConfirmation" />
      </div>
      <button type="submit">Submit</button>
    </Form>
  </div>
</template>

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

defineRule("required", (value) => {
  if (!value) {
    return "This is required";
  }
  return true;
});

defineRule("min", (value, [min]) => {
  if (value && value.length < min) {
    return `Should be at least ${min} characters`;
  }
  return true;
});

defineRule("confirmed", (value, [other]) => {
  if (value !== other) {
    return `Passwords do not match`;
  }
  return true;
});

export default {
  name: "App",
  components: {
    Field,
    Form,
    ErrorMessage,
  },
  methods: {
    onSubmit(values) {
      alert(JSON.stringify(values, null, 2));
    },
  },
};
</script>

We called defineRule to define the required rule to check if the field is filled in.

min checks if the field has a minimum amount of characters.

confirmed checks that the other field’s value matches the current field’s value.

value has the value of the field. And other has the value of the other field.

Array Fields

We can validate fields that are rendered from an array.

To do this, we write:

<template>
  <div>
    <Form @submit="onSubmit" :validation-schema="schema">
      <fieldset v-for="(user, idx) in users" :key="user.id">
        <legend>User {{ idx }}</legend>
        <label :for="`name_${idx}`">Name</label>
        <Field :id="`name_${idx}`" :name="`users[${idx}].name`" />
        <ErrorMessage :name="`users[${idx}].name`" as="p" />

        <label :for="`email_${idx}`">Email</label>
        <Field
          :id="`email_${idx}`"
          :name="`users[${idx}].email`"
          type="email"
        />
        <ErrorMessage :name="`users[${idx}].email`" as="p" />
        <button type="button" @click="remove(user)">X</button>
      </fieldset>

      <button type="button" @click="add">Add User +</button>
      <button type="submit">Submit</button>
    </Form>
  </div>
</template>

<script>
import { Field, Form, ErrorMessage } from "vee-validate";
import * as yup from "yup";

export default {
  name: "App",
  components: {
    Field,
    Form,
    ErrorMessage,
  },
  data: () => {
    const schema = yup.object().shape({
      users: yup
        .array()
        .of(
          yup.object().shape({
            name: yup.string().required().label("Name"),
            email: yup.string().email().required().label("Email"),
          })
        )
        .strict(),
    });

    return {
      schema,
      users: [
        {
          id: Date.now(),
        },
      ],
    };
  },
  methods: {
    onSubmit(values) {
      alert(JSON.stringify(values, null, 2));
    },
    add() {
      this.users.push({
        id: Date.now(),
      });
    },
    remove(item) {
      const index = this.users.indexOf(item);
      if (index === -1) {
        return;
      }

      this.users.splice(index, 1);
    },
  },
};
</script>

We create the schema object with Yup to call yup.array to let us validate array.

Then we call of to let us specify the validation schema for the objects inside.

The name attributes of each field should be a string that takes the form of an array path.

Now when we type in something invalid in any entry, we’ll see errors displayed.

Conclusion

We can do cross-field validation and array field validations in our Vue 3 app with Vee-Validate 4.

Categories
Vue 3

Form Validation in a Vue 3 App with Vee-Validate 4 — Async and Cross-Field Validation

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.

Async Validation

We can validate form fields asynchronously with Vee-Validate 4.

For example, we can write:

<template>
  <div>
    <Form @submit="onSubmit">
      <label for="email">Email</label>
      <Field id="email" name="email" :rules="validateEmail" type="email" />
      <ErrorMessage name="email" />

      <button type="submit">Submit</button>
    </Form>
  </div>
</template>

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

const mockApiRequest = (value) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(value === "abc@abc.com");
    }, 1000);
  });
};

export default {
  name: "App",
  components: {
    Field,
    Form,
    ErrorMessage,
  },
  methods: {
    onSubmit(values) {
      alert(JSON.stringify(values, null, 2));
    },
    async validateEmail(value) {
      const result = await mockApiRequest(value);
      return result ? true : "This email is already taken";
    },
  },
};
</script>

We have the mockApiRequest function that returns a promise to simulate async validation.

We use that in the validateEmail method.

If it resolves to true , then the field is valid.

Otherwise, it’s not valid.

Then we pass the method into the rules prop to do the validation.

When we press Submit, validation will be run.

If it’s valid, then the onSubmit method will be run.

Cross-Field Validation

We can add cross-field validation with Vee-Validate 4.

For example, to check if the confirm password field matches the password field, we can write:

<template>
  <div>
    <Form @submit="onSubmit" :validation-schema="schema">
      <div>
        <label for="password">Password</label>
        <Field id="password" name="password" type="password" />
        <ErrorMessage name="password" />
      </div>

      <div>
        <label for="passwordConfirmation">Confirm Password </label>
        <Field
          id="passwordConfirmation"
          name="passwordConfirmation"
          type="password"
        />
        <ErrorMessage name="passwordConfirmation" />
      </div>

      <button type="submit">Submit</button>
    </Form>
  </div>
</template>

<script>
import { Field, Form, ErrorMessage, defineRule } from "vee-validate";
import * as yup from "yup";

defineRule("required", (value) => {
  if (!value) {
    return "This is required";
  }

  return true;
});

defineRule("min", (value, [min]) => {
  if (value && value.length < min) {
    return `Should be at least ${min} characters`;
  }

  return true;
});

defineRule("confirmed", (value, [other]) => {
  if (value !== other) {
    return `Passwords do not match`;
  }

return true;
});

export default {
  name: "App",
  components: {
    Field,
    Form,
    ErrorMessage,
  },
  data: () => {
    const schema = yup.object().shape({
      password: yup.string().min(5).required(),
      passwordConfirmation: yup
        .string()
        .required()
        .oneOf([yup.ref("password")], "Passwords do not match"),
    });

    return {
      schema,
    };
  },
  methods: {
    onSubmit(values) {
      alert(JSON.stringify(values, null, 2));
    },
  },
};
</script>

We called defineRule to define the required and min rules to check that the field has a value filled in and that it has the minimum length required.

The confirmed rule is defined with the other variable to check the value of the other field.

We define the Yup schema in the data method.

The validation rules for the passwordConfirmation rule calls oneOf with yup.ref("password") to check that the value of it matches the value of the password field.

Then we pass in the schema into the validation-schema prop.

Conclusion

We can validate form fields asynchronously and do cross-field validation in our Vue 3 app with Vee-Validate 4.

Categories
Vue 3

Form Validation in a Vue 3 App with Vee-Validate 4 — Validating Custom Inputs

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.

Validating Custom Inputs

We can use Vee-Validate 4 to validate custom input components.

For example, we can write:

components/TextInput.vue

<template>
  <div
    class="TextInput"
    :class="{ 'has-error': !!errorMessage, success: meta.valid }"
  >
    <label :for="name">{{ label }}</label>
    <input
      :name="name"
      :id="name"
      :type="type"
      :value="inputValue"
      @input="handleChange"
      @blur="handleBlur"
    />

    <p class="help-message" v-show="errorMessage || meta.valid">
      {{ errorMessage || successMessage }}
    </p>
  </div>
</template>

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

export default {
  props: {
    type: {
      type: String,
      default: "text",
    },
    value: {
      type: String,
      default: "",
    },
    name: {
      type: String,
      required: true,
    },
    label: {
      type: String,
      required: true,
    },
    successMessage: {
      type: String,
      default: "",
    },
  },
  setup(props) {
    const {
      value: inputValue,
      errorMessage,
      handleBlur,
      handleChange,
      meta,
    } = useField(props.name, undefined, {
      initialValue: props.value,
    });

    return {
      handleChange,
      handleBlur,
      errorMessage,
      inputValue,
      meta,
    };
  },
};
</script>

App.vue

<template>
  <Form :validation-schema="schema" @submit="onSubmit">
    <TextInput
      name="name"
      type="text"
      label="Full Name"
      placeholder="Your Name"
      success-message="Nice to meet you!"
    />
  </Form>
</template>

<script>
import { Form } from "vee-validate";
import TextInput from "@/components/TextInput.vue";
import * as yup from 'yup'

export default {
  components: {
    Form,
    TextInput,
  },
  data() {
    const schema = yup.object().shape({
      name: yup.string().required(),
    });

    return {
      schema,
    };
  },
  methods: {
    onSubmit(values) {
      alert(JSON.stringify(values, null, 2));
    },
  },
};
</script>

We create the TextInput component that takes some props.

name is the name attribute value of our component.

type has the type attribute value.

label has the text for the label.

placeholder has the input placeholder value.

success-message has the validation success message.

We then defined the schema in the data method and passed that into the validation-schema prop.

In TextInput.vue , we get the props and put them in the place we want.

In the setup method, we call the useField function to add validation for our field.

We pass in the name attribute value as the first argument.

And then we set the initial value with the props.value value in the 3rd argument.

It returns an object with the value property which we can use to set the value prop.

handleChange is passed into the input prop to set the inputted value.

handleBlur is used to handle the blur event.

meta has is an object with the validity state of the field.

errorMessage has the form field error message.

Now when we type in some value into the field, we should see different messages depending on if the value is valid or not.

Conclusion

We can use Vee-Validate 4 to validate Vue 3 custom input components.