Andrew Welch

Andrew Welch · Insights · #craftcms #vuejs #frontend

Making the web better one site at a time, with a focus on performance, usability & SEO

2016.12.09 · 5 min read ·

Using VueJS 2.0 with Craft CMS

VueJS is a simple yet powerful library for building modern web interfaces, and it works great with Craft CMS

Nice View

While the current state of the JavaScript world is indeed borderline insanity, occasionally a project comes along that merits a look. VueJS is one of them, specifically the recently released VueJS 2.0.

So what is it, exactly? Essentially, it’s a way to create interactive, data-driven web interfaces using JavaScript. It’s not a replacement for jQuery, but you may find that you don’t need jQuery anymore if you use it. It’s most similar to React or Angular, at least in terms of the problems that it tries to solve.

jQuery is all about DOM manipulation; Vue is about making interactive interfaces. Or as the cool kids are calling it, reactive. There’s definitely some overlap, but Vue provides an architecture for building frontend interfaces, whereas jQuery provides a toolkit for DOM manipulation.

The appealing thing about VueJS is that despite the fact that it scales well, the learning curve isn’t that steep at all. And it makes frontend JavaScript fun again.

First, the bad news. I’m not going to teach you VueJS in this article. There are plenty of resources for that which do a far better job than I ever will, from the official VueJS documentation to some completely free Laracasts Learn Vue 2: Step By Step on the subject. There’s a really good paid course Vue JS 2 - The Complete Guide, too (no affiliation, etc.), and a nice summary article Why you should start using Vue.js.

But what I am going to do is show you how I replaced jQuery on this very website you’re reading with VueJS, and how you can integrate it with Craft CMS.

The simplistic examples provided here barely begin to scratch the surface of what Vue can do. However, they do present a real-world example of how you can adapt it to the websites you build. It’s not just for “web apps.”

Diving Into the Code

The first thing we need a bit of JavaScript for is the little “hamburger” menu in the masthead banner. When people click on it, it should show the menu, and then hide it when they click on it again. Pretty simple stuff. Using jQuery, we might do something like this:

$("#nav-icon").click(function() {
  $(this).toggleClass("open");
});

All this does is add the class .open when our #nav-icon is clicked on, and it removes it when it’s clicked on again. The .open class looks like this:

ul.nav-items.open {
  height: 100%;
  opacity: 1;
}

So okay, how do we do this in Vue? The HTML markup looks like this:

<div id="nav-icon" v-bind:class="{ open: menuOpen }" v-on:click="toggle">

Or we can make it even more concise using the shortcuts for v-bind and v-on:

<div id="nav-icon" :class="{ open: menuOpen }" @click="toggle">

And then we just include a little bit of Vue JavaScript to do the work for us:

new Vue({
    el: '#nav-menu',
    data: {
        menuOpen: false,
    },
    methods: {
        toggle: function() {
            this.menuOpen = !this.menuOpen;
        }
    },
});

We are creating the new Vue object, setting the value of the menuOpen data to false initially, and providing a toggle method that toggles the value of menuOpen.

What we did in our HTML is we told Vue that we wanted to bind the class .open to the value of our menuOpen data. So when menuOpen is true, our #nav-icon has the class .open applied, and when menuOpen is false, it doesn’t.

Then we told Vue that we wanted to bind the click event to our toggle method. That’s it! Not so bad, is it? If you don’t “get it” right away, don’t feel bad. I’m just trying to show you how it works from a gestalt perspective, you don’t need to learn the semantics now.

So okay, big deal, you might say. The jQuery version looks just as easy, and makes sense to me already! The big deal comes in when you are making something of substance, where the architecture that Vue provides makes it easier and quicker. And more robust.

Building a front-end UI in jQuery can be done; but it’s akin to playing a game of Jenga: eventually, it’s going to fall over. Vue gives you scaffolding to ensure that what you’re building has a solid foundation, so that adding a penthouse suite at the top is painless.

It’s All About the Mustaches

So the other thing we need a bit of JavaScript for is the checkboxes on the home page. We need to ensure that only two of them can ever be checked at any given time, and we want to display custom message depending on the combination of checkboxes that are selected.

Normally, VueJS uses {{ }} as delimeters to display variables on the frontend, so for instance if we did:

<h1>{{ menuOpen }}</h1>

…it’d print out the value of our menuOpen data. Yep, you can mix this into your HTML code just like a templating language! Vue has a virtual DOM that it tracks behind the scenes, so that when changes are made it intelligently calculates the difference, and pushes only those small changes into the real DOM that is displayed in the web browser. This is how the {{ menuOpen }} statement can display something on the webpage.

We want to use this functionality to output our special message that’s based on the checkboxes the user has selected. Aha, but wait! That’s what Twig uses as delimiters, too! We have one too many mustaches here…

Mustache

