Introducing Vue Router and Vuex to Your Sitecore Solution | Perficient Digital

Introducing Vue Router and Vuex to Your Sitecore Solution

Vue.js has been gaining a lot of popularity as a JavaScript framework. There are plenty of blogs out there on Vue.js. In this article I am going to provide some overview on Vue Router and Vuex, and walk through how I made use of these tools in a Sitecore solution.

Use Case

The use case is a Single Page Application for searching product items. The site consists of a sidebar area for category filters and search facets, and a main content area for displaying the product items. Both areas are different (and use different Vue components) for when the user first lands on the Search page, and when the user initiates a search. The search can be applied category filters and other search facets such as size or brand. The product items are pulled with WebAPI calls. The relative URL of a search of ‘sunscreen’ with a category filter ‘skincare’ and a search facet size ‘medium’ looks like: /search/skincare?keyword=sunscreen&facet=size%3A%3Amedium. The site uses Sitecore fields such as header text and background image.

Vue Router Setup

Vue Router is the official router for Vue.js. It has a lot of great features to help build SPAs. Vue Router is modular, which means it can be used to map components to routes. Use <router-view> to let Vue Router know where to render the matching components.

For my solution, I have a Search.cshtml file in which lives the SearchPage.vue component. In SearchPage.vue I have <router-view>s for the sidebar area and the main area to display matching components for each area based on the routes: when the user first lands on the search page (‘/search’), SidebarLanding.vue and MainLanding.vue are matched; when the user initiates a search, SidebarSearch.vue and MainSearch.vue are matched. This is how the SearchPage.vue component’s template looks like:

<router-view name="sidebar" />     
<router-view name="main" />

Inside ‘created()’ of SearchPage.vue I use the addRoutes method on the globally available this.$router  to add route configurations:

created () {
    this.$router.addRoutes(SearchRoutes);
}

In SearchRoutes.js I define the route configurations – which Vue components match a certain path; when a path is requested, those matching components will render in their corresponding outlets. Here is the simplified route configurations inside SearchRoutes.js:

{
    path: '/search',
    components: {
        sidebar: SidebarLanding,
        main: MainLanding
    }
},
{
    path: '/search/:categoryName',
    components: {
        sidebar: SidebarSearch,
        main: MainSearch
    },
    props: {
        sidebar: (route) => ({
            keyword: route.query.keyword,
        }),
        main: (route) => ({
            keyword: route.query.keyword,
            categoryName: route.params.categoryName
        }),
    }
}

‘:categoryName’ in the second path above is a dynamic segment (denoted by a colon), and can match any value at the corresponding segment of a URL of the same structure. When a route is matched, the value of the dynamic segments will be exposed as this.$route.params in every component. Similarly, I can get the query string using this.$route.query.{queryStringName}. As you can see, information (such as keyword and categoryName) contained in the path can be passed as props to the components and used in their containing methods to make WebAPI calls to display the correct search results.

Inside SidebarSearch.vue, <router-link>s are used for navigation. The link URL is specified using the ‘to’ prop. This is how category filters work (simplified):

<router-link :to="getCategoryLink(categoryName)" >{{categoryName}}</router-link>

And the getCategoryLink method:

getCategoryLink (categoryName) {
    searchUrl = { path: '/search/' + category.Name.replace(/[^\w\s]/gi, ''), query: this.$route.query };
    return searchUrl;
}

A URL like “/search/skincare?keyword=sunscreen&facet=size%3A%3Amedium” is constructed by getCategoryLink and passed to <router-link>’s ‘to’ prop, and when the user clicks on that link, Vue Router renders the matching components at the corresponding <router-view>s. Also notice, in the example above, query strings in the route are persisted when constructing the URL using this.$route.query.

Let’s now take a look at how search facets are handled. For facets I have groups of checkboxes that are bound using v-model=”checkedNames”. I have a watcher to push the selected facets to the route query and also navigate to the new route, using this.$router.push.

watch: {
    checkedNames: function () {
        this.$router.push({ query: Object.assign({}, this.$route.query, { facet: this.checkedNames.join('//') }) });
    }
},

On navigating to the new route, MainSearch.vue has logic to pick up the facet from the query of the path using this.$route.query, and the category filter from the dynamic segment (:categoryName) of the path, to make a new WebAPI call to display the updated results that are filtered and faceted.

Sitecore Configuration to Work with History Mode

When using History Mode to get rid of the default Hash Mode, you need some kind of server-side configuration to avoid the 404 error the user gets when directly accessing /search/:categoryName. The search part of my site is a Single Page App; there is only a ‘search’ page in my Sitecore instance and it doesn’t have a child page for the category. Since I am working with Sitecore, I can register custom ASP.NET MVC routes in a way that does not conflict with the default Sitecore routes with a pipeline processor in the initialize pipeline.

public virtual void Process(PipelineArgs args)
{
    RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
    routes.MapRoute("search", "search/{categoryName}", new { scItemPath = "/Search" });         
}
<sitecore>
    <pipelines>
        <initialize>
            <processor type=" MyNamespace.RegisterCustomRoute, MyAssembly" patch:after="processor[@type='Sitecore.Pipelines.Loader.EnsureAnonymousUsers, Sitecore.Kernel']" />
        </initialize>
    </pipelines>
</sitecore>

Vuex for Passing Sitecore Fields

When you have multiple layers of nested Vue components, it can be cumbersome to pass props from your .cshtml file down the chain to all the related Vue components – Vuex to the rescue! Of course that’s not the only thing Vuex is good for. Vuex is Vue.js’s library for state management. It provides a single object that contains all your application level state, with rules ensuring that the state can only be mutated in a predictable fashion. Other than the situation of nested components, when you catch yourself synchronizing multiple copies of the state via events, it’s probably also a good time to use Vuex. You can divide the Vuex container for all application state (a.k.a. the store) into modules when scaling your application.

For my use case I can register a module for the search functionality (since it’s a part of a bigger solution) and store search related state there, including the Sitecore fields that need to be shared among all related Vue components.

this.$store.registerModule('search', searchStore);
const searchStore = {
    namespaced: true,
    state: {
        header: ''
        …
    }
    mutations: {
     …
    }
    …

And inside each component I can make use of the state using Vuex’s mapState helper in computed:

computed: {
    ...mapState('search’, [
        ‘header’,… ])
}

There are a lot more implementation details omitted here, but this is a quick overview if you are looking for how to make use of Vue Router and Vuex in your Sitecore solution to build Single Page Applications.

h49pHk3ORDWKylIlU7bE_maxresdefault

Subscribe to the Perficient Digital Weekly Digest

* indicates required

Leave a Reply