Holy crap it is definitely yet another jQuery tour/walkthrough plugin! And, are you sitting down?, it's written in CoffeeScript! Arrrggg!
Well, too bad for you! I wanted a simpler, less things-happen-automatically toolkit for making tours. If I need auto-advance/progress between 'legs' (get it, legs of the tour :)) I just need some callbacks where I can write my setTimeouts. If I want to highlight some elements, I just write some styles to do that and apply them appropriately. I much prefer this approach, maybe you do too :)
A word about dependencies: jQuery is the only hard dependency, and I expect it will be fairly straight-forward to support Zepto as well. scrollTo and imagesLoaded are optional and depend on what you're trying to do and how your tour legs are implemented. See the docs for more on that.
jQuery TourBus does not need to exist, there are so many plugins that do exactly this already, aren't there?
You know how it is, I just didn't really like them. Either too much configuration in the DOM, or not enough. And, I prefer the simplicity of event-driven APIs. A ton of styles and animation that results in performance problems. All of these things ultimately stem from trying to do too much automatically.
This plugin was written by me, Ryan Funduk, as a weekend project so I could use it in two other side-projects of mine: CourseCraft ('the easiest way to make an e-course and sell it') and Bugrocket ('a lean, hassle-free bug tracker for small dev teams').
Do me a favor and check them out :)Start the Demo Tour »
jQuery TourBus takes more of a toolkit approach than some of the alternatives that try to have focusing elements, auto-progress with timers and indicators, on and on, all built in... too much stuff! Less is more!
Start by including the jQuery plugin and base styles on your page, of course. Then we can define some 'legs' of our tour. Imagine you have a page with some stuff you want to lead the user through, maybe some elements like #nav and #sell...
Now the tour variable has the ordered list of legs we defined above. You can trigger events on this element:
Here are the events it responds to:
|depart.tourbus||Starts this tour from the beginning.|
|stop.tourbus||Stop the tour and take everyone home :(|
|next.tourbus||Advance to the next leg of the tour.|
|prev.tourbus||Go back to the previous leg.|
If you prefer, you can get a reference to the Bus instance itself and call methods directly. You can build it the same way as above and grab the instance:
Or you can build it directly:
Here are its methods:
|depart||Starts this tour from the beginning.|
|stop||Stop the tour and take everyone home :(|
|next||Advance to the next leg of the tour.|
|prev||Go back to the previous leg.|
|currentLeg||The Leg instance of the current leg.|
|showLeg( [index] )||Forcibly show the current leg of the tour, or the leg at an optional index.|
|hideLeg( [index] )||Forcibly hide the current leg of the tour, or the leg at an optional index.|
|repositionLegs||Set/reset the position of all legs (physical positions on the page).|
|destroy||destroy all legs, unbind all event handlers. Tour is over!|
A tour is made up of 'legs', so there is an instance
for each of those as well. Here are the methods on Leg.
Note: You should not need to call these methods yourself unless doing custom stuff. See convenience classes.
|render||Generate markup for this leg and insert it into the DOM, replacing any that already exists.|
|destroy||Remove this leg from the DOM and unbind event handlers.|
|reposition||Set/reset the position of this leg (its physical position on the page).|
|scrollIntoView||Scroll the page, if possible, so that this leg is in view, and according to it's various settings (such as context).|
|show||Set visibility, opacity and z-index such that this leg is visible.|
|hide||Set visibility, opacity and z-index such that this leg is invisible.|
The tourbus plugin constructors take some global options as an object like you would expect. They are:
You don't often want to only use globals that affect every single leg... so you can specify the above 'leg' options on a per-leg basis. This is done in the markup with data-* attributes. Here's all of them, they match up with the global defaults above pretty obviously. Only the first one is special and usually required, it defines the target of the leg (what it should point to/be near):
|data-el||The jQuery selector of the element that this leg should 'target' on-page. If you leave this off, it will assume you want a modal, and it will be centered inside the container of the tour (or you can override it with data-top, as seen below).|
|data-scroll-to||Override scroll position (0 for top of page, etc) for this leg (normally it would be computed based on the offset and context).|
|data-scroll-speed||The duration of the scrolling animation (can be 0 for instant, or just don't include jQuery-scrollTo on your page).|
|data-scroll-context||How much space to leave above the leg when scrolled to (if scrolled to).|
|data-orientation||Position of this leg in relation to its target. Default is bottom, can be any of top, right, bottom, left or centered to force a modal regardless of data-el.|
|data-align||Alignment of leg in relation to its target. Default is left for orientations of top/bottom or top for orientations of left/right. Valid values (again depending on orientation): top, right, bottom, left, center.|
|data-width||Specify a static width for this leg.|
|data-margin||Extra space between the leg and it's target.|
|data-top||Override the top offset of this leg.|
|data-left||Override the left offset of this leg.|
|data-arrow||Specifies where the arrow/pointer should be shown (if applicable). Defaults to '50%' and can be any valid CSS value. If you only pass in a number, it will assume pixels.|
So you know how to define legs, how to customize them, how to setup globals, how to start/stop the tour, etc. This section covers some other bits that don't fit anywhere else.
Any element with one of these convenience classes will perform the associated action when clicked – easy.
|tourbus-stop||Stop the tour and take everyone home :(|
|tourbus-next||Advance to the next leg of the tour.|
|tourbus-prev||Go back to the previous leg.|
For more fine-grained control, simply bind your own event handlers.
Note: However, since the elements you want to bind event handlers to aren't in the DOM yet, you'll probably want to use this handy helper:
You will probably want to modify the look/feel of the leg popups (including the arrow it displays, etc). In jquery-tourbus.css you'll find the basics of the leg styles (padding, background color, etc) which you can either edit or override in your own stylesheets.
One thing that is a bit tricky is the arrow. You'll need to look for the following styles:
Notice they have different border widths, that's because the 2px difference results in a 2px border, colored in the styles on the following lines.
When you change these values, the same values need to be changed in the rest of the arrow styles as well, eg:
So, if you changed 14px or 16px above then they also need to be changed everywhere else those values appear... except they need to be negative!
Hopefully changing colors here is self explanatory. Even better would be to use the LESS version as it uses variables for colors (and the margins described above) and is more succinct and easier to read.
At times you'll want to put images either in your legs themselves, or in your document and target them with legs. If you do, you'll notice that the positioning is probably all wrong, and that's because at the time of the calculations for positioning the popups the images are not likely to be loaded yet.
I recommend you use jQuery-imagesLoaded to solve this problem. Simply wrap your entire tour initialization in a call to imagesLoaded:
Now your tour won't get setup at all until the images are loaded. You will probably need this any time the layout of the page is determined by the size of the loading images. If you've got more of an application where things are static sizes/positions and images just load into that layout (icons and the like) then you're probably ok.
Here's some example code and suggestions on how you might implement some advanced use-cases.
The first thing you need to know is that data-* attributes unused/unknown to TourBus will be available on your legs as rawData. Also anything passed into a TourBus constructor that is unrecognized will be available as options.
This uses jQuery conventions. The property data-some-custom-property will be available in rawData as rawData.someCustomProperty.
Internal properties of Leg and Bus instances that you will find useful for advanced use-cases.
|currentLegIndex||The index of the current leg. If you modify this then the next calls to showLeg, next and so on will use this index. Eg. you could use this to skip N legs by binding a click handler that increases this value by N and calling showLeg.|
|options||All of the merged options (from the constructor and defaults).|
|running||Whether this tour is running or not right now.|
|legs||An array of Leg instances, in the order they were defined in the DOM. Note: The legs are not actually inserted until departure.|
|$target||jQuery wrapped element where the legs have been inserted. Note: The legs are not actually inserted until departure.|
|$el||jQuery wrapped element which contains the steps. This is the original element you called tourbus on.|
|bus||The Bus this leg belongs to.|
|rawData||All the data-* attributes on this leg.|
|index||The index of this leg in its tour.|
|$target||The element this leg targets (will be positioned near, etc).|
|$el||The leg element in the DOM.|
|content||The innerHTML content of this leg.|
This is how you can implement custom behavior easily, simply give your legs individual custom properties, or give them to TourBus as global options.
If you're doing something seriously weird, you might want access to the core Bus and Leg classes. They are entirely hidden in the plugin's scope, so you can't really get at them. expose to the rescue!
expose takes any object-like as a parameter, here we just use window. Now you can easily monkey patch methods or add methods to the prototypes all you like.
The simplest case of auto-progression would look like this. First, the markup for a leg which should auto-progress to the next leg after 3 seconds:
Simple! This also includes a check so that we don't progress to the next leg automatically unless we are still on the original leg when the timeout fires. You could skip that if it was unnecessary.
You can also show a specific leg without actually progressing further through the stack by passing an index to showLeg. For example, if you wanted to always show the first leg (a banner at the top of the page or similar), you could call showLeg(0) at the beginning of every onLegStart.
Say we want to fade in or do some fancy sliding and stuff of a leg. We can use the onLegStart for animating in and onLegEnd for animating out.
Here we use the fact that returning false from onLegStart (or onLegEnd) will prevent the normal behavior, and we just implement it ourselves. $el has the actual step element in it...
Lastly, maybe you want to draw a lot more attention to a specific target element as the tour is going on. We want the rest of the page to gray out, and only the element pointed to by this leg to be visible as usual. Easy.
Let's start with some styles:
Next we just write an onLegStart and onLegEnd handler that applies these styles.
That's it. Simple.
It also scrolls for you, if you have jQuery.scrollTo included on your page.
That's right, you're looking at a real tour right now. Well, sort of 'real'. It's more like a demo of a tour with silly nonsense in it instead of actual content. Later on there is some documentation... that's useful content, but it's not part of this tour.
This is a pretty cool bus...
I'm aligned 'center', btw...