Bootstrapping your first Vue.js project using Vue CLI

 Required prerequisite knowledge

For this guide, you will need to have a good understanding of Javascript. You will also need to have had some experience using a Javascript framework as we’ll be making use of automated build tools. The Vue.js documentation also recommends against beginners starting out with Vue CLI. I will be releasing a more beginner friendly tutorial shortly but this is expected to be a practical guide for more experienced developers.

 Introduction

If you don’t already know, Vue.js is a progressive Javascript framework quite similar to the likes of Angular and React. It is used on the front-end to handle user events, present dynamic DOM content and it provides an easy way to architect apps that are modular and performant as required by the apps of today. In this guide, I will be taking you through the process of bootstrapping your very first Vue.js project quite similarly to how you will do it in a real-life scenario.

Disclaimer: This guide is based on the structure provided by vue-cli. You can choose to structure your app in an entirely different way but this gives a general introduction to how Vue.js apps can be structured.

 Objectives

At the end of this guide, you should have a good understanding of the following:

The entire code for the project is hosted on GitHub.

 Introduction to Vue.js

The Vue.js homepage describes the framework as approachable, versatile and performant. Based on my experience using the framework so far, I will say that this has been very true. It has a reasonable learning curve with very intuitive syntax, it’s fast (this page give details on how it matches up to more popular frameworks) and it’s extremely diverse in its functions. IMO, Vue.js brings together the best parts of different frameworks and this makes working with it such a joy. I’m beginning to gush now so I’ll let you read for yourself. You can check out this guide to see more about what Vue.js is and isn’t, and can and can’t do.

In recent times, especially since the release of v2, the framework has gained popularity as being a stable front-end framework able to support the needs of most modern web applications and the adoption rate is seeing a quite rapid increase.

Google Trend Vue.js

 Problem statement: Develop a simple notes application

In this guide, we will be developing a notes app as a basic example to illustrate all the concepts that we will be discussing. The features of the notes app we are building are as follows:

 Setup and Installation

We will start off by generating the boilerplate for the application. For this guide, we’ll be making use of vue-cli to generate the necessary boilerplate. Install vue-cli globally using npm from within your terminal:

npm install -g vue-cli

To generate the initial boilerplate, navigate to the directory within which you intend to create your project and run this command:

vue init webpack notes-app

vue-cli comes with a number of templates you can use to scaffold you project. There are templates that use either of the popular code bundling tools i.e. webpack and browserify. The above command takes a more general form of vue init <template-name> <project-name> where template-name refers to the template we would like to use for our scaffolding. Possible options for template include webpack, browserify, simple etc. See the vue-cli GitHub repo for more details. We’re using the webpack generator for this project.

In the prompts that follow, use the default options. For code linting, I usually prefer to use AirBNB’s Javascript style guide for its conciseness but you can choose anyone that works for you.

Navigate to your project folder using cd notes-app and run npm install to install all the dependencies. Run npm run dev to start the development server. This boilerplate is configured with hot-reloading features that enable us see changes as soon as we make them in the editor.

Navigate to http://localhost:8080 to see your generated application. You should see something similar to this:

vue-cli default home screen

The vue-cli documentation linked above has more information on how you can customize your setup to suit your needs.

Great! We now have our project folder generated.

 Folder structure

vue-cli generates a bunch of folders for us and on navigating into the notes-app folder created, we will find the following folder structure:

