Categories
JavaScript

Things that You Don’t Know About the JavaScript’s Object Constructor

In JavaScript, the Object constructor lets us create object wrapper with the given values. It will create an empty object if null or undefined is passed into the Object constructor. If the value passed into the constructor is an object already then, it will return the object.

The Object constructor has 2 properties. It has a length property that is always 1, and like all other objects, the Object constructor has a prototype to get all the property additions to the type Object.

The Object constructor has many useful methods that can be used without constructor a new object. Part 1 of this list is below:

Object.assign()

The Object.assign() method makes a shallow copy of an object. The first parameter is a target object that you copy the object to, and the second parameter accepts an object that you want to copy. Note that if the source and target objects have the same properties, the source object’s property value to overwrite the one in the target object. For example, we can write:

const target = { a: 1, b: 2 };  
const source = { b: 3, c: 4};
const newObj = Object.assign(target, source);  
console.log(newObj)

If we run the code, we will get { a: 1, b: 3, c: 4} . We can also copy arrays. For example, we can write:

const targetArr = [1,2];  
const sourceArr = [2,3];
const newArr = Object.assign(targetArr, sourceArr);  
console.log(newArr)

We get [2,3] logged when we run the code above. For arrays, it will overwrite the whole target array with the source array.

Object.create()

The Object.create() method creates a new object with the object you pass in as the prototype of the new object. For example, we can write:

const obj = { a: 1, b: 2, c: 3 };  
const newObj = Object.create(obj);  
console.log(newObj);

In the code above, when we log newObj, it doesn’t have its own properties that weren’t inherited from obj. This is because we only passed in the first argument to the constructor, which is the prototype for the object that’s returned. If we want to add properties that are available only in the returned object, we pass in an object with the property names as keys and the properties writable, configurable, enumerable and value as properties of the property name keys, which is called the property descriptor. For example, we can write:

const obj = {  
  a: 1,  
  b: 2,  
  c: 3  
};

const childObj = {  
  a: {  
    writable: true,  
    configurable: true,  
    value: 'hello'  
  },  
  d: {  
    writable: true,  
    configurable: true,  
    value: 'hello'  
  }  
}  
const newObj = Object.create(obj, childObj);  
console.log(newObj);

In the code above, writable means that the property’s value can be changed. configurable means the property descriptor may be changed and if the property can be deleted from the object. The enumerable property means that the property shows up during enumeration of the properties with the for...in loop, and value is the value of the property.

If we want to create an object that has properties that can’t be changed, then we set writable to false, like in the following code:

const obj = {  
  a: 1,  
  b: 2,  
  c: 3  
};

const childObj = {  
  a: {  
    writable: false,  
    configurable: true,  
    value: 'hello'  
  },  
  d: {  
    writable: true,  
    configurable: true,  
    value: 'hello'  
  }  
}  
const newObj = Object.create(obj, childObj);  
newObj.a = 1;  
newObj.d = 1;  
console.log(newObj);

Notice that the assignment operator has no effect. If we have strict mode enabled, then a TyepError will be thrown. We have {a: “hello”, d: 1} logged in the console.log . This means that writable set to false is working for the property a and writable value set to true is working for the property d .

If we pass in null to the constructor, we get an empty object:

const nullObj = Object.create(null)  
console.log(nullObj)

We will get an error ‘Object prototype may only be an Object or null: undefined’ is we pass in undefined as the prototype of an object like in the code below:

const undefinedObj = Object.create(undefined);  
console.log(undefinedObj)

Object.defineProperty()

The Object.defineProperty() method defines a new property on an object. The first parameter is the object that you want to add the property to. The second parameter is the name of the property you want to add passed in as a string, and the last parameter is the property descriptor included in the Object.create() method when we try to add properties to the returned object. The property descriptor should have the properties writable , configurable , enumerable and value as properties of the property name keys. The writable means that the property’s value can be changed. configurable means the property descriptor may be changed and if the property can be deleted from the object. The enumerable property means that the property shows up during enumeration of the properties with the for...in loop, and value is the value of the property. For example, we can write:

let obj = {};
Object.defineProperty(obj, 'a', {  
  writable: false,  
  configurable: true,  
  value: 'hello'  
})console.log(obj.a)  
obj.a  = 1;  
console.log(obj.a)

As we can see, the property descriptor acts the same way as in Object.create(). When writable is false, assignment to the property has no effect. If we have strict mode enabled, then a TyepError will be thrown.

We can also define getters and setter function for properties called get and set respectively for a property:

let obj = {};  
let value;Object.defineProperty(obj, 'a', {  
  get() {  
    return value;  
  },  
  set(a) {  
    value = a;  
  }  
});console.log(obj.a)  
obj.a = 1;  
console.log(obj.a)

As we can see, in the code above, we defined the property a for the object obj with the get and set functions to get the value of the property and set the value respectively.

Accessor properties are set on the prototype if we defined it on the prototype, but value properties are set on the current object. If an object inherits non-writable properties, it will still be non-writable on the current object. For example, if we have:

let ObjClass = function() {};  
ObjClass.prototype.a = 1;
Object.defineProperty(ObjClass.prototype, "b", {  
  writable: false,  
  value: 1  
});
const obj = new ObjClass();  
ObjClass.prototype.a = 3  
obj.a = 2  
ObjClass.prototype.b = 3  
obj.b = 2  
console.log(obj);

Then assigning to property b is no effect. If we have strict mode enabled, then a TyepError will be thrown.

Object.defineProperties()

The Object.defineProperties method let us define more than one property on an object. The first parameter of the method is the object that you want to define the properties on, and the second object contains the property names as key and the corresponding property descriptors as values. The property descriptor should have the properties writable, configurable, enumerable and value as properties of the property name keys. The writable means that the property’s value can be changed. configurable means the property descriptor may be changed and if the property can be deleted from the object. The enumerable property means that the property shows up during enumeration of the properties with the for...in loop, and value is the value of the property. For example, we can write:

let obj = {}  
Object.defineProperties(obj, {  
  a: {  
    writable: true,  
    configurable: true,  
    value: 'hello'  
  },  
  d: {  
    writable: false,  
    configurable: true,  
    value: 'hello'  
  }  
})  
obj.a = 1;  
obj.d = 1;  
console.log(obj);

To add the a and d properties where a ‘s value can change and d ‘s value can’t. The console.log will show {a: 1, d: “hello”} since the value assignment to d fails because the writable property of property d ‘s descriptor is set to false . We can also set configurable to false to prevent it from being deleted or having its property descriptor changed. For example, we can write:

let obj = {}  
Object.defineProperties(obj, {  
  a: {  
    writable: true,  
    configurable: true,  
    value: 'hello'  
  },  
  d: {  
    writable: false,  
    configurable: false,  
    value: 'hello'  
  }  
})  
obj.a = 1;  
delete obj.d;  
console.log(obj);

If we run the code above, then we can see that the delete operator had no effect on property d of obj.

Object.entries()

The Object.entries() method returns an array of key-value pairs of an object that are enumerable by the for...in loop. They are returned in the same order as they are in if they are iterated with the for...in loop. Each entry of the array will have the key as the first element and the value of the corresponding key as the second element. For example, we can use it to loop through the properties of an object in the following code:

const obj = {  
  a: 1,  
  b: 2  
};
for (let [key, value] of Object.entries(obj)) {  
  console.log(key, value);  
}

When we run the code above, we get a 1 and b 2. These are the key-value pairs of this object.

Object.freeze()

The Object.freeze() method freezes an object. This means that all properties’ values in an object can’t be changed. Also, new properties can’t be added to it, and existing property descriptors for the frozen object can’t be changed. The object is frozen in place. This method doesn’t return a new frozen object. Instead, it returns the original object before it’s frozen. The frozen object’s prototype also can’t be changed. For example, if we run the following to freeze an object:

const obj = {  
  a: 1  
};
Object.freeze(obj);
obj.a = 2;
console.log(obj.a);

We get that obj.a is still 1. This is because the object’s properties’ values can’t be changed. If we have strict mode enabled, a TypeError will be raised. It’s important to note that values that are objects can still be modified. For example, if we have the following code:

const obj = {  
  a: 1,  
  b: {  
    c: 2  
  }  
};
Object.freeze(obj);
obj.b.c = 3;
console.log(obj.b.c);

Then obj.b.c is 3 since Object.freeze doesn’t freeze properties that are inside of nested objects unless they’re frozen explicitly. So if we have:

const obj = {  
  a: 1,  
  b: {  
    c: 2  
  }  
};
Object.freeze(obj);  
Object.freeze(obj.b);
obj.b.c = 3;
console.log(obj.b.c);

Then obj.b.c is 2 since we frozen obj.b so that we can’t modify the value of obj.b .

The Object constructor has many more methods for constructing objects from an array of arrays with key and value of properties, and also methods to get property descriptors from objects, property names, property symbols, gets the keys of objects and prevent properties from being added or deleted or modify their property descriptors.

Categories
JavaScript

Using JavaScript Objects Built into Browsers

JavaScript is commonly used in web browsers for rendering content. To help with this, almost all browsers have built in support for JavaScript, which includes a standard library of objects that can be used by web apps to do common functionality. The browser itself has a user interface where users type in a URL and then the website loads up and rendered for the user to see. Most websites have HTML, CSS, and JavaScript code. Loading the page can take some time on poorly coded websites. HTML defines the sections of a website, the CSS controls the static styling and layout, and the JavaScript contains the code for controlling the dynamic functionality.

When a user types in a URL, the HTML portion of the website first loads along with the CSS. The the JavaScript code loads line by line in the browser. All the code that is loaded has to be parsed and interpreted by the browser to render them properly on screen. Browsers parse HTML code into a tree model called the Document Object Model (DOM). It’s a full map of a web page, and you can access different parts of the DOM by using JavaScript. Then CSS is parsed which will load the styles defined by the CSS code. Finally, the JavaScript code runs which manipulates the DOM elements that was loaded previously depending on the JavaScript code written. Styling can also be applied with JavaScript dynamically. Loading a website quickly is crucial if you want to keep your visitors since they’re impatient. We can load code that don’t need to be loaded immediately in the background to avoid code holding up a page from loading.

