time

Getting started with Vue.js: from Hello world to REST client

vuejs_logo.png

Vue.js is a JavaScript library focused on building web user interfaces. It is simple, flexible, and very fast, while still providing a lot of features and optional tools that can help you build a modern web app efficiently.

Vue roughly follows a Model-View-ViewModel architecture, which means the View (the user interface) and the Model (the data) are separated, with the ViewModel (Vue) being a mediator between the two. It handles the updates automatically and has been already optimized for you. Therefore, you don't have to specify when a part of the View should update because Vue will choose the right way and time to do so.

Vue doesn't have any dependency and can be used in any ECMAScript 5 minimum-compliant browser.

Hello world

Without further ado, let's start creating our first Vue app with a very quick setup. Vue is flexible enough to be included in any web page with a simple script tag. Let's create a very simple web page that includes the library, with a simple div element and another script tag:

new Vue({
    el: "#app",
    data: {
        message: "Vue version is " + Vue.version
    }
})

The whole library is based on Vue instances, which are the mediators between your View and your data. So, we need to create a new Vue instance to start our app. The Vue constructor is called with the new keyword to create a new instance. It has one argument - the option object. It can have multiple attributes (called options). It contains all the information needed to create Vue instances and components.

With the el option, we tell Vue where to add (or "mount") the instance on our web page using a CSS selector. The #app parameter is a selector that will return the element in the page with app as the identifier.

We will also initialize some data in the data option with a message property that contains a string. Now the Vue app is running, but it doesn't do much, yet.

You can add as many Vue apps as you like on a single web page. Just create a new Vue instance for each of them and mount them on different DOM elements. This comes in handy when you want to integrate Vue in an existing project.

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js app</title>
</head>
<body>
    <div id="app">
    <h3>{{message}}</h3>
    <ul>
        <li v-for="n in items">{{n}}</li>
    </ul>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
    <script>
        new Vue({
            el:'#app',
            data: {
                message: "Hello Vue",
                items: [1,2,3,4,5]
            }
        })
    </script>
</body>
</html>

This will result in a header with "Hello Vue" and list with numbers.

Everything that we will write inside the <div> with the ID as app will be under the scope of Vue.

A fundamental part of every application is the interaction with the user. Vue has shorthand to intercept most user events and connect them to relevant actions.

The following bit of code shows how to react to a click event:

Fill in the following HTML:

<div id="app">
<button v-on:click="notify">Show alert</button>
</div>

As for the JavaScript, write the following:

new Vue({
    el:'#app',
    methods: {
        notify(){
            alert('Alert!')
        }
    }
})

Two-way data binding

In Vue most data is reactive. In practice this means that if something is going to change in our view-model, we will see the results immediately. This is what lets you concentrate on the app itself, leaving aside all the drawing logic.

The v-on attribute will have you covered in most cases, especially if the event comes from the element. On the other hand, it may sometimes be too verbose for some tasks. For example, if we had a input and we wanted to update a variable with the content of the input and ensure that the input always has an updated value of the variable (which is called two-way data binding), we would have to write a couple of handlers.

Instead, this operation is carried out by the v-model attribute, as the following code shows:

<div id="app">
    <input v-model="name" />
    <p>Hello: {{name}}</p>
    <button v-on:click="greet">Hello Vue</button>
</div>
new Vue({
    el: '#app',
    data: {
        name: "User"
    },
    methods: {
        greet(){
            this.name = "Vue"
        }
    }
})

Play a little with this application and notice how handler is necessary to keep the input in sync. Every time name is updated, the text will update too; conversely, every time you write a name, the name gets updated as well.

Formatting text with filters

In Vue it's very easy to write your own filter and also very easy to find online libraries that do a much better job in specialized situations. Filters are for post-processing.

To demonstrate how easy it is to create a filter, we will recreate the capitalize filter.

We want to write a filter that will capitalize whatever string we put into it. If, for example, we want the string hello world to start with a capital H, we'd like to be able to write:

{{'hello world' | capitalize }}

Let's create the filter and add it to Vue's internal list of filters:

