Bootstrap Tour doesn't remember where I left

前端 未结 2 1794
别那么骄傲
别那么骄傲 2020-12-28 10:33

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

相关标签:
2条回答
  • 2020-12-28 11:07

    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:

    • bootstrap.css
    • bootstrap-tour.min.css
    • jquery.min.js
    • bootstrap.js
    • bootstrap-tour.min.js

    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

    0 讨论(0)
  • 2020-12-28 11:29

    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.

    0 讨论(0)
提交回复
热议问题