So does this mean that we can’t use Vue with Craft CMS? Of course not, otherwise it’d make the title of this article pretty specious. As it turns out, all we have to do is tell Vue we want it to use different delimiters, like this:

new Vue({
    el: '#nav-menu',
    delimiters: ['${', '}'],
    data: {
        menuOpen: false,
    },
    methods: {
        toggle: function() {
            this.menuOpen = !this.menuOpen;
        }
    },
});

And then we can output things via Vue like this ${ menuOpen } and we can mix and match it with our normal Twig {{ }} syntax. When a frontend request happens, Craft CMS fields the request, then uses Twig to render the template, and finally Vue is instantiated in the browser (along with all of your other JavaScript), and does its frontend rendering.

Another way you could do it is use the Twig {% verbatim %} block tag, which causes Twig to not process anything within it. Just wrap your VueJS code in {% verbatim %} {% endverbatim %} and away you go.

I told you I wasn’t going to teach you VueJS, and I’m going to stick to that. Plenty of people with far more expertise than me have created some wonderful resources for learning Vue. But I will show you the code for the checkboxes on the website. Here’s the HTML markup:

<div class="checkbox-wrapper">
    <input :value="0" v-model="picked" type="checkbox" id="fast-checkbox"  />
    <label for="fast-checkbox" class="check-box"></label>
    <h1 @click="select(0)" class="siteSlogan">Fast.</h1>
</div>
<div class="checkbox-wrapper">
    <input :value="1" v-model="picked" type="checkbox" id="cheap-checkbox" />
    <label for="cheap-checkbox" class="check-box"></label>
    <h1 @click="select(1)" class="siteSlogan">Cheap.</h1>
</div>
<div class="checkbox-wrapper">
    <input :value="2" v-model="picked" type="checkbox" id="quality-checkbox" />
    <label for="quality-checkbox" class="check-box"></label>
    <h1 @click="select(2)" class="siteSlogan">Quality.</h1>
</div>
<h4 class="siteSlogan"><br /></h4>
<h4 id="slogan-subtitle" class="siteSlogan">&nbsp;<span v-cloak>${ message }</span>&nbsp;</h4>

And here is the Vue JavaScript code to implement them:

new Vue({
    el: '#checkboxes',
    delimiters: ['${', '}'],
  data: {
    picked: [],
  },
  methods: {
    select(id) {
     if ( this.picked.indexOf(id) == -1 ) {
        this.picked.push(id)
     } else {
        this.picked.splice(this.picked.indexOf(id), 1)
     }
    }
  },
  watch: {
    picked(value) {
      if (value.length > 2) {
        this.picked.shift()
      }

    }
  },
  computed: {
    message() {
        if ( this.picked.length < 2 ) {
            return '(check what you need)'
        } else if ( this.picked.indexOf(0) === -1 ) {
            return '(cheap & quality will take a long time)'
        } else if ( this.picked.indexOf(1) === -1 ) {
            return '(fast & quality costs money)'
        } else if ( this.picked.indexOf(2) === -1 ) {
            return '(fast & cheap can never be good)'
        }
    }
  }
});

I’m sure there are even more efficient ways to accomplish this; I’m still learning Vue myself! I’d love to hear any feedback on how to make it better or more concise.

Give Vue a View!

Learning VueJS is not hard, and it’s something that could very much improve the projects you’re working on right now. And it works great with Craft CMS & Twig, so there’s no excuse!

I also think that you will very likely be hearing a lot more about Vue in the future. It’s really a hidden jewel.

We haven’t even gotten into the really cool stuff like VueJS server-side rendering, Vue components, and Vue’s fantastic performance. But we have to leave something for future articles…

Buried In Tag Manager Tags

· 2017.04.19

Tags Gone Wild! Managing Tag Managers

#frontend #devops #performance

Homestead Prairie Schooner

Insights · 2017.03.31

Local Development with Vagrant / Homestead

#devops #frontend #homestead

Tidal Wave Disaster

Insights · 2017.03.20

Mitigating Disaster via Website Backups

#backups #devops #craftcms

Web Host Server

Insights · 2017.03.09

How Agencies & Freelancers Should Do Web Hosting

#devops #hosting #vps

Critical Css Stopwatch

Insights · 2017.02.28

Implementing Critical CSS on your website

#critical-css #frontend #performance

Autocomplete Search

Insights · 2017.02.22

Autocomplete Search with the Element API & VueJS

#autocomplete #elementapi #vuejs

Structured Data

Insights · 2017.02.11

JSON-LD, Structured Data and Erotica

#json-ld #structured-data #SEO

Craft Cms 3 Beta

Reviews · 2017.02.09

Craft 3 Beta Executive Summary

#craftcms #Craft 3 #Review

No Robots

Insights · 2017.02.02

Preventing Google from Indexing Staging Sites

#frontend #craftcms #SEOmatic

${ category } · ${ blog.postDate }

${ blog.title }

#${ tag }