Write the following JavaScript to register a filter and instantiate Vue. Remember to write it before the actual instantiation of a Vue instance because otherwise Vue will not find it.

Vue.filter('capitalize', function (string) {
    var capitalFirst = string.charAt(0).toUpperCase()
    var noCaseTail = string.slice(1, string.length)
    return capitalFirst + noCaseTail
})

new Vue({el:'#app'})

In the HTML section, write:

{{'hello world' | capitalize }}

Run your code and notice how the text now reads Hello world.

Computed properties

Computed properties are data in Vue components that depend on some calculation on other, more primitive data. When this primitive data is reactive, the computed properties are up-to-date and reactive themselves. In this context, primitive is a relative term. You can certainly build computed properties based on other computed properties.

So, computed property allows us to define new properties that combine any amount of properties and use transformations, such as converting a markdown string into HTML--that's why its value is defined by a function. A computed property has the following features:

  • The value is cached so that the function doesn't rerun if it's not necessary, preventing useless computation
  • It is automatically updated as needed when a property used inside the function has changed
  • A computed property can be used exactly like any property (and you can use computed properties inside other computed properties)
  • It is not computed until it is really used somewhere in the app

A simple example will clarify what a computed property is:

<div id="app">
    <p><input type="text" v-model="price" id="price" /></p>
    <p>{{computePrice}}</p>
</div>

Following is JavaScript snippet

new Vue({
    el: '#app',
    data: {
        price: 7
    },
    computed: {
        computedGreeting() {
            console.log(this.$el.querySelector('#price').value)
            return 'Banna - ' + this.name + ' $'
        }
    }
})

Filtering a list with a computed property

To get started with this step, we need an example list from which to filter elements.

data: {
    fruit: [
        {name: "Lime", price: 14.39},
        {name: "Orange", price: 23.71},
        {name: "Apple", price: 5.82},
        {name: "Tomato", price: 9.46}
    ]
}

Let's print the list right away using a simple <ul> element:

<div id="app">
<h3>List of fruit</h3>
<ul>
    <li v-for="item in fruit">{{item.name}} ({{item.price}}$)</li>
</ul>
</div>

Let's filter our fruit which price is more then 10. To do this, we create a new variable that will hold only cheap. This variable will be a computed property:

computed: {
    cheap () {
        return this.fruit.filter(item => item.price < 10)
    }
}

Also, of course, we now want the list to draw an element from here:

<li v-for="fruit in cheap">{{fruit.name}} ({{fruit.price}}$)</li>

Displaying and hiding an element conditionally

Displaying and hiding an element on a web page is fundamental to some designs. You could have a popup, a set of elements that you want to display one at a time, or something that shows only when you click on a button. You can reach it via v-if and v-show directives.

Let's build a miracle that is only visible at day:

<div id="miracle">
    <div v-show="isDay">
        It's a miracle!
    </div>
</div>

The v-show guarantees that the <div> miracle will be displayed only when isDay is true. For example, we may write as follows:

new Vue({
    el: '#miracle',
    data: {
        isDay: true
    }
});

This will make the miracle visible. To make the example more real, we can write isDay as a computed property:

new Vue({
    el: '#miracle',
    computed: {
        isDay () {
            return new Date().getHours() > 9
        }
    }
})

The other directive for displaying elements conditionally is the v-if directive. The behavior is the same as that of v-show except that you won't find the element in the DOM at all. When v-if evaluates to true, the element will be added dynamically, no element styling involved.

Adding styles conditionally

You are going to learn basics of styling with Vue.

We will build a textarea that warns you when you are reaching the maximum allowed number of characters:

<div id="app">
    <textarea v-model="text" :maxlength="limit"></textarea>
    {{text.length}}
</div>

The text written inside will be bound to the text variable and the length of our text is written at the end via mustaches.

We want to change the background color when only 10 characters are left. For this, we have to bake a little CSS class warn:

.warn {background-color: coral;}

We will use this class on the textarea to signal the imminent alt on writing. Let's take a look at our JavaScript:

new Vue({
    el: '#app',
    data: {
        text: 'Lorem ipsum',
        limit: 30
    }
})