β”œβ”€β”€ README.md
β”œβ”€β”€ build
β”‚Β Β  β”œβ”€β”€ build.js
β”‚Β Β  β”œβ”€β”€ check-versions.js
β”‚Β Β  β”œβ”€β”€ dev-client.js
β”‚Β Β  β”œβ”€β”€ dev-server.js
β”‚Β Β  β”œβ”€β”€ utils.js
β”‚Β Β  β”œβ”€β”€ vue-loader.conf.js
β”‚Β Β  β”œβ”€β”€ webpack.base.conf.js
β”‚Β Β  β”œβ”€β”€ webpack.dev.conf.js
β”‚Β Β  β”œβ”€β”€ webpack.prod.conf.js
β”‚Β Β  └── webpack.test.conf.js
β”œβ”€β”€ config
β”‚Β Β  β”œβ”€β”€ dev.env.js
β”‚Β Β  β”œβ”€β”€ index.js
β”‚Β Β  β”œβ”€β”€ prod.env.js
β”‚Β Β  └── test.env.js
β”œβ”€β”€ index.html
β”œβ”€β”€ package.json
β”œβ”€β”€ src
β”‚Β Β  β”œβ”€β”€ App.vue
β”‚Β Β  β”œβ”€β”€ assets
β”‚Β Β  β”‚Β Β  └── logo.png
β”‚Β Β  β”œβ”€β”€ components
β”‚Β Β  β”‚Β Β  └── Hello.vue
β”‚Β Β  β”œβ”€β”€ main.js
β”‚Β Β  └── router
β”‚Β Β      └── index.js
β”œβ”€β”€ static
└── test
    β”œβ”€β”€ e2e
    β”‚Β Β  β”œβ”€β”€ custom-assertions
    β”‚Β Β  β”‚Β Β  └── elementCount.js
    β”‚Β Β  β”œβ”€β”€ nightwatch.conf.js
    β”‚Β Β  β”œβ”€β”€ runner.js
    β”‚Β Β  └── specs
    β”‚Β Β      └── test.js
    └── unit
        β”œβ”€β”€ index.js
        β”œβ”€β”€ karma.conf.js
        └── specs
            └── Hello.spec.js

13 directories, 29 files

These are the most important files/folders to us:

There are 4 important things we will need know in order to understand the way the application is put together.

I - Entry Point and Root Vue Instance: The first one is the application entry point, src/main.js. This is the root of the import tree and the entry file for webpack. Transpiling and bundling of our code starts here and the output file will be embedded in index.html (no need to worry, the build process is already automatically configured in the boilerplate).

The important bit to take note of here are the lines that create the root Vue instance:

newΒ Vue({
Β Β el:Β '#app',
Β Β router,
Β Β template:Β '<App/>',
Β Β components:Β {Β AppΒ },
});

The root Vue instance is used to initialize Vue within our app, giving our app reactive abilities. This is done by instantiating the Vue class with a bunch of properties including an el property which tells Vue where to mount itself.

Here are what the different properties mean:

  1. el - This is the application mount point. It is a reference to the DOM element where the root instance will be injected.
  2. router - This property is used to configure the router and make it available to all our app components.
  3. template - This is the raw HTML template string that will be output at the specified mount point.
  4. components - This is a collection of components to be made available while rendering the template. In this case, the App component is registered here and as earlier seen was used within the template string. Hence will be parsed as a component after rendering.

We can imagine that parsing index.html will result in the following:

<divΒ id="app">
    <App />
</div>

Since App is a registered component, it will be further parsed. The next item will help us understand what the App component consists of.

II - Base App Component: The base App component is specified in the src/App.vue file. This component is used in the root Vue instance and rendered at the mount point. Notice how components are composed in single files? That’s one of the cool features of Vue.js we’ll be exploring. Vue.js gives us the ability to compose single file components as .vue files. These files are preprocessed by webpack using a loader called vue-loader. You can read more on how this works here. Using vue-loader, we are able to import this similarly to how we would regular ES6 Javascript modules.

import Component from '@/components/Component'

In the single file components, we are able to specify three main portions required to define a component:

III - Router: Although not strictly required for all application types, for the purpose of this example, we shall be taking advantage of routing to navigate between views.

Vue.js like other similar front-end frameworks has an accompanying router that we can use in our SPAs. The official router for Vue.js is [vue-router](router.vuejs.org) and it can be installed in your project using the npm install command:

npm install vue-router --save