The Browser Object Model

Almost all web browsers support JavaScript and all of those browser will have a set of built in objects which can be accessed by developers to manipulate the page. This is called the Browser Object Model or BOM for short, and it’s series of APIs that can be used by developers to write their web apps.

The Navigator Object

The navigator object is a JavaScript object built into browsers that has the basic properties of the browser, the kind of computer the browser is installed on, and some geolocation data. Pretty much all modern browsers have this, and you can access the following data from it:

  • appCodeName , has the code name of the browser
  • appName, has the name of the browser
  • appVersion , has the browser version
  • cookieEnabled , tells us whether the browser has cookies enabled
  • geolocation , has physical location data if geolocation is enabled
  • language , gets the language of the browser
  • onLine , tells us whether the browser is online
  • platform , gets the platform that the browser is running on.
  • product , gets the engine name of the browser
  • userAgent, has the user agent string that’s sent in HTTP requests to web servers
  • battery , tells us the charging status for the battery of the device if it exists
  • connection , tells us the connection status of the browser
  • hardwareConcurrency , has the number of logical processors of the device,
  • keyboard , tells us the keyword layout of the device
  • serviceWorker , tells us data about service workers, which lets web apps have offline functionality and installation / un-installation functionality for progressive web apps
  • storage , tells us the storage capabilities of the device the browser is running on

All the properties of the navigator object listed above are read only.

If we enter navigator in our browser console, we get the full list of properties of the navigator object.

<img class="do t u gu ak" src="https://miro.medium.com/max/2732/1*qJVRv6xdNNEToXeyfdcEHQ.png" width="1366" height="728" role="presentation"/>

The navigator object’s properties

To detect whether a browser support certain functionality, we should check if the built in browser object has the properties and methods that you want to use.

The Window Object

The window object has the data about the browser window, which is where web pages in the browser is shown. Each tab of the browser has its own instance of the window object.

The following properties are available in the window object in most browsers:

  • closed , boolean value indicating whether a window has been closed or not
  • defaultStatus , gets or sets the default text in the status bar of a window
  • document , the document object of the window, which lets us manipulate the DOM.
  • frameElement , Gets the element, such as <iframe> or <object> that the window is embedded in
  • frames , list all frames in the current window
  • history , has the browser history of the current window
  • innerHeight , inner height of the current window, a read only property
  • innerWidth , inner width of the current window, a read only property
  • length , number of frames in the window, a read only property
  • location , gets the location object, which lets us traverse to different URLs and manipulate the browser history.
  • name , lets us get or set the name of the window
  • navigator , has the navigator object we discussed above
  • opener , gets the window object that created the current window
  • outerHeight , the outer height of the window, including scrollbars and toolbars
  • pageXOffset , retrieves the number of pixels that’s scrolled horizontally in the window
  • pageYOffset, retrieves the number of pixels that’s scrolled vertically in the window
  • parent , object for the parent of the current window
  • screen , Screen object of the window
  • screenLeft , the horizontal distance in pixels from the left side of the main screen to the left side of the current window
  • screenTop, the vertical distance in pixels from the top of the window relative to the top of the screen
  • screenX, the horizontal coordinate relative to the screen
  • screenY, the vertical coordinate relative to the screen
  • self, the current window
  • top , the topmost browser window

We can do lots of things with the window object. Some of the most common include getting screen size, the scroll position, and navigation between pages.

Note that if we define a global variable, it automatically attaches itself as a property of the window object, so if we have a = 1 defined globally, then window.a is 1.

With the window.location object, we can navigate to a URL by setting window.location.href . So if we want to go to the Medium website we write:

window.location.href = 'http://www.medium.com';

The window.innerWidth and window.innerHeight provides us with the width and height of the current browser window.

We can go back to the previous page by writing:

window.history.go(-1)

And get the size of the browser history with:

window.history.length;

The window object also has a list of methods that we can use to display alert boxes, prompt users for data, move and resize the window, and many other things. A list of methods in the window object is below:

  • alert() , displays an alert box with a message and an OK button
  • atob() , decodes a base-64 encoded string
  • blur() , make the current window lose focus
  • clearInterval() , cancel the timer returned by setInterval()
  • clearTimeout() , cancel the timer returned by setTimeout()
  • close() , close the window instance created
  • confirm() , displays a dialog box with an optional message with OK and cancel buttons
  • createPopup() , creates a pop-up window
  • focus() , sets the current window into focus
  • moveBy() , moves the current window by a specified amount
  • moveTo() , move a window to a specified position
  • open() , opens a new window
  • print() , prints the contents of the current window
  • prompt() , displays a dialogue box prompting the user for input
  • resizeBy() , resizes the window by a specified number of pixels
  • resizeTo() , resizes a window to a specified height and width i pixels.
  • scrollBy() , scrolls the document by a specified amount of pixels
  • scrollTo() , scrolls the document to a specific set of pixel coordinates
  • setInterval() , repeatedly call a function at an interval specified in milliseconds
  • setTimeout() , calls function a specified amount of time in milliseconds
  • stop() , stops the current window from loading

Because of security reasons, the focus, open, move and resize methods can only be called on a window that is opened by your own code, because being able to open window that’s not generated by someone’s own code will be exploited by malicious programmers to do undesired things to other user’s computers.

For example, to use the moveBy method must be used as follows:

const newWindow = window.open('', 'My Window', 'width=250, height=250'); newWindow.document.write("<p>This is my window.</p>");  
newWindow.moveBy(250, 250);                                  
newWindow.focus();

These are the most common built in browser objects and properties and methods associated with it. They can be used for some manipulation, and functions like setInterval and setTimeout for executing things that depend on time.

Categories
JavaScript

Useful New Features in ES2016 and 2017

ES2015 introduced lots of features, like arrow functions, class syntax for object creation and inheritance, let and const, and many other new features. In the next few years, more great features will be introduced into the language and the standard library. ES2016 was a minor revision with new features, such as introducing the includes function to arrays and the exponential operator. ES2017 introduced more features like Object.values and Object.entries and string functions like padStart and padEnd, and async and await. These are handy features that bring even more convenience to JavaScript developers, making JavaScript app development even easier.


ES2016 Features

Array.includes

Array.includes checks if an item exists in an array. It takes a number or string, which the function can compare as the first argument and the index to search from as the second argument. The second argument can be positive or negative.

const array = [1,2,3];  
const includesTwo = array.includes(2); // returns true

We can also search the array starting with a negative index, then the function search beginning from the computed index, which is the length of the array plus the second argument. So if we write:

array.includes(2, -2);

Then the includes function will search starting with index 3 +(-2). This means that array.includes(2, -2); should return true. If the absolute number of what you pass in is bigger than the length, then the whole array is searched.

The includes function was also added to TypedArray types like Int8Array , or Uint8Array .

Exponential operator

The exponential operator is another new feature of ES2016. The exponential operator is denoted by **. It’s the new syntax for computing exponential values, which is the alternative to the Math.pow function. For example, to compute 2 to the power of 3, we can write,

2**3

which is 8. Note that in JavaScript, it’s impossible to write ambiguous expressions when we combine the exponential operator with unary operators like -, so something like -2**2 is invalid in JavaScript. However, -(2**2) is valid since it’s unambiguous in that we know that the value is -4 since the exponential is in parentheses and the negative sign is outside.

We can force the base of the exponentiation expression to be a negative number by writing:

(-2)**2

So this means the base has to be in parentheses.


ES2017 Features

Object.values

The Object.values lets us get an array with the values of an object in an array. The values are returned in the same order as it is provided by the for...in loop, except that Object.values do not return values that are in the prototype chain.

For example, we can write:

const obj = { a: 1, b: 2 };  
console.log(Object.values(obj)); // [1, 2]  
  
const obj = { 0: 'd', 1: 'e', 2: 'f' };  
console.log(Object.values(obj)); // ['d', 'e', 'f']  
  
const obj2 = { 100: 'd', 2: 'e', 7: 'f' };  
console.log(Object.values(obj2)); // ['e', 'f', 'd']  
  
console.log(Object.values('abc')); // ['a', 'b', 'c']

As we can see, Object.values works with objects and strings. If the keys are numbers, then they are returned in with the keys in increasing order, like in the obj2 example above. For strings, it returns an array of the individual characters of the string.

Object.entries()

The Object.entries function returns an array with each entry being the key-value pairs in their individual arrays. The entries are returned in the same order as it is provided by the for...in loop, except that Object.values do not return values that are in the prototype chain. As with any object, we can sort the array to get the entries in the order we want with the sort function.

For example, we can write:

const obj = { foo: 1, bar: 2 };  
console.log(Object.entries(obj)); // [ ['foo', 1], ['bar', 2] ]  
  
const obj = { 0: 'x', 1: 'y', 2: 'z' };  
console.log(Object.entries(obj)); // [ ['0', 'x'], ['1', 'y'], ['2', 'z'] ]  
  
const obj2 = { 100: 'x', 2: 'y', 7: 'z' };  
console.log(Object.entries(obj2)); // [ ['2', 'x'], ['7', 'y'], ['100', 'z'] ]  
  
console.log(Object.entries('abc')); // [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ]  
  
console.log(Object.entries(100)); // [ ]  
  
const obj = { a: 1, b: 2, c: 3};  
for (const [key, value] of Object.entries(obj)) {  
  console.log(`${key} ${value}`); // "a 1", "b 2", "c 3"  
}  
  
Object.entries(obj).forEach(([key, value]) => {  
  console.log(`${key} ${value}`); // "a 1", "b 2", "c 3"  
});