This is just our model, also we need add a function, longText, that will evaluate to true whenever we reach 20 characters (10 characters away from 30):

computed: {
    longText () {
        if (this.limit - this.text.length <= 10) {
            return true
        } else {
            return false
        }
    }
}

Now everything is in place to actually add the warn style conditionally. To do this, we have two options: object syntax and array syntax. Let's first try with the object syntax:

<div id="app">
    <textarea v-model="text" :class="{warn: longText}" :maxlength="limit"></textarea>
    {{text.length}}
</div>

This means that, whenever longText evaluates to true (or in general to a truthy), the class warn will be added to the textarea.

Creating a new directive

Directives are like mini functions that you can use to quickly drop in to your code, mainly to improve the user experience, and to add new low-level features to your graphic interface. The main reason directives are advanced is because you should usually prefer composition to add functionality and style to your apps. When components won't cut it, use directives.

We will build a v-modifybg directive that will change background color of any element. A changebg element is created with a green background and changes color when you click on it.

The HTML code for the modifybg element is as follows:

<div id="app">
    <p v-modifybg>Lorem ipsum ...</p>
    <p>Ipsum lorem ...</p>
</div>

Just to show the difference, I've included a normal p element. In our JavaScript section, write the following:

Vue.directive('modifybg', {
    bind (el) {
        el.style.backgroundColor = 'olive'
    }
})

This is how you declare a new directive. The bind hook is called when the directive is bound to the element. The only thing we are doing now is setting the background color. We also want to make it change color after each click. To do this, you have to add this code:

Vue.directive('modifybg', {
    bind (el) {
        el.style.backgroundColor = 'olive'
        el.onclick = () => {
            el.style.backgroundColor = 'tan'
        }
    }
})

Here, we are creating an onclick listener that will set color to tan and assign it as a new background color.

At the end of our JavaScript, remember to create a Vue instance:

new Vue({
    el: '#app'
})

Creating and registering a component

Having components all the way down makes your program, no matter how big, workable in isolated chunks. You can always add a new one without affecting others, and you can always throw away what you don't need, being sure that nothing will break.

The first step in dealing with components is to create one. Once the component is registered, we need to tell a Vue instance about it so that it can use the component. In this step, you will build your first component.

Here's the relevant code:

Vue.component('yellow-submarine', {
    template: `
        <div class='yellow-submarine'>
            <p>We all live in ...</p>
        </div>
    `
})

To use our component, we need our usual Vue instance:

new Vue({
    el: '#app'
})

Also, we need some HTML to actually place it in the page:

<div id="app">
    <yellow-submarine></yellow-submarine>
    <yellow-submarine></yellow-submarine>
    <yellow-submarine></yellow-submarine>
</div>

Simple components behave a little like stamps. You can save yourself from creating the same element many times using components. While it's fine to have the exact same copy of the same component throughout the page, we must have some means of telling the component what to do. This way, we can have the same components thrice, each of which does a slightly different function.

Since everything is reactive in Vue, with props we have a direct line of communication with our components, and you will learn how to use this line.

We will build a rating component that represents the rating value. Adjusting the rating value will change the view. The rating itself will be a component, like the following:

Vue.component('rating', {
    template: "<span>{{values[rate - 1]}}</span>",
    props: ['rate'],
    data() {
        return {
            values: ['star1.jpg', 'star2.jpg', 'star3.jpg', 'star4.jpg', 'star5.jpg']
        }
    }
})

Note how the data option is not an object but a function. The following Vue instance will save the current rating value:

new Vue({
    el: '#app',
    data: {
        rate: 1
    }
})

This rating value will be passed down as a props to the component by an input box. The following HTML displays how:

<div id="app">
    <label>Rating</label>
    <input type="number" v-model.number="rate">
    <rating :rate="rate"></rating>
</div>

You have to keep two things in mind when declaring props in your components:

  • props are one-way only communication.
  • They can be fixed or dynamic.

props are specifically for communicating parent to child. In our example, the Vue instance told the icon what the level was. There was no way for the icon to reply to the instance.

Lifecycle hooks

