Skip to content

Creating an app with Vue

qiubee edited this page Jan 25, 2021 · 7 revisions

Vue is a framework for building applications on the web. It's among the most popular frameworks currently. There are more frameworks available that do the same but have their own implementation. The most popular frameworks at this time of writing are:

  • React
  • Svelte
  • Vue

My choice for using Vue is mainly because of the size of the community and the good documentation it provides. Due to a new big release of the Vue framework, version 3.x.x, it was quite confusing to learn the correct way of how to use the framework, because the documentation was not complete. But eventually I understood the basics of the framework.

In the next paragraphs I explain how the Vue framework works and how I used Vue to create my app.

Vue's building blocks

To create a Vue app you can either use the command-line tool to let Vue initialize a project or manually create it yourself. If you do it yourself you need to manually configure the project. By doing it with the command-line tool it'll be done for you by answering some questions.

After you initialized the project you have to folders: public & src (source). Inside the public folder you create the HTML-file where the Vue app will be built. Inside the source folder is where most of your development takes place. Here you create the main.js JavaScript file where you create the app and mount it to the HTML-file. It means that the app will be created in the HTML-file by referencing a HTML-element and creating the app in it. Commonly this is done by creating a <div> element with an ID called app. Then you also need the initial Vue file that will be mounted. This is commonly called App.vue and imported in the main.js file. In the App.vue file you build your application with components you create.

Components

Indside the source folder there is commonly a folder called components. This is the place where you create reusable templates. These templates are the building blocks of your app. They contain the HTML, JavaScript (logic) and CSS code. In these components you create the elements and programm how the component handles the data from the user or from external sources. Based on the data the component behaves a certain way. The main benefits for using a framework like Vue is that you have control when and how a element is implemented. To know how and when to take certain actions, when the component will be created and when it updates, you have in Vue a paradigm called lifecycle hooks.

Lifecycle Hooks

A lifecycle is the life of a component: from creation to deletion. In Vue a component is created, added to the DOM, updated or destroyed. Each of these steps you can influence. You can decide yourself what happens before or after a component takes a certain step. To do this you have hooks that allow you to add your code that influence what a component does. There are multiple hooks where you can interfere. In this diagram you can see what hooks are available and what the lifecycle of a component is:

Diagram with the steps Vue takes to create and update a component

You can use these hooks by using the corresponding function and apply your code.

Data binding and component properties

When you use a component you commonly work with data. Either from the user or from an external source. To use this data in Vue you can pass it on inside or outside a component.

To use data inside you component Vue has a method called data(). Here you state what type of data it beholdes. Inside the template you can use the data by binding it. It mainly comes down to passing on the data to an element or attribute in your template. With the mustache {{ }} syntax for text interpolation or v-bind, v-on or v-if for attributes, you can create dynamic behavior. Based on the data the template changes.

To use data in other components e.g. child components, you can use component properties. This is almost the same as passing down data inside the component, but instead it will be in the props object. Here you add the name of the data to the props object and add the data type. By doing this you can use the data inside child components and have a child component react on the parent component.

My application

Now with the basics explained, I want to show you how I structured my application in Vue.

First I created the main.js file and imported the createApp function and the main file of the application App.vue.

main.js:

import { createApp } from "vue";
import App from "./App.vue";

createApp(App).mount("#app");

Inside the App.vue file I imported a component called Article. This is where the content and visualisations are created. I added it to the template so Vue knows that to build.

App.vue:

<template>
  <Article />
</template>

<script>
import Article from "./components/Article.vue"
export default {
    name: "App",
    components: {
        Article
    }
}
</script>

<style>
</style>

Then we go inside the Article component and see how the article is built up. In the template I've created the structure of the article by adding a <header>, multiple <section> elements and a <footer>. In the <script> I've added several components: Markdown, ProvinceMap and MunicipalityMap. I will explain how I created the last two components in detail in another article Integrating D3 in Vue. The Markdown component I created has a property called content where I can pass in markdown content. By importing the content from markdown files to the Markdown component, the Markdown component will then convert the markdown to valid HTML. When I update the markdown file(s) it will automatically update the content in the template. Then I add the ProvinceMap component that contains the map with information about parking facilities in the Netherlands in each province. The same can be said about the MunicipalityMap component but then instead of provinces being displayed, the municipalities in the Netherlands are visualized. Together with some styling the article is built.

Article.vue:

<template>
    <article>
        <header>
            <h1>Nederland parkeerland?</h1>
        </header>
        <section>
            <Markdown :content="intro" />
        </section>
        <section>
            <Markdown :content="provinces" />
            <ProvinceMap />
        </section>
        <section>
            <Markdown :content="municipalities" />
            <MunicipalityMap />
        </section>
        <footer>
            <Markdown :content="resources" />
        </footer>
    </article>
</template>