Like any other Vue.js plugin, to use the plugin in our app, we need to add the plugin using Vue.use() just before creating the root Vue instance. Configuring vue-router will look something like this:

import Vue from `vue`
import VueRouter from 'vue-router'

Vue.use(VueRouter)

In our setup, all of this has already been done for us and properly configured within our app. The part that concerns us, for the purpose of this guide, is the router/index.js file which is where our routes are configured. This object is exported and used when creating the root Vue instance as shown earlier.

import Vue from 'vue';
import Router from 'vue-router';
import NoteList from '@/components/Hello';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Hello',
      component: Hello,
    },
  ],
});

vue-router basically maps routes signatures, composed of a name and/or url path, to components and renders the respective component within a <router-view> tag. Currently, In our application, the <router-view> component is embedded in our base App component. Hence, the App component will act as a container. Content within the App is rendered and then the <router-view> within it will render the respective component based on the route we’re accessing. Consider the following diagram that illustrates the concept.

Component Diagram for Vue router

You will observe that there is currently one route mapping to one component which is rendered every time we access http://localhost:8000.

IV - Components: Finally, the components folder contains all the components we will be using in our app. We will be storing a bunch of .vue files in here as we break our application into modular maintainable pieces. For now, only one component is in there. We will be removing this shortly to define our own components.

OK! That should cover all the elements of the boilerplate and should also give you a very good understanding of what’s going on overall in the application. Chances are that, if you’re a seasoned React developer, this information is sufficient to get you started building applications with Vue.js right away πŸ˜‚. But hang on, don’t get too excited, we still have a bunch of stuff to cover. So sit tight.

 Installing Element - Vue component library

Element

As shown in the section above where we were discussing vue-router, installing plugins in Vue.js is pretty straightforward. To attach the plugin to the Vue instance, you just call Vue.use() with the plugin.

For this guide, we shall be making use of Element, a Vue.js component library for building beautiful user interfaces. It’s great for whipping up quick prototypes and also for creating stable highly interactive UI as well. See installation guide here.

To install the library, run the npm install command:

npm i element-ui -S

Then you will need to add it before you create your root Vue instance. Because VueRouter necessarily needs to be initialized before this library, we will be embedding the initialization code for Element within routes/index.js like so:

import Vue from 'vue';
import Router from 'vue-router';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-default/index.css';
import NoteList from '@/components/Hello';

Vue.use(Router);
Vue.use(ElementUI);

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Hello',
      component: Hello,
    },
  ],
});

 Creating your first component

As a warm up exercise, we will be diving right into creating our first component and our corresponding first route. We will be creating a component that will display a list of notes to our users. For now, we will store the notes locally within the program. This is a diagram of what we intend to create.

List Notes component

To do this, create a new file called NotesList.vue in your components folder and enter the following code for the component within that file. We will start with the template section.

<template>
  <div class="NoteList u-absolute-flex-column">
    <header class="app-header">APP_HEADER</header>
    <ul class="u-scroller">
      <li v-for="note in notes">
        <a>
          <i class="el-icon-arrow-right"></i>
          <p class="note-title">NOTE_TITLE</p>
          <p class="small">NOTE_META_DATA</p>
        </a>
      </li>
    </ul>
    <div class="app-controls">
      <div class="u-flex-row">
        <el-button type="primary" class="u-elastic">Add Note</el-button>
      </div>
    </div>
  </div>
</template>

This is structurally how the page will look. Taking a close look at the code, you will observe that we have made use special v-for attribute within our template. Custom attributes like these are called directives and can be used to add extra functionality to your HTML tags. Besides inbuilt directives like v-for, with Vue.js, we can also create our own directives.

The v-for directive specifically repeats the tags it’s attached to for as many times as there are elements in the collection we are iterating over. We will be diving deeper into this and more template syntax in a few sections.

Now that we have our template defined, we need to load actual dynamic content at points where we currently have placeholders. To do this, we need to understand how Vue.js interacts with data in the components.