In the above examples, we can see that Object.entries return an array of key-value pairs with each entry as an array with the key being the first element and the value being the second element. If the keys are integers, then they are sorted in ascending numerical order. So Object.entries(obj2) returns [ ['2', 'x'], ['7', 'y'], ['100', 'z'] ]. It also works with strings with the individual characters return with the index of the characters as the key, so the key is the index of the string, which is the first element of each entry, and the individual character is the value of the string, which is returned as the second character. For objects that have no properties, like numbers and booleans, Object.entries returns an empty array.

The array returned by Object.entries can be converted to a Map object. It can be used like in the following example:

const obj = { a: 1, b: 2 };   
const map = new Map(Object.entries(obj));  
console.log(map); // Map { a: 1, b: 2 }

String.padStart()

The padStart() function adds a string the number of times before a string until it reaches the length that you specify. The function takes two parameters. The first is the target length of your string. If the target length is less than the length of your string, then the string is returned as-is. The second parameter is the string that you want to add the padding with. It’s an optional parameter and it defaults to ' ' is nothing is specified.

For example, we can write the following:

'def'.padStart(10);         // "       def"  
'def'.padStart(10, "123");  // "1231231def"  
'def'.padStart(6,"123465"); // "abcdef"  
'def'.padStart(8, "0");     // "00000def"  
'def'.padStart(1);          // "def"

Note that each string is filled up to the target length with the string in the second argument. The whole string in the second argument may not always be included. Only the part that lets the function fill the string up to the target length is included.

String.padEnd()

The padEnd() function adds a string the number of times after a string until it reaches the length that you specify. The function takes 2 parameters. The first is the target length of your string. If the target length is less than the length of your string, then the string is returned as is. The second parameter is the string that you want to add the padding with. It’s an optional parameter, and it defaults to ' ' is nothing is specified.

For example, we can write the following:

'def'.padEnd(10);         // "def       "  
'def'.padEnd(10, "123");  // "def1231231"  
'def'.padEnd(6,"123465"); // "defabc"  
'def'.padEnd(8, "0");     // "def00000"  
'def'.padEnd(1);          // "def"

Object.getOwnPropertyDescriptors()

The Object.getOwnPropertyDescriptors() function returns all the property descriptors of an object.

For example, we can use it like the following code:

const obj = {  
  a: 1  
};const descriptors = Object.getOwnPropertyDescriptors(obj);

The descriptors object should have:

{  
  a: {  
    configurable: true,  
    enumerable: true,  
    value: 1,  
    writable: true  
  }  
}

Async and Await

With async and await, we can shorten promise code. Before async and await, we have to use the then function, we make to put callback functions as an argument of all of our then functions. This makes the code long is we have lots of promises. Instead, we can use the async and await syntax to replace the then and its associated callbacks as follows. For example, we have the code to make requests to the back end with the Fetch API:

const APIURL = "http://localhost:3000";
const subscribe = async data => {  
  const response = await fetch(`${APIURL}/subscribers`, {  
    method: "POST",  
    mode: "cors",  
    cache: "no-cache",  
    headers: {  
      "Content-Type": "application/json"  
    },  
    body: JSON.stringify(data)  
  });  
  return response.json();  
};

window.onload = () => {  
  nameForm.method = "post";  
  nameForm.target = "_blank";  
  nameForm.action = "";  
  nameForm.addEventListener("submit", async e => {  
    e.preventDefault();  
    const firstName = document.getElementById("firstName").value;  
    const lastName = document.getElementById("lastName").value;  
    const email = document.getElementById("email").value;  
    let errors = [];  
    if (!firstName) {  
      errors.push("First name is required.");  
    } 

    if (!lastName) {  
      errors.push("Last name is required.");  
    } 

    if (!email) {  
      errors.push("Email is required.");  
    } 

    if (!/\[^@\]+@\[^\.\]+\..+/.test(email)) {  
      errors.push("Email is invalid.");  
    } 

    if (errors.length > 0) {  
      alert(errors.join(" "));  
      return;  
    }  
    try {  
      const response = await subscribe({  
        firstName,  
        lastName,  
        email  
      });  
      alert(`${response.firstName} ${response.lastName} has subscribed`);  
    } 
    catch (error) {  
      alert(error.toString());  
    }  
  });  
};

We replaced the then and callbacks with await. Then we can assign the resolved values of each promise as variables. Note that if we use await for our promise code then we have to put async in the function signature as we did in the above example. To catch errors, instead of chaining the catch function in the end, we use the catch clause to do it instead. Also, instead of chaining the finally function at the bottom to run code when a promise ends, we use the finally clause after the catch clause to do that instead.

In the code above, we got the resolved value of the promise assigned to a variable instead of getting the value in the callback of the then function, like in the const response = await subscribe({...}) line above.

async functions always return promises and cannot return anything else like any other function that uses promises. In the example above, we used the promised based Fetch API and async and await syntax, we showed that we can chain promises in a much shorter way than with the then function with callbacks passed in as an argument.

ES2016 and 2017 gave us convenient features that enhance the already great syntactic sugar and functions that were added to ES2015, which was a significant improvement over its predecessor. These two versions of JavaScript make JavaScript development even better with new Object, string, and Array methods and shorthand for chaining promises.

Categories
JavaScript Vue

How To Use Vuetify in Vue.js

There is great support for Material Design in Vue.js. One of the libraries available for Vue.js is Vuetify. It is easy to incorporate into your Vue.js app and the result is appealing to the users’ eyes.

In this piece, we will build an app that displays data from the New York Times API. You can register for an API key at https://developer.nytimes.com/. After that, we can start building the app.

To start building the app, we have to install the Vue CLI. We do this by running:

npm install -g @vue/cli

Node.js 8.9 or later is required for Vue CLI to run. I did not have success getting the Vue CLI to run with the Windows version of Node.js. Ubuntu had no problem running Vue CLI for me.

Then, we run:

vue create vuetify-nyt-app

To create the project folder and create the files. In the wizard, instead of using the default options, we choose ‘Manually select features’. We select Babel, Router, and Vuex from the list of options by pressing space on each. If they are green, it means they’re selected.

Now we need to install a few libraries. We need to install an HTTP client, a library for formatting dates, one for generating GET query strings from objects, and another one for form validation.

Also, we need to install the Vue Material library itself. We do this by running:

npm i axios moment querystring vee-validate

axios is our HTTP client, moment is for manipulating dates, querystring is for generating query strings from objects, and vee-validate is an add-on package for Vue.js to do validation.

Then, we have to add the boilerplate for vuetify. We do this by running vue add vuetify. This adds the library and the references to it in our app, in their appropriate locations in our code.

Now that we have all the libraries installed, we can start building our app.

First, we create some components. In the views folder, we create Home.vue and Search.vue. Those are the code files for our pages. Then, create a mixins folder and create a file, called nytMixin.js.

Mixins are code snippets that can be incorporated directly into Vue.js components and used as if they are directly in the component. Then, we add some filters.

Filters are Vue.js code that map from one thing to another. We create a filters folder and add capitalize.js and formatDate.js.

In the components folder, we create a file, called SearchResults.vue. The components folder contains Vue.js components that aren’t pages.

To make passing data between components easier and more organized, we use Vuex for state management. As we selected Vuex when we ran vue create, we should have a store.js in our project folder. If not, create it.

In store.js, we put:

import Vue from 'vue'  
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({  
  state: {  
    searchResults: []  
  },  
  mutations: {  
    setSearchResults(state, payload) {  
      state.searchResults = payload;  
    }  
  },  
  actions: {}  
})

The state object is where the state is stored. The mutations object is where we can manipulate our state.

When we call this.$store.commit(“setSearchResults”, searchResults) in our code, given that searchResults is defined, state.searchResults will be set to searchResults.

We can then get the result by using this.$store.state.searchResults.

We need to add some boilerplate code to our app. First, we add our filter. In capitalize.js, we put:

export const capitalize = (str) => {  
    if (typeof str == 'string') {  
        if (str == 'realestate') {  
            return 'Real Estate';  
        }  
        if (str == 'sundayreview') {  
            return 'Sunday Review';  
        }
        if (str == 'tmagazine') {  
            return 'T Magazine';  
        }  
        return `${str[0].toUpperCase()}${str.slice(1)}`;  
    }  
}

This allows us to map capitalize our New York Times section names, listed in the New York Times developer pages. Then, in formatDate.js, we put:

import * as moment from 'moment';
export const formatDate = (date) => {  
    if (date) {  
        return moment(date).format('YYYY-MM-DD hh:mm A');  
    }  
}

To format our dates into a human-readable format.

In main.js, we put:

import Vue from 'vue'  
import App from './App.vue'  
import router from './router'  
import store from './store'  
import { formatDate } from './filters/formatDate';  
import { capitalize } from './filters/capitalize';  
import VeeValidate from 'vee-validate';  
import Vuetify from 'vuetify/lib'  
import vuetify from './plugins/vuetify';  
import '@mdi/font/css/materialdesignicons.css'
Vue.config.productionTip = false;
Vue.use(VeeValidate);  
Vue.use(Vuetify);  
Vue.filter('formatDate', formatDate);  
Vue.filter('capitalize', capitalize);
new Vue({  
  router,  
  store,  
  vuetify,  
  render: h => h(App)  
}).$mount('#app')

Notice that, in the file above, we have to register the libraries we use with Vue.js by calling Vue.use on them, so they can be used in our app templates.

We call Vue.filter on our filter functions so we can use them in our templates by adding a pipe and the filter name to the right of our variable.

Then, in router.js, we put:

import Vue from 'vue'  
import Router from 'vue-router'  
import Home from './views/Home.vue';  
import Search from './views/Search.vue';
Vue.use(Router)
export default new Router({  
  mode: 'history',  
  base: process.env.BASE_URL,  
  routes: [  
    {  
      path: '/',  
      name: 'home',  
      component: Home  
    },  
    {  
      path: '/search',  
      name: 'search',  
      component: Search  
    }  
  ]  
})

So that we can go to the pages when we enter the URLs listed.