<script>
import Markdown from "./Markdown.vue";
import ProvinceMap from "./ProvinceMap.vue"
import MunicipalityMap from "./MunicipalityMap.vue"
import intro from "../assets/story/intro.md"
import provinces from "../assets/story/parkeren-provincies.md"
import municipalities from "../assets/story/parkeren-gemeenten.md"
import resources from "../assets/story/bronnen.md"

export default {
    name: "Article",
    components: {
        Markdown,
        ProvinceMap,
        MunicipalityMap
    },
    data() {
        return {
            intro: String,
            provinces: String,
            municipalities: String,
            resources: String
        }
    },
    created() {
        this.intro = intro;
        this.provinces = provinces;
        this.municipalities = municipalities;
        this.resources = resources;

    }
}
</script>

<style>
:root {
    --main-blue: #90DBE0;
    --dark-blue: #92B0F7;
    --light: #BDFBFF;
    --grey-blue: #215D61;
    --dark-grey: #184447;
    --light-blue: #DFD5F0;
    --light-green: #92F7B9;
    --green: #B0ED8C;
    --red: #E0A379;
}

@font-face {
    font-family: Neufreit;
    font-weight: 900;
    src: url("../assets/fonts/neufreit-extrabold.otf") format("opentype");
}

@font-face {
    font-family: Tommy Soft;
    font-weight: 700;
    src: url("../assets/fonts/tommy-soft-bold.otf") format("opentype");
}

@font-face {
    font-family: Tommy Soft;
    font-weight: 600;
    src: url("../assets/fonts/tommy-soft-medium.otf") format("opentype");
}

@font-face {
    font-family: Tommy Soft;
    font-weight: 400;
    src: url("../assets/fonts/tommy-soft-regular.otf") format("opentype");
}

body, * {
    margin: 0;
    padding: 0;
    font-family: Avenir, Helvetica, Roboto, Ubuntu, Arial, sans-serif;
}

ul, ol {
    list-style-type: none;
}

a {
    text-decoration: none;
}

h1, h2, h3, h4, h5, h6 {
    font-family: Neufreit;
    font-weight: 900;
}

h1 {
    font-size: 2rem;
    margin: 1.5rem 0;
}

h2 {
    font-size: 1.75rem;
}

p, a, form *, text, option, li, em {
    font-family: Tommy Soft;
    font-weight: 400;
    color: var(--grey-blue);
}

strong {
    font-family: Tommy Soft;
    font-weight: 700;
}

em {
    font-style: italic;
}

{...}

</style>

Markdown component

The Markdown component is the component to help me add content. Instead of manually adding HTML and the content, I wanted to use markdown. Markdown is a type of markup language to write content. The benefit is that Vue supports markdown. That means it can interpret markdown and convert it HTML. But instead of letting Vue handle the markdown I wanted to create my own component to convert markdown to HTML. I'll explain how I did exactly that.

First I added two libraries: Showdown and axios. Showdown is a library to convert markdown to HTML and the opposite, and Axios is a library to create HTTP requests. First I created two properties content and url. The content is the content from a markdown file and the url can link to a markdown file. The component can now receive input, either the content or url as a string.

Then I use the created() method where I add the logic where it will check if content or a url has been given. When markdown content is given it will convert the markdown correctly to HTML and pass it to the html data property. Inside the template it will directly add the HTML with the v-html directive. It is not recommended to use this directive when allowing users to directly modify the HTML because it can cause for security issues. That's why I only use it to convert it to HTML through markdown files. If I wanted to use an url I need to further validate the input to counter potential XSS attacks.

When an url is given, it will use the get() function to try and create a get request for the markdown file. Then it will convert it to HTML and add it to the template.

Markdown.vue:

<template>
    <div v-html="html"></div>
</template>

<script>
import * as showdown from "showdown";
import * as axios from "axios";

export default {
    name: "Markdown",
    props: {
        content: String,
        url: String
    },
    data () {
        return {
            html: String
        }
    },
    async created() {
        const url = this.url;
        const content = this.content;
        if (content) {
            this.html = convertMarkdownToHtml(content);
        } else if (url) {
            const markdown = await get(url);
            this.html = convertMarkdownToHtml(markdown);
        }

        async function get(url) {
            try {
                const { data } = await axios.get(url, {
                    responseType: "text"
                });
                if (data) {
                    return data;
                }
            } catch (err) {
                return "Could not load content";
            }
        }

        function convertMarkdownToHtml(text) {
            const converter = new showdown.Converter({
            noHeaderId: true
            });
            return converter.makeHtml(text);
        }
    }
    }
</script>

<style>
</style>

To let Vue know how to interpret a markdown file you have to add a loader. This will look for any markdown file and convert it to text. I used the raw-loader to convert the markdown to plain text. You have to add the loader inside the vue.config.js file for it to work.Now I could import markdown and add it to the Markdown component.

vue.config.js:

module.exports = {
    chainWebpack: function (config) {
        config.module
            .rule("md")
            .test(/\.md$/)
            .use("raw-loader")
                .loader("raw-loader")
                .end();
    }
};
Clone this wiki locally