Each Vue instance follows a precise lifecycle with several steps--it will be created, mounted on the page, updated, and finally, destroyed. For example, during the creating step, Vue will make the instance data reactive.

Hooks are a specific set of functions that are automatically called at some point in time. They allow us to customize the logic of the framework. For example, we can call a method when a Vue instance is created.

We have multiple hooks at our disposal to execute logic when, or just before, each of these steps occurs:

  • beforeCreate : This is called when the Vue instance object is created (for example, with new Vue({})), but before Vue has done anything with it.
  • created : This is called after the instance is ready and fully operating. Note that, at this point, the instance is not in the DOM yet.
  • beforeMount : This is called just before the instance is added (or mounted) on the web page.
  • mounted : This is called when the instance is on the page and visible in the DOM.
  • beforeUpdate : This is called when the instance needs to be updated (generally, when a data or computed property has changed).
  • updated : This is called after the data changes are applied to the template. Note that the DOM may not be up to date yet.
  • beforeDestroy : This is called just before the instance is torn down.
  • destroyed : This is called when the instance is fully removed.

To add a lifecycle hook, just add a function with the corresponding name into the Vue instance options:

new Vue({
    // This will be called when the instance is ready
    created () {
        console.log('created');
    },
})

Sending basic AJAX requests with Axios

Axios is the recommended library for Vue for making HTTP requests. It's a very simple library, but it has some built-in features that help you in carrying out common operations. It implements a REST pattern for making requests with HTTP verbs and can also deal with concurrency (spawning multiple requests at the same time) in a function call.

The first thing you will need is to install Axios in your application. If you are using npm, you can just issue the following command:

npm install axios

If you are working on a single page, you can import the following file from CDN, at https://unpkg.com/axios/dist/axios.js.

Our HTML looks like this:

<div id="app">
    <h2>Advice of the day</h2>
    <p>{{advice}}</p>
</div>

Our Vue instance is as follows:

new Vue({
    el: '#app',
    data: {
        advice: 'loading...'
    },
    created() {
        axios.get('http://api.adviceslip.com/advice')
        .then(response => {
            this.advice = response.data.slip.advice
        })
        .catch(error => {
            this.advice = 'There was an error: ' + error.message
        })
    }
})

This will return a promise. We can use the then method on any promise to act on the result if the promise resolves successfully. The response object will contain some data about the result of our request.

Validating user data before sending it

HTML forms are a standard way to interact with your user. You can gather their data to register within the site, make them log in, or even carry out more advanced interactions. In this step, you will build your first form with Vue.

We will build a very simple form: one field for the title and one for the post body, plus one button to submit the information.

Type in this HTML:

<div id="app">
    <form @submit.prevent="submit">
    <div>
        <label>Title</label>
        <input type="text" v-model="title" required>
    </div>
    <div>
        <label>Body</label>
        <input type="text" v-model="body" required>
    </div>
    <div>
        <label>Submit</label>
        <button type="submit">Submit</button>
    </div>
    </form>
</div>

The Vue instance is trivial, as shown:

new Vue({
    el: "#app",
    data: {
        title: "",
        body: ""
    },
    methods: {
        submit () {
           // get form, validate and send
           axios.post("http://jsonplaceholder.typicode.com/posts", {
                title: this.title,
                body: this.body,
                userId: 1
            }).then(response => {
                console.log(response);
                //this.response = JSON.stringify(response, null, "")
            }).catch(error => {
                console.log(error)
            });
        }
    }
})

Creating a REST client

REST means REpresentational State Transfer and transfers a representation of the state of some resource. In practice, we are using a set of verbs (GET, POST, PUT, DELETE) to transfer the representation of the state of our messages.

To build a REST client, we will need a server that exposes a REST interface. For this purpose we are going to use jsonplaceholder.typicode.com.

Type in this HTML:

<div id="app">
    <ul>
        <li v-for="p in posts.slice(posts.length - 10, posts.length)">
            {{p.id}} - {{p.title}}
            <button @click="deleteItem(p.id)">Delete</button>
            <button @click="edit(p.id)">Edit</button></li>
    </ul>
    <button @click="add">Add</button>
