Bootstrap Tour Extended

Powerful JavaScript Tour library built for highly dynamic applications (a.k.a SaaS/RIA)

Bootstrap Tour Extended is a fork of Bootstrap Tour by sorich87. I forked it because it was not enough powerful to be useful as a tour library for my own products Bringr and Redsmin.

I wanted a persistant, clean, evented, deferred based Tour library with optional overlay support.

Online app Tour services could not offer (from what I saw) this depth of customization.

Bootstrap Tour Extended allows developers to build quickly and easily extremely configurable product tours using Twitter Bootstrap Popovers.

Some Tour global parameters can be strings or functions because sometimes the thing you want to expose does not exist when the Tour start.

Each #defaults.step parameters specified through new Tour([options]) can be overriden at the step level with tour.addStep([options]).

The default template can be completely replaced at the Tour level or even for a single step. It's a function(step) -> string, so underscore _.template(), jQuery $.tmpl() etc... are supported even if they are not Bootstrap Tour Extended dependencies.

See the demo from the original version by sorich87

I accept pull-requests!

In your web page:

<div id="a"></div><hr><div id="b"></div>

<script src="jquery.js"></script>
<script src="bootstrap.tooltip.js"></script>
<script src="bootstrap.popover.js"></script>
<script src="bootstrap-tour.js"></script>

<script type="text/javascript">
// Initialize the tour
var tour = new Tour({
  name:"myTour",
  persistence:"Memory",
  
  /**
   * Configure step wide parameters
   */
  step:{
    /**
     * Css class to add to the .popover element
     * @description Note: if `addClass` is defined at the step level.
     *              The two defined `addClass` will be taken into account in the popover
     * @type {String}
     */
    addClass:'myPopover',
    
    /**
     * Step wide title function can be defined (and even overriden at each step level)
     * @type {String, Function(step)}
     */
    title: function(step){return "step "+step.index;},
    
    /**
     * Default step content
     * @type {String | Function(step)}
     */
    content: "Default content"
  }
  
  /**
   * etc... See #defaults
   */
});

/**
 * Add a step
 */
tour
  .addStep({
    /**
     * {jQuery | Css Selector | Function} HTML element on which the step popover will be shown
     * @type {String}
     */
    element:   "#a",   
    
    /**
     * {String} Popover position - top | bottom | left | right.
     * @type {String}
     */
    placement: "bottom",
    
    /**
     * Popover title
     * @type {String | Function}
     */
    title:     "Hello world!"
    
    /**
     * etc... See #stepDefaults
     */
  });
  
/**
 * Oh yes, addStep/on/off/one/trigger are chainable
 */
tour
  .addStep({
    /**
     * #c does not exist yet but will be created by the following "show" event handler
     * @type {String}
     */
    element: "#c",
    
    /**
     * This popover will have the css class .bootstrap-tour.myTour-step1.myPopover.myowncssclass
     * @type {String}
     */
    addClass: "myowncssclass" 
  })
  .on("show:step1", function(e){
    // Deferred support for asynchronous operation
    var def = $.Deferred();
    
    // Tell Bootstrap Tour to wait for this promise to resolve before displaying the step
    // Note: in the case of multiple event handler, Bootstrap Tour will wait that all promise are resolved
    // before showing the step
    // Note2: e.setPromise() is optional, if no promises were specified the step will be immediately displayed.
    e.setPromise(def.promise());
    
    // "show" is the only event that does not have a "e.element" attribute
    // simply because the "show" event handler can wait, if necessary, for this element to be created.
    setTimeout(function(){
      $('<div>',{id:"c"}).appendTo("body");
      def.resolve();
    }, 1000);
  });
  
  
tour
  .on("show", function(e){
    console.log("Trying to show step", e.step.index);
  })
  .on("shown", function(e){
    console.log("Step", e.step.index, "shown");
  })
  .on("shown:step0", console.log.bind(console, "First step is displayed"))
  .on("hide", function(e){
    console.log("Hidding step", e.step.index);
  })
  .on("hidden:step1", function(e){
    console.log("Step", 
        e.step.index, 
        "is hidden, this action was triggered by", 
        e.trigger, 
        "the resolved step element was", 
        e.element);
  })
  .on("hidden", function(e){
    console.log("Step", e.step.index, "is now hidden");
  })
  .on("end", console.log.bind(console, "Tour ended"));
  
tour.on("skip", function(e){
  console.log("Bootstrap Tour is skipping step", 
    e.step.index,
    "because this step element",
    e.step.element,
    "was neither found nor visible.");
  // Thus this event handler will never be called
});

// Start the tour
tour.start();

// One more thing, .start(), .next(), .prev() and .end() all return promises!
</script>