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
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:
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 init
ialize 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.
Editable Plunk | Runnable Demo
$(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();
});
});
<!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>
<!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:
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:
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:
"KyleMit", you might protest, "can't it just figure out what I want?"
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:
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:
Tip: Get a better sense of what's going on by passing in
debug:true
when creating your tour, which will log every redirect:
Specifically for SPAs built with Angular
The fantastic answer by @KyleMit helped me a lot: I followed all three recommendations, but the tour did not continue. I believe that bootstrap-tour still has a problem with single-page applications.
Since I'm using Angular on an SPA, it's actually the same "web page" but different Angular "views". The tour didn't continue…
Until I added a $(window).resize();
to my fixed navbar's controller. The navbar is fixed at the top of the screen. Therefore, the controller is instantiated once. And I already had a location change handler:
$scope.$on("$locationChangeSuccess", $scope._handleLocationChange);
A hint to that "solution" was the fact that I happened to press F12 to view console messages in the (docked) Firebug panel, and lo and behold, the tour continued! I checked and it would continue when I closed or opened the Firebug panel. I still can't figure out why resize
is necessary but it is innocuous in the context of my app and I need to move on, after so much time spent on this issue. Remove the call to resize
and the tour won't continue after the redirection. Add it back, and the tour will continue as intended.
I had "basePath": "/app/#"
in the configuration of the tour, which spans only two views. Paths were like "path": "/"
(for the "home" view) and "path": "/show/chair"
(a typical view in the application).
I'm using Angular 1.4.8 on this app.
HTH.