mode: ‘history’ means that we won’t have a hash sign between the base URL and our routes.

If we deploy our app, we need to configure our web server so that all requests will be redirected to index.html so we won’t have errors when we reload the app.

For example, in Apache, we do:

<IfModule mod_rewrite.c>  
  RewriteEngine On  
  RewriteBase /  
  RewriteRule ^index\.html$ - [L]  
  RewriteCond %{REQUEST_FILENAME} !-f  
  RewriteCond %{REQUEST_FILENAME} !-d  
  RewriteRule . /index.html [L]  
</IfModule>

And, in NGINX, we put:

location / {  
  try_files $uri $uri/ /index.html;  
}

See your web server’s documentation for info on how to do the same thing in your web server.

Now, we write the code for our components. In SearchResult.vue, we put:

<template>  
  <v-container>  
    <v-card v-for="s in searchResults" :key="s.id" class="mx-auto">  
      <v-card-title>{{s.headline.main}}</v-card-title><v-list-item>  
        <v-list-item-content>Date: {{s.pub_date | formatDate}}</v-list-item-content>  
      </v-list-item>  
      <v-list-item>  
        <v-list-item-content>  
          <a :href="s.web_url">Link</a>  
        </v-list-item-content>  
      </v-list-item>  
      <v-list-item v-if="s.byline.original">  
        <v-list-item-content>{{s.byline.original}}</v-list-item-content>  
      </v-list-item>  
      <v-list-item>  
        <v-list-item-content>{{s.lead_paragraph}}</v-list-item-content>  
      </v-list-item>  
      <v-list-item>  
        <v-list-item-content>{{s.snippet}}</v-list-item-content>  
      </v-list-item>  
    </v-card>  
  </v-container>  
</template>

<script>  
export default {  
  computed: {  
    searchResults() {  
      return this.$store.state.searchResults;  
    }  
  }  
};  
</script>

<style scoped>  
.title {  
  margin: 0 15px !important;  
}

#search-results {  
  margin: 0 auto;  
  width: 95vw;  
}  
</style>

This is where get our search results from the Vuex store and display them.

We return this.$store.state.searchResults in a function in the computed property in our app so the search results will be automatically refreshed when the store’s searchResults state is updated.

md-card is a card widget for displaying data in a box. v-for is for looping the array entries and displaying everything. md-list is a list widget for displaying items in a list, neatly on the page. {{s.pub_date | formatDate}} is where our formatDate filter is applied.

Next, we write our mixin. We will add code for our HTTP calls in our mixin.

In nytMixin.js, we put:

const axios = require('axios');  
const querystring = require('querystring');  
const apiUrl = 'https://api.nytimes.com/svc';  
const apikey = 'your api key';
export const nytMixin = {  
    methods: {  
        getArticles(section) {  
            return axios.get(`${apiUrl}/topstories/v2/${section}.json?api-key=${apikey}`);  
        },
        searchArticles(data) {  
            let params = Object.assign({}, data);  
            params['api-key'] = apikey;  
            Object.keys(params).forEach(key => {  
                if (!params\[key]) {  
                    delete params[key];  
                }  
            })  
            const queryString = querystring.stringify(params);  
            return axios.get(`${apiUrl}/search/v2/articlesearch.json?${queryString}`);  
        }  
    }  
}

We return the promises for HTTP requests to get articles in each function. In the searchArticles function, we message the object that we pass in into a query string that we pass into our request.

Make sure you put your API key into your app into the apiKey constant and delete anything that is undefined, with:

Object.keys(params).forEach(key => {  
  if (!params[key]) {  
     delete params[key];  
  }  
})

In Home.vue, we put:

<template>  
  <div>  
    <div class="text-center" id="header">  
      <h1>{{selectedSection | capitalize}}</h1>  
      <v-spacer></v-spacer>  
      <v-menu offset-y>  
        <template v-slot:activator="{ on }">  
          <v-btn color="primary" dark v-on="on">Sections</v-btn>  
        </template>  
        <v-list>  
          <v-list-item v-for="(s, index) in sections" :key="index"@click="selectSection(s)">  
            <v-list-item-title>{{ s | capitalize}}</v-list-item-title>  
          </v-list-item>  
        </v-list>  
      </v-menu>  
      <v-spacer></v-spacer>  
      <v-spacer></v-spacer>  
    </div>  
    <v-spacer></v-spacer>  
    <v-card v-for="a in articles" :key="a.id" class="mx-auto">  
      <v-card-title>{{a.title}}</v-card-title>  
      <v-card-text>  
        <v-list-item>  
          <v-list-item-content>Date: {{a.published_date | formatDate}}</v-list-item-content>  
        </v-list-item>  
        <v-list-item>  
          <v-list-item-content>  
            <a :href="a.url">Link</a>  
          </v-list-item-content>  
        </v-list-item>  
        <v-list-item v-if="a.byline">  
          <v-list-item-content>{{a.byline}}</v-list-item-content>  
        </v-list-item>  
        <v-list-item>  
          <v-list-item-content>{{a.abstract}}</v-list-item-content>  
        </v-list-item>  
        <v-list-item>  
          <v-list-item-content>  
            <img  
              v-if="a.multimedia[a.multimedia.length - 1]"  
              :src="a.multimedia[a.multimedia.length - 1].url"  
              :alt="a.multimedia[a.multimedia.length - 1].caption"  
              class="image"  
            />  
          </v-list-item-content>  
        </v-list-item>  
      </v-card-text>  
    </v-card>  
  </div>  
</template>

<script>  
import { nytMixin } from "../mixins/nytMixin";