</div>

Our Vue instance state will consist of a list of posts:

new Vue({
    el: '#app',
    data: {
        posts: [],
    },
})

The first thing that we want to do is ask the server for a list of posts. Write a method for this:

created () {
    axios.get('http://jsonplaceholder.typicode.com/posts/')
    .then(response => {
        console.log(response)
        this.posts = response.data
    })
}

For creating a new post, write a method that binds to the click of the add button and send what's written in the input box to the server:

methods: {
    add() {
        axios.post('http://jsonplaceholder.typicode.com/posts/', {
            title: "Post 1",
            body: "Body 1",
            userId: 1
        })
        .then(response => {
            if (response.status === 201) {
                this.posts.push(response.data)
            }
        })
    }
}

Similarly, write a method for deleting a post and for editing a post:

deleteItem(id) {
    axios.delete('http://jsonplaceholder.typicode.com/posts/' + id)
    .then(response => {
        if (response.status < 400) {
            this.posts.splice(this.posts.findIndex(e => e.id === id), 1)
        }
    })
},
edit(id) {
    axios.put('http://jsonplaceholder.typicode.com/posts/' + id, {
        id: id,
        title: "Post " + id,
        body: "Body " + id,
        userId: 777
    }).then(response => {
        if (response.status < 400) {
            console.info(response.status)
        }
    })
}

Implementing infinite scrolling

Infinite scrolling is quite popular and can improve interaction for some kind of content. You will build a random advice generator that works with infinite scrolling.

To make our app work, we will ask random words from the http://api.adviceslip.com/advice endpoint. Every time you point the browser at this address, you will get a random advice.

The whole page will consist solely of an infinite list of advices. Write the following HTML:

<div id="app">
    <p v-for="advice in advices">{{advice}}</p>
</div>

The list of advices needs to grow as we scroll down. So we need two things: understanding when the user reaches the bottom of the page, and getting new advices.

To know when the user has reached the bottom of the page, we add a method in our Vue instance:

new Vue({
    el: '#app',
    methods: {
        bottomVisible () {
            const visibleHeight = document.documentElement.clientHeight
            const pageHeight = document.documentElement.scrollHeight
            const scrolled = window.scrollY
            const reachedBottom = visibleHeight + scrolled >= pageHeight
            return reachedBottom || pageHeight < visibleHeight
        }
    }
})

This will return true if either the page is scrolled until the bottom or the page itself is smaller than the browser.

Next, we need to add a mechanism to bind the result of this function to a state variable bottom and update it every time the user scrolls the page. We can do that in the created hook:

created () {
    window.addEventListener('scroll', () => {
        this.bottom = this.bottomVisible()
    })
}

The state will be composed of the bottom variable and the list of random advices:

data: {
    bottom: false,
    advices: []
}

We now need a method to add advice to the list. Add the following method to the existing method:

addAdvice () {
    axios.get('http://api.adviceslip.com/advice')
    .then(response => {
        this.advices.push(response.data.slip.advice)
        if (this.bottomVisible()) {
            this.addAdvice()
        }
    })
}

The method will recursively call itself until the page has enough advices to fill the whole browser view. Since this method needs to be called every time we reach the bottom, we will watch for the bottom variable and fire the method if it's true. Add the following option to the Vue instance just after the data:

watch: {
    bottom(bottom) {
        if (bottom) {
            this.addAdvice()
        }
    }
}

In this snippet, we used an option called watch, which uses the following syntax:

watch: {
    'name of sate variable'(newValue, oldValue) {
        ...
    }
}

This is the counterpart of computed properties when we are not interested in a result after some reactive variable changes. As a matter of fact, we used it to just fire another method. Had we been interested in the result of some calculations, we would have used a computed property.

We also need to call the addAdvice method in the created method to kick-start the page:

created () {
    window.addEventListener('scroll', () => {
        this.bottom = this.bottomVisible()
    })
    this.addAdvice()
}

If you launch the page now, you will have an infinite stream of random advices.

Adding vue-router for page switch

Many modern applications are based on the SPA or Single Page Application model. From the users perspective, this means that the whole website looks similar to an application in a single page.

