ceyer ceyer - 1 month ago 9
Javascript Question

Bootstrap Tour - Initiating Multipage

I've looked over other questions/answers but I just can't find one that addresses my issue. I'm having trouble initiating Bootstrap Tour on a multipage tour once I get to the second page.

I have the tour start with a click event and

localStorage
is set to
false
. The tour starts fine with the click event but then, when I go to the second step of the tour and load a new page, the tour does not pick up where it left off.

How do I go about resuming the tour from Step two on this new page? I know I need to re-initialize the tour, but I am apparently not doing this correctly.

$(document).ready(function () {
// Instance the tour
var tour = new Tour({
name: "CiteTour",
steps: [{
element: "",
title: "#1",
content: "You can find help with formatting citations on the Guide page. Click 'Next' to go there now.",
placement: ""
}, {
element: "#CiteTour2",
title: "#2 - Citation Resources",
content: "There are several options for getting help with formatting citations. Once on the Guide page, look for the box labeled 'Citation Help.'",
placement: "right",
path: "/newpath",
onNext: function (tour) {
tour.init();
tour.restart();
}
}, {
element: "#CiteTour3",
title: "#3",
content: "This site can help format your research paper and references in APA, MLA, and the other major citation formats.",
placement: "right",
}, {
element: "#AskTour1",
title: "#4 - Ask-a-Librarian",
content: "If you still have questions about citations or citation format, feel free to contact the librarians. Good luck!",
placement: "left",
}],
storage: false,
});

// Initialize the tour
tour.init();
$('#CiteTour-go').on('click', function () {
// Start the tour
tour.start();
});
});

Answer

Issues and Explanation

First, you must make sure you are using storage: window.localStorage, which uses the Storage API. This is the default tour option, so all you have to do is not override it to false as you have done. What this does is allow Bootstrap Tour to persist the current step information across multiple pages within the same domain.

Want Proof? - Open up your dev tools and see: Resources

Second, if you are specifying the path option for any step, you should specify it for all steps. When a single page tour starts, it doesn't need to worry about navigating to different pages, but as soon as you've moved to a new page, if you haven't specified paths for previous steps, bootstrap tour has no way of knowing where to navigate back to.

Furthermore, you need to use an absolute-path reference by prefxing the url with a single slash so it is relative to the root directory. If you use relative paths, the path will be changed as you move through pages/steps. For more info, see my section at the bottom on the Infinite Page Refreshing Issue

Third, as long as you define the tour object and initialize it, the tour will pickup automatically on a new page.

Let's look at a simplified version of what init() does:

Tour.prototype.init = function(force) {
  // other code omitted for brevity

  if (this._current !== null) {
    this.showStep(this._current);
  }
};

So once you've initialized the tour, as long as it notices that a tour has started and has not yet ended (i.e. it has a current step), it will automatically start-up that step. So you don't need to initialize by tapping into the onNext event on your second step.

Multipage Tour Proof of Concept

Editable Plunk | Runnable Demo

script.js

$(function() {

  // define tour
  var tour = new Tour({
    steps: [{
      path: "/index.html",
      element: "#my-element",
      title: "Title of my step",
      content: "Content of my step"
    }, {
      path: "/newPage.html",
      element: "#my-other-element",
      title: "Title of my step",
      content: "Content of my step"
    }]
  });

  // init tour
  tour.init();

  // start tour
  $('#start-tour').click(function() {
    tour.restart();
  });

});

index.html:

<!DOCTYPE html>
<html>
<head>
  <title>Multipage Bootstrap Tour - Page 1</title>
  <link rel="stylesheet" href="bootstrap.css" />
  <link rel="stylesheet" href="bootstrap-tour.min.css">
</head>
<body>
  <div class="container">
    <h1>First Page</h1>

    <button class="btn btn-lg btn-primary" id="start-tour">
      Start Tour
    </button><br/><br/>


    <span id="my-element">
      My First Element
    </span>

  </div>
  <script src="jquery.min.js"></script>
  <script src="bootstrap.js"></script>
  <script src="bootstrap-tour.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

newPage.html

<!DOCTYPE html>
<html>
<head>
  <title>Multipage Bootstrap Tour - Page 2</title>
  <link rel="stylesheet" href="bootstrap.css" />
  <link rel="stylesheet" href="bootstrap-tour.min.css">
</head>
<body>
  <div class="container">
    <h1>New Page</h1>

    <span id="my-other-element">
      My Second Elemennt
    </span>

  </div>
  <script src="jquery.min.js"></script>
  <script src="bootstrap.js"></script>
  <script src="bootstrap-tour.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

Where you've brought in the following libraries:

Infinite Page Refreshing Issue

In a lot of configurations, you'll get into a loop where the page infinitely refreshes, continually attempting to resolve to the path of the current step. Here's a look into why this issue occurs and how to fix it.

How does Bootstrap Tour go to the next step?

When you hit the Next Button, the tour will call showStep(i) for the next step

Here's a simplified version of showStep:

Tour.prototype.showStep = function (i) {
    // other code omitted for brevity

    // get step path
    path = tour._options.basePath + step.path;

    // get current path - join location and hash
    current_path = [document.location.pathname, document.location.hash].join('');

    // determine if we need to redirect and do so
    if (_this._isRedirect(path, current_path)) {
        _this._redirect(step, path);
        return;
    }
};

So, if the current path in the document is different than the path for the next step, then tour will automatically redirect to the next step.

Here's a simplified form of the redirection that just takes into account string values:
I've omitted regex based paths although Bootstrap Tour also supports them

Tour.prototype._isRedirect = function(path, currentPath) {
    var checkPath = path.replace(/\?.*$/, '').replace(/\/?$/, '');
    var checkCurrent = currentPath.replace(/\/?$/, '');
    return (checkPath !== checkCurrent);
};

Tour.prototype._redirect = function(step, path) {
    this._debug("Redirect to " + path);
    return document.location.href = path;
};

Note: The regex is just there to remove query parameters (/\?.*$/) and trailing forward slashes (//?$/`)

When any page loads, it's not sure if Bootstrap Tour has redirected it, or you're just coming back and trying to pickup the tour where you left off.

So on any page when you initialize the tour:

  1. it will launch the current step based off the value in local storage.
  2. when the current step loads, it will confirm that the path for the step is the same as the current url
  3. if not, it will redirect to the step path and start back over with step 1

In other words, it knows how to get to where it needs to go next, but has no way of confirming if that's the case once it gets there. Take this situation for example with a step that looks like this:

var step = {
      path: "index.html",
      element: "#my-element",
      title: "Title of my step",
      content: "Content of my step"
    }

It can be redirected to the relative reference just fine, but when the page loads again, and checks that it has been loaded at the correct address, this will happen:

infinite loop

"KyleMit", you might protest, "can't it just figure out what I want?"

-No!

If you rely on relative paths for redirection, when it's loading a step, it can't gaurantee that you've actually arrived at the step and it will try to redirect you again.

That's because, in web addresses, "index.html" !== "\index.html". They are two different paths! One is guaranteed to be at the domain root, while the other could be anywhere. Imagine you have some nested views like this:

  • Views
    • Shared
      • index.html
      • newPage.html
    • Person
      • index.html
      • newPage.html

When navigating between pages, how can bootstrap know if it's arrived at the correct destination if you've only told it the correct page name.

Which brings us to the resolution of this issue:

Use Absolute URLS Absolutely

Tip: Get a better sense of what's going on by passing in debug:true when creating your tour, which will log every redirect:
redirect

Comments