Creating Vue Components

Heads Up!

This article is several years old now, and much has happened since then, so please keep that in mind while reading it.

Then we have a treat for you: This article aims at helping Umbraco and Vue.js beginners figure out how to get started with their Vue project. If you have a basic understanding of JavaScript and Umbraco, this step-by-step guide is for you! We hope to provide you with everything you need to get your first Vue component with Typescript and Umbraco up and running.

Goal

We want to show a neat “Vue Media Gallery” based on the built-in Media Section in Umbraco. The component will be inserted into an MVC Context, so we will get the best of both worlds: routing via MVC – flexibility via Vue Component!

A Match Made in Heaven: Why You Should Pair Umbraco and Vue.js

Umbraco and Vue.js are a perfect fit! With this duo, you can create a Single Page Application (SPA) or even a hybrid with MVC and as many reactive components as you need. For instance, you can build a page structure and navigation entirely with MVC and only a few components with Vue. This is a great way to keep the cost low, but still provide a modern and fast experience. Additionally, as Vue and the Umbraco backend – which is based on AngularJS – have a similar templating system, it is much easier to get started with the development of Vue components, if you already have used AngularJS before.

Why Use Typescript?

Typescript was developed by Microsoft and released in 2012. It is a typed superset of JavaScript which has to be compiled down to pure JavaScript for the browser to interpret it correctly. One of its biggest advantages are the type annotations with the help which a good IDE continuous type checking and highlighting can be performed. At compile time, the typescript files get checked again.

Setting Up Umbraco

Setting up Umbraco is as simple and intuitive as it is to work with the CMS. One of the biggest benefits Umbraco offers is its flexibility which makes it easy to combine with other systems. Once your Umbraco is ready to go, start by creating an Umbraco API.

When creating an API Controller, make sure to use "System.Web.Http" instead of "System.Web.Mvc" as your function attribute declaration, otherwise it won't work. For our demonstration, we are creating a public API, therefore the controller needs to inherit from "UmbracoApiController".

Setting Up Webpack

The Webpack docs describe it as a module bundler. Using Webpack, we compile our Typescript to pure JavaScript. Webpack as well is versatile:

"It can be configured/extended to do lots more. The main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset."