data() function: This function is used to specify initialization data for the component. The function is expected to return an object containing the initialization variables as key-value pairs. For instance, in this component, we can initialize a notes variable to contain all the notes in the component as an array in the data() function.

export default {
  data() {
    return {
      notes: []
    }
  },
}

The notes variable will now be accessible from all the component methods as this.notes and within the component template as notes. Let’s add some initialization code for the notes variable. For now, we will use raw data in the component logic. Remember to nest all this code within the <script> section of your NoteList.vue file.

<script>
export default {
  data() {
    return {
      notes: [],
    };
  },
  methods: {
    loadNotes() {
      this.notes = [
        { title: 'A new note', meta: 'Added 2 days ago' },
        { title: 'Another new note', meta: 'Added 2 days ago' },
        { title: 'And another one', meta: 'Added 2 days ago' },
      ];
    },
  },
  mounted() {
    this.loadNotes();
  },
}
</script>

We have defined a few new properties here:

Vue.js component lifecycle

Other methods that we can implement in the component lifecycle include beforeMount(), created(), beforeUpdate(), updated() etc. These methods do what they most likely sound like they’re doing πŸ˜‚. See the diagram above for more details.

One more special property we can define within components is computed. The computed property is used to define computed properties in the component. Computed properties are values that are computed based on other values. A major reason to use this is that a change to the value the computed property relies on triggers a recomputation and hence they are reactive.

We will create a new computed property for this component to illustrate this and add in the final CSS for the component.

<template>
  <div class="NoteList u-absolute-flex-column">
    <header class="app-header">All Notes ({{ notesCount }})</header>
    <ul class="u-scroller">
      <li v-for="note in notes">
        <a>
          <i class="el-icon-arrow-right"></i>
          <p class="note-title">{{ note.title }}</p>
          <p class="small">{{ note.meta }}</p>
        </a>
      </li>
    </ul>
    <div class="app-controls">
      <div class="u-flex-row">
        <el-button type="primary" class="u-elastic">Add Note</el-button>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      notes: [],
    };
  },
  methods: {
    loadNotes() {
      this.notes = [
        { title: 'A new note', meta: 'Added 2 days ago' },
        { title: 'Another new note', meta: 'Added 2 days ago' },
        { title: 'And another one', meta: 'Added 2 days ago' },
      ];
    },
  },
  mounted() {
    this.loadNotes();
  },
  computed: {
    notesCount() {
      return this.notes.length;
    },
  },
};
</script>

<style scoped>
.NoteList ul {
  padding: 0;
  margin: 0;
  list-style: none;
}

.NoteList ul li {
  border-bottom: solid 1px #EFEFEF;
}

.NoteList ul li a {
  position: relative;
  display: block;
  padding: 12px 25px;
  cursor: pointer;
  color: #2c3e50;
  text-decoration: none;
}

.NoteList ul li a:hover {
  color: #006699;
}

.NoteList ul li a i {
  position: absolute;
  top: 22px;
  right: 25px;
  margin: auto 0;
}

.NoteList ul li a .note-title {
  width: 90%;
  margin: 0;
  font-size: 14px;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

.NoteList ul li a .small {
  margin: 0;
  margin-top: 3px;
  font-size: 11px;
}
</style>

We created a computed property called notesCount that holds the number of items in the notes list. It will update itself each time notes changes and we are using it similarly to any data property or methods function in the header section of the template.

Here, we’ve also changed the template to display content from the component data. The variable values are parsed in the template using variable interpolation. In Vue, this is done using the mustache {{ }} syntax. We are able to access object properties as usual and evaluate regular javascript expressions within the braces as well. See here for more details on what you can do within Vue.js template.

Before we round this off, we need to go back to make some modification to the App component to make for cleaner UI and to also get a better understanding of how the routing is working.

<template>
  <div id="App">
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'app',
};
</script>

<style>
#App {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  width: 400px;
  height: 80%;
  margin: auto auto;
  border: solid 1px #EFEFEF;
  box-shadow: 0 1px 4px 0 rgba(0,0,0,0.08);
  display: flex;
  flex-direction: column;
}

