June 20, 2017

VueJS ❤️ Craft CMS - Single Page App: Vue Router

Welcome back.

In my last post, one of the first things we touched on was editing Craft's general.php and then either creating (or editing) routes.php to override Craft's default routing function.

The regex told Craft that for any route called, with the exception of any route starting with /api (we'll come to creating the API later), then we want Craft to load our index.twig template.

So given you'd normally have a Twig template for a single entry, how does this translate to Vue?

I need Vue Router to handle the lifting here. Vue Router is an official plugin for Vue and as you've probably guessed, allows us to route the URLs of our Vue app.

Down to business

First things first, we need to install Vue Router and then let our Vue app know we want to use that plugin.

npm i vue-router --save-dev

And then main.js we would add

import VueRouter from 'vue-router'
import { routes } from './routes'

Vue.use(VueRouter)

const router = new VueRouter({
  mode: 'history',
  routes
})

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

So in the code block above, I've imported Vue Router to my app, I've imported my routes from my yet to be created routes.js.

Then I'm telling Vue I want to use Vue Router, then setting a new ES6 constant, configuring a Vue Router instance to history mode and to use routes from router.js.

I'm then using that constant within my Vue instance.

So far, nothing has changed on the front end.

Next I'll configure some actual routes for the app. I'm going to need:

  • default route that will show my posts
  • a route to view a single entry
  • a route for a user to login
  • a route for a user profile
  • a route to post a new entry to Craft

To do that, I need to create my route file src/js/routes.js

import Detail from './views/Detail'
import Home from './views/Home'
import Profile from './views/Profile'
import New from './views/New'

export const routes = [
  { path: '/', component: Home },
  { path: '/:slug/:id', component: Detail, name: 'job', props: true },
  { path: '/profile', component: Profile, name: 'profile', props: true },
  { path: '/new', component: New, name: 'new', props: true }
]

What's going on here is that I'm importing the different views that the app is going to use. These views are standard Vue components that are going to reside at src/js/views/ and inside that directory, I'm going to have Home.vue, Detail.vue, Profile.vue and New.vue.

Now, you might look at those and say "Steven ya rocket (if you're Glaswegian), you're one short".

My intention is to handle the login with some conditional logic on attempt to create a new entry or viewing the profile of the logged in user, so we don't need that. At least not for now.

The first view I'm going to need is Home.vue so I'll scaffold this like so

<template>
  <div>
    <h1>This is the home page</h1>
  </div>
</template>

<script>

export default {
  name: 'home'
}

</script>

<style>

</style>

Hopefully now you're starting to see a pattern with Vue components where they have a template, script and style block.

The template always needs a wrapping div. Of course I've given the template a name of home to match what I'm using in routes.js. I'll save Home.vue for now and get back to the rest of my routes configuration.

As I configure the routes constant (which gets passed back to main.js then { path: '/', component: Home }, is saying that for the default route, load the Vue component I've named Home.

I can then scaffold my other 'view' Vue components in the same way. I'm struggling to think of another name for these other than 'views' - too confusing so if you think of any, help a guy out 😘.

I'll keep these real simple for now and just change the name and static text within.

I'm pretty much done with routes.js but I wanted to go over some of the things that are happening with my other routes.

{ path: '/:slug/:id', component: Detail, name: 'detail', props: true },

I want the path to my single entry to use the entry slug and entry id that Craft will provide and we'll consume from my API.

That will give me a URL of http://mycraftapp.dev/frontend-craftcms-developer/12345 with everything hooked up when I'm done.

Remember, my initial Craft site has a bunch of job listings as entries so your slugs will match your entries.

Next, I'm telling that route I want to serve the Detail.vue component, giving it the name detail and finally I want this route to accept props (properties relating to the Craft entry).

Lastly, I need to make App.vue aware of my use of Vue Router and to do that I'm going to add <router-view></router-view> to it.

In its entirety, App.vue is now looking like

<template>
  <div id="app">
    <mast></mast>
    <div class="container-fluid">
      <div class="row">
        <div class="col-md-12">
          <router-view></router-view>
        </div>
      </div>
      <siteFooter></siteFooter>
    </div>
  </div>
</template>

<script>
import mast from './components/Header/Mast'
import siteFooter from './components/SiteFooter'

export default {
  name: 'app',
  components: { siteFooter, mast },
}

</script>

<style lang="scss">
@import '../scss/app';
</style>

Of course, <router-view> is the important part here but I've add another couple of components here. Again, standard Vue components using conventions I've already laid out. Mast will eventually contain links to my various routes. For now, feel free to create a new component and put some basic markup in there.

Now, if I npm run hot and look at the app, I should see This is the homepage in an <h1> tag.

Then, to check Vue Router is working if I try to visit /new then I should see Post a new entry within an <h1>

And that's us for this one. Next we'll look at configuring the API using the Craft's Element API plugin.

Thanks for reading 🍻

Leave Your Comments