Skip to content

Example of how to use Managed Pages in an SPA

This page describes an example of how to implement personalized category pages in a SPA using Managed Hello Retail Pages. It is also possible to use the pages API or SDK directly giving the developer more control of the rendering. This example is focused on integrating Managed Pages in a SPA

The example tries to simulate a very simple SPA where the fragment (hash) part of the url changes when the user navigates. This part would of course be specific to any real SPA where the url and content changes are controlled by the router of a framework like React of Vuejs.

helloretail.js contains code that will automatically inject category pages if a div with a certain id can be found on the page. The filters selecting what products to show on the page is controlled by a data attribute on the div. For a SPA to use this functionality we have to make sure

  1. A div with the correct id is present on the page
  2. The data attribute is updated to contain the correct filter for the category page to be shown

After those two things have been configured the reload function on helloretail.js must be called to make it look for category pages to execute.

Note that calling reload will also perform automatic view tracking and will also look for managed search and recoms to execute. So reload should be called in a central place once for every navigation event.

Example

Comments in the example explain the purpose of each part. This example should be able to run if served from a local webserver and opened in a browser. It needs to be served from a server to overcome limitations with cross site requests if the file is opened directly from the filesystem.

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Shop</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <!-- Initialize helloretail -->
    <script async src="https://helloretailcdn.com/helloretail.js"></script>
    <script>
        window.hrq = window.hrq || [];
        hrq.push(['init', {
            websiteUuid: "4dded48a-1a04-43a0-8c82-c6df5c689827"
        }]);
    </script>
</head>
<body>
    <div class="container">
        <header class="d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom">
            <a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
                <span class="fs-4">My Shop</span>
            </a>

            <ul class="nav nav-pills">
                <li class="nav-item"><a href="#Music" class="nav-link">Music</a></li>
                <li class="nav-item"><a href="#Clothing>Hoodies" class="nav-link">Clothing &gt; Hoodies</a></li>
                <li class="nav-item"><a href="#Clothing>Accessories" class="nav-link">Clothing &gt; Accessories</a></li>
            </ul>
        </header>
      </div>
      <div class="container">
        <div class="row">
            <div class="col" id="page-content">
                <h2>SPA example page</h2>
                <p>Use the navigation links at the top to switch between category pages</p>
            </div>
        </div>

      </div>

      <script>
        // Add code in your routing system (could be Vue Routing or React Router) that handles
        // the case where the visitor has navigated to a category page. In this simple example we
        // are just listening for hashchange and navigate based on that
        addEventListener("hashchange", hashChanged);
        function hashChanged() {
            // Code in this function is just boilerplate to make the example work
            handleNavigation();

            // Use the command queue to make sure hello retail is ready
            window.hrq = window.hrq || [];
            hrq.push(sdk => {
                // If the user has navigated to a category page
                // set the correct filter parameters to the data attribute
                const currentHierarchy = getCurrentHierarchy();
                if (currentHierarchy.length > 0) {
                    insertPageDiv(currentHierarchy);
                }
                // On every navigation call reload for automatic view tracking and 
                // initialization of other managed products
                sdk.reload();
            });
        }
        hashChanged();

        // Code for inserting the page div in the correct container with
        // the correct filter
        function insertPageDiv(currentHierarchy) {
            const div = document.createElement("div");
            div.id = "helloretail-category-page-649ebd2e80e793164328b45a";
            div.dataset.filters = JSON.stringify({
                hierarchies: currentHierarchy
            });
            const container = document.getElementById("page-content");
            container.innerHTML = "";
            container.appendChild(div);
        }
        // Get the current hierarchy from the url. In real
        // applications there are presumably better way of getting 
        // parameters from the url. Maybe as part of the routing system
        function getCurrentHierarchy() {
            let hash = decodeURIComponent(document.location.hash);
            const currentHierarchy = hash.length > 1 ? 
            hash.substring(1).split(">") : [];
            return currentHierarchy;
        }
        // Boilerplate code for handling navigation in this example
        function handleNavigation() {
            let hash = decodeURIComponent(document.location.hash);
            hash = hash == "" ? "#" : hash;
            const elems = document.querySelectorAll(".nav-link");
            [].forEach.call(elems, function(el) {
                el.classList.remove("active");
                if (el.getAttribute("href") == hash) {
                    el.classList.add("active");
                }
            });

            const params = new URLSearchParams(document.location.search);
            params.delete('hr-page');  
            const url = [location.protocol, '//', location.host, location.pathname, params.toString(), location.hash].join('');
            window.history.replaceState({}, "", url);
        }
    </script>

</body>
</html>