.app-header {
  border-bottom: solid 1px #EFEFEF;
  padding: 10px 20px;
  font-weight: 700;
}

.app-header .back-button {
  display: inline-block;
  border-right: solid 1px #EFEFEF;
  padding-right: 10px;
}

.app-header a {
  margin-right: 10px;
  border-right: solid 1px #EFEFEF;
  padding-right: 10px;
  cursor: pointer;
}

.app-header a:hover {
  color: #006699;
}

.app-header a:last-child {
  margin-right: 0;
  border-right: none;
  padding-right: 0;
}

.u-flex-row {
  display: flex;
  flex-direction: row;
}

.u-absolute-flex-column {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  flex-direction: column;
}

.u-scroller {
  flex-grow: 1;
  overflow-y: scroll;
  margin: 0;
  padding: 0;
}

.u-elastic {
  flex-grow: 1;
  border-radius: 0 !important;
}
</style>

I have added in code for the utility CSS classes as well as modified the .App container to only contain the <router-view> element explained earlier. We will need to modify our routes to connect to the newly created page. You may now delete the Hello.vue component and put in the correct component within the router/index.js file.

import Vue from 'vue';
import Router from 'vue-router';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-default/index.css';
import NoteList from '@/components/NoteList';

Vue.use(Router);
Vue.use(ElementUI);

export default new Router({
  routes: [
    {
      path: '/',
      name: 'All Notes',
      component: NoteList,
    },
  ],
});

We are now pointing the root url to our NoteList component.

And that’s it! You have created the first component and route for this application. We will now dive deeper into implementing the remaining features as I point out important aspects of Vue.js that you will need very frequently.

Your component should look like this when you’re done.

Final List notes component

 Working with component props

We are going to go ahead to over-engineer this example a bit, but for good reason.

You should have a good idea of what components in Vue are right now. They are basically custom elements. One of the great things about using components in Vue.js, especially when nesting them, is that you can pass data to the component. Ideally, you would pass in instance data that the component can use while rendering. This can be done using props in Vue.js (Sound weirdly familiar 😏).

props are defined in the component as a list of strings. These strings will represent special attributes that your component can accept. Each prop’s value will be accessible with the component using this.propKey similar to other component data. For example, this simplified example creates a component with a myMessage prop.

export default {
  props: ['myMessage'],
  template: '<span>{{ myMessage }}</span>'
}

To pass a value to a prop, simply assign it a value as you would any other HTML attribute. For example, if the above component was called Messager, we can pass the prop value like this:

<Messager my-message="Hello Child!"/>

This will render the span tag with the message specified.

I have used this example for a specific reason. Notice how for camel cased props like these, we will be referencing it in the template using the respective kebab-case notation. This is a nice feature Vue provides to deal with HTML’s case-insensitiveness.

Besides passing literal values to the props, we can also pass in component data using the v-bind directive.

export default {
  data() {
    return {
      message: "Some message",
    }
  },
  template: '<Messager v-bind:my-message="message"/>'
}

The value of message variable is parsed as passed into the prop. The template is basically equivalent to this:

<Messager my-message="Some message"/>

Let’s apply this to our application. We will break out the individual note list items into its own component called NoteListItem.

<template>
  <div class="NoteListItem">
    <a>
      <i class="el-icon-arrow-right"></i>
      <p class="note-title">{{ note.text }}</p>
      <p class="small">{{ note.meta }}</p>
    </a>
  </div>
</template>

<script>
export default {
  props: ['note'],
};
</script>

<style>
.NoteListItem a {
  position: relative;
  display: block;
  padding: 12px 25px;
  cursor: pointer;
  color: #2c3e50;
  text-decoration: none;
}

.NoteListItem a:hover {
  color: #006699;
}

.NoteListItem a i {
  position: absolute;
  top: 22px;
  right: 25px;
  margin: auto 0;
}

