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

vuejs_logo.png

Hello world

Vue is a very powerful framework but one of its strengths is that it is very lightweight and easy to pick up.

new Vue({
    el: "#app",
    data: {
        message: "Hello there"
    }
})

new Vue({el: "#app"}) will instantiate a new Vue instance. It accepts an options object as a parameter. This object is central in Vue, and defines and controls data and behavior. It contains all the information needed to create Vue instances and components. In our case, we only specified the el option which accepts a selector or an element as an argument. The #app parameter is a selector that will return the element in the page with app as the identifier.

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

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

This will result in a header with "Hello there" 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="showAlert">Show alert</button>
</div>

As for the JavaScript, write the following:

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

Run the code! An event listener will be installed on the button.

Click the button and you should see a popup that says 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="greeting" />
    <p>Hello: {{greeting}}</p>
    <button v-on:click="helloChuck">Hello Chuck</button>
</div>
new Vue({
    el: '#app',
    data: {
        greeting: "Neo"
    },
    methods: {
        helloChuck(){
            this.greeting = "Chuck"
        }
    }
})

Play a little with this application and notice how no handler is necessary to keep the input in sync. Every time greeting is updated, the text will update too; conversely, every time you write a greeting, the greeting 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.

A simple example will clarify what a computed property is:

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

Following is JavaScript snippet

let surname = 'Bond'

new Vue({
    el: '#app',
    data: {
        name: 'James'
    },
    computed: {
        computedGreeting() {
            console.log(this.$el.querySelector('#name').value)
            return "Hello " + 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: {
    planets: [
        {name: "Mercury", radius: 2439.7},
        {name: "Earth", radius: 6371},
        {name: "Saturn", radius: 58232},
        {name: "Neptune", radius: 24622},
        {name: "Venus", radius: 6051.8}
    ]
}

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

<div id="app">
<h3>List of planets</h3>
<ul>
    <li v-for="planet in planets">{{planet.name}} ({{planet.radius}}m)</li>
</ul>
</div>

Let's filter out planets which radius is more then 6500. To do this, we create a new variable that will hold only smallPlanets. This variable will be a computed property:

computed: {
    smallPlanets () {
        return this.planets.filter(planet => planet.radius < 6500)
    }
}

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

<li v-for="planet in smallPlanets">{{planet.name}} ({{planet.radius}}m)</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 ghost that is only visible at night:

<div id="ghost">
    <div v-show="isNight">
        I'm a ghost!
    </div>
</div>

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

new Vue({
    el: '#ghost',
    data: {
        isNight: true
    }
});

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

new Vue({
    el: '#ghost',
    computed: {
        isNight () {
            return new Date().getHours() < 7
        }
    }
})

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: mistyrose;}

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-changebg 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 changebg element is as follows:

<div id="app">
    <p v-changebg>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('changebg', {
    bind (el) {
        el.style.backgroundColor = 'green'
    }
})

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('changebg', {
    bind (el) {
        el.style.backgroundColor = 'green'
        el.onclick = () => {
            el.style.backgroundColor = 'red'
        }
    }
})

Here, we are creating an onclick listener that will set color to red 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('lorem-block', {
    template: `
        <div class='lorem-block'>
            <p>Lorem ipsum ...</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">
    <lorem-block></lorem-block>
    <lorem-block></lorem-block>
    <lorem-block></lorem-block>
</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 sound icon what the sound level was. There was no way for the sound icon to reply to the instance.

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 username and one for the user e-mail, 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 messages. Write the created hook 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 message and for editing a message:

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 word 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 array. 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 hook 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 tutorial, 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: ["Site 1", "Site 2", "App 1", "App 2"]}
    },
    beforeRouteEnter (to, from, next) {
        NProgress.done()
        next(vm => {
            vm.list.push("App 3")
        })
    }
}
const About = { template: '<div>I am 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.

comments powered by Disqus