This is good because, if done correctly, it enhances the user experience, mainly reducing waiting times, because there are no new pages to load - the whole website is on a single page. This is how Facebook, Google and many other websites work.

URLs don't point to HTML pages anymore, but to particular states of your application (that most often look like different pages). In practice, on a server, assuming that your application is inside the index.html page, this is implemented by redirecting the user that is requesting, say, about me to index.html. The latter page will take the suffix of the URL and will interpret it as a route, which in turn will create a page-like component with biographical information.

Vue.js implements the SPA pattern through vue-router core plugin. To vue-router, every route URL corresponds to a component. This means that we will tell vue-router how to behave when the user goes to a particular URL in terms of its component. In other words, every component in this new system is a page in the old system.

In this section, you will only need to install vue-router and have some knowledge about Vue components.

To install vue-router, follow the instructions at https://router.vuejs.org/en/installation.html.

We are preparing a modern website for a developer and we will use the SPA pattern.

The website will consist of three pages: a home page, a portfolio page, and the about page.

The whole HTML code will be like this:

<div id="app">
    <h1>My blog</h1>
    <ul>
        <li><router-link to="/" exact>Home</router-link></li>
        <li><router-link to="/portfolio">Portfolio</router-link></li>
        <li><router-link to="/about">About</router-link></li>
    </ul>
    <transition mode="out-in">
        <router-view></router-view>
    </transition>
</div>

To add a progress bar to load pages add the following two lines inside the head of your page

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.js"></script>

The <router-view> component is the entry point for vue-router. It's where the components are displayed as pages.

To instruct Vue that we want to use the vue-router plugin, write the following in the JavaScript:

Vue.use(VueRouter)

The part of three pages we listed at the beginning will be played by these three dummy components (add them to the JavaScript):

const Home = {
    template: '<div>Welcome to my blog</div>'
    beforeRouteEnter (to, from, next) {
        NProgress.done()
        next(vm => {})
    }
}

const Portfolio = {
    template: '<div><h3>My portfolio</h3><ul><li v-for="i in list">{{i}}</li></ul></div>',
    data() {
        return {list: ["Vinbus app", "Opus app", "Shop site", "Gallery site"]}
    },
    beforeRouteEnter (to, from, next) {
        NProgress.done()
        next(vm => {
            vm.list.push("Meeting app")
        })
    }
}
const About = { template: '<div>I the developer</div>' }

The vue-router, before actually loading the component onto the scene, will look for an option in our object, called beforeRouteEnter, we will use this to update list for Portfolio.

The beforeRouteEnter hook takes three parameters:

  • to. This is a Route object that represents the route requested by the user.
  • from. This is also a Route object that represents the current route. This is the route the user will be kept at in case of errors.
  • next. This is a function we can use when we are ready to go on with the switching of the route. Calling this function with false will prevent the route from being changed, and it is useful in case of errors.

Now, you can finally create the router. The code for it is as follows:

const router = new VueRouter({})

This router doesn't do much; we have to add routes (which correspond to URLs) and their associated components:

const router = new VueRouter({
    routes: [
        { path: '/', component: Home},
        { path: '/portfolio', component: Portfolio},
        { path: '/about', component: About}
    ]
})

router.beforeEach((to, from, next) => {
    NProgress.start({ showSpinner: false })
    next()
})

Now our application is almost complete; we only need to declare a simple Vue instance:

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

Our application will now work; before launching it, add this CSS rule to have slightly better feedback:

a.router-link-active, li.router-link-active>a {
    background-color: gainsboro;
}
.v-enter-active, .v-leave-active {
    transition: opacity .5s;
}
.v-enter, .v-leave-active {
    opacity: 0
}

The first thing your program does is to register vue-router as a plugin. The vue-router, in turn, registers the routes (which are parts of URLs) and connects components to each of them.

When we visit the application for the first time, the URL on the browser will end with index.html/#/. Everything after the hash symbol is a route for the vue-router. In this case, it is only a slash / and so it matches the first home route.

When we click on the links, the content of the <router-view> changes according to the component we associated with that route.

Useful links