.NoteListItem a .note-title {
  width: 90%;
  margin: 0;
  font-size: 14px;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

.NoteListItem a .small {
  margin: 0;
  margin-top: 3px;
  font-size: 11px;
}
</style>

As you can see, we have defined a note prop. This component will now accept the note object from the parent component. Modify your NoteList component like so:

...
    <ul class="u-scroller">
      <li v-for="note in notes">
        <NoteListItem v-bind:note="note" />
      </li>
    </ul>
...

<script>
import NoteListItem from '@/components/NoteListItem';

export default {
  ...
  components: { NoteListItem },
};
</script>

We have used v-bind to pass the note value to the component. We also had to import the component here to register it in the component. We do this by adding it to the components property.

 Notes App Component Model

We are going to go on to create the remaining components required for the other parts of the app.

 A - Single note view component

We will go on to create a new component that we will use to display a single note. We will transition to this view when we click on the link in the NoteList component. We will write the logic that handles the routing shortly.

Before we dive into creating the component, let’s create a new route.

import Vue from 'vue';
import Router from 'vue-router';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-default/index.css';
import NoteList from '@/components/NoteList';
import Note from '@/components/Note';

Vue.use(Router);
Vue.use(ElementUI);

export default new Router({
  routes: [
    {
      path: '/',
      name: 'All Notes',
      component: NoteList,
    },
    {
      path: '/view/:id',
      name: 'View Note',
      component: Note,
      props: true,
    },
  ],
});

We’ve added a new route here, /view/:id that points to the now fictitious Note component. The :id url parameter specified is used to capture the id of the note we are trying to view. We will be able to access this value within the component from the this.$route object. Vue router provides us this.$router and this.$route objects accessible within all components. These objects give us access to many routing features from within our component logic including programmatic navigation which we will see shortly.

So, Ideally, route parameters will be accessible within the component using this.$route.params. So in this case, we will be able to access the :id parameter using this.$route.params.id. However, it is preferred to pass route parameters as props as this will make it possible to use these components outside the scope of the router. To do this, you will need to set props: true on the respective route as shown above.

What this all means is that on navigating to /view/ANY_RANDOM_SEQUENCE_OF_CHARACTERS, the id value, ANY_RANDOM_SEQUENCE_OF_CHARACTERS, will be captured as a prop accessible within the component.

Hence, as earlier mentioned, we will need to define the prop within the component. See the component logic below to see how this done.

Create a new component Note.vue.

<template>
  <div class="Note u-absolute-flex-column">
    <header class="app-header">
      <div class="u-flex-row">
        <a class="back-button" @click="goBack()"><i class="el-icon-arrow-left"></i></a>
        <div class="menu-text u-elastic">Viewing Note</div>
        <a @click="editNote()"><i class="el-icon-edit"></i></a>
        <a @click="deleteNote()"><i class="el-icon-delete"></i></a>
      </div>
    </header>
    <div class="u-scroller">
      <div class="content">
        {{ note.text }}
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: ['id'],
  data() {
    return {
      note: {},
    };
  },
  methods: {
    loadNote() {
      // Load a single note with id based on whatever storage methodology we pick
    },
    goBack() {
      this.$router.push('/');
    },
    editNote() {
      // Logic to edit a note
    },
    deleteNote() {
      // Logic to delete note
    }
  },
  mounted() {
    this.loadNote();
  },
};
</script>

<style scoped>
.content {
  padding: 25px;
  font-size: 14px;
  line-height: 21px;
}
</style>

By now, we should be familiar with the methods, props and data properties and what they are used for but a few things stand out here and I will explain [IN KEVIN HART’S VOICE], shortly.

First off, we are making use of those directives again. This time, we’ve repeatedly made use of special @click directive. This is actually a shortcut specifically for the v-on:click directive which is Vue.js’ way of defining event handlers for DOM elements. All the usual predefined Javascript event handlers are available by default in Vue.js using the v-on directive and their corresponding shortcuts.