export default {  
  name: "home",  
  mixins: [nytMixin],  
  computed: {},
  data() {  
    return {  
      selectedSection: "home",  
      articles: [],  
      sections: `arts, automobiles, books, business, fashion, food, health,  
    home, insider, magazine, movies, national, nyregion, obituaries,  
    opinion, politics, realestate, science, sports, sundayreview,  
    technology, theater, tmagazine, travel, upshot, world\`  
        .replace(/ /g, "")  
        .split(",")  
    };  
  },
  beforeMount() {  
    this.getNewsArticles(this.selectedSection);  
  },
  methods: {  
    async getNewsArticles(section) {  
      const response = await this.getArticles(section);  
      this.articles = response.data.results;  
    },selectSection(section) {  
      this.selectedSection = section;  
      this.getNewsArticles(section);  
    }  
  }  
};  
</script>

<style scoped>  
.image {  
  width: 100%;  
}

.title {  
  color: rgba(0, 0, 0, 0.87) !important;  
  margin: 0 15px !important;  
}

.md-card {  
  width: 95vw;  
  margin: 0 auto;  
}

#header {  
  margin-bottom: 10px;  
}  
</style>

This page component is where we get the articles for the selected section, defaulting to the home section. We also have a menu to select the section we want to see, by adding:

<v-menu offset-y>  
   <template v-slot:activator="{ on }">  
     <v-btn color="primary" dark v-on="on">Sections</v-btn>  
   </template>  
   <v-list>  
      <v-list-item v-for="(s, index) in sections" :key="index" @click="selectSection(s)">  
        <v-list-item-title>{{ s | capitalize}}</v-list-item-title>  
      </v-list-item>  
   </v-list>  
</v-menu>

Notice that we use the async and await keywords in our promises code, instead of using then.

It is much shorter and the functionality between then, await, and async is equivalent. However, it is not supported in Internet Explorer. In the beforeMount block, we run the this.getNewsArticles to get the articles as the page loads.

Note that the Vuetify library uses the slots of the features of Vue.js extensively. The elements with nesting, like the v-slot prop, are in:

<v-menu offset-y>  
  <template v-slot:activator="{ on }">  
     <v-btn color="primary" dark v-on="on">Sections</v-btn>  
  </template>  
  <v-list>  
    <v-list-item v-for="(s, index) in sections" :key="index" @click="selectSection(s)">  
       <v-list-item-title>{{ s | capitalize}}</v-list-item-title>  
     </v-list-item>  
  </v-list>  
</v-menu>

See the Vue.js guide for details.

In Search.vue, we put:

<template>  
  <div>  
    <form>  
      <v-text-field  
        v-model="searchData.keyword"  
        v-validate="'required'"  
        :error-messages="errors.collect('keyword')"  
        label="Keyword"  
        data-vv-name="keyword"  
        required  
      ></v-text-field> <v-menu  
        ref="menu"  
        v-model="toggleBeginDate"  
        :close-on-content-click="false"  
        transition="scale-transition"  
        offset-y  
        full-width  
        min-width="290px"  
      >  
        <template v-slot:activator="{ on }">  
          <v-text-field  
            v-model="searchData.beginDate"  
            label="Begin Date"  
            prepend-icon="event"  
            readonly  
            v-on="on"  
          ></v-text-field>  
        </template>  
        <v-date-picker  
          v-model="searchData.beginDate"  
          no-title  
          scrollable  
          :max="new Date().toISOString()"  
        >  
          <v-spacer></v-spacer>  
          <v-btn text color="primary" @click="toggleBeginDate = false">Cancel</v-btn>  
          <v-btn  
            text  
            color="primary"  
           @click="$refs.menu.save(searchData.beginDate); toggleBeginDate = false"  
          >OK</v-btn>  
        </v-date-picker>  
      </v-menu> <v-menu  
        ref="menu"  
        v-model="toggleEndDate"  
        :close-on-content-click="false"  
        transition="scale-transition"  
        offset-y  
        full-width  
        min-width="290px"  
      >  
        <template v-slot:activator="{ on }">  
          <v-text-field  
            v-model="searchData.endDate"  
            label="End Date"  
            prepend-icon="event"  
            readonly  
            v-on="on"  
          ></v-text-field>  
        </template>  
        <v-date-picker  
          v-model="searchData.endDate"  
          no-title  
          scrollable  
          :max="new Date().toISOString()"  
        >  
          <v-spacer></v-spacer>  
          <v-btn text color="primary" @click="toggleEndDate = false">Cancel</v-btn>  
          <v-btn  
            text  
            color="primary"  
            @click="$refs.menu.save(searchData.endDate); toggleEndDate = false"  
          >OK</v-btn>  
        </v-date-picker>  
      </v-menu> 
      <v-select  
        v-model="searchData.sort"  
        :items="sortChoices"  
        label="Sort By"  
        data-vv-name="sort"  
        item-value="value"  
        item-text="name"  
      >  
        <template slot="selection" slot-scope="{ item }">{{ item.name }}</template>  
        <template slot="item" slot-scope="{ item }">{{ item.name }}</template>  
      </v-select>
      <v-btn class="mr-4" type="submit" @click="search">Search</v-btn>  
    </form>  
    <SearchResults />  
  </div>  
</template>

<script>  
import { nytMixin } from "../mixins/nytMixin";  
import SearchResults from "@/components/SearchResults.vue";  
import * as moment from "moment";  
import { capitalize } from "@/filters/capitalize";export default {  
  name: "search",  
  mixins: [nytMixin],  
  components: {  
    SearchResults  
  },  
  computed: {  
    isFormDirty() {  
      return Object.keys(this.fields).some(key => this.fields[key].dirty);  
    }  
  },  
  data: () => {  
    return {  
      searchData: {  
        sort: "newest"  
      },  
      disabledDates: date => {  
        return +date >= +new Date();  
      },  
      sortChoices: [  
        {  
          value: "newest",  
          name: "Newest"  
        },  
        {  
          value: "oldest",  
          name: "Oldest"  
        },  
        {  
          value: "relevance",  
          name: "Relevance"  
        }  
      ],  
      toggleBeginDate: false,  
      toggleEndDate: false  
    };  
  },  
  methods: {  
    async search(evt) {  
      evt.preventDefault();  
      if (!this.isFormDirty || this.errors.items.length > 0) {  
        return;  
      }  
      const data = {  
        q: this.searchData.keyword,  
        begin_date: moment(this.searchData.beginDate).format("YYYYMMDD"),  
        end_date: moment(this.searchData.endDate).format("YYYYMMDD"),  
        sort: this.searchData.sort  
      };  
      const response = await this.searchArticles(data);  
      this.$store.commit("setSearchResults", response.data.response.docs);  
    }  
  }  
};  
</script>

This is where we have a form to search for articles. We also have two date-pickers to label users to set the start and end dates. We only restrict the dates to today and earlier so that the search query makes sense.

In this block:

<v-text-field  
  v-model="searchData.keyword"  
  v-validate="'required'"  
  :error-messages="errors.collect('keyword')"  
  label="Keyword"  
  data-vv-name="keyword"  
  required  
></v-text-field>

We use vee-validate to check if the required search keyword field is filled in. If it’s not, it’ll display an error message and prevent the query from proceeding.

We also nested our SearchResults component into the Search page component, by including:

components: {  
  SearchResults  
}

Between the script tag and <SearchResults /> in the template.

Finally, we add our top bar and menu by putting the following in App.vue:

<template>  
  <v-app>  
    <v-navigation-drawer v-model="drawer" app>  
      <v-list nav dense>  
        <v-list-item-group v-model="group" active-class="deep-purple--text text--accent-4">  
          <v-list-item>  
            <v-list-item-title>New Yourk Times Vuetify App</v-list-item-title>  
          </v-list-item><v-list-item>  
            <v-list-item-title>  
              <router-link to="/">Home</router-link>  
            </v-list-item-title>  
          </v-list-item><v-list-item>  
            <v-list-item-title>  
              <router-link to="/search">Search</router-link>  
            </v-list-item-title>  
          </v-list-item>  
        </v-list-item-group>  
      </v-list>  
    </v-navigation-drawer><v-app-bar app>  
      <v-toolbar-title class="headline text-uppercase">  
        <v-app-bar-nav-icon @click.stop="drawer = !drawer"></v-app-bar-nav-icon>  
        <span>New York Times Vuetify App</span>  
      </v-toolbar-title>  
      <v-spacer></v-spacer>  
    </v-app-bar><v-content>  
      <v-container fluid>  
        <router-view />  
      </v-container>  
    </v-content>  
  </v-app>  
</template>

<script>  
export default {  
  name: "app",  
  data: () => {  
    return {  
      showNavigation: false,  
      drawer: false,  
      group: null  
    };  
  }  
};  
</script>

<style>  
.center {  
  text-align: center;  
}

form {  
  width: 95vw;  
  margin: 0 auto;  
}

.md-toolbar.md-theme-default {  
  background: #009688 !important;  
  height: 60px;  
}

.md-title,  
.md-toolbar.md-theme-default .md-icon {  
  color: #fff !important;  
}  
</style>

If you want a top bar with a left navigation drawer, you have to follow the code structure above precisely.

Categories
JavaScript Vue

Form Validation with Vee-Validate

Vue.js is a great framework for building front end web apps. It uses a component-based architecture which makes organizing code easy. It allows you to use the latest features JavaScript has to offer which means writing code to build your apps is easy than ever. It has a lot of add-ons like routing and flux store that you can add when you scaffold your app.

However, one thing that is missing is form validation. This means that we have to find our own form validation library to do form validation or write the form validation code ourselves.

If we choose to use a library to do form validation, Vee-Validate is a great choice plugging directly into Vue.js form code to do form validation. Vee-Validate primary adds code to Vue.js component templates to enable form validation for Vue.js forms. It has form validation rules for many kinds of inputs. Therefore, it is a great choice for validating Vue.js forms.

In this story, we will build an address book app with Vue.js that uses Vee-Validate to validate our inputs. The form allows us to add and edit our contacts; also, we can get and delete contacts.

To build our app, first, we need to quickly set up a back end. To do this, we use a Node.js package called JSON Server to run our back end. Find the package’s documentation here.

Once this is running, it provides us with routes for us to save our contact entries from front end.

To install the package, run:

npm install -g json-server

We will run this later so we can save our contacts.

Now we can start building our app. To do this, install the Vue CLI by running:

npm install -g @vue/cli

Then create the app by running:

vue create vee-validate-address-book-app

vee-validate-address-book-app is our app name. When running the wizard, be sure you choose to include Vuex and Vue Router as we will use it later. Next, we have to install some libraries. We need an HTTP client, a Material Design library for making our app look good, and the Vee-Validate library.

To do this, run npm i axios vee-validate vue-material . Axios is our HTTP client for communicating to back end. Vue Material is our Material Design library.

Next, we create our components that we nest in our page components. To do this, create a components folder in our project folder and create a file within it called ContactForm.vue .

In this file, we put:

<template>
  <div class="contact-form">
    <div class="center">
      <h1>{{editing ? 'Edit': 'Add'}} Contact</h1>
    </div>
    <form novalidate class="md-layout" @submit="save">
      <md-field :class="{ 'md-invalid': errors.has('firstName') }">
        <label for="firstName">First Name</label>
        <md-input
          name="firstName"
          v-model="contact.firstName"
          v-validate="'required'"
          :disabled="sending"
        />
        <span class="md-error" v-if="errors.has('firstName')">First Name is required.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('lastName') }">
        <label for="lastName">Last Name</label>
        <md-input
          name="lastName"
          v-model="contact.lastName"
          :disabled="sending"
          v-validate="'required'"
        />
        <span class="md-error" v-if="errors.has('lastName')">Last Name is required.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('addressLineOne') }">
        <label for="addressLineOne">Address Line 1</label>
        <md-input
          name="addressLineOne"
          v-model="contact.addressLineOne"
          :disabled="sending"
          v-validate="'required'"
        />
        <span class="md-error" v-if="errors.has('addressLineOne')">Address line 1 is required.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('addressLineTwo') }">
        <label for="addressLineTwo">Address Line 2</label>
        <md-input name="addressLineTwo" v-model="contact.addressLineTwo" :disabled="sending" />
        <span class="md-error" v-if="errors.has('addressLineTwo')">Address line 2 is required</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('city') }">
        <label for="city">City</label>
        <md-input name="city" v-model="contact.city" :disabled="sending" v-validate="'required'" />
        <span class="md-error" v-if="errors.has('city')">City is required.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('country') }">
        <label for="country">Country</label>
        <md-select
          name="country"
          v-model="contact.country"
          md-dense
          :disabled="sending"
          v-validate.continues="'required'"
        >
          <md-option :value="c" :key="c" v-for="c in countries">{{c}}</md-option>
        </md-select>
        <span class="md-error" v-if="errors.firstByRule('country', 'required')">Country is required.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('postalCode') }">
        <label for="postalCode">Postal Code</label>
        <md-input
          name="postalCode"
          v-model="contact.postalCode"
          :disabled="sending"
          v-validate="{ required: true, regex: getPostalCodeRegex() }"
        />
        <span
          class="md-error"
          v-if="errors.firstByRule('postalCode', 'required')"
        >Postal Code is required.</span>
        <span
          class="md-error"
          v-if="errors.firstByRule('postalCode', 'regex')"
        >Postal Code is invalid.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('phone') }">
        <label for="phone">Phone</label>
        <md-input
          name="phone"
          v-model="contact.phone"
          :disabled="sending"
          v-validate="{ required: true, regex: getPhoneRegex() }"
        />
        <span class="md-error" v-if="errors.firstByRule('phone', 'required')">Phone is required.</span>
        <span class="md-error" v-if="errors.firstByRule('phone', 'regex')">Phone is invalid.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('gender') }">
        <label for="gender">Gender</label>
        <md-select
          name="gender"
          v-model="contact.gender"
          md-dense
          :disabled="sending"
          v-validate.continues="'required'"
        >
          <md-option value="male">Male</md-option>
          <md-option value="female">Female</md-option>
        </md-select>
        <span class="md-error" v-if="errors.firstByRule('gender', 'required')">Gender is required.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('age') }">
        <label for="age">Age</label>
        <md-input
          type="number"
          id="age"
          name="age"
          autocomplete="age"
          v-model="contact.age"
          :disabled="sending"
          v-validate="'required|between:0,200'"
        />
        <span class="md-error" v-if="errors.firstByRule('age', 'required')">Age is required.</span>
        <span class="md-error" v-if="errors.firstByRule('age', 'between')">Age must be 0 and 200.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('email') }">
        <label for="email">Email</label>
        <md-input
          type="email"
          name="email"
          autocomplete="email"
          v-model="contact.email"
          :disabled="sending"
          v-validate="'required|email'"
        />
        <span class="md-error" v-if="errors.firstByRule('email', 'required')">Email is required.</span>
        <span class="md-error" v-if="errors.firstByRule('email', 'email')">Email is invalid.</span>
      </md-field>
<md-progress-bar md-mode="indeterminate" v-if="sending" />
<md-button type="submit" class="md-raised">{{editing ? 'Edit':'Create'}} Contact</md-button>
    </form>
  </div>
</template>

<script>
import { COUNTRIES } from "@/helpers/exports";
import { contactMixin } from "@/mixins/contactMixin";
export default {
  name: "ContactForm",
  mixins: [contactMixin],
  props: {
    editing: Boolean,
    contactId: Number
  },
  computed: {
    isFormDirty() {
      return Object.keys(this.fields).some(key => this.fields[key].dirty);
    },
    contacts() {
      return this.$store.state.contacts;
    }
  },
  data() {
    return {
      sending: false,
      contact: {},
      countries: COUNTRIES.map(c => c.name)
    };
  },
  beforeMount() {
    this.contact = this.contacts.find(c => c.id == this.contactId) || {};
  },
  methods: {
    async save(evt) {
      evt.preventDefault();
      try {
        const result = await this.$validator.validateAll();
        if (!result) {
          return;
        }
        if (this.editing) {
          await this.updateContact(this.contact, this.contactId);
          await this.getAllContacts();
          this.$emit("contactSaved");
        } else {
          await this.addContact(this.contact);
          await this.getAllContacts();
          this.$router.push("/");
        }
      } catch (ex) {
        console.log(ex);
      }
    },
    async getAllContacts() {
      try {
        const response = await this.getContacts();
        this.$store.commit("setContacts", response.data);
      } catch (ex) {
        console.log(ex);
      }
    },
    getPostalCodeRegex() {
      if (this.contact.country == "United States") {
        return /^[0-9]{5}(?:-[0-9]{4})?$/;
      } else if (this.contact.country == "Canada") {
        return /^[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d$/;
      }
      return /./;
    },
    getPhoneRegex() {
      if (["United States", "Canada"].includes(this.contact.country)) {
        return /^[2-9]\d{2}[2-9]\d{2}\d{4}$/;
      }
      return /./;
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.contact-form {
  margin: 0 auto;
  width: 90%;
}
</style>

In the file above, we have the contact form for adding and updating contacts in our address book — where Vee-Validate is used the most. Notice that in most input controls within the form tag, we have the v-validate prop.

This is where we specify what kind of input the control accepts.

required means that the form field is required.

regex means we validate against a specified regular expression.

This allows for custom form validation where there are no built-in rules for Vee-Validate available, or when you need to validate the field differently, depending on the value of another field.

For example for a phone number, we have this function:

getPhoneRegex() {  
  if (["United States", "Canada"].includes(this.contact.country)){  
    return /^\[2-9\]\\d{2}\[2-9\]\\d{2}\\d{4}$/;  
  }  
  return /./;  
}

It allows us to validate the number to see if it matches the North American telephone format when we enter United States or Canada. Otherwise, we let people enter whatever they want.

Similarly, for postal code, we have:

getPostalCodeRegex() {  
  if (this.contact.country == "United States") {  
    return /^[0-9]{5}(?:-\[0-9\]{4})?$/;  
  } 
  else if (this.contact.country == "Canada") {  
    return /^[A-Za-z]\d\[A-Za-z]\[ -]?\d\[A-Za-z]\d$/;  
  }  
  return /./;  
}

This allows us to check for U.S. and Canadian postal codes.

To display errors, we can check if errors exist for a form field, then display them. For example, for a first name, we have:

<span class="md-error" v-if="errors.has('firstName')">First Name is required.</span>

errors.has(‘firstName’) checks if the first name field meets the specified validation criteria. Since we’re checking if it’s filled in, there is only one possible error, so we can just display the only error when errors.has(‘firstName’) returns true .

For something more complex like a phone number, we have:

<span class="md-error" v-if="errors.firstByRule('phone', 'required')">Phone is required.</span>
<span class="md-error" v-if="errors.firstByRule('phone', 'regex')">Phone is invalid.</span>

This allows us to check for each validation rule separately. For the phone number field, we have to check if it’s filled in and if what’s filled in has a valid format. The errors.firstByRule function allows us to do that.

errors.firstByRule(‘phone’, ‘required’) returns true if the field is not filled in and false otherwise.

errors.firstByRule(‘phone’, ‘regex’) returns true is the phone number format is filled in incorrectly and false otherwise.

Vee-Validate provides a this.field object to your component. So we can check if fields are dirty, meaning whether they have been manipulated or not, by adding:

Object.keys(this.fields).some(key => this.fields\[key\].dirty)

Each property is a form field and each property of the this.fields object has a dirty property, so we can check whether fields are manipulated or not.

In the save function of the methods object, we have:

async save(evt) {  
  evt.preventDefault();  
  try {  
    const result = await this.$validator.validateAll();  
    if (!result) {  
      return;  
    }  
    if (this.editing) {  
      await this.updateContact(this.contact, this.contactId);  
      await this.getAllContacts();  
      this.$emit("contactSaved");  
    } 
    else {  
      await this.addContact(this.contact);  
      await this.getAllContacts();  
      this.$router.push("/");  
    }  
  } 
  catch (ex) {  
    console.log(ex);  
  }  
},

We need evt.preventDefault() to stop the form from submitting the normal way, i.e. without calling the Ajax code below.

this.$validator.validateAll() validates the form.

this.$validator is an object provided by Vee-Validate.

It returns a promise, so we need the function to be async, and we need await before the function call.

If result is falsy, the form validation failed, so we run return to stop the rest of the function from executing. Finally, if form fields are all valid, we can submit. Since this form is used for both adding and editing contacts, we have to check which action we’re doing. If we edit, then we call await this.updateContact(this.contact, this.contactId); to update our contact. Otherwise, we add contact so we call await this.addContact(this.contact);

In either case, we call await this.getAllContacts(); to refresh our contacts and put them in the store. If we are adding, then we will redirect to the home page at the end by calling this.$router.push(“/”); . this.updateContact , this.addContact , and this.getAllContacts are all from our contactMixin which we will write shortly.

Next, we write some helper code.

Create a folder called helpers and make a file within it called export.js — put in the following:

export const COUNTRIES = [  
    { "name": "Afghanistan", "code": "AF" },  
    { "name": "Aland Islands", "code": "AX" },  
    { "name": "Albania", "code": "AL" },  
    { "name": "Algeria", "code": "DZ" },  
    { "name": "American Samoa", "code": "AS" },  
    { "name": "AndorrA", "code": "AD" },  
    { "name": "Angola", "code": "AO" },  
    { "name": "Anguilla", "code": "AI" },  
    { "name": "Antarctica", "code": "AQ" },  
    { "name": "Antigua and Barbuda", "code": "AG" },  
    { "name": "Argentina", "code": "AR" },  
    { "name": "Armenia", "code": "AM" },  
    { "name": "Aruba", "code": "AW" },  
    { "name": "Australia", "code": "AU" },  
    { "name": "Austria", "code": "AT" },  
    { "name": "Azerbaijan", "code": "AZ" },  
    { "name": "Bahamas", "code": "BS" },  
    { "name": "Bahrain", "code": "BH" },  
    { "name": "Bangladesh", "code": "BD" },  
    { "name": "Barbados", "code": "BB" },  
    { "name": "Belarus", "code": "BY" },  
    { "name": "Belgium", "code": "BE" },  
    { "name": "Belize", "code": "BZ" },  
    { "name": "Benin", "code": "BJ" },  
    { "name": "Bermuda", "code": "BM" },  
    { "name": "Bhutan", "code": "BT" },  
    { "name": "Bolivia", "code": "BO" },  
    { "name": "Bosnia and Herzegovina", "code": "BA" },  
    { "name": "Botswana", "code": "BW" },  
    { "name": "Bouvet Island", "code": "BV" },  
    { "name": "Brazil", "code": "BR" },  
    { "name": "British Indian Ocean Territory", "code": "IO" },  
    { "name": "Brunei Darussalam", "code": "BN" },  
    { "name": "Bulgaria", "code": "BG" },  
    { "name": "Burkina Faso", "code": "BF" },  
    { "name": "Burundi", "code": "BI" },  
    { "name": "Cambodia", "code": "KH" },  
    { "name": "Cameroon", "code": "CM" },  
    { "name": "Canada", "code": "CA" },  
    { "name": "Cape Verde", "code": "CV" },  
    { "name": "Cayman Islands", "code": "KY" },  
    { "name": "Central African Republic", "code": "CF" },  
    { "name": "Chad", "code": "TD" },  
    { "name": "Chile", "code": "CL" },  
    { "name": "China", "code": "CN" },  
    { "name": "Christmas Island", "code": "CX" },  
    { "name": "Cocos (Keeling) Islands", "code": "CC" },  
    { "name": "Colombia", "code": "CO" },  
    { "name": "Comoros", "code": "KM" },  
    { "name": "Congo", "code": "CG" },  
    { "name": "Congo, The Democratic Republic of the", "code": "CD" },  
    { "name": "Cook Islands", "code": "CK" },  
    { "name": "Costa Rica", "code": "CR" },  
    {  
        "name": "Cote D\\"Ivoire", "code": "CI"  
    },  
    { "name": "Croatia", "code": "HR" },  
    { "name": "Cuba", "code": "CU" },  
    { "name": "Cyprus", "code": "CY" },  
    { "name": "Czech Republic", "code": "CZ" },  
    { "name": "Denmark", "code": "DK" },  
    { "name": "Djibouti", "code": "DJ" },  
    { "name": "Dominica", "code": "DM" },  
    { "name": "Dominican Republic", "code": "DO" },  
    { "name": "Ecuador", "code": "EC" },  
    { "name": "Egypt", "code": "EG" },  
    { "name": "El Salvador", "code": "SV" },  
    { "name": "Equatorial Guinea", "code": "GQ" },  
    { "name": "Eritrea", "code": "ER" },  
    { "name": "Estonia", "code": "EE" },  
    { "name": "Ethiopia", "code": "ET" },  
    { "name": "Falkland Islands (Malvinas)", "code": "FK" },  
    { "name": "Faroe Islands", "code": "FO" },  
    { "name": "Fiji", "code": "FJ" },  
    { "name": "Finland", "code": "FI" },  
    { "name": "France", "code": "FR" },  
    { "name": "French Guiana", "code": "GF" },  
    { "name": "French Polynesia", "code": "PF" },  
    { "name": "French Southern Territories", "code": "TF" },  
    { "name": "Gabon", "code": "GA" },  
    { "name": "Gambia", "code": "GM" },  
    { "name": "Georgia", "code": "GE" },  
    { "name": "Germany", "code": "DE" },  
    { "name": "Ghana", "code": "GH" },  
    { "name": "Gibraltar", "code": "GI" },  
    { "name": "Greece", "code": "GR" },  
    { "name": "Greenland", "code": "GL" },  
    { "name": "Grenada", "code": "GD" },  
    { "name": "Guadeloupe", "code": "GP" },  
    { "name": "Guam", "code": "GU" },  
    { "name": "Guatemala", "code": "GT" },  
    { "name": "Guernsey", "code": "GG" },  
    { "name": "Guinea", "code": "GN" },  
    { "name": "Guinea-Bissau", "code": "GW" },  
    { "name": "Guyana", "code": "GY" },  
    { "name": "Haiti", "code": "HT" },  
    { "name": "Heard Island and Mcdonald Islands", "code": "HM" },  
    { "name": "Holy See (Vatican City State)", "code": "VA" },  
    { "name": "Honduras", "code": "HN" },  
    { "name": "Hong Kong", "code": "HK" },  
    { "name": "Hungary", "code": "HU" },  
    { "name": "Iceland", "code": "IS" },  
    { "name": "India", "code": "IN" },  
    { "name": "Indonesia", "code": "ID" },  
    { "name": "Iran, Islamic Republic Of", "code": "IR" },  
    { "name": "Iraq", "code": "IQ" },  
    { "name": "Ireland", "code": "IE" },  
    { "name": "Isle of Man", "code": "IM" },  
    { "name": "Israel", "code": "IL" },  
    { "name": "Italy", "code": "IT" },  
    { "name": "Jamaica", "code": "JM" },  
    { "name": "Japan", "code": "JP" },  
    { "name": "Jersey", "code": "JE" },  
    { "name": "Jordan", "code": "JO" },  
    { "name": "Kazakhstan", "code": "KZ" },  
    { "name": "Kenya", "code": "KE" },  
    { "name": "Kiribati", "code": "KI" },  
    {  
        "name": "Korea, Democratic People\"s Republic of", "code": "KP"  
    },  
    { "name": "Korea, Republic of", "code": "KR" },  
    { "name": "Kuwait", "code": "KW" },  
    { "name": "Kyrgyzstan", "code": "KG" },  
    {  
        "name": "Lao People\"s Democratic Republic", "code": "LA"  
    },  
    { "name": "Latvia", "code": "LV" },  
    { "name": "Lebanon", "code": "LB" },  
    { "name": "Lesotho", "code": "LS" },  
    { "name": "Liberia", "code": "LR" },  
    { "name": "Libyan Arab Jamahiriya", "code": "LY" },  
    { "name": "Liechtenstein", "code": "LI" },  
    { "name": "Lithuania", "code": "LT" },  
    { "name": "Luxembourg", "code": "LU" },  
    { "name": "Macao", "code": "MO" },  
    { "name": "Macedonia, The Former Yugoslav Republic of", "code": "MK" },  
    { "name": "Madagascar", "code": "MG" },  
    { "name": "Malawi", "code": "MW" },  
    { "name": "Malaysia", "code": "MY" },  
    { "name": "Maldives", "code": "MV" },  
    { "name": "Mali", "code": "ML" },  
    { "name": "Malta", "code": "MT" },  
    { "name": "Marshall Islands", "code": "MH" },  
    { "name": "Martinique", "code": "MQ" },  
    { "name": "Mauritania", "code": "MR" },  
    { "name": "Mauritius", "code": "MU" },  
    { "name": "Mayotte", "code": "YT" },  
    { "name": "Mexico", "code": "MX" },  
    { "name": "Micronesia, Federated States of", "code": "FM" },  
    { "name": "Moldova, Republic of", "code": "MD" },  
    { "name": "Monaco", "code": "MC" },  
    { "name": "Mongolia", "code": "MN" },  
    { "name": "Montenegro", "code": "ME" },  
    { "name": "Montserrat", "code": "MS" },  
    { "name": "Morocco", "code": "MA" },  
    { "name": "Mozambique", "code": "MZ" },  
    { "name": "Myanmar", "code": "MM" },  
    { "name": "Namibia", "code": "NA" },  
    { "name": "Nauru", "code": "NR" },  
    { "name": "Nepal", "code": "NP" },  
    { "name": "Netherlands", "code": "NL" },  
    { "name": "Netherlands Antilles", "code": "AN" },  
    { "name": "New Caledonia", "code": "NC" },  
    { "name": "New Zealand", "code": "NZ" },  
    { "name": "Nicaragua", "code": "NI" },  
    { "name": "Niger", "code": "NE" },  
    { "name": "Nigeria", "code": "NG" },  
    { "name": "Niue", "code": "NU" },  
    { "name": "Norfolk Island", "code": "NF" },  
    { "name": "Northern Mariana Islands", "code": "MP" },  
    { "name": "Norway", "code": "NO" },  
    { "name": "Oman", "code": "OM" },  
    { "name": "Pakistan", "code": "PK" },  
    { "name": "Palau", "code": "PW" },  
    { "name": "Palestinian Territory, Occupied", "code": "PS" },  
    { "name": "Panama", "code": "PA" },  
    { "name": "Papua New Guinea", "code": "PG" },  
    { "name": "Paraguay", "code": "PY" },  
    { "name": "Peru", "code": "PE" },  
    { "name": "Philippines", "code": "PH" },  
    { "name": "Pitcairn", "code": "PN" },  
    { "name": "Poland", "code": "PL" },  
    { "name": "Portugal", "code": "PT" },  
    { "name": "Puerto Rico", "code": "PR" },  
    { "name": "Qatar", "code": "QA" },  
    { "name": "Reunion", "code": "RE" },  
    { "name": "Romania", "code": "RO" },  
    { "name": "Russian Federation", "code": "RU" },  
    { "name": "RWANDA", "code": "RW" },  
    { "name": "Saint Helena", "code": "SH" },  
    { "name": "Saint Kitts and Nevis", "code": "KN" },  
    { "name": "Saint Lucia", "code": "LC" },  
    { "name": "Saint Pierre and Miquelon", "code": "PM" },  
    { "name": "Saint Vincent and the Grenadines", "code": "VC" },  
    { "name": "Samoa", "code": "WS" },  
    { "name": "San Marino", "code": "SM" },  
    { "name": "Sao Tome and Principe", "code": "ST" },  
    { "name": "Saudi Arabia", "code": "SA" },  
    { "name": "Senegal", "code": "SN" },  
    { "name": "Serbia", "code": "RS" },  
    { "name": "Seychelles", "code": "SC" },  
    { "name": "Sierra Leone", "code": "SL" },  
    { "name": "Singapore", "code": "SG" },  
    { "name": "Slovakia", "code": "SK" },  
    { "name": "Slovenia", "code": "SI" },  
    { "name": "Solomon Islands", "code": "SB" },  
    { "name": "Somalia", "code": "SO" },  
    { "name": "South Africa", "code": "ZA" },  
    { "name": "South Georgia and the South Sandwich Islands", "code": "GS" },  
    { "name": "Spain", "code": "ES" },  
    { "name": "Sri Lanka", "code": "LK" },  
    { "name": "Sudan", "code": "SD" },  
    { "name": "Suriname", "code": "SR" },  
    { "name": "Svalbard and Jan Mayen", "code": "SJ" },  
    { "name": "Swaziland", "code": "SZ" },  
    { "name": "Sweden", "code": "SE" },  
    { "name": "Switzerland", "code": "CH" },  
    { "name": "Syrian Arab Republic", "code": "SY" },  
    { "name": "Taiwan, Province of China", "code": "TW" },  
    { "name": "Tajikistan", "code": "TJ" },  
    { "name": "Tanzania, United Republic of", "code": "TZ" },  
    { "name": "Thailand", "code": "TH" },  
    { "name": "Timor-Leste", "code": "TL" },  
    { "name": "Togo", "code": "TG" },  
    { "name": "Tokelau", "code": "TK" },  
    { "name": "Tonga", "code": "TO" },  
    { "name": "Trinidad and Tobago", "code": "TT" },  
    { "name": "Tunisia", "code": "TN" },  
    { "name": "Turkey", "code": "TR" },  
    { "name": "Turkmenistan", "code": "TM" },  
    { "name": "Turks and Caicos Islands", "code": "TC" },  
    { "name": "Tuvalu", "code": "TV" },  
    { "name": "Uganda", "code": "UG" },  
    { "name": "Ukraine", "code": "UA" },  
    { "name": "United Arab Emirates", "code": "AE" },  
    { "name": "United Kingdom", "code": "GB" },  
    { "name": "United States", "code": "US" },  
    { "name": "United States Minor Outlying Islands", "code": "UM" },  
    { "name": "Uruguay", "code": "UY" },  
    { "name": "Uzbekistan", "code": "UZ" },  
    { "name": "Vanuatu", "code": "VU" },  
    { "name": "Venezuela", "code": "VE" },  
    { "name": "Viet Nam", "code": "VN" },  
    { "name": "Virgin Islands, British", "code": "VG" },  
    { "name": "Virgin Islands, U.S.", "code": "VI" },  
    { "name": "Wallis and Futuna", "code": "WF" },  
    { "name": "Western Sahara", "code": "EH" },  
    { "name": "Yemen", "code": "YE" },  
    { "name": "Zambia", "code": "ZM" },  
    { "name": "Zimbabwe", "code": "ZW" }  
]

This provides the countries that we reference in ContactForm.vue .

Next, we add our mixin to manipulate our contacts by communicating with our back end. We make a folder call mixins and create a file called contactMixin.js within it.

In the file, we put:

const axios = require('axios');  
const apiUrl = 'http://localhost:3000';

export const contactMixin = {  
    methods: {  
        getContacts() {  
            return axios.get(`${apiUrl}/contacts`);  
        }, 

        addContact(data) {  
            return axios.post(`${apiUrl}/contacts`, data);  
        }, 

        updateContact(data, id) {  
            return axios.put(`${apiUrl}/contacts/${id}`, data);  
        }, 

        deleteContact(id) {  
            return axios.delete(`${apiUrl}/contacts/${id}`);  
        }  
    }  
}

This will let us include our functions in the methods object of the component object we include or mixin with by putting it in the mixins array of our component object.

Then we add our pages. To do this, create a views folder if it doesn’t already exists and add ContactFormPage.vue .

In there, put:

<template>  
  <div class="about">  
    <ContactForm :edit="false" />  
  </div>  
</template>

<script>  
// @ is an alias to /src  
import ContactForm from "@/components/ContactForm.vue";

export default {  
  name: "ContactFormPage",  
  components: {  
    ContactForm  
  }  
};  
</script>

This just displays the ContactForm component that we created. We set the :edit prop to false so that it’ll add our contact instead of editing.

Next, we add our home page to display a list of contacts. In the views folder, we add a file called Home.vue if it doesn’t already exist. In there we put:

<template>  
  <div class="home">  
    <div class="center">  
      <h1>Address Book Home</h1>  
    </div>  
    <md-table>  
      <md-table-row>  
        <md-table-head md-numeric>ID</md-table-head>  
        <md-table-head>First Name</md-table-head>  
        <md-table-head>Last Name</md-table-head>  
        <md-table-head>Address Line 1</md-table-head>  
        <md-table-head>Address Line 2</md-table-head>  
        <md-table-head>City</md-table-head>  
        <md-table-head>Country</md-table-head>  
        <md-table-head>Postal Code</md-table-head>  
        <md-table-head>Gender</md-table-head>  
        <md-table-head>Age</md-table-head>  
        <md-table-head>Email</md-table-head>  
        <md-table-head></md-table-head>  
        <md-table-head></md-table-head>  
      </md-table-row><md-table-row v-for="c in contacts" :key="c.id">  
        <md-table-cell md-numeric>{{c.id}}</md-table-cell>  
        <md-table-cell>{{c.firstName}}</md-table-cell>  
        <md-table-cell>{{c.lastName}}</md-table-cell>  
        <md-table-cell>{{c.addressLineOne}}</md-table-cell>  
        <md-table-cell>{{c.addressLineTwo}}</md-table-cell>  
        <md-table-cell>{{c.city}}</md-table-cell>  
        <md-table-cell>{{c.country}}</md-table-cell>  
        <md-table-cell>{{c.postalCode}}</md-table-cell>  
        <md-table-cell>{{c.gender}}</md-table-cell>  
        <md-table-cell md-numeric>{{c.age}}</md-table-cell>  
        <md-table-cell>{{c.email}}</md-table-cell>  
        <md-table-cell>  
          <md-button class="md-primary" @click="selectedContactId = c.id; showDialog = true">Edit</md-button>  
        </md-table-cell>  
        <md-table-cell>  
          <md-button class="md-accent" @click="removeContact(c.id)">Delete</md-button>  
        </md-table-cell>  
      </md-table-row>  
    </md-table><md-dialog :md-active.sync="showDialog">  
      <md-dialog-content>  
        <ContactForm  
          :editing="true"  
          :contactId="selectedContactId"  
          @contactSaved="selectedContactId = undefined; showDialog = false"  
        />  
      </md-dialog-content>  
    </md-dialog>  
  </div>  
</template>

<script>  
import { contactMixin } from "@/mixins/contactMixin";  
import ContactForm from "@/components/ContactForm.vue";

export default {  
  name: "HomePage",  
  mixins: [contactMixin],  
  components: {  
    ContactForm  
  },  
  props: {  
    editing: Boolean,  
    id: Number  
  },  
  computed: {  
    contacts() {  
      return this.$store.state.contacts;  
    }  
  },  
  data() {  
    return {  
      showDialog: false,  
      selectedContactId: undefined  
    };  
  }, 

  beforeMount() {  
    this.getAllContacts();  
  }, 

  methods: {  
    async getAllContacts() {  
      try {  
        const response = await this.getContacts();  
        this.$store.commit("setContacts", response.data);  
      } catch (ex) {  
        console.log(ex);  
      }  
    }, 

    async removeContact(id) {  
      try {  
        await this.deleteContact(id);  
        await this.getAllContacts();  
      } catch (ex) {  
        console.log(ex);  
      }  
    }  
  }  
};  
</script>

<style scoped>  
.md-dialog-container {  
  padding: 20px;  
}

.md-content.md-table.md-theme-default {  
  width: 95%;  
  margin: 0 auto;  
}  
</style>

We get our contacts during page load by call the this.getAllContacts function in the beforeMount function. Notice that we have this.getContacts function from our mixin. Mixins allows us to reuse code.

Code in our mixinx cannot have the same name as the functions in our methods objects in our components because mixin functions hooks straight into our methods, since we exported an object with methods field in our Mixin code.

In App.vue , we add our menu and top bar by putting the following:

<template>  
  <div id="app">  
    <md-toolbar class="md-accent">  
      <md-button class="md-icon-button" @click="showNavigation = true">  
        <md-icon>menu</md-icon>  
      </md-button>  
      <h3 class="md-title">Vee Validate Address Book App</h3>  
    </md-toolbar>  
    <md-drawer :md-active.sync="showNavigation" md-swipeable>  
      <md-toolbar class="md-transparent" md-elevation="0">  
        <span class="md-title">Vee Validate Address Book App</span>  
      </md-toolbar><md-list>  
        <md-list-item>  
          <router-link to="/">  
            <span class="md-list-item-text">Home</span>  
          </router-link>  
        </md-list-item><md-list-item>  
          <router-link to="/contact">  
            <span class="md-list-item-text">Add Contact</span>  
          </router-link>  
        </md-list-item>  
      </md-list>  
    </md-drawer>
    <router-view />  
  </div>  
</template>

<script>  
export default {  
  name: "app",  
  data: () => {  
    return {  
      showNavigation: false  
    };  
  }  
};  
</script>

<style lang="scss">  
.center {  
  text-align: center;  
}  
</style>

In main.js , we add our boilerplate code to include Vue Material and Vee-Validate in our app:

import Vue from 'vue'  
import App from './App.vue'  
import router from './router'  
import store from './store'  
import VueMaterial from 'vue-material'  
import 'vue-material/dist/vue-material.min.css'  
import VeeValidate from 'vee-validate';Vue.use(VeeValidate);  
Vue.use(VueMaterial);Vue.config.productionTip = falsenew Vue({  
  router,  
  store,  
  render: h => h(App)  
}).$mount('#app')

In router.js , we add our routes so we can see our pages:

import Vue from 'vue'  
import Router from 'vue-router'  
import HomePage from './views/HomePage.vue'  
import ContactFormPage from './views/ContactFormPage.vue'
Vue.use(Router)

export default new Router({  
  mode: 'history',  
  base: process.env.BASE_URL,  
  routes: [  
    {  
      path: '/',  
      name: 'home',  
      component: HomePage  
    },  
    {  
      path: '/contact',  
      name: 'contact',  
      component: ContactFormPage  
    }  
  ]  
})

In store.js , we put:

import Vue from 'vue'  
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({  
  state: {  
    contacts: []  
  },  
  mutations: {  
    setContacts(state, payload) {  
      state.contacts = payload;  
    }  
  },  
  actions: {}  
})

to store our contact in a place where all components can access. The store uses the Vuex library so that we have a this.$store object to call our mutation with the this.$store.commit function and get the latest data from the store via the computed property of our component object, like so:

contacts() {  
  return this.$store.state.contacts;  
}

Finally in index.html , we put:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  <link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:400,500,700,400italic|Material+Icons">
  <link rel="stylesheet" href="https://unpkg.com/vue-material/dist/vue-material.min.css">
  <link rel="stylesheet" href="https://unpkg.com/vue-material/dist/theme/default.css">
  <title>Address Book App</title>
</head>
<body>
  <noscript>
    <strong>We're sorry but vee-validate-tutorial-app doesn't work properly without JavaScript enabled. Please enable it
      to continue.</strong>
  </noscript>
  <div id="app"></div>
  <!-- built files will be auto injected -->
</body>
</html>

to add the Roboto font and Material icons to our app.

Now we are ready to start our JSON server. Go to our project folder and run json-server — watch db.json to start the server. It will allow us to call these routes without any configuration:

GET    /contacts  
POST   /contacts  
PUT    /contacts/1  
DELETE /contacts/1

These are all the routes we need. Data will be saved to db.json of the folder that we’re in, which should be our app’s project folder.