The following npm packages are needed:

  • ts-loader compiles Typescript to JavaScript
  • vue-loader is needed for Single-File Components (SFC's)
module: {
    rules: [
        {
            test: /\.ts$/,
            exclude: /node_modules/,
            use: [{
                loader: "ts-loader",
                options: {
                    appendTsSuffixTo: [/\.vue$/]
                }
            }]
        },
        {
            test: /\.vue$/,
            use: "vue-loader"
        },
    ]
},
plugins: [
    new VueLoaderPlugin(),
    ...
]

 

Want to learn more about Webpack? Why not go read Dennis Zille's 24 Days in Umbraco article, "Increasing Umbraco Performance with Webpack" after this!

Preparing Umbraco

To get the most out of the Vue Approach, we need to define an API endpoint as described in „Setting Up Umbraco“. Our goal is to show a nice media gallery based on the Media Section which includes categories and pagination.

Umbraco API

We create our own controller, derived from the base UmbracoApiController. With a Dependency Injection, we insert a service which is able to consume the built-in „Umbraco Media Library“.

 

using System;
using System.Web.Http;
using byte5.Web.BusinessLogic.Interfaces;
using Umbraco.Core.Logging;
using Umbraco.Web.WebApi;

namespace byte5.Web.BusinessLogic.Controller
{
    public class MediaLibraryController : UmbracoApiController
    {
        private readonly ILogger _logger;
        private readonly IMediaLibraryService _mediaLibraryService;

        public MediaLibraryController(ILogger logger, IMediaLibraryService mediaLibraryService)
        {
            _logger = logger;
            _mediaLibraryService = mediaLibraryService;
        }

        [HttpGet]
        public IHttpActionResult GetMediaByTypeAndCategory(int id, string type = "", string category = "", int page = 0, int pageSize = 300)
        {
            try
            {
                return Ok(_mediaLibraryService.GetMediaByTypeAndCategory(id, type, category, page, pageSize));
            }
            catch (Exception ex)
            {
                _logger.Error<MediaLibraryController>(ex);
                return InternalServerError();
            }
        }
    }
}

ASP.net MediaLibraryService

 

public MediaLibraryViewModel GetMediaByTypeAndCategory(int currentNodeId[……])
        {
            var mediaLibrary = new MediaLibraryViewModel();
            var mediaItemList = new List<MediaLibraryItem>();

            var mediaLibraryMeta = new MediaLibraryMetaInformations()
            {
                PageNumber = page,
                PageSize = pageSize 
            };

            var cache = _context.Content;
            var currentNode = cache.GetById(currentNodeId) as MediaLibrary;

            int currentPageItems = 0;

            [……]

            mediaLibrary.MediaItems = mediaItemList;
            mediaLibrary.Meta = mediaLibraryMeta;

            return mediaLibrary;
        }

Getting Data

Our next step is to access the API end point:

 

import axios, { AxiosResponse } from "axios";

const baseURL: string = "/umbraco/api/keyDate";

class MediaLibraryAPI {
        public async get(id: number, type: string = "", category: string = "", page: number = 0, pageSize: number = 300): Promise<MediaLibraryModel> {
        try {
            const res = await axios.get(`"/umbraco/api/MediaLibrary/GetMediaByTypeAndCategory?id=${id}&type=${type}&category=${category}&page=${page}&pageSize=${pageSize}`);
            if (res && res.status === 200) {
                return res.data;
            }
            throw new Error(res.data);
        } catch (ex) {
            throw new Error(ex);
        }
    }
}
export const mediaLibraryAPI: MediaLibraryAPI = new MediaLibraryAPI();

Your Vue.js Component

Now on to creating our first component: We are using Single File Components (SFC), this means our "template (html)", "script (js|ts)" and "style (css|sass|scss)" are stored in one file.

This is what an SFC boilerplate looks like:

 

<template></template>
<script lang="ts">
</script>
<style></style>

 

 

We want to use class-based components instead of the default "Options API". Hence, we use the "vueproperty-decorator" which extends "vue-class-component". With Vue 3, we can use the "Composition API" to achieve a similar result. This helps us to better organise, split and structure our code. With these libraries, we can declare the "MediaLibrary":

 

    import Vue from "vue";
    import { Component, Prop } from "vue-property-decorator";

    @Component();
    export default class MediaLibrary extends Vue {
        @Prop() public nodeid!: number;
        @Prop() public language!: string;
        @Prop() public headertext!: string;


        // get data from out umbraco api
        public mediaList: MediaLibraryModel = new MediaLibraryModel();

        public async mounted() {
            try {
                this.mediaList = await MediaLibraryAPI.get(this.nodeid);
            }
            catch (err) {
                // do something with it
            }
        }
    }

 

 To display our media items, we loop over them with "v-for".

    <div class="link-tile col-lg-3" v-for="(media, i) in mediaList.mediaItems" :key="i">
        <div class="tile-text">
            <div class="row">
                <div class="col-lg-8">
                    <span>{{media.imageText}}</span>
                </div>
            </div>
        </div>
    </div>

main.ts

The component entry point is "main.ts": This is where we attach the Vue components to our div containers. As you can see, we are looking for a div container with the ID "mediaLibrary" and expose the components with the custom tag "media-library".

import MediaLibrary from "@components/MediaLibrary.vue";
import Vue from "vue";

const mediaLibrary: HTMLElement | null = document.getElementById("mediaLibrary");
if (mediaLibrary) {
    const mediaLibraryApp = new Vue({
        components: {
            "media-library": MediaLibrary,
        },
        el: mediaLibrary,
    });
}

 

partial.cshtml

The last step is to render the div container to which the Vue component will be attached. We will also be passing a few properties to our mediaLibrary.

<div id="mediaLibrary">
    <media-library nodeid="@Model.Id" language="@culture" headertext="@headerText"></media-library>
</div>

 

Umbraco and Vue: One of our favourite couples! With your new skills in Vue components, you will be able to provide interactive content on your Umbraco website and have a smoother experience. We hope with the help of our guide, you will get started right away and use Umbraco and Typescript to create Vue components in no time!

Malte Arzt

Laurens Bergmann