v-on:click    =>    @click
v-on:change   =>    @change
v-on:dblclick =>    @dblclick

Technically speaking, and I will talk about this in more detail in an upcoming guide, this is a more general form for directives that take arguments. Other examples of these include the v-bind directive which can be used to assign a template attribute value to any value within the component’s data model .e.g <a v-bind:href='componentUrlValue'>Click here</a>.

The value of this event directive is a function which will be triggered once the event happens. We have in all three instances, pointed to functions defined within the methods field of our component. To explore more of Vue.js’ event handling features, check this out.

Secondly, we have compulsorily defined an id prop for our component as earlier explained.

Thirdly, we have defined a bunch of empty function, with the exception of the goBack() function which we’ve defined to do exactly what it says (proper variable names breh!). We are, within this method, taking advantage of one of the nice features the this.$router object provides us, programmatic navigation. In this case, we are calling the push() method, one of whose forms accepts and navigates to a given path. We are navigating to the home route on clicking the back button.

See more features of the $router object here.

We will use local storage to store our data in the browser. Using local storage with Vue.js is really straightforward using the vue-localstorage plugin. To configure it, install using npm:

npm install --save vue-localstorage

And then initialize the plugin within router/index.js. Remember, you can also choose to do your plugin initializations within main.js. We are only doing this because we had already earlier defined a component here.

import VueLocalStorage from 'vue-localstorage';

...

Vue.use(VueLocalStorage);

Using this plugin, we can initialize our local storage keys within the Vue instance and we will do just that within main.js.

new Vue({
  el: '#app',
  router,
  template: '<App/>',
  localStorage: {
    notes: {
      type: Array,
      default: [],
    },
  },
  components: { App },
});

We have initialized the notes key to an empty array. This only happens if that key doesn’t already exist. Now we can go ahead to implement the logic for loadNote() and deleteNote().

...
  methods: {
    loadNote() {
      const notes = this.$localStorage.get('notes');
      this.note = notes.find(note => note.id === this.id);
      if (!this.note) {
        this.$router.push('/');
      }
    },
    deleteNote() {
      if (confirm('Delete note?')) {
        const notes = this.$localStorage.get('notes');
        const noteIndex = notes.findIndex(note => note.id === this.id);
        notes.splice(noteIndex, 1);
        this.$localStorage.set('notes', notes);
        this.$router.push('/');
      }
    },
  }
...

The loadNote() function simply loads notes from local storage and searches for the note by id. It will redirect to the home screen if no note is found. As you can see, similar to with Vue router, this plugin provides us a variable, $localStorage which we can use to access the local storage. Here we have made use of the get() method to get the contents of the notes key. We’ve also used set() to replace the content in local storage after deleting the note in the delete() function.

OK! Lots of content now but we’re making progress.

We’re going to go ahead to modify the list component to read the notes list from local storage.

In NoteList.vue, add the following to load the notes from local storage:

...
    loadNotes() {
      this.notes = this.$localStorage.get('notes');
    },
...

Next, we will adjust the NoteListItem component to link to the respective note. We will also introduce a slight change to our data model.

...
    <router-link :to="{ name: 'View Note', params: { id: note.id } }">
      <i class="el-icon-arrow-right"></i>
      <p class="note-title">{{ note.text }}</p>
      <p class="small">{{ parseTimestamp(note.timestamp) }}</p>
    </router-link>
...

<script>
const moment = require('moment');

export default {
  props: ['note'],
  methods: {
    parseTimestamp(ts) {
      return moment(ts).fromNow();
    },
  },
};
</script>

We have done a couple of things here.

First off, we changed the <a> element to make use of a special router-link component provided by vue-router. Using this component we are able to dynamically generate links to our routes from within the template. In this instance, we have created a link, using the name parameter, to the View Note route. We have also passed parameters. As you should have already guessed, this automatically populates :id in the /view/:id route.

Secondly, we have modified our note data model slightly to include a timestamp property to store the time when the note was created/last modified. We defined a function parseTimestamp() which uses [momentjs](momentjs.com) to parse this time in human readable “time ago” format.

Great! That’s all for the single note view component. Sadly, except you want to manually populate the local storage data, we will need to be able to create notes from our app. Let’s go ahead to create the CreateNote component.

 B - Create/Edit note component

This component is a very special one. We are going to be using this component for dual purposes. To create notes and to edit notes. Create the following routes in router/index.js

import CreateNote from '@/components/CreateNote';
...
    {
      path: '/new',
      name: 'Create Note',
      component: CreateNote,
    },
    {
      path: '/edit/:id',
      name: 'Edit Note',
      component: CreateNote,
      props: true,
    },
...

The /new route will be used for creating new routes and this does not pass any props into this component. The /edit/:id route, on the other hand, will pass the id prop to the component. Within the component, we will define that prop as well as do some customization to control the flow of things.

<template>
  <div class="CreateNote u-absolute-flex-column">
    <header class="app-header">
      <div class="u-flex-row">
        <a class="back-button" @click="goBack()"><i class="el-icon-arrow-left"></i></a>
        <div class="menu-text u-elastic">{{ id ? 'Editing Note' : 'Create Note' }}</div>
      </div>
    </header>
    <ul class="u-scroller">
      <div class="note-create-form">
        <el-input
          type="textarea"
          autosize
          placeholder="Enter note content"
          v-model="note.text">
        </el-input>
      </div>
    </ul>
    <div class="app-controls">
      <div class="u-flex-row">
        <el-button type="primary" class="u-elastic" @click="saveNote()">Save</el-button>
      </div>
    </div>
  </div>
</template>

<script>
const uuidV4 = require('uuid/v4');

export default {
  props: ['id'],
  data() {
    return {
      note: {},
      noteIndex: -1,
    };
  },
  methods: {
    saveNote() {
      const notes = this.$localStorage.get('notes');
      if (this.note.text !== '') {
        const note = Object.assign({}, this.note, {
          id: this.note.id || uuidV4(),
          timestamp: Date.now(),
        });
        if (this.noteIndex >= 0) {
          notes[this.noteIndex] = note;
        } else {
          notes.push(note);
        }
      } else if (this.noteIndex >= 0) {
        notes.splice(this.noteIndex, 1);
      }
      this.$localStorage.set('notes', notes);
      this.$router.push('/');
    },
    loadNote() {
      const notes = this.$localStorage.get('notes');
      this.noteIndex = notes.findIndex(note => note.id === this.id);
      if (this.noteIndex >= 0) {
        this.note = notes[this.noteIndex];
      } else {
        this.$router.push('/');
      }
    },
    goBack() {
      this.$router.push('/');
    },
  },
  mounted() {
    if (this.id) {
      this.loadNote();
    }
  },
};
</script>

<style>
.note-create-form {
  padding: 20px 16px;
}

.el-textarea__inner {
  border: none;
}
</style>

There are a number of things going on here. I assume you get the hang of things overall now and so I’ll just gloss of the key points.

And, that concludes if for the application! You should be able to view the following screens in your application. Click around to see how the application works.

Viewing note

Create note

The entire code for the project is hosted on GitHub.

 Summary

In this guide, I have managed to introduce the basics of Vue.js and core features and parts of the library that you will most likely use very frequently. We learnt how to use vue-cli to generate our projects, about single file components in Vue.js, a bit of routing, components and basically everything you need to get started building actual applications.

This guide is imperfect and will continue to undergo modifications but for now you have all the tools required to get started using Vue.js.

I hope you’re excited about that! Happy building!

 
80
Kudos
 
80
Kudos

Now read this

Demystifying Token-Based Authentication using Django REST Framework

Authentication is one of those things which have now been considered a rote and repetitive task when doing web development. Most applications you will ever develop almost always need to have some form of user authentication to allow... Continue →