├── .editorconfig ├── .gitignore ├── .travis.yml ├── CNAME ├── LICENSE ├── README.md ├── _config.yml ├── bower.json ├── build ├── css │ ├── bootstrap-tour-standalone.css │ ├── bootstrap-tour-standalone.min.css │ ├── bootstrap-tour.css │ └── bootstrap-tour.min.css └── js │ ├── bootstrap-tour-standalone.js │ ├── bootstrap-tour-standalone.min.js │ ├── bootstrap-tour.js │ └── bootstrap-tour.min.js ├── coffeelint.json ├── composer.json ├── gulpfile.js ├── karma.conf.js ├── package.js ├── package.json ├── smart.json ├── src ├── coffee │ ├── bootstrap-tour.coffee │ ├── bootstrap-tour.docs.coffee │ └── bootstrap-tour.spec.coffee ├── docs │ ├── CNAME │ ├── _includes │ │ ├── footer.html │ │ ├── header.html │ │ └── nav.html │ ├── _layouts │ │ └── default.html │ ├── api.html │ ├── assets │ │ ├── css │ │ │ ├── bootstrap-tour.css │ │ │ └── bootstrap-tour.docs.css │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ │ ├── img │ │ │ ├── apple-touch-icon-144-precomposed.png │ │ │ ├── favicon.png │ │ │ └── masthead-pattern.png │ │ ├── js │ │ │ └── bootstrap-tour.js │ │ └── vendor │ │ │ └── pygments-manni.css │ └── index.html └── scss │ ├── bootstrap-tour-standalone.scss │ └── bootstrap-tour.scss ├── test └── bootstrap-tour.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | bootstrap-tour.sublime-project 4 | bootstrap-tour.sublime-workspace 5 | npm-debug.log 6 | test 7 | docs 8 | _SpecRunner.html 9 | *.DS_Store 10 | smart.lock 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: node_js 4 | node_js: 5 | - 8 6 | addons: 7 | chrome: stable 8 | before_install: 9 | - "export CHROME_BIN=chromium-browser" 10 | - "export DISPLAY=:99.0" 11 | - "sh -e /etc/init.d/xvfb start" 12 | before_script: 13 | - "yarn global add gulp-cli" 14 | - "npm rebuild node-sass" 15 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | bootstraptour.com 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2015 The Bootstrap Tour community 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bootstrap Tour 2 | [![Build Status](http://img.shields.io/travis/sorich87/bootstrap-tour.svg?style=flat)](https://travis-ci.org/sorich87/bootstrap-tour) 3 | [![Dependency Status](http://img.shields.io/david/sorich87/bootstrap-tour.svg?style=flat)](https://david-dm.org/sorich87/bootstrap-tour) 4 | [![devDependency Status](http://img.shields.io/david/dev/sorich87/bootstrap-tour/dev-status.svg?style=flat)](https://david-dm.org/sorich87/bootstrap-tour#info=devDependencies) 5 | [![NPM Version](http://img.shields.io/npm/v/bootstrap-tour.svg?style=flat)](https://www.npmjs.org/) 6 | [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com) 7 | 8 | Quick and easy way to build your product tours with Bootstrap Popovers. 9 | 10 | *Compatible with Bootstrap >= 2.3.0* 11 | 12 | ## Demo and Documentation 13 | [http://bootstraptour.com](http://bootstraptour.com) 14 | 15 | ## Contributing 16 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Gulp](http://gulpjs.com/). 17 | 18 | Feel free to contribute with pull requests, bug reports or enhancement suggestions. 19 | 20 | We use [Gulp](http://gulpjs.com/) and [Jasmine](http://jasmine.github.io/). Both make your life easier ;) 21 | 22 | ### Develop 23 | 24 | Files to be developed are located under `./src/`. 25 | Compiled sources are then automatically put under `./build/`, `./test/` and `./docs/`. 26 | 27 | #### Requirements 28 | 29 | To begin, you need a few standard dependencies installed. These commands will install ruby, gem, node, yarn, and gulp's command line runner: 30 | 31 | ##### Debian/Ubuntu Linux 32 | 33 | ```bash 34 | $ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - 35 | $ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list 36 | $ sudo apt-get update && sudo apt-get install ruby-full yarn 37 | ``` 38 | 39 | ##### Mac OS X 40 | 41 | ```bash 42 | $ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 43 | $ brew install ruby yarn 44 | ``` 45 | 46 | ##### Development requirements 47 | 48 | ```bash 49 | $ yarn global add gulp-cli 50 | $ yarn 51 | $ gem install jekyll 52 | ``` 53 | 54 | For Mac OS X Mavericks (10.9) users: You will need to [jump through all these hoops](http://dean.io/setting-up-a-ruby-on-rails-development-environment-on-mavericks/) before you can install Jekyll. 55 | 56 | #### Gulp usage 57 | 58 | Run gulp and start to develop with ease: 59 | 60 | ```bash 61 | $ gulp 62 | $ gulp dist 63 | $ gulp test 64 | $ gulp docs 65 | $ gulp clean 66 | $ gulp server 67 | $ gulp bump --type minor (major.minor.patch) 68 | ``` 69 | 70 | Check `gulpfile.coffee` to know more. 71 | 72 | ## License 73 | 74 | Code licensed under the [MIT license](https://opensource.org/licenses/MIT). 75 | Documentation licensed under [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/). 76 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | markdown: rdiscount 2 | permalink: pretty 3 | source: ./src/docs 4 | destination: ./docs 5 | encoding: UTF-8 6 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | @$% Not a Bower package. Use a better package management tool instead. ^&* 2 | -------------------------------------------------------------------------------- /build/css/bootstrap-tour-standalone.css: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-tour - v0.12.0 3 | * http://bootstraptour.com 4 | * ======================================================================== 5 | * Copyright 2012-2017 Ulrich Sossou 6 | * 7 | * ======================================================================== 8 | * Licensed under the MIT License (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://opensource.org/licenses/MIT 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | .btn { 23 | display: inline-block; 24 | font-weight: normal; 25 | text-align: center; 26 | white-space: nowrap; 27 | vertical-align: middle; 28 | user-select: none; 29 | border: 1px solid transparent; 30 | padding: 0.5rem 0.75rem; 31 | font-size: 1rem; 32 | line-height: 1.25; 33 | border-radius: 0.25rem; 34 | transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } 35 | .btn:focus, .btn:hover { 36 | text-decoration: none; } 37 | .btn:focus, .btn.focus { 38 | outline: 0; 39 | box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25); } 40 | .btn.disabled, .btn:disabled { 41 | opacity: .65; } 42 | .btn:active, .btn.active { 43 | background-image: none; } 44 | 45 | a.btn.disabled, 46 | fieldset[disabled] a.btn { 47 | pointer-events: none; } 48 | 49 | .btn-primary { 50 | color: #fff; 51 | background-color: #007bff; 52 | border-color: #007bff; } 53 | .btn-primary:hover { 54 | color: #fff; 55 | background-color: #0069d9; 56 | border-color: #0062cc; } 57 | .btn-primary:focus, .btn-primary.focus { 58 | box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.5); } 59 | .btn-primary.disabled, .btn-primary:disabled { 60 | background-color: #007bff; 61 | border-color: #007bff; } 62 | .btn-primary:active, .btn-primary.active, 63 | .show > .btn-primary.dropdown-toggle { 64 | background-color: #0069d9; 65 | background-image: none; 66 | border-color: #0062cc; } 67 | 68 | .btn-secondary { 69 | color: #fff; 70 | background-color: #868e96; 71 | border-color: #868e96; } 72 | .btn-secondary:hover { 73 | color: #fff; 74 | background-color: #727b84; 75 | border-color: #6c757d; } 76 | .btn-secondary:focus, .btn-secondary.focus { 77 | box-shadow: 0 0 0 3px rgba(134, 142, 150, 0.5); } 78 | .btn-secondary.disabled, .btn-secondary:disabled { 79 | background-color: #868e96; 80 | border-color: #868e96; } 81 | .btn-secondary:active, .btn-secondary.active, 82 | .show > .btn-secondary.dropdown-toggle { 83 | background-color: #727b84; 84 | background-image: none; 85 | border-color: #6c757d; } 86 | 87 | .btn-success { 88 | color: #fff; 89 | background-color: #28a745; 90 | border-color: #28a745; } 91 | .btn-success:hover { 92 | color: #fff; 93 | background-color: #218838; 94 | border-color: #1e7e34; } 95 | .btn-success:focus, .btn-success.focus { 96 | box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.5); } 97 | .btn-success.disabled, .btn-success:disabled { 98 | background-color: #28a745; 99 | border-color: #28a745; } 100 | .btn-success:active, .btn-success.active, 101 | .show > .btn-success.dropdown-toggle { 102 | background-color: #218838; 103 | background-image: none; 104 | border-color: #1e7e34; } 105 | 106 | .btn-info { 107 | color: #fff; 108 | background-color: #17a2b8; 109 | border-color: #17a2b8; } 110 | .btn-info:hover { 111 | color: #fff; 112 | background-color: #138496; 113 | border-color: #117a8b; } 114 | .btn-info:focus, .btn-info.focus { 115 | box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.5); } 116 | .btn-info.disabled, .btn-info:disabled { 117 | background-color: #17a2b8; 118 | border-color: #17a2b8; } 119 | .btn-info:active, .btn-info.active, 120 | .show > .btn-info.dropdown-toggle { 121 | background-color: #138496; 122 | background-image: none; 123 | border-color: #117a8b; } 124 | 125 | .btn-warning { 126 | color: #111; 127 | background-color: #ffc107; 128 | border-color: #ffc107; } 129 | .btn-warning:hover { 130 | color: #111; 131 | background-color: #e0a800; 132 | border-color: #d39e00; } 133 | .btn-warning:focus, .btn-warning.focus { 134 | box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.5); } 135 | .btn-warning.disabled, .btn-warning:disabled { 136 | background-color: #ffc107; 137 | border-color: #ffc107; } 138 | .btn-warning:active, .btn-warning.active, 139 | .show > .btn-warning.dropdown-toggle { 140 | background-color: #e0a800; 141 | background-image: none; 142 | border-color: #d39e00; } 143 | 144 | .btn-danger { 145 | color: #fff; 146 | background-color: #dc3545; 147 | border-color: #dc3545; } 148 | .btn-danger:hover { 149 | color: #fff; 150 | background-color: #c82333; 151 | border-color: #bd2130; } 152 | .btn-danger:focus, .btn-danger.focus { 153 | box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.5); } 154 | .btn-danger.disabled, .btn-danger:disabled { 155 | background-color: #dc3545; 156 | border-color: #dc3545; } 157 | .btn-danger:active, .btn-danger.active, 158 | .show > .btn-danger.dropdown-toggle { 159 | background-color: #c82333; 160 | background-image: none; 161 | border-color: #bd2130; } 162 | 163 | .btn-light { 164 | color: #111; 165 | background-color: #f8f9fa; 166 | border-color: #f8f9fa; } 167 | .btn-light:hover { 168 | color: #111; 169 | background-color: #e2e6ea; 170 | border-color: #dae0e5; } 171 | .btn-light:focus, .btn-light.focus { 172 | box-shadow: 0 0 0 3px rgba(248, 249, 250, 0.5); } 173 | .btn-light.disabled, .btn-light:disabled { 174 | background-color: #f8f9fa; 175 | border-color: #f8f9fa; } 176 | .btn-light:active, .btn-light.active, 177 | .show > .btn-light.dropdown-toggle { 178 | background-color: #e2e6ea; 179 | background-image: none; 180 | border-color: #dae0e5; } 181 | 182 | .btn-dark { 183 | color: #fff; 184 | background-color: #343a40; 185 | border-color: #343a40; } 186 | .btn-dark:hover { 187 | color: #fff; 188 | background-color: #23272b; 189 | border-color: #1d2124; } 190 | .btn-dark:focus, .btn-dark.focus { 191 | box-shadow: 0 0 0 3px rgba(52, 58, 64, 0.5); } 192 | .btn-dark.disabled, .btn-dark:disabled { 193 | background-color: #343a40; 194 | border-color: #343a40; } 195 | .btn-dark:active, .btn-dark.active, 196 | .show > .btn-dark.dropdown-toggle { 197 | background-color: #23272b; 198 | background-image: none; 199 | border-color: #1d2124; } 200 | 201 | .btn-outline-primary { 202 | color: #007bff; 203 | background-color: transparent; 204 | background-image: none; 205 | border-color: #007bff; } 206 | .btn-outline-primary:hover { 207 | color: #fff; 208 | background-color: #007bff; 209 | border-color: #007bff; } 210 | .btn-outline-primary:focus, .btn-outline-primary.focus { 211 | box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.5); } 212 | .btn-outline-primary.disabled, .btn-outline-primary:disabled { 213 | color: #007bff; 214 | background-color: transparent; } 215 | .btn-outline-primary:active, .btn-outline-primary.active, 216 | .show > .btn-outline-primary.dropdown-toggle { 217 | color: #fff; 218 | background-color: #007bff; 219 | border-color: #007bff; } 220 | 221 | .btn-outline-secondary { 222 | color: #868e96; 223 | background-color: transparent; 224 | background-image: none; 225 | border-color: #868e96; } 226 | .btn-outline-secondary:hover { 227 | color: #fff; 228 | background-color: #868e96; 229 | border-color: #868e96; } 230 | .btn-outline-secondary:focus, .btn-outline-secondary.focus { 231 | box-shadow: 0 0 0 3px rgba(134, 142, 150, 0.5); } 232 | .btn-outline-secondary.disabled, .btn-outline-secondary:disabled { 233 | color: #868e96; 234 | background-color: transparent; } 235 | .btn-outline-secondary:active, .btn-outline-secondary.active, 236 | .show > .btn-outline-secondary.dropdown-toggle { 237 | color: #fff; 238 | background-color: #868e96; 239 | border-color: #868e96; } 240 | 241 | .btn-outline-success { 242 | color: #28a745; 243 | background-color: transparent; 244 | background-image: none; 245 | border-color: #28a745; } 246 | .btn-outline-success:hover { 247 | color: #fff; 248 | background-color: #28a745; 249 | border-color: #28a745; } 250 | .btn-outline-success:focus, .btn-outline-success.focus { 251 | box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.5); } 252 | .btn-outline-success.disabled, .btn-outline-success:disabled { 253 | color: #28a745; 254 | background-color: transparent; } 255 | .btn-outline-success:active, .btn-outline-success.active, 256 | .show > .btn-outline-success.dropdown-toggle { 257 | color: #fff; 258 | background-color: #28a745; 259 | border-color: #28a745; } 260 | 261 | .btn-outline-info { 262 | color: #17a2b8; 263 | background-color: transparent; 264 | background-image: none; 265 | border-color: #17a2b8; } 266 | .btn-outline-info:hover { 267 | color: #fff; 268 | background-color: #17a2b8; 269 | border-color: #17a2b8; } 270 | .btn-outline-info:focus, .btn-outline-info.focus { 271 | box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.5); } 272 | .btn-outline-info.disabled, .btn-outline-info:disabled { 273 | color: #17a2b8; 274 | background-color: transparent; } 275 | .btn-outline-info:active, .btn-outline-info.active, 276 | .show > .btn-outline-info.dropdown-toggle { 277 | color: #fff; 278 | background-color: #17a2b8; 279 | border-color: #17a2b8; } 280 | 281 | .btn-outline-warning { 282 | color: #ffc107; 283 | background-color: transparent; 284 | background-image: none; 285 | border-color: #ffc107; } 286 | .btn-outline-warning:hover { 287 | color: #fff; 288 | background-color: #ffc107; 289 | border-color: #ffc107; } 290 | .btn-outline-warning:focus, .btn-outline-warning.focus { 291 | box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.5); } 292 | .btn-outline-warning.disabled, .btn-outline-warning:disabled { 293 | color: #ffc107; 294 | background-color: transparent; } 295 | .btn-outline-warning:active, .btn-outline-warning.active, 296 | .show > .btn-outline-warning.dropdown-toggle { 297 | color: #fff; 298 | background-color: #ffc107; 299 | border-color: #ffc107; } 300 | 301 | .btn-outline-danger { 302 | color: #dc3545; 303 | background-color: transparent; 304 | background-image: none; 305 | border-color: #dc3545; } 306 | .btn-outline-danger:hover { 307 | color: #fff; 308 | background-color: #dc3545; 309 | border-color: #dc3545; } 310 | .btn-outline-danger:focus, .btn-outline-danger.focus { 311 | box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.5); } 312 | .btn-outline-danger.disabled, .btn-outline-danger:disabled { 313 | color: #dc3545; 314 | background-color: transparent; } 315 | .btn-outline-danger:active, .btn-outline-danger.active, 316 | .show > .btn-outline-danger.dropdown-toggle { 317 | color: #fff; 318 | background-color: #dc3545; 319 | border-color: #dc3545; } 320 | 321 | .btn-outline-light { 322 | color: #f8f9fa; 323 | background-color: transparent; 324 | background-image: none; 325 | border-color: #f8f9fa; } 326 | .btn-outline-light:hover { 327 | color: #fff; 328 | background-color: #f8f9fa; 329 | border-color: #f8f9fa; } 330 | .btn-outline-light:focus, .btn-outline-light.focus { 331 | box-shadow: 0 0 0 3px rgba(248, 249, 250, 0.5); } 332 | .btn-outline-light.disabled, .btn-outline-light:disabled { 333 | color: #f8f9fa; 334 | background-color: transparent; } 335 | .btn-outline-light:active, .btn-outline-light.active, 336 | .show > .btn-outline-light.dropdown-toggle { 337 | color: #fff; 338 | background-color: #f8f9fa; 339 | border-color: #f8f9fa; } 340 | 341 | .btn-outline-dark { 342 | color: #343a40; 343 | background-color: transparent; 344 | background-image: none; 345 | border-color: #343a40; } 346 | .btn-outline-dark:hover { 347 | color: #fff; 348 | background-color: #343a40; 349 | border-color: #343a40; } 350 | .btn-outline-dark:focus, .btn-outline-dark.focus { 351 | box-shadow: 0 0 0 3px rgba(52, 58, 64, 0.5); } 352 | .btn-outline-dark.disabled, .btn-outline-dark:disabled { 353 | color: #343a40; 354 | background-color: transparent; } 355 | .btn-outline-dark:active, .btn-outline-dark.active, 356 | .show > .btn-outline-dark.dropdown-toggle { 357 | color: #fff; 358 | background-color: #343a40; 359 | border-color: #343a40; } 360 | 361 | .btn-link { 362 | font-weight: normal; 363 | color: #007bff; 364 | border-radius: 0; } 365 | .btn-link, .btn-link:active, .btn-link.active, .btn-link:disabled { 366 | background-color: transparent; } 367 | .btn-link, .btn-link:focus, .btn-link:active { 368 | border-color: transparent; 369 | box-shadow: none; } 370 | .btn-link:hover { 371 | border-color: transparent; } 372 | .btn-link:focus, .btn-link:hover { 373 | color: #0056b3; 374 | text-decoration: underline; 375 | background-color: transparent; } 376 | .btn-link:disabled { 377 | color: #868e96; } 378 | .btn-link:disabled:focus, .btn-link:disabled:hover { 379 | text-decoration: none; } 380 | 381 | .btn-lg, .btn-group-lg > .btn { 382 | padding: 0.5rem 1rem; 383 | font-size: 1.25rem; 384 | line-height: 1.5; 385 | border-radius: 0.3rem; } 386 | 387 | .btn-sm, .btn-group-sm > .btn { 388 | padding: 0.25rem 0.5rem; 389 | font-size: 0.875rem; 390 | line-height: 1.5; 391 | border-radius: 0.2rem; } 392 | 393 | .btn-block { 394 | display: block; 395 | width: 100%; } 396 | 397 | .btn-block + .btn-block { 398 | margin-top: 0.5rem; } 399 | 400 | input[type="submit"].btn-block, 401 | input[type="reset"].btn-block, 402 | input[type="button"].btn-block { 403 | width: 100%; } 404 | 405 | .fade { 406 | opacity: 0; 407 | transition: opacity 0.15s linear; } 408 | .fade.show { 409 | opacity: 1; } 410 | 411 | .collapse { 412 | display: none; } 413 | .collapse.show { 414 | display: block; } 415 | 416 | tr.collapse.show { 417 | display: table-row; } 418 | 419 | tbody.collapse.show { 420 | display: table-row-group; } 421 | 422 | .collapsing { 423 | position: relative; 424 | height: 0; 425 | overflow: hidden; 426 | transition: height 0.35s ease; } 427 | 428 | .btn-group, 429 | .btn-group-vertical { 430 | position: relative; 431 | display: inline-flex; 432 | vertical-align: middle; } 433 | .btn-group > .btn, 434 | .btn-group-vertical > .btn { 435 | position: relative; 436 | flex: 0 1 auto; 437 | margin-bottom: 0; } 438 | .btn-group > .btn:hover, 439 | .btn-group-vertical > .btn:hover { 440 | z-index: 2; } 441 | .btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active, 442 | .btn-group-vertical > .btn:focus, 443 | .btn-group-vertical > .btn:active, 444 | .btn-group-vertical > .btn.active { 445 | z-index: 2; } 446 | .btn-group .btn + .btn, 447 | .btn-group .btn + .btn-group, 448 | .btn-group .btn-group + .btn, 449 | .btn-group .btn-group + .btn-group, 450 | .btn-group-vertical .btn + .btn, 451 | .btn-group-vertical .btn + .btn-group, 452 | .btn-group-vertical .btn-group + .btn, 453 | .btn-group-vertical .btn-group + .btn-group { 454 | margin-left: -1px; } 455 | 456 | .btn-toolbar { 457 | display: flex; 458 | flex-wrap: wrap; 459 | justify-content: flex-start; } 460 | .btn-toolbar .input-group { 461 | width: auto; } 462 | 463 | .btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { 464 | border-radius: 0; } 465 | 466 | .btn-group > .btn:first-child { 467 | margin-left: 0; } 468 | .btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { 469 | border-top-right-radius: 0; 470 | border-bottom-right-radius: 0; } 471 | 472 | .btn-group > .btn:last-child:not(:first-child), 473 | .btn-group > .dropdown-toggle:not(:first-child) { 474 | border-top-left-radius: 0; 475 | border-bottom-left-radius: 0; } 476 | 477 | .btn-group > .btn-group { 478 | float: left; } 479 | 480 | .btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { 481 | border-radius: 0; } 482 | 483 | .btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, 484 | .btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { 485 | border-top-right-radius: 0; 486 | border-bottom-right-radius: 0; } 487 | 488 | .btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { 489 | border-top-left-radius: 0; 490 | border-bottom-left-radius: 0; } 491 | 492 | .btn + .dropdown-toggle-split { 493 | padding-right: 0.5625rem; 494 | padding-left: 0.5625rem; } 495 | .btn + .dropdown-toggle-split::after { 496 | margin-left: 0; } 497 | 498 | .btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { 499 | padding-right: 0.375rem; 500 | padding-left: 0.375rem; } 501 | 502 | .btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { 503 | padding-right: 0.75rem; 504 | padding-left: 0.75rem; } 505 | 506 | .btn-group-vertical { 507 | display: inline-flex; 508 | flex-direction: column; 509 | align-items: flex-start; 510 | justify-content: center; } 511 | .btn-group-vertical .btn, 512 | .btn-group-vertical .btn-group { 513 | width: 100%; } 514 | .btn-group-vertical > .btn + .btn, 515 | .btn-group-vertical > .btn + .btn-group, 516 | .btn-group-vertical > .btn-group + .btn, 517 | .btn-group-vertical > .btn-group + .btn-group { 518 | margin-top: -1px; 519 | margin-left: 0; } 520 | 521 | .btn-group-vertical > .btn:not(:first-child):not(:last-child) { 522 | border-radius: 0; } 523 | 524 | .btn-group-vertical > .btn:first-child:not(:last-child) { 525 | border-bottom-right-radius: 0; 526 | border-bottom-left-radius: 0; } 527 | 528 | .btn-group-vertical > .btn:last-child:not(:first-child) { 529 | border-top-left-radius: 0; 530 | border-top-right-radius: 0; } 531 | 532 | .btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { 533 | border-radius: 0; } 534 | 535 | .btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, 536 | .btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { 537 | border-bottom-right-radius: 0; 538 | border-bottom-left-radius: 0; } 539 | 540 | .btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { 541 | border-top-left-radius: 0; 542 | border-top-right-radius: 0; } 543 | 544 | [data-toggle="buttons"] > .btn input[type="radio"], 545 | [data-toggle="buttons"] > .btn input[type="checkbox"], 546 | [data-toggle="buttons"] > .btn-group > .btn input[type="radio"], 547 | [data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { 548 | position: absolute; 549 | clip: rect(0, 0, 0, 0); 550 | pointer-events: none; } 551 | 552 | .popover { 553 | position: absolute; 554 | top: 0; 555 | left: 0; 556 | z-index: 1060; 557 | display: block; 558 | max-width: 276px; 559 | padding: 1px; 560 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 561 | font-style: normal; 562 | font-weight: normal; 563 | line-height: 1.5; 564 | text-align: left; 565 | text-align: start; 566 | text-decoration: none; 567 | text-shadow: none; 568 | text-transform: none; 569 | letter-spacing: normal; 570 | word-break: normal; 571 | word-spacing: normal; 572 | white-space: normal; 573 | line-break: auto; 574 | font-size: 0.875rem; 575 | word-wrap: break-word; 576 | background-color: #fff; 577 | background-clip: padding-box; 578 | border: 1px solid rgba(0, 0, 0, 0.2); 579 | border-radius: 0.3rem; } 580 | .popover .arrow { 581 | position: absolute; 582 | display: block; 583 | width: 10px; 584 | height: 5px; } 585 | .popover .arrow::before, 586 | .popover .arrow::after { 587 | position: absolute; 588 | display: block; 589 | border-color: transparent; 590 | border-style: solid; } 591 | .popover .arrow::before { 592 | content: ""; 593 | border-width: 11px; } 594 | .popover .arrow::after { 595 | content: ""; 596 | border-width: 11px; } 597 | .popover.bs-popover-top, .popover.bs-popover-auto[x-placement^="top"] { 598 | margin-bottom: 10px; } 599 | .popover.bs-popover-top .arrow, .popover.bs-popover-auto[x-placement^="top"] .arrow { 600 | bottom: 0; } 601 | .popover.bs-popover-top .arrow::before, .popover.bs-popover-auto[x-placement^="top"] .arrow::before, 602 | .popover.bs-popover-top .arrow::after, .popover.bs-popover-auto[x-placement^="top"] .arrow::after { 603 | border-bottom-width: 0; } 604 | .popover.bs-popover-top .arrow::before, .popover.bs-popover-auto[x-placement^="top"] .arrow::before { 605 | bottom: -11px; 606 | margin-left: -6px; 607 | border-top-color: rgba(0, 0, 0, 0.25); } 608 | .popover.bs-popover-top .arrow::after, .popover.bs-popover-auto[x-placement^="top"] .arrow::after { 609 | bottom: -10px; 610 | margin-left: -6px; 611 | border-top-color: #fff; } 612 | .popover.bs-popover-right, .popover.bs-popover-auto[x-placement^="right"] { 613 | margin-left: 10px; } 614 | .popover.bs-popover-right .arrow, .popover.bs-popover-auto[x-placement^="right"] .arrow { 615 | left: 0; } 616 | .popover.bs-popover-right .arrow::before, .popover.bs-popover-auto[x-placement^="right"] .arrow::before, 617 | .popover.bs-popover-right .arrow::after, .popover.bs-popover-auto[x-placement^="right"] .arrow::after { 618 | margin-top: -8px; 619 | border-left-width: 0; } 620 | .popover.bs-popover-right .arrow::before, .popover.bs-popover-auto[x-placement^="right"] .arrow::before { 621 | left: -11px; 622 | border-right-color: rgba(0, 0, 0, 0.25); } 623 | .popover.bs-popover-right .arrow::after, .popover.bs-popover-auto[x-placement^="right"] .arrow::after { 624 | left: -10px; 625 | border-right-color: #fff; } 626 | .popover.bs-popover-bottom, .popover.bs-popover-auto[x-placement^="bottom"] { 627 | margin-top: 10px; } 628 | .popover.bs-popover-bottom .arrow, .popover.bs-popover-auto[x-placement^="bottom"] .arrow { 629 | top: 0; } 630 | .popover.bs-popover-bottom .arrow::before, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::before, 631 | .popover.bs-popover-bottom .arrow::after, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::after { 632 | margin-left: -7px; 633 | border-top-width: 0; } 634 | .popover.bs-popover-bottom .arrow::before, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::before { 635 | top: -11px; 636 | border-bottom-color: rgba(0, 0, 0, 0.25); } 637 | .popover.bs-popover-bottom .arrow::after, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::after { 638 | top: -10px; 639 | border-bottom-color: #fff; } 640 | .popover.bs-popover-bottom .popover-header::before, .popover.bs-popover-auto[x-placement^="bottom"] .popover-header::before { 641 | position: absolute; 642 | top: 0; 643 | left: 50%; 644 | display: block; 645 | width: 20px; 646 | margin-left: -10px; 647 | content: ""; 648 | border-bottom: 1px solid #f7f7f7; } 649 | .popover.bs-popover-left, .popover.bs-popover-auto[x-placement^="left"] { 650 | margin-right: 10px; } 651 | .popover.bs-popover-left .arrow, .popover.bs-popover-auto[x-placement^="left"] .arrow { 652 | right: 0; } 653 | .popover.bs-popover-left .arrow::before, .popover.bs-popover-auto[x-placement^="left"] .arrow::before, 654 | .popover.bs-popover-left .arrow::after, .popover.bs-popover-auto[x-placement^="left"] .arrow::after { 655 | margin-top: -8px; 656 | border-right-width: 0; } 657 | .popover.bs-popover-left .arrow::before, .popover.bs-popover-auto[x-placement^="left"] .arrow::before { 658 | right: -11px; 659 | border-left-color: rgba(0, 0, 0, 0.25); } 660 | .popover.bs-popover-left .arrow::after, .popover.bs-popover-auto[x-placement^="left"] .arrow::after { 661 | right: -10px; 662 | border-left-color: #fff; } 663 | 664 | .popover-header { 665 | padding: 8px 14px; 666 | margin-bottom: 0; 667 | font-size: 1rem; 668 | color: inherit; 669 | background-color: #f7f7f7; 670 | border-bottom: 1px solid #ebebeb; 671 | border-top-left-radius: calc(0.3rem - 1px); 672 | border-top-right-radius: calc(0.3rem - 1px); } 673 | .popover-header:empty { 674 | display: none; } 675 | 676 | .popover-body { 677 | padding: 9px 14px; 678 | color: #212529; } 679 | 680 | .tour-backdrop { 681 | background-color: #000; 682 | filter: alpha(opacity=80); 683 | opacity: .8; 684 | position: absolute; 685 | z-index: 1100; } 686 | 687 | .popover[class*="tour-"] { 688 | z-index: 1102; } 689 | .popover[class*="tour-"] .popover-navigation { 690 | overflow: hidden; 691 | padding: 9px 14px; } 692 | .popover[class*="tour-"] .popover-navigation *[data-role="end"] { 693 | float: right; } 694 | .popover[class*="tour-"] .popover-navigation *[data-role="prev"], 695 | .popover[class*="tour-"] .popover-navigation *[data-role="next"], 696 | .popover[class*="tour-"] .popover-navigation *[data-role="end"] { 697 | cursor: pointer; } 698 | .popover[class*="tour-"] .popover-navigation *[data-role="prev"].disabled, 699 | .popover[class*="tour-"] .popover-navigation *[data-role="next"].disabled, 700 | .popover[class*="tour-"] .popover-navigation *[data-role="end"].disabled { 701 | cursor: default; } 702 | .popover[class*="tour-"].orphan { 703 | left: 50%; 704 | margin-top: 0; 705 | position: fixed; 706 | top: 50%; 707 | transform: translate(-50%, -50%); } 708 | .popover[class*="tour-"].orphan .arrow { 709 | display: none; } 710 | -------------------------------------------------------------------------------- /build/css/bootstrap-tour-standalone.min.css: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-tour - v0.12.0 3 | * http://bootstraptour.com 4 | * ======================================================================== 5 | * Copyright 2012-2017 Ulrich Sossou 6 | * 7 | * ======================================================================== 8 | * Licensed under the MIT License (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://opensource.org/licenses/MIT 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | .btn{display:inline-block;font-weight:normal;text-align:center;white-space:nowrap;vertical-align:middle;user-select:none;border:1px solid transparent;padding:0.5rem 0.75rem;font-size:1rem;line-height:1.25;border-radius:0.25rem;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out}.btn:focus,.btn:hover{text-decoration:none}.btn:focus,.btn.focus{outline:0;box-shadow:0 0 0 3px rgba(0,123,255,0.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:active,.btn.active{background-image:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary:focus,.btn-primary.focus{box-shadow:0 0 0 3px rgba(0,123,255,0.5)}.btn-primary.disabled,.btn-primary:disabled{background-color:#007bff;border-color:#007bff}.btn-primary:active,.btn-primary.active,.show>.btn-primary.dropdown-toggle{background-color:#0069d9;background-image:none;border-color:#0062cc}.btn-secondary{color:#fff;background-color:#868e96;border-color:#868e96}.btn-secondary:hover{color:#fff;background-color:#727b84;border-color:#6c757d}.btn-secondary:focus,.btn-secondary.focus{box-shadow:0 0 0 3px rgba(134,142,150,0.5)}.btn-secondary.disabled,.btn-secondary:disabled{background-color:#868e96;border-color:#868e96}.btn-secondary:active,.btn-secondary.active,.show>.btn-secondary.dropdown-toggle{background-color:#727b84;background-image:none;border-color:#6c757d}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success:focus,.btn-success.focus{box-shadow:0 0 0 3px rgba(40,167,69,0.5)}.btn-success.disabled,.btn-success:disabled{background-color:#28a745;border-color:#28a745}.btn-success:active,.btn-success.active,.show>.btn-success.dropdown-toggle{background-color:#218838;background-image:none;border-color:#1e7e34}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info:focus,.btn-info.focus{box-shadow:0 0 0 3px rgba(23,162,184,0.5)}.btn-info.disabled,.btn-info:disabled{background-color:#17a2b8;border-color:#17a2b8}.btn-info:active,.btn-info.active,.show>.btn-info.dropdown-toggle{background-color:#138496;background-image:none;border-color:#117a8b}.btn-warning{color:#111;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#111;background-color:#e0a800;border-color:#d39e00}.btn-warning:focus,.btn-warning.focus{box-shadow:0 0 0 3px rgba(255,193,7,0.5)}.btn-warning.disabled,.btn-warning:disabled{background-color:#ffc107;border-color:#ffc107}.btn-warning:active,.btn-warning.active,.show>.btn-warning.dropdown-toggle{background-color:#e0a800;background-image:none;border-color:#d39e00}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger:focus,.btn-danger.focus{box-shadow:0 0 0 3px rgba(220,53,69,0.5)}.btn-danger.disabled,.btn-danger:disabled{background-color:#dc3545;border-color:#dc3545}.btn-danger:active,.btn-danger.active,.show>.btn-danger.dropdown-toggle{background-color:#c82333;background-image:none;border-color:#bd2130}.btn-light{color:#111;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#111;background-color:#e2e6ea;border-color:#dae0e5}.btn-light:focus,.btn-light.focus{box-shadow:0 0 0 3px rgba(248,249,250,0.5)}.btn-light.disabled,.btn-light:disabled{background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:active,.btn-light.active,.show>.btn-light.dropdown-toggle{background-color:#e2e6ea;background-image:none;border-color:#dae0e5}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark:focus,.btn-dark.focus{box-shadow:0 0 0 3px rgba(52,58,64,0.5)}.btn-dark.disabled,.btn-dark:disabled{background-color:#343a40;border-color:#343a40}.btn-dark:active,.btn-dark.active,.show>.btn-dark.dropdown-toggle{background-color:#23272b;background-image:none;border-color:#1d2124}.btn-outline-primary{color:#007bff;background-color:transparent;background-image:none;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:focus,.btn-outline-primary.focus{box-shadow:0 0 0 3px rgba(0,123,255,0.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:active,.btn-outline-primary.active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-secondary{color:#868e96;background-color:transparent;background-image:none;border-color:#868e96}.btn-outline-secondary:hover{color:#fff;background-color:#868e96;border-color:#868e96}.btn-outline-secondary:focus,.btn-outline-secondary.focus{box-shadow:0 0 0 3px rgba(134,142,150,0.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#868e96;background-color:transparent}.btn-outline-secondary:active,.btn-outline-secondary.active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#868e96;border-color:#868e96}.btn-outline-success{color:#28a745;background-color:transparent;background-image:none;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:focus,.btn-outline-success.focus{box-shadow:0 0 0 3px rgba(40,167,69,0.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:active,.btn-outline-success.active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-info{color:#17a2b8;background-color:transparent;background-image:none;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:focus,.btn-outline-info.focus{box-shadow:0 0 0 3px rgba(23,162,184,0.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:active,.btn-outline-info.active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-warning{color:#ffc107;background-color:transparent;background-image:none;border-color:#ffc107}.btn-outline-warning:hover{color:#fff;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:focus,.btn-outline-warning.focus{box-shadow:0 0 0 3px rgba(255,193,7,0.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:active,.btn-outline-warning.active,.show>.btn-outline-warning.dropdown-toggle{color:#fff;background-color:#ffc107;border-color:#ffc107}.btn-outline-danger{color:#dc3545;background-color:transparent;background-image:none;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:focus,.btn-outline-danger.focus{box-shadow:0 0 0 3px rgba(220,53,69,0.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:active,.btn-outline-danger.active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-light{color:#f8f9fa;background-color:transparent;background-image:none;border-color:#f8f9fa}.btn-outline-light:hover{color:#fff;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:focus,.btn-outline-light.focus{box-shadow:0 0 0 3px rgba(248,249,250,0.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:active,.btn-outline-light.active,.show>.btn-outline-light.dropdown-toggle{color:#fff;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-dark{color:#343a40;background-color:transparent;background-image:none;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:focus,.btn-outline-dark.focus{box-shadow:0 0 0 3px rgba(52,58,64,0.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:active,.btn-outline-dark.active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-link{font-weight:normal;color:#007bff;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link:disabled{background-color:transparent}.btn-link,.btn-link:focus,.btn-link:active{border-color:transparent;box-shadow:none}.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#0056b3;text-decoration:underline;background-color:transparent}.btn-link:disabled{color:#868e96}.btn-link:disabled:focus,.btn-link:disabled:hover{text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:0.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:0.3rem}.btn-sm,.btn-group-sm>.btn{padding:0.25rem 0.5rem;font-size:0.875rem;line-height:1.5;border-radius:0.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:0.5rem}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;transition:opacity 0.15s linear}.fade.show{opacity:1}.collapse{display:none}.collapse.show{display:block}tr.collapse.show{display:table-row}tbody.collapse.show{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;transition:height 0.35s ease}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:0 1 auto;margin-bottom:0}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover{z-index:2}.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group,.btn-group-vertical .btn+.btn,.btn-group-vertical .btn+.btn-group,.btn-group-vertical .btn-group+.btn,.btn-group-vertical .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn+.dropdown-toggle-split{padding-right:0.5625rem;padding-left:0.5625rem}.btn+.dropdown-toggle-split::after{margin-left:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:0.375rem;padding-left:0.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:0.75rem;padding-left:0.75rem}.btn-group-vertical{display:inline-flex;flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical .btn,.btn-group-vertical .btn-group{width:100%}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;padding:1px;font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";font-style:normal;font-weight:normal;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,0.2);border-radius:0.3rem}.popover .arrow{position:absolute;display:block;width:10px;height:5px}.popover .arrow::before,.popover .arrow::after{position:absolute;display:block;border-color:transparent;border-style:solid}.popover .arrow::before{content:"";border-width:11px}.popover .arrow::after{content:"";border-width:11px}.popover.bs-popover-top,.popover.bs-popover-auto[x-placement^="top"]{margin-bottom:10px}.popover.bs-popover-top .arrow,.popover.bs-popover-auto[x-placement^="top"] .arrow{bottom:0}.popover.bs-popover-top .arrow::before,.popover.bs-popover-auto[x-placement^="top"] .arrow::before,.popover.bs-popover-top .arrow::after,.popover.bs-popover-auto[x-placement^="top"] .arrow::after{border-bottom-width:0}.popover.bs-popover-top .arrow::before,.popover.bs-popover-auto[x-placement^="top"] .arrow::before{bottom:-11px;margin-left:-6px;border-top-color:rgba(0,0,0,0.25)}.popover.bs-popover-top .arrow::after,.popover.bs-popover-auto[x-placement^="top"] .arrow::after{bottom:-10px;margin-left:-6px;border-top-color:#fff}.popover.bs-popover-right,.popover.bs-popover-auto[x-placement^="right"]{margin-left:10px}.popover.bs-popover-right .arrow,.popover.bs-popover-auto[x-placement^="right"] .arrow{left:0}.popover.bs-popover-right .arrow::before,.popover.bs-popover-auto[x-placement^="right"] .arrow::before,.popover.bs-popover-right .arrow::after,.popover.bs-popover-auto[x-placement^="right"] .arrow::after{margin-top:-8px;border-left-width:0}.popover.bs-popover-right .arrow::before,.popover.bs-popover-auto[x-placement^="right"] .arrow::before{left:-11px;border-right-color:rgba(0,0,0,0.25)}.popover.bs-popover-right .arrow::after,.popover.bs-popover-auto[x-placement^="right"] .arrow::after{left:-10px;border-right-color:#fff}.popover.bs-popover-bottom,.popover.bs-popover-auto[x-placement^="bottom"]{margin-top:10px}.popover.bs-popover-bottom .arrow,.popover.bs-popover-auto[x-placement^="bottom"] .arrow{top:0}.popover.bs-popover-bottom .arrow::before,.popover.bs-popover-auto[x-placement^="bottom"] .arrow::before,.popover.bs-popover-bottom .arrow::after,.popover.bs-popover-auto[x-placement^="bottom"] .arrow::after{margin-left:-7px;border-top-width:0}.popover.bs-popover-bottom .arrow::before,.popover.bs-popover-auto[x-placement^="bottom"] .arrow::before{top:-11px;border-bottom-color:rgba(0,0,0,0.25)}.popover.bs-popover-bottom .arrow::after,.popover.bs-popover-auto[x-placement^="bottom"] .arrow::after{top:-10px;border-bottom-color:#fff}.popover.bs-popover-bottom .popover-header::before,.popover.bs-popover-auto[x-placement^="bottom"] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:20px;margin-left:-10px;content:"";border-bottom:1px solid #f7f7f7}.popover.bs-popover-left,.popover.bs-popover-auto[x-placement^="left"]{margin-right:10px}.popover.bs-popover-left .arrow,.popover.bs-popover-auto[x-placement^="left"] .arrow{right:0}.popover.bs-popover-left .arrow::before,.popover.bs-popover-auto[x-placement^="left"] .arrow::before,.popover.bs-popover-left .arrow::after,.popover.bs-popover-auto[x-placement^="left"] .arrow::after{margin-top:-8px;border-right-width:0}.popover.bs-popover-left .arrow::before,.popover.bs-popover-auto[x-placement^="left"] .arrow::before{right:-11px;border-left-color:rgba(0,0,0,0.25)}.popover.bs-popover-left .arrow::after,.popover.bs-popover-auto[x-placement^="left"] .arrow::after{right:-10px;border-left-color:#fff}.popover-header{padding:8px 14px;margin-bottom:0;font-size:1rem;color:inherit;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:9px 14px;color:#212529}.tour-backdrop{background-color:#000;filter:alpha(opacity=80);opacity:.8;position:absolute;z-index:1100}.popover[class*="tour-"]{z-index:1102}.popover[class*="tour-"] .popover-navigation{overflow:hidden;padding:9px 14px}.popover[class*="tour-"] .popover-navigation *[data-role="end"]{float:right}.popover[class*="tour-"] .popover-navigation *[data-role="prev"],.popover[class*="tour-"] .popover-navigation *[data-role="next"],.popover[class*="tour-"] .popover-navigation *[data-role="end"]{cursor:pointer}.popover[class*="tour-"] .popover-navigation *[data-role="prev"].disabled,.popover[class*="tour-"] .popover-navigation *[data-role="next"].disabled,.popover[class*="tour-"] .popover-navigation *[data-role="end"].disabled{cursor:default}.popover[class*="tour-"].orphan{left:50%;margin-top:0;position:fixed;top:50%;transform:translate(-50%, -50%)}.popover[class*="tour-"].orphan .arrow{display:none} 23 | -------------------------------------------------------------------------------- /build/css/bootstrap-tour.css: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-tour - v0.12.0 3 | * http://bootstraptour.com 4 | * ======================================================================== 5 | * Copyright 2012-2017 Ulrich Sossou 6 | * 7 | * ======================================================================== 8 | * Licensed under the MIT License (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://opensource.org/licenses/MIT 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | .tour-backdrop { 23 | background-color: #000; 24 | filter: alpha(opacity=80); 25 | opacity: .8; 26 | position: absolute; 27 | z-index: 1100; } 28 | 29 | .popover[class*="tour-"] { 30 | z-index: 1102; } 31 | .popover[class*="tour-"] .popover-navigation { 32 | overflow: hidden; 33 | padding: 9px 14px; } 34 | .popover[class*="tour-"] .popover-navigation *[data-role="end"] { 35 | float: right; } 36 | .popover[class*="tour-"] .popover-navigation *[data-role="prev"], 37 | .popover[class*="tour-"] .popover-navigation *[data-role="next"], 38 | .popover[class*="tour-"] .popover-navigation *[data-role="end"] { 39 | cursor: pointer; } 40 | .popover[class*="tour-"] .popover-navigation *[data-role="prev"].disabled, 41 | .popover[class*="tour-"] .popover-navigation *[data-role="next"].disabled, 42 | .popover[class*="tour-"] .popover-navigation *[data-role="end"].disabled { 43 | cursor: default; } 44 | .popover[class*="tour-"].orphan { 45 | left: 50%; 46 | margin-top: 0; 47 | position: fixed; 48 | top: 50%; 49 | transform: translate(-50%, -50%); } 50 | .popover[class*="tour-"].orphan .arrow { 51 | display: none; } 52 | -------------------------------------------------------------------------------- /build/css/bootstrap-tour.min.css: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-tour - v0.12.0 3 | * http://bootstraptour.com 4 | * ======================================================================== 5 | * Copyright 2012-2017 Ulrich Sossou 6 | * 7 | * ======================================================================== 8 | * Licensed under the MIT License (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://opensource.org/licenses/MIT 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | .tour-backdrop{background-color:#000;filter:alpha(opacity=80);opacity:.8;position:absolute;z-index:1100}.popover[class*="tour-"]{z-index:1102}.popover[class*="tour-"] .popover-navigation{overflow:hidden;padding:9px 14px}.popover[class*="tour-"] .popover-navigation *[data-role="end"]{float:right}.popover[class*="tour-"] .popover-navigation *[data-role="prev"],.popover[class*="tour-"] .popover-navigation *[data-role="next"],.popover[class*="tour-"] .popover-navigation *[data-role="end"]{cursor:pointer}.popover[class*="tour-"] .popover-navigation *[data-role="prev"].disabled,.popover[class*="tour-"] .popover-navigation *[data-role="next"].disabled,.popover[class*="tour-"] .popover-navigation *[data-role="end"].disabled{cursor:default}.popover[class*="tour-"].orphan{left:50%;margin-top:0;position:fixed;top:50%;transform:translate(-50%, -50%)}.popover[class*="tour-"].orphan .arrow{display:none} 23 | -------------------------------------------------------------------------------- /build/js/bootstrap-tour.min.js: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-tour - v0.12.0 3 | * http://bootstraptour.com 4 | * ======================================================================== 5 | * Copyright 2012-2017 Ulrich Sossou 6 | * 7 | * ======================================================================== 8 | * Licensed under the MIT License (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://opensource.org/licenses/MIT 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | var bind=function(t,e){return function(){return t.apply(e,arguments)}};!function(t,e){"function"==typeof define&&define.amd?define(["jquery"],function(o){return t.Tour=e(o)}):"object"==typeof exports?module.exports=e(require("jquery")):t.Tour=e(t.jQuery)}(window,function(t){var e;return e=window.document,function(){function o(e){this._showPopoverAndOverlay=bind(this._showPopoverAndOverlay,this);var o;try{o=window.localStorage}catch(t){o=!1}this._options=t.extend({name:"tour",steps:[],container:"body",autoscroll:!0,keyboard:!0,storage:o,debug:!1,backdrop:!1,backdropContainer:"body",backdropPadding:0,redirect:!0,orphan:!1,duration:!1,delay:!1,basePath:"",template:'',afterSetState:function(t,e){},afterGetState:function(t,e){},afterRemoveState:function(t){},onStart:function(t){},onEnd:function(t){},onShow:function(t){},onShown:function(t){},onHide:function(t){},onHidden:function(t){},onNext:function(t){},onPrev:function(t){},onPause:function(t,e){},onResume:function(t,e){},onRedirectError:function(t){}},e),this._force=!1,this._inited=!1,this._current=null,this.backdrops=[]}return o.prototype.addSteps=function(t){var e,o,n;for(e=0,o=t.length;e

",next:e===this._options.steps.length-1?-1:e+1,prev:e-1,animation:!0,container:this._options.container,autoscroll:this._options.autoscroll,backdrop:this._options.backdrop,backdropContainer:this._options.backdropContainer,backdropPadding:this._options.backdropPadding,redirect:this._options.redirect,reflexElement:this._options.steps[e].element,backdropElement:this._options.steps[e].element,orphan:this._options.orphan,duration:this._options.duration,delay:this._options.delay,template:this._options.template,onShow:this._options.onShow,onShown:this._options.onShown,onHide:this._options.onHide,onHidden:this._options.onHidden,onNext:this._options.onNext,onPrev:this._options.onPrev,onPause:this._options.onPause,onResume:this._options.onResume,onRedirectError:this._options.onRedirectError},this._options.steps[e])},o.prototype.init=function(t){return this._force=t,this.ended()?(this._debug("Tour ended, init prevented."),this):(this.setCurrentStep(),this._initMouseNavigation(),this._initKeyboardNavigation(),null!==this._current&&this.showStep(this._current),this._inited=!0,this)},o.prototype.start=function(t){var e;return null==t&&(t=!1),this._inited||this.init(t),null===this._current&&(e=this._makePromise(null!=this._options.onStart?this._options.onStart(this):void 0),this._callOnPromiseDone(e,this.showStep,0)),this},o.prototype.next=function(){var t;return t=this.hideStep(this._current,this._current+1),this._callOnPromiseDone(t,this._showNextStep)},o.prototype.prev=function(){var t;return t=this.hideStep(this._current,this._current-1),this._callOnPromiseDone(t,this._showPrevStep)},o.prototype.goTo=function(t){var e;return e=this.hideStep(this._current,t),this._callOnPromiseDone(e,this.showStep,t)},o.prototype.end=function(){var o,n;return o=function(o){return function(n){if(t(e).off("click.tour-"+o._options.name),t(e).off("keyup.tour-"+o._options.name),o._setState("end","yes"),o._inited=!1,o._force=!1,o._clearTimer(),null!=o._options.onEnd)return o._options.onEnd(o)}}(this),n=this.hideStep(this._current),this._callOnPromiseDone(n,o)},o.prototype.ended=function(){return!this._force&&!!this._getState("end")},o.prototype.restart=function(){return this._removeState("current_step"),this._removeState("end"),this._removeState("redirect_to"),this.start()},o.prototype.pause=function(){var t;return(t=this.getStep(this._current))&&t.duration?(this._paused=!0,this._duration-=(new Date).getTime()-this._start,window.clearTimeout(this._timer),this._debug("Paused/Stopped step "+(this._current+1)+" timer ("+this._duration+" remaining)."),null!=t.onPause?t.onPause(this,this._duration):void 0):this},o.prototype.resume=function(){var t;return(t=this.getStep(this._current))&&t.duration?(this._paused=!1,this._start=(new Date).getTime(),this._duration=this._duration||t.duration,this._timer=window.setTimeout(function(t){return function(){return t._isLast()?t.next():t.end()}}(this),this._duration),this._debug("Started step "+(this._current+1)+" timer with duration "+this._duration),null!=t.onResume&&this._duration!==t.duration?t.onResume(this,this._duration):void 0):this},o.prototype.hideStep=function(e,o){var n,i,r,s;if(s=this.getStep(e))return this._clearTimer(),r=this._makePromise(null!=s.onHide?s.onHide(this,e):void 0),i=function(n){return function(i){var r,a;if((r=t(s.element)).data("bs.popover")||(r=t("body")),r.popover("dispose").removeClass("tour-"+n._options.name+"-element tour-"+n._options.name+"-"+e+"-element").removeData("bs.popover"),s.reflex&&t(s.reflexElement).removeClass("tour-step-element-reflex").off(n._reflexEvent(s.reflex)+".tour-"+n._options.name),s.backdrop&&((a=null!=o&&n.getStep(o))&&a.backdrop&&a.backdropElement===s.backdropElement||n._hideOverlayElement(s)),null!=s.onHidden)return s.onHidden(n)}}(this),n=s.delay.hide||s.delay,"[object Number]"==={}.toString.call(n)&&n>0?(this._debug("Wait "+n+" milliseconds to hide the step "+(this._current+1)),window.setTimeout(function(t){return function(){return t._callOnPromiseDone(r,i)}}(this),n)):this._callOnPromiseDone(r,i),r},o.prototype.showStep=function(t){var o,n,i,r,s,a;return this.ended()?(this._debug("Tour ended, showStep prevented."),this):(a=this.getStep(t))&&(s=t0?(this._debug("Wait "+i+" milliseconds to show the step "+(this._current+1)),window.setTimeout(function(t){return function(){return t._callOnPromiseDone(n,r)}}(this),i)):this._callOnPromiseDone(n,r),n):void 0},o.prototype.getCurrentStep=function(){return this._current},o.prototype.setCurrentStep=function(t){return null!=t?(this._current=t,this._setState("current_step",t)):(this._current=this._getState("current_step"),this._current=null===this._current?null:parseInt(this._current,10)),this},o.prototype.redraw=function(){return this._showOverlayElement(this.getStep(this.getCurrentStep()))},o.prototype._setState=function(t,e){var o;if(this._options.storage){o=this._options.name+"_"+t;try{this._options.storage.setItem(o,e)}catch(t){t.code===DOMException.QUOTA_EXCEEDED_ERR&&this._debug("LocalStorage quota exceeded. State storage failed.")}return this._options.afterSetState(o,e)}return null==this._state&&(this._state={}),this._state[t]=e},o.prototype._removeState=function(t){var e;return this._options.storage?(e=this._options.name+"_"+t,this._options.storage.removeItem(e),this._options.afterRemoveState(e)):null!=this._state?delete this._state[t]:void 0},o.prototype._getState=function(t){var e,o;return this._options.storage?(e=this._options.name+"_"+t,o=this._options.storage.getItem(e)):null!=this._state&&(o=this._state[t]),void 0!==o&&"null"!==o||(o=null),this._options.afterGetState(t,o),o},o.prototype._showNextStep=function(){var t,e,o;return o=this.getStep(this._current),e=function(t){return function(e){return t.showStep(o.next)}}(this),t=this._makePromise(null!=o.onNext?o.onNext(this):void 0),this._callOnPromiseDone(t,e)},o.prototype._showPrevStep=function(){var t,e,o;return o=this.getStep(this._current),e=function(t){return function(e){return t.showStep(o.prev)}}(this),t=this._makePromise(null!=o.onPrev?o.onPrev(this):void 0),this._callOnPromiseDone(t,e)},o.prototype._debug=function(t){if(this._options.debug)return window.console.log("Bootstrap Tour '"+this._options.name+"' | "+t)},o.prototype._isRedirect=function(t,e,o){var n;return!(null==t||""===t||!("[object RegExp]"==={}.toString.call(t)&&!t.test(o.origin)||"[object String]"==={}.toString.call(t)&&this._isHostDifferent(t,o)))||(n=[o.pathname,o.search,o.hash].join(""),null!=e&&""!==e&&("[object RegExp]"==={}.toString.call(e)&&!e.test(n)||"[object String]"==={}.toString.call(e)&&this._isPathDifferent(e,n)))},o.prototype._isHostDifferent=function(t,e){switch({}.toString.call(t)){case"[object RegExp]":return!t.test(e.origin);case"[object String]":return this._getProtocol(t)!==this._getProtocol(e.href)||this._getHost(t)!==this._getHost(e.href);default:return!0}},o.prototype._isPathDifferent=function(t,e){return this._getPath(t)!==this._getPath(e)||!this._equal(this._getQuery(t),this._getQuery(e))||!this._equal(this._getHash(t),this._getHash(e))},o.prototype._isJustPathHashDifferent=function(t,e,o){var n;return(null==t||""===t||!this._isHostDifferent(t,o))&&(n=[o.pathname,o.search,o.hash].join(""),"[object String]"==={}.toString.call(e)&&(this._getPath(e)===this._getPath(n)&&this._equal(this._getQuery(e),this._getQuery(n))&&!this._equal(this._getHash(e),this._getHash(n))))},o.prototype._redirect=function(o,n,i){var r;return t.isFunction(o.redirect)?o.redirect.call(this,i):(r="[object String]"==={}.toString.call(o.host)?""+o.host+i:i,this._debug("Redirect to "+r),this._getState("redirect_to")!==""+n?(this._setState("redirect_to",""+n),e.location.href=r):(this._debug("Error redirection loop to "+i),this._removeState("redirect_to"),null!=o.onRedirectError?o.onRedirectError(this):void 0))},o.prototype._isOrphan=function(e){return null==e.element||!t(e.element).length||t(e.element).is(":hidden")&&"http://www.w3.org/2000/svg"!==t(e.element)[0].namespaceURI},o.prototype._isLast=function(){return this._current").parent().html()},o.prototype._reflexEvent=function(t){return"[object Boolean]"==={}.toString.call(t)?"click":t},o.prototype._scrollIntoView=function(e){var o,n,i,r,s,a,h,p;if(h=this.getStep(e),!(o=t(h.element)).length)return this._showPopoverAndOverlay(e);switch(n=t(window),s=o.offset().top,r=o.outerHeight(),p=n.height(),a=0,h.placement){case"top":a=Math.max(0,s-p/2);break;case"left":case"right":a=Math.max(0,s+r/2-p/2);break;case"bottom":a=Math.max(0,s+r-p/2)}return this._debug("Scroll into view. ScrollTop: "+a+". Element offset: "+s+". Window height: "+p+"."),i=0,t("body, html").stop(!0,!0).animate({scrollTop:Math.ceil(a)},function(t){return function(){if(2==++i)return t._showPopoverAndOverlay(e),t._debug("Scroll into view.\nAnimation end element offset: "+o.offset().top+".\nWindow height: "+n.height()+".")}}(this))},o.prototype._initMouseNavigation=function(){var o;return o=this,t(e).off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='prev']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='next']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='end']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='pause-resume']").on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='next']",function(t){return function(e){return e.preventDefault(),t.next()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='prev']",function(t){return function(e){if(e.preventDefault(),t._current>0)return t.prev()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='end']",function(t){return function(e){return e.preventDefault(),t.end()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='pause-resume']",function(e){var n;return e.preventDefault(),(n=t(this)).text(o._paused?n.data("pause-text"):n.data("resume-text")),o._paused?o.resume():o.pause()})},o.prototype._initKeyboardNavigation=function(){if(this._options.keyboard)return t(e).on("keyup.tour-"+this._options.name,function(t){return function(e){if(e.which)switch(e.which){case 39:return e.preventDefault(),t._isLast()?t.next():t.end();case 37:if(e.preventDefault(),t._current>0)return t.prev()}}}(this))},o.prototype._makePromise=function(e){return e&&t.isFunction(e.then)?e:null},o.prototype._callOnPromiseDone=function(t,e,o){return t?t.then(function(t){return function(n){return e.call(t,o)}}(this)):e.call(this,o)},o.prototype._showBackground=function(o,n){var i,r,s,a,h,p,u,l,c;for(s=t(e).height(),c=t(e).width(),l=[],a=0,h=(u=["top","bottom","left","right"]).length;a",{class:"tour-backdrop "+p}),t(o.backdropContainer).append(i),p){case"top":l.push(i.height(n.offset.top>0?n.offset.top:0).width(c).offset({top:0,left:0}));break;case"bottom":l.push(i.offset({top:n.offset.top+n.height,left:0}).height(s-(n.offset.top+n.height)).width(c));break;case"left":l.push(i.offset({top:n.offset.top,left:0}).height(n.height).width(n.offset.left>0?n.offset.left:0));break;case"right":l.push(i.offset({top:n.offset.top,left:n.offset.left+n.width}).height(n.height).width(c-(n.offset.left+n.width)));break;default:l.push(void 0)}return l},o.prototype._showOverlayElement=function(e){var o,n;return 0===(o=t(e.backdropElement)).length?n={width:0,height:0,offset:{top:0,left:0}}:(n={width:o.innerWidth(),height:o.innerHeight(),offset:o.offset()},o.addClass("tour-step-backdrop"),e.backdropPadding&&(n=this._applyBackdropPadding(e.backdropPadding,n))),this._showBackground(e,n)},o.prototype._hideOverlayElement=function(e){var o,n,i;t(e.backdropElement).removeClass("tour-step-backdrop"),i=this.backdrops;for(n in i)(o=i[n])&&void 0!==o.remove&&o.remove();return this.backdrops=[]},o.prototype._applyBackdropPadding=function(t,e){return"object"==typeof t?(null==t.top&&(t.top=0),null==t.right&&(t.right=0),null==t.bottom&&(t.bottom=0),null==t.left&&(t.left=0),e.offset.top=e.offset.top-t.top,e.offset.left=e.offset.left-t.left,e.width=e.width+t.left+t.right,e.height=e.height+t.top+t.bottom):(e.offset.top=e.offset.top-t,e.offset.left=e.offset.left-t,e.width=e.width+2*t,e.height=e.height+2*t),e},o.prototype._clearTimer=function(){return window.clearTimeout(this._timer),this._timer=null,this._duration=null},o.prototype._getProtocol=function(t){return(t=t.split("://")).length>1?t[0]:"http"},o.prototype._getHost=function(t){return t=t.split("//"),(t=t.length>1?t[1]:t[0]).split("/")[0]},o.prototype._getPath=function(t){return t.replace(/\/?$/,"").split("?")[0].split("#")[0]},o.prototype._getQuery=function(t){return this._getParams(t,"?")},o.prototype._getHash=function(t){return this._getParams(t,"#")},o.prototype._getParams=function(t,e){var o,n,i,r,s;if(1===(r=t.split(e)).length)return{};for(s={},o=0,n=(r=r[1].split("&")).length;o - v<%= pkg.version %> 23 | * <%= pkg.homepage %> 24 | * ======================================================================== 25 | * Copyright 2012-2017 <%= pkg.author.name %> 26 | * 27 | * ======================================================================== 28 | * Licensed under the MIT License (the "License"); 29 | * you may not use this file except in compliance with the License. 30 | * You may obtain a copy of the License at 31 | * 32 | * https://opensource.org/licenses/MIT 33 | * 34 | * Unless required by applicable law or agreed to in writing, software 35 | * distributed under the License is distributed on an "AS IS" BASIS, 36 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 37 | * See the License for the specific language governing permissions and 38 | * limitations under the License. 39 | * ======================================================================== 40 | */ 41 | 42 | \ 43 | `; 44 | 45 | // coffee 46 | gulp.task('coffee', () => 47 | gulp 48 | .src(`${paths.src}/coffee/${name}.coffee`) 49 | .pipe($.changed(`${paths.dist}/js`)) 50 | .pipe($.coffeelint('./coffeelint.json')) 51 | .pipe($.coffeelint.reporter()) 52 | .on('error', $.util.log) 53 | .pipe($.coffee({bare: true})) 54 | .on('error', $.util.log) 55 | .pipe($.header(banner, {pkg})) 56 | .pipe(gulp.dest(`${paths.dist}/js`)) 57 | .pipe(gulp.dest(`${paths.src}/docs/assets/js`)) 58 | .pipe(gulp.dest(paths.test)) 59 | .pipe($.uglify()) 60 | .pipe($.header(banner, {pkg})) 61 | .pipe($.rename({suffix: '.min'})) 62 | .pipe(gulp.dest(`${paths.dist}/js`)) 63 | ); 64 | 65 | gulp.task('coffee-standalone', () => 66 | streamqueue({objectMode: true}, 67 | gulp 68 | .src([ 69 | './node_modules/popper.js/dist/umd/popper.js', 70 | './node_modules/bootstrap/js/dist/util.js', 71 | './node_modules/bootstrap/js/dist/tooltip.js', 72 | './node_modules/bootstrap/js/dist/popover.js' 73 | ]) 74 | , 75 | gulp 76 | .src(`${paths.src}/coffee/${name}.coffee`) 77 | .pipe($.changed(`${paths.dist}/js`)) 78 | .pipe($.coffeelint('./coffeelint.json')) 79 | .pipe($.coffeelint.reporter()) 80 | .on('error', $.util.log) 81 | .pipe($.coffee({bare: true})) 82 | .on('error', $.util.log)).pipe($.concat(`${name}-standalone.js`)) 83 | .pipe($.header(banner, {pkg})) 84 | .pipe(gulp.dest(`${paths.dist}/js`)) 85 | .pipe(gulp.dest(paths.test)) 86 | .pipe($.uglify()) 87 | .pipe($.header(banner, {pkg})) 88 | .pipe($.rename({suffix: '.min'})) 89 | .pipe(gulp.dest(`${paths.dist}/js`)) 90 | ); 91 | 92 | // scss 93 | gulp.task('scss', () => 94 | gulp 95 | .src([ 96 | `${paths.src}/scss/${name}.scss` 97 | ]) 98 | .pipe($.changed(`${paths.dist}/css`)) 99 | .pipe($.sass().on('error', $.sass.logError)) 100 | .pipe($.header(banner, {pkg})) 101 | .pipe(gulp.dest(`${paths.dist}/css`)) 102 | .pipe(gulp.dest(`${paths.src}/docs/assets/css`)) 103 | .pipe($.sass({outputStyle: 'compressed'})) 104 | .pipe($.header(banner, {pkg})) 105 | .pipe($.rename({suffix: '.min'})) 106 | .pipe(gulp.dest(`${paths.dist}/css`)) 107 | ); 108 | 109 | gulp.task('scss-standalone', () => 110 | gulp 111 | .src(`${paths.src}/scss/${name}-standalone.scss`) 112 | .pipe($.changed(`${paths.dist}/css`)) 113 | .pipe($.sass({ includePaths: ['./node_modules/bootstrap/scss/'] }).on('error', $.sass.logError)) 114 | .pipe($.header(banner, {pkg})) 115 | .pipe(gulp.dest(`${paths.dist}/css`)) 116 | .pipe($.sass({ outputStyle: 'compressed' })).pipe($.header(banner, {pkg})) 117 | .pipe($.rename({suffix: '.min'})) 118 | .pipe(gulp.dest(`${paths.dist}/css`)) 119 | ); 120 | 121 | // test 122 | gulp.task('test-coffee', ['coffee'], () => 123 | gulp 124 | .src(`${paths.src}/coffee/${name}.spec.coffee`) 125 | .pipe($.changed(paths.test)) 126 | .pipe($.coffeelint('./coffeelint.json')) 127 | .pipe($.coffeelint.reporter()) 128 | .on('error', $.util.log) 129 | .pipe($.coffee()) 130 | .on('error', $.util.log) 131 | .pipe(gulp.dest(paths.test)) 132 | ); 133 | 134 | gulp.task('test-go', ['test-coffee'], done => new KarmaServer({ configFile: __dirname + '/karma.conf.js', singleRun: true}, done).start()); 135 | 136 | // docs 137 | gulp.task('docs-build', ['coffee', 'scss'], done => 138 | spawn((process.platform === 'win32' ? 'jekyll.bat' : 'jekyll'), ['build']) 139 | .on('close', done) 140 | ); 141 | 142 | gulp.task('docs-copy', ['docs-build'], () => 143 | gulp 144 | .src([ 145 | './node_modules/blueimp-md5/js/md5.min.js', 146 | './node_modules/blueimp-md5/js/md5.min.js.map', 147 | './node_modules/bootstrap/dist/css/bootstrap.min.css', 148 | './node_modules/bootstrap/dist/css/bootstrap.min.css.map', 149 | './node_modules/bootstrap/dist/js/bootstrap.min.js', 150 | './node_modules/jquery/dist/jquery.min.js', 151 | './node_modules/popper.js/dist/umd/popper.min.js', 152 | './node_modules/popper.js/dist/umd/popper.min.js.map' 153 | ]) 154 | .pipe(gulp.dest(`${paths.docs}/components`)) 155 | ); 156 | 157 | gulp.task('docs-coffee', ['docs-build'], () => 158 | gulp 159 | .src(`${paths.src}/coffee/${name}.docs.coffee`) 160 | .pipe($.changed(`${paths.docs}/assets/js`)) 161 | .pipe($.coffeelint.reporter()) 162 | .on('error', $.util.log) 163 | .pipe($.coffee()) 164 | .on('error', $.util.log) 165 | .pipe(gulp.dest(`${paths.docs}/assets/js`)) 166 | ); 167 | 168 | // clean 169 | gulp.task('clean-dist', () => 170 | gulp 171 | .src(paths.dist) 172 | .pipe($.clean()) 173 | ); 174 | 175 | gulp.task('clean-test', () => 176 | gulp 177 | .src(paths.test) 178 | .pipe($.clean()) 179 | ); 180 | 181 | gulp.task('clean-docs', () => 182 | gulp 183 | .src(paths.docs) 184 | .pipe($.clean()) 185 | ); 186 | 187 | // connect 188 | gulp.task('connect', ['docs'], () => 189 | $.connect.server({ 190 | root: [paths.docs], 191 | host: server.host, 192 | port: server.port, 193 | livereload: true 194 | }) 195 | ); 196 | 197 | // open 198 | gulp.task('open', ['connect'], () => 199 | gulp 200 | .src(`${paths.docs}/index.html`) 201 | .pipe($.open({uri: `http://${server.host}:${server.port}`})) 202 | ); 203 | 204 | gulp.task('watch', ['connect'], function() { 205 | gulp.watch(`${paths.src}/coffee/${name}.coffee`, ['coffee', 'coffee-standalone']); 206 | gulp.watch(`${paths.src}/scss/${name}.scss`, ["scss", "scss-standalone"]); 207 | gulp.watch(`${paths.src}/scss/${name}-standalone.scss`, ['scss-standalone']); 208 | gulp.watch(`${paths.src}/coffee/${name}.spec.coffee`, ['test']); 209 | gulp.watch([ 210 | `${paths.src}/coffee/${name}.docs.coffee`, 211 | `${paths.src}/docs/**/*` 212 | ], ['docs']); 213 | gulp.watch([ 214 | `${paths.dist}/js/**/*.js`, 215 | `${paths.dist}/css/**/*.css`, 216 | `${paths.docs}/index.html` 217 | ]) 218 | .on('change', event => 219 | gulp.src(event.path) 220 | .pipe($.connect.reload()) 221 | ); 222 | }); 223 | 224 | // bump 225 | gulp.task('bump', ['test'], function() { 226 | const bumpType = $.util.env.type || 'patch'; 227 | 228 | gulp.src(['./package.json', './smart.json']) 229 | .pipe($.bump({type: bumpType})) 230 | .pipe(gulp.dest('./')); 231 | }); 232 | 233 | // tasks 234 | gulp.task('clean', ['clean-dist', 'clean-test', 'clean-docs']); 235 | gulp.task('server', ['connect', 'open', 'watch']); 236 | gulp.task('dist', ['coffee', 'coffee-standalone', 'scss', 'scss-standalone']); 237 | gulp.task('test', ['coffee', 'test-coffee', 'test-go']); 238 | gulp.task('docs', ['coffee', 'scss', 'docs-build', 'docs-copy', 'docs-coffee']); 239 | gulp.task('default', ['dist', 'docs', 'server']); 240 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | browsers: ['ChromeHeadless', 'Firefox'], 4 | files: [ 5 | 'node_modules/jquery/dist/jquery.js', 6 | 'test/bootstrap-tour-standalone.js', 7 | 'test/bootstrap-tour.spec.js' 8 | ], 9 | frameworks: ['jasmine'] 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | summary: "Quick and easy way to build your product tours with Bootstrap Popovers." 3 | }); 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap-tour", 3 | "description": "Quick and easy way to build your product tours with Bootstrap Popovers.", 4 | "version": "0.12.0", 5 | "keywords": [ 6 | "tour", 7 | "bootstrap", 8 | "js", 9 | "tour", 10 | "intro" 11 | ], 12 | "homepage": "http://bootstraptour.com", 13 | "author": { 14 | "name": "Ulrich Sossou", 15 | "email": "sorich87@gmail.com", 16 | "url": "http://ulrichsossou.com" 17 | }, 18 | "contributors": [ 19 | { 20 | "name": "Emanuele Marchi", 21 | "email": "emanuele@lostcrew.it", 22 | "url": "http://lostcrew.it" 23 | }, 24 | { 25 | "name": "Nicola Molinari", 26 | "email": "emmenko@gmail.com" 27 | } 28 | ], 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/sorich87/bootstrap-tour.git" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/sorich87/bootstrap-tour/issues" 35 | }, 36 | "licenses": [ 37 | { 38 | "type": "MIT", 39 | "url": "https://opensource.org/licenses/MIT" 40 | } 41 | ], 42 | "dependencies": { 43 | "bootstrap": ">=4.0.0-beta", 44 | "jquery": "^3.0.0", 45 | "popper.js": "^1.12.3" 46 | }, 47 | "devDependencies": { 48 | "blueimp-md5": "^2.10.0", 49 | "coffee-script": "~1.12.7", 50 | "gulp": "~3.9.0", 51 | "gulp-bump": "~2.7", 52 | "gulp-changed": "~3.1.0", 53 | "gulp-clean": "~0.3.1", 54 | "gulp-coffee": "~2.3.4", 55 | "gulp-coffeelint": "~0.6.0", 56 | "gulp-concat": "~2.6.1", 57 | "gulp-connect": "~5.0.0", 58 | "gulp-header": "~1.8.9", 59 | "gulp-jasmine": "~2.4.2", 60 | "gulp-load-plugins": "~1.5.0", 61 | "gulp-open": "~2.0.0", 62 | "gulp-rename": "~1.2.0", 63 | "gulp-sass": "^3.1.0", 64 | "gulp-uglify": "~3.0.0", 65 | "gulp-util": "~3.0.0", 66 | "karma": "~1.7.1", 67 | "karma-chrome-launcher": "^2.2.0", 68 | "karma-firefox-launcher": "^1.0.1", 69 | "karma-jasmine": "~1.1.0", 70 | "phantomjs": "^2.1.7", 71 | "streamqueue": "1.1.1" 72 | }, 73 | "engines": { 74 | "node": ">= 0.8.0" 75 | }, 76 | "main": "./build/js/bootstrap-tour.js", 77 | "scripts": { 78 | "build": "gulp dist", 79 | "test": "gulp test" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /smart.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap-tour", 3 | "description": "Quick and easy way to build your product tours with Bootstrap Popovers.", 4 | "homepage": "http://bootstraptour.com", 5 | "author": "Ulrich Sossou (http://ulrichsossou.com)", 6 | "version": "0.12.0", 7 | "git": "https://github.com/sorich87/bootstrap-tour.git", 8 | "packages": {} 9 | } 10 | -------------------------------------------------------------------------------- /src/coffee/bootstrap-tour.coffee: -------------------------------------------------------------------------------- 1 | ((window, factory) -> 2 | if typeof define is 'function' and define.amd 3 | define ['jquery'], (jQuery) -> (window.Tour = factory(jQuery)) 4 | else if typeof exports is 'object' 5 | module.exports = factory(require('jquery')) 6 | else 7 | window.Tour = factory(window.jQuery) 8 | )(window, ($) -> 9 | document = window.document 10 | 11 | class Tour 12 | constructor: (options) -> 13 | try 14 | storage = window.localStorage 15 | catch 16 | # localStorage may be unavailable due to security settings 17 | storage = false 18 | @_options = $.extend 19 | name: 'tour' 20 | steps: [] 21 | container: 'body' 22 | autoscroll: true 23 | keyboard: true 24 | storage: storage 25 | debug: false 26 | backdrop: false 27 | backdropContainer: 'body' 28 | backdropPadding: 0 29 | redirect: true 30 | orphan: false 31 | duration: false 32 | delay: false 33 | basePath: '' 34 | template: '' 50 | afterSetState: (key, value) -> 51 | afterGetState: (key, value) -> 52 | afterRemoveState: (key) -> 53 | onStart: (tour) -> 54 | onEnd: (tour) -> 55 | onShow: (tour) -> 56 | onShown: (tour) -> 57 | onHide: (tour) -> 58 | onHidden: (tour) -> 59 | onNext: (tour) -> 60 | onPrev: (tour) -> 61 | onPause: (tour, duration) -> 62 | onResume: (tour, duration) -> 63 | onRedirectError: (tour) -> 64 | , options 65 | 66 | @_force = false 67 | @_inited = false 68 | @_current = null 69 | @backdrops = [] 70 | @ 71 | 72 | # Add multiple steps 73 | addSteps: (steps) -> 74 | @addStep step for step in steps 75 | @ 76 | 77 | # Add a new step 78 | addStep: (step) -> 79 | @_options.steps.push step 80 | @ 81 | 82 | # Get a step by its indice 83 | getStep: (i) -> 84 | if @_options.steps[i]? 85 | $.extend 86 | id: "step-#{i}" 87 | path: '' 88 | host: '' 89 | placement: 'right' 90 | title: '' 91 | content: '

' # no empty as default, otherwise popover won't show up 92 | next: if i is @_options.steps.length - 1 then -1 else i + 1 93 | prev: i - 1 94 | animation: true 95 | container: @_options.container 96 | autoscroll: @_options.autoscroll 97 | backdrop: @_options.backdrop 98 | backdropContainer: @_options.backdropContainer 99 | backdropPadding: @_options.backdropPadding 100 | redirect: @_options.redirect 101 | reflexElement: @_options.steps[i].element 102 | backdropElement: @_options.steps[i].element 103 | orphan: @_options.orphan 104 | duration: @_options.duration 105 | delay: @_options.delay 106 | template: @_options.template 107 | onShow: @_options.onShow 108 | onShown: @_options.onShown 109 | onHide: @_options.onHide 110 | onHidden: @_options.onHidden 111 | onNext: @_options.onNext 112 | onPrev: @_options.onPrev 113 | onPause: @_options.onPause 114 | onResume: @_options.onResume 115 | onRedirectError: @_options.onRedirectError 116 | , @_options.steps[i] 117 | 118 | # Setup event bindings and continue a tour that has already started 119 | init: (force) -> 120 | @_force = force 121 | 122 | if @ended() 123 | @_debug 'Tour ended, init prevented.' 124 | return @ 125 | 126 | @setCurrentStep() 127 | 128 | @_initMouseNavigation() 129 | @_initKeyboardNavigation() 130 | 131 | # Continue a tour that had started on a previous page load 132 | @showStep @_current unless @_current is null 133 | 134 | @_inited = true 135 | @ 136 | 137 | # Start tour from current step 138 | start: (force = false) -> 139 | @init force unless @_inited # Backward compatibility 140 | 141 | if @_current is null 142 | promise = @_makePromise(@_options.onStart(@) if @_options.onStart?) 143 | @_callOnPromiseDone(promise, @showStep, 0) 144 | @ 145 | 146 | # Hide current step and show next step 147 | next: -> 148 | promise = @hideStep @_current, @_current+1 149 | @_callOnPromiseDone promise, @_showNextStep 150 | 151 | # Hide current step and show prev step 152 | prev: -> 153 | promise = @hideStep @_current, @_current-1 154 | @_callOnPromiseDone promise, @_showPrevStep 155 | 156 | goTo: (i) -> 157 | promise = @hideStep @_current, i 158 | @_callOnPromiseDone promise, @showStep, i 159 | 160 | # End tour 161 | end: -> 162 | endHelper = (e) => 163 | $(document).off "click.tour-#{@_options.name}" 164 | $(document).off "keyup.tour-#{@_options.name}" 165 | @_setState('end', 'yes') 166 | @_inited = false 167 | @_force = false 168 | 169 | @_clearTimer() 170 | 171 | @_options.onEnd(@) if @_options.onEnd? 172 | 173 | promise = @hideStep(@_current) 174 | @_callOnPromiseDone(promise, endHelper) 175 | 176 | # Verify if tour is enabled 177 | ended: -> 178 | not @_force and not not @_getState 'end' 179 | 180 | # Restart tour 181 | restart: -> 182 | @_removeState 'current_step' 183 | @_removeState 'end' 184 | @_removeState 'redirect_to' 185 | @start() 186 | 187 | # Pause step timer 188 | pause: -> 189 | step = @getStep @_current 190 | return @ unless step and step.duration 191 | 192 | @_paused = true 193 | @_duration -= new Date().getTime() - @_start 194 | window.clearTimeout(@_timer) 195 | 196 | @_debug "Paused/Stopped step #{@_current + 1} timer (#{@_duration} remaining)." 197 | 198 | step.onPause @, @_duration if step.onPause? 199 | 200 | # Resume step timer 201 | resume: -> 202 | step = @getStep @_current 203 | return @ unless step and step.duration 204 | 205 | @_paused = false 206 | @_start = new Date().getTime() 207 | @_duration = @_duration or step.duration 208 | @_timer = window.setTimeout => 209 | if @_isLast() then @next() else @end() 210 | , @_duration 211 | 212 | @_debug "Started step #{@_current + 1} timer with duration #{@_duration}" 213 | 214 | step.onResume @, @_duration if step.onResume? and @_duration isnt step.duration 215 | 216 | # Hide the specified step 217 | hideStep: (i, iNext) -> 218 | step = @getStep i 219 | return unless step 220 | 221 | @_clearTimer() 222 | 223 | # If onHide returns a promise, let's wait until it's done to execute 224 | promise = @_makePromise(step.onHide @, i if step.onHide?) 225 | 226 | hideStepHelper = (e) => 227 | $element = $ step.element 228 | $element = $('body') unless $element.data('bs.popover') 229 | $element 230 | .popover('dispose') 231 | .removeClass("tour-#{@_options.name}-element tour-#{@_options.name}-#{i}-element") 232 | .removeData('bs.popover') 233 | 234 | if step.reflex 235 | $ step.reflexElement 236 | .removeClass('tour-step-element-reflex') 237 | .off "#{@_reflexEvent(step.reflex)}.tour-#{@_options.name}" 238 | 239 | if step.backdrop 240 | next_step = iNext? and @getStep iNext 241 | if !next_step or !next_step.backdrop or next_step.backdropElement != step.backdropElement 242 | @_hideOverlayElement(step) 243 | 244 | step.onHidden(@) if step.onHidden? 245 | 246 | hideDelay = step.delay.hide || step.delay 247 | 248 | if ({}).toString.call(hideDelay) is '[object Number]' and hideDelay > 0 249 | @_debug "Wait #{hideDelay} milliseconds to hide the step #{@_current + 1}" 250 | window.setTimeout => 251 | @_callOnPromiseDone promise, hideStepHelper 252 | , hideDelay 253 | else 254 | @_callOnPromiseDone promise, hideStepHelper 255 | 256 | promise 257 | 258 | # Show the specified step 259 | showStep: (i) -> 260 | if @ended() 261 | @_debug 'Tour ended, showStep prevented.' 262 | return @ 263 | 264 | step = @getStep i 265 | return unless step 266 | 267 | skipToPrevious = i < @_current 268 | 269 | # If onShow returns a promise, let's wait until it's done to execute 270 | promise = @_makePromise(step.onShow @, i if step.onShow?) 271 | 272 | @setCurrentStep i 273 | 274 | # Support string or function for path 275 | path = switch ({}).toString.call step.path 276 | when '[object Function]' then step.path() 277 | when '[object String]' then @_options.basePath + step.path 278 | else step.path 279 | 280 | # Redirect to step path if not already there 281 | if step.redirect and @_isRedirect step.host, path, document.location 282 | @_redirect step, i, path 283 | 284 | return unless @_isJustPathHashDifferent(step.host, path, document.location) 285 | 286 | showStepHelper = (e) => 287 | # Skip if step is orphan and orphan options is false 288 | if @_isOrphan step 289 | if step.orphan is false 290 | @_debug """Skip the orphan step #{@_current + 1}. 291 | Orphan option is false and the element does not exist or is hidden.""" 292 | if skipToPrevious then @_showPrevStep() else @_showNextStep() 293 | return 294 | 295 | @_debug "Show the orphan step #{@_current + 1}. Orphans option is true." 296 | 297 | # Show backdrop 298 | # @_showBackdrop(step) if step.backdrop 299 | 300 | if step.autoscroll 301 | @_scrollIntoView i 302 | else 303 | @_showPopoverAndOverlay i 304 | 305 | # Play step timer 306 | @resume() if step.duration 307 | 308 | showDelay = step.delay.show || step.delay 309 | 310 | if ({}).toString.call(showDelay) is '[object Number]' and showDelay > 0 311 | @_debug "Wait #{showDelay} milliseconds to show the step #{@_current + 1}" 312 | window.setTimeout => 313 | @_callOnPromiseDone promise, showStepHelper 314 | , showDelay 315 | else 316 | @_callOnPromiseDone promise, showStepHelper 317 | 318 | promise 319 | 320 | getCurrentStep: -> 321 | @_current 322 | 323 | # Setup current step variable 324 | setCurrentStep: (value) -> 325 | if value? 326 | @_current = value 327 | @_setState 'current_step', value 328 | else 329 | @_current = @_getState 'current_step' 330 | @_current = if @_current is null then null else parseInt @_current, 10 331 | @ 332 | 333 | # Manually trigger a redraw on the overlay element 334 | redraw: -> 335 | @_showOverlayElement(@getStep(@getCurrentStep())) 336 | 337 | # Set a state in storage 338 | _setState: (key, value) -> 339 | if @_options.storage 340 | keyName = "#{@_options.name}_#{key}" 341 | try @_options.storage.setItem keyName, value 342 | catch e 343 | if e.code is DOMException.QUOTA_EXCEEDED_ERR 344 | @_debug 'LocalStorage quota exceeded. State storage failed.' 345 | @_options.afterSetState keyName, value 346 | else 347 | @_state ?= {} 348 | @_state[key] = value 349 | 350 | # Remove the current state from the storage layer 351 | _removeState: (key) -> 352 | if @_options.storage 353 | keyName = "#{@_options.name}_#{key}" 354 | @_options.storage.removeItem keyName 355 | @_options.afterRemoveState keyName 356 | else 357 | delete @_state[key] if @_state? 358 | 359 | # Get the current state from the storage layer 360 | _getState: (key) -> 361 | if @_options.storage 362 | keyName = "#{@_options.name}_#{key}" 363 | value = @_options.storage.getItem keyName 364 | else 365 | value = @_state[key] if @_state? 366 | 367 | value = null if value is undefined or value is 'null' 368 | 369 | @_options.afterGetState key, value 370 | return value 371 | 372 | # Show next step 373 | _showNextStep: -> 374 | step = @getStep @_current 375 | showNextStepHelper = (e) => @showStep step.next 376 | 377 | promise = @_makePromise(step.onNext @ if step.onNext?) 378 | @_callOnPromiseDone promise, showNextStepHelper 379 | 380 | # Show prev step 381 | _showPrevStep: -> 382 | step = @getStep @_current 383 | showPrevStepHelper = (e) => @showStep step.prev 384 | 385 | promise = @_makePromise(step.onPrev @ if step.onPrev?) 386 | @_callOnPromiseDone promise, showPrevStepHelper 387 | 388 | # Print message in console 389 | _debug: (text) -> 390 | window.console.log "Bootstrap Tour '#{@_options.name}' | #{text}" if @_options.debug 391 | 392 | # Check if step path equals current document path 393 | _isRedirect: (host, path, location) -> 394 | return true if host? and host isnt '' and ( 395 | (({}).toString.call(host) is '[object RegExp]' and not host.test(location.origin)) or 396 | (({}).toString.call(host) is '[object String]' and @_isHostDifferent(host, location)) 397 | ) 398 | 399 | currentPath = [ 400 | location.pathname, 401 | location.search, 402 | location.hash 403 | ].join('') 404 | 405 | path? and path isnt '' and ( 406 | (({}).toString.call(path) is '[object RegExp]' and not path.test(currentPath)) or 407 | (({}).toString.call(path) is '[object String]' and @_isPathDifferent(path, currentPath)) 408 | ) 409 | 410 | _isHostDifferent: (host, location) -> 411 | switch ({}).toString.call(host) 412 | when '[object RegExp]' 413 | not host.test(location.origin) 414 | when '[object String]' 415 | @_getProtocol(host) isnt @_getProtocol(location.href) or 416 | @_getHost(host) isnt @_getHost(location.href) 417 | else 418 | true 419 | 420 | _isPathDifferent: (path, currentPath) -> 421 | @_getPath(path) isnt @_getPath(currentPath) or not 422 | @_equal(@_getQuery(path), @_getQuery(currentPath)) or not 423 | @_equal(@_getHash(path), @_getHash(currentPath)) 424 | 425 | _isJustPathHashDifferent: (host, path, location) -> 426 | if host? and host isnt '' 427 | return false if @_isHostDifferent(host, location) 428 | 429 | currentPath = [ 430 | location.pathname, 431 | location.search, 432 | location.hash 433 | ].join('') 434 | 435 | if ({}).toString.call(path) is '[object String]' 436 | return @_getPath(path) is @_getPath(currentPath) and 437 | @_equal(@_getQuery(path), @_getQuery(currentPath)) and not 438 | @_equal(@_getHash(path), @_getHash(currentPath)) 439 | 440 | false 441 | 442 | # Execute the redirect 443 | _redirect: (step, i, path) -> 444 | if $.isFunction step.redirect 445 | step.redirect.call this, path 446 | else 447 | href = if ({}).toString.call(step.host) is '[object String]' then "#{step.host}#{path}" else path 448 | @_debug "Redirect to #{href}" 449 | 450 | if @_getState('redirect_to') is "#{i}" 451 | @_debug "Error redirection loop to #{path}" 452 | @_removeState 'redirect_to' 453 | 454 | step.onRedirectError @ if step.onRedirectError? 455 | else 456 | @_setState 'redirect_to', "#{i}" 457 | document.location.href = href 458 | 459 | _isOrphan: (step) -> 460 | # Do not check for is(':hidden') on svg elements. jQuery does not work properly on svg. 461 | not step.element? or 462 | not $(step.element).length or 463 | $(step.element).is(':hidden') and 464 | ($(step.element)[0].namespaceURI isnt 'http://www.w3.org/2000/svg') 465 | 466 | _isLast: -> 467 | @_current < @_options.steps.length - 1 468 | 469 | _showPopoverAndOverlay: (i) => 470 | return if @getCurrentStep() isnt i or @ended() 471 | 472 | step = @getStep i 473 | 474 | @_showOverlayElement step if step.backdrop 475 | @_showPopover step, i 476 | step.onShown @ if step.onShown? 477 | @_debug "Step #{@_current + 1} of #{@_options.steps.length}" 478 | 479 | # Show step popover 480 | _showPopover: (step, i) -> 481 | # Remove previously existing tour popovers. This prevents displaying of 482 | # multiple inactive popovers when user navigates the tour too quickly. 483 | $(".tour-#{@_options.name}").remove() 484 | 485 | options = $.extend {}, @_options 486 | isOrphan = @_isOrphan step 487 | 488 | step.template = @_template step, i 489 | 490 | if isOrphan 491 | step.element = 'body' 492 | step.placement = 'top' 493 | 494 | $element = $ step.element 495 | $element.addClass "tour-#{@_options.name}-element tour-#{@_options.name}-#{i}-element" 496 | 497 | $.extend options, step.options if step.options 498 | if step.reflex and not isOrphan 499 | $ step.reflexElement 500 | .addClass('tour-step-element-reflex') 501 | .off("#{@_reflexEvent(step.reflex)}.tour-#{@_options.name}") 502 | .on "#{@_reflexEvent(step.reflex)}.tour-#{@_options.name}", => 503 | if @_isLast() then @next() else @end() 504 | 505 | $element 506 | .popover( 507 | placement: step.placement 508 | trigger: 'manual' 509 | title: step.title 510 | content: step.content 511 | html: true 512 | animation: step.animation 513 | container: step.container 514 | template: step.template 515 | selector: step.element 516 | ) 517 | .popover 'show' 518 | 519 | # Tip adjustment 520 | $tip = $($element.data('bs.popover').getTipElement()) 521 | $tip.attr 'id', step.id 522 | 523 | # Get popover template 524 | _template: (step, i) -> 525 | template = step.template 526 | 527 | if @_isOrphan(step) and ({}).toString.call(step.orphan) isnt '[object Boolean]' 528 | template = step.orphan 529 | 530 | $template = if $.isFunction template then $(template i, step) else $(template) 531 | $navigation = $template.find '.popover-navigation' 532 | $prev = $navigation.find '[data-role="prev"]' 533 | $next = $navigation.find '[data-role="next"]' 534 | $resume = $navigation.find '[data-role="pause-resume"]' 535 | 536 | $template.addClass 'orphan' if @_isOrphan step 537 | $template.addClass "tour-#{@_options.name} tour-#{@_options.name}-#{i}" 538 | $template.addClass "tour-#{@_options.name}-reflex" if step.reflex 539 | 540 | if step.prev < 0 541 | $prev.addClass('disabled') 542 | .prop('disabled', true) 543 | .prop('tabindex', -1) 544 | 545 | if step.next < 0 546 | $next.addClass('disabled') 547 | .prop('disabled', true) 548 | .prop('tabindex', -1) 549 | 550 | $resume.remove() unless step.duration 551 | $template.clone().wrap('
').parent().html() 552 | 553 | _reflexEvent: (reflex) -> 554 | if ({}).toString.call(reflex) is '[object Boolean]' then 'click' else reflex 555 | 556 | # Scroll to the popup if it is not in the viewport 557 | _scrollIntoView: (i) -> 558 | step = @getStep i 559 | $element = $(step.element) 560 | return @_showPopoverAndOverlay(i) unless $element.length 561 | 562 | $window = $(window) 563 | offsetTop = $element.offset().top 564 | height = $element.outerHeight() 565 | windowHeight = $window.height() 566 | scrollTop = 0 567 | 568 | switch step.placement.replace('auto','').trim() 569 | when 'top' 570 | scrollTop = Math.max(0, offsetTop - (windowHeight / 2)) 571 | when 'left', 'right' 572 | scrollTop = Math.max(0, (offsetTop + height / 2) - (windowHeight / 2)) 573 | when 'bottom' 574 | scrollTop = Math.max(0, (offsetTop + height) - (windowHeight / 2)) 575 | 576 | @_debug "Scroll into view. ScrollTop: #{scrollTop}. Element offset: #{offsetTop}. Window height: #{windowHeight}." 577 | counter = 0 578 | $('body, html').stop(true, true).animate 579 | scrollTop: Math.ceil(scrollTop), 580 | => 581 | if ++counter is 2 582 | @_showPopoverAndOverlay(i) 583 | @_debug """Scroll into view. 584 | Animation end element offset: #{$element.offset().top}. 585 | Window height: #{$window.height()}.""" 586 | 587 | # Event bindings for mouse navigation 588 | _initMouseNavigation: -> 589 | _this = @ 590 | 591 | # Go to next step after click on element with attribute 'data-role=next' 592 | # Go to previous step after click on element with attribute 'data-role=prev' 593 | # End tour after click on element with attribute 'data-role=end' 594 | # Pause/resume tour after click on element with attribute 'data-role=pause-resume' 595 | $(document) 596 | .off("click.tour-#{@_options.name}", ".popover.tour-#{@_options.name} *[data-role='prev']") 597 | .off("click.tour-#{@_options.name}", ".popover.tour-#{@_options.name} *[data-role='next']") 598 | .off("click.tour-#{@_options.name}", ".popover.tour-#{@_options.name} *[data-role='end']") 599 | .off("click.tour-#{@_options.name}", ".popover.tour-#{@_options.name} *[data-role='pause-resume']") 600 | .on "click.tour-#{@_options.name}", ".popover.tour-#{@_options.name} *[data-role='next']", (e) => 601 | e.preventDefault() 602 | @next() 603 | .on "click.tour-#{@_options.name}", ".popover.tour-#{@_options.name} *[data-role='prev']", (e) => 604 | e.preventDefault() 605 | @prev() if @_current > 0 606 | .on "click.tour-#{@_options.name}", ".popover.tour-#{@_options.name} *[data-role='end']", (e) => 607 | e.preventDefault() 608 | @end() 609 | .on "click.tour-#{@_options.name}", ".popover.tour-#{@_options.name} *[data-role='pause-resume']", (e) -> 610 | e.preventDefault() 611 | $this = $ @ 612 | 613 | $this.text if _this._paused then $this.data 'pause-text' else $this.data 'resume-text' 614 | if _this._paused then _this.resume() else _this.pause() 615 | 616 | # Keyboard navigation 617 | _initKeyboardNavigation: -> 618 | return unless @_options.keyboard 619 | 620 | $(document).on "keyup.tour-#{@_options.name}", (e) => 621 | return unless e.which 622 | 623 | switch e.which 624 | when 39 625 | e.preventDefault() 626 | if @_isLast() then @next() else @end() 627 | when 37 628 | e.preventDefault() 629 | @prev() if @_current > 0 630 | 631 | # Checks if the result of a callback is a promise 632 | _makePromise: (result) -> 633 | if result and $.isFunction(result.then) then result else null 634 | 635 | _callOnPromiseDone: (promise, cb, arg) -> 636 | if promise 637 | promise.then (e) => 638 | cb.call(@, arg) 639 | else 640 | cb.call(@, arg) 641 | 642 | _showBackground: (step, data) -> 643 | height = $(document).height() 644 | width = $(document).width() 645 | for pos in ['top', 'bottom', 'left', 'right'] 646 | $backdrop = @backdrops[pos] ?= $('
', class: "tour-backdrop #{pos}") 647 | $(step.backdropContainer).append($backdrop) 648 | 649 | switch pos 650 | when 'top' 651 | $backdrop 652 | .height(if data.offset.top > 0 then data.offset.top else 0) 653 | .width(width) 654 | .offset(top: 0, left: 0) 655 | when 'bottom' 656 | $backdrop 657 | .offset(top: data.offset.top + data.height, left: 0) 658 | .height(height - (data.offset.top + data.height)) 659 | .width(width) 660 | when 'left' 661 | $backdrop 662 | .offset(top: data.offset.top, left: 0) 663 | .height(data.height) 664 | .width(if data.offset.left > 0 then data.offset.left else 0) 665 | when 'right' 666 | $backdrop 667 | .offset(top: data.offset.top, left: data.offset.left + data.width) 668 | .height(data.height) 669 | .width(width - (data.offset.left + data.width)) 670 | 671 | _showOverlayElement: (step) -> 672 | $backdropElement = $ step.backdropElement 673 | 674 | if $backdropElement.length is 0 675 | elementData = 676 | width: 0 677 | height: 0 678 | offset: 679 | top: 0 680 | left: 0 681 | else 682 | elementData = 683 | width: $backdropElement.innerWidth() 684 | height: $backdropElement.innerHeight() 685 | offset: $backdropElement.offset() 686 | 687 | $backdropElement.addClass 'tour-step-backdrop' 688 | elementData = @_applyBackdropPadding step.backdropPadding, elementData if step.backdropPadding 689 | 690 | @_showBackground(step, elementData) 691 | 692 | _hideOverlayElement: (step) -> 693 | $(step.backdropElement).removeClass 'tour-step-backdrop' 694 | 695 | for pos, $backdrop of @backdrops 696 | $backdrop.remove() if $backdrop and $backdrop.remove isnt undefined 697 | 698 | @backdrops = [] 699 | 700 | _applyBackdropPadding: (padding, data) -> 701 | if typeof padding is 'object' 702 | padding.top ?= 0 703 | padding.right ?= 0 704 | padding.bottom ?= 0 705 | padding.left ?= 0 706 | 707 | data.offset.top = data.offset.top - padding.top 708 | data.offset.left = data.offset.left - padding.left 709 | data.width = data.width + padding.left + padding.right 710 | data.height = data.height + padding.top + padding.bottom 711 | else 712 | data.offset.top = data.offset.top - padding 713 | data.offset.left = data.offset.left - padding 714 | data.width = data.width + (padding * 2) 715 | data.height = data.height + (padding * 2) 716 | 717 | data 718 | 719 | _clearTimer: -> 720 | window.clearTimeout @_timer 721 | @_timer = null 722 | @_duration = null 723 | 724 | _getProtocol: (url) -> 725 | url = url.split('://') 726 | return if url.length > 1 then url[0] else 'http' 727 | 728 | _getHost: (url) -> 729 | url = url.split('//') 730 | url = if url.length > 1 then url[1] else url[0] 731 | 732 | return url.split('/')[0] 733 | 734 | _getPath: (path) -> 735 | return path.replace(/\/?$/, '').split('?')[0].split('#')[0] 736 | 737 | _getQuery: (path) -> 738 | return @_getParams(path, '?') 739 | 740 | _getHash: (path) -> 741 | return @_getParams(path, '#') 742 | 743 | _getParams: (path, start) -> 744 | params = path.split(start) 745 | return {} if params.length is 1 746 | 747 | params = params[1].split('&') 748 | paramsObject = {} 749 | 750 | for param in params 751 | param = param.split('=') 752 | paramsObject[param[0]] = param[1] or '' 753 | 754 | return paramsObject 755 | 756 | _equal: (obj1, obj2) -> 757 | if ({}).toString.call(obj1) is '[object Object]' and ({}).toString.call(obj2) is '[object Object]' 758 | obj1Keys = Object.keys(obj1) 759 | obj2Keys = Object.keys(obj2) 760 | return false if obj1Keys.length isnt obj2Keys.length 761 | 762 | for k,v of obj1 763 | return false if not @_equal(obj2[k], v) 764 | 765 | return true 766 | else if ({}).toString.call(obj1) is '[object Array]' and ({}).toString.call(obj2) is '[object Array]' 767 | return false if obj1.length isnt obj2.length 768 | 769 | for v,k in obj1 770 | return false if not @_equal(v, obj2[k]) 771 | 772 | return true 773 | else 774 | return obj1 is obj2 775 | 776 | Tour 777 | ) 778 | -------------------------------------------------------------------------------- /src/coffee/bootstrap-tour.docs.coffee: -------------------------------------------------------------------------------- 1 | $ -> 2 | $demo = $("#demo") 3 | duration = 5000 4 | remaining = duration 5 | tour = new Tour( 6 | onStart: -> $demo.addClass "disabled", true 7 | onEnd: -> $demo.removeClass "disabled", true 8 | debug: true 9 | steps: [ 10 | path: "/" 11 | element: "#navbar" 12 | placement: "bottom" 13 | title: "Welcome to Bootstrap Tour!" 14 | content: """ 15 | Introduce new users to your product by walking them through it step by step. 16 | """ 17 | , 18 | path: "/" 19 | element: "#usage" 20 | placement: "top" 21 | title: "A super simple setup" 22 | content: "Easy is better, right? The tour is up and running with just a 23 | few options and steps." 24 | , 25 | path: "/" 26 | element: "#license" 27 | placement: "top" 28 | title: "Best of all, it's free!" 29 | content: "Yeah! Free as in beer... or speech. Use and abuse, but don't forget to contribute!" 30 | , 31 | path: "/api" 32 | element: "#options .page-header" 33 | placement: "top" 34 | title: "Flexibilty and expressiveness" 35 | content: """ 36 | There are more options for those who want to get on the dark side.
37 | Power to the people! 38 | """ 39 | reflex: true 40 | , 41 | path: "/api" 42 | element: "#duration" 43 | placement: "top" 44 | title: "Automagically expiring step", 45 | content: """ 46 | A new addition: make your tour (or step) completely automatic. You set the duration, Bootstrap 47 | Tour does the rest. For instance, this step will disappear in 5 seconds. 48 | """ 49 | duration: 5000 50 | , 51 | path: "/api" 52 | element: "#methods table" 53 | placement: "top" 54 | title: "A new shiny Backdrop option" 55 | content: """ 56 | If you need to highlight the current step's element, activate the backdrop and you won't lose 57 | focus anymore! 58 | """ 59 | backdrop: true 60 | backdropPadding: 5 61 | , 62 | path: "/api" 63 | element: "#reflex" 64 | placement: "bottom" 65 | title: "Reflex mode" 66 | content: "Reflex mode is enabled, click on the text in the cell to continue!" 67 | reflex: true 68 | , 69 | path: "/api" 70 | title: "And support for orphan steps" 71 | content: """ 72 | If you activate the orphan property, the step(s) are shown centered in the page, and you can 73 | forget to specify element and placement! 74 | """ 75 | orphan: true 76 | onHidden: -> window.location.assign "/" 77 | ] 78 | ) 79 | .init() 80 | 81 | $('
You ended the demo tour. Restart the demo tour.
').prependTo(".content").alert() if tour.ended() 82 | 83 | $(document).on "click", "[data-demo]", (e) -> 84 | e.preventDefault() 85 | return if $(this).hasClass "disabled" 86 | tour.restart() 87 | $(".alert").alert "close" 88 | 89 | $(".gravatar").each -> 90 | $this = $(@) 91 | email = md5 $this.data "email" 92 | 93 | $(@).attr "src", "http://www.gravatar.com/avatar/#{email}?s=60" 94 | -------------------------------------------------------------------------------- /src/docs/CNAME: -------------------------------------------------------------------------------- 1 | bootstraptour.com 2 | -------------------------------------------------------------------------------- /src/docs/_includes/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /src/docs/_includes/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% if page.title == "Bootstrap Tour" %} 7 | {{ page.title }} 8 | {% else %} 9 | {{ page.title }} · Bootstrap Tour 10 | {% endif %} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 30 | -------------------------------------------------------------------------------- /src/docs/_includes/nav.html: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /src/docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include header.html %} 5 | 6 | 7 | Fork me on GitHub 8 | {% include nav.html %} 9 | {{ content }} 10 | {% include footer.html %} 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/docs/api.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: API Documentation 4 | slug: api 5 | --- 6 | 7 |
8 |
9 | 12 |

Global options

13 | {% highlight javascript %} 14 | var tour = new Tour({ 15 | name: "tour", 16 | steps: [], 17 | container: "body", 18 | keyboard: true, 19 | storage: window.localStorage, 20 | debug: false, 21 | backdrop: false, 22 | backdropContainer: 'body', 23 | backdropPadding: 0, 24 | redirect: true, 25 | orphan: false, 26 | duration: false, 27 | delay: false, 28 | basePath: "", 29 | template: "
30 |
31 |

32 |
33 |
34 | 35 | | 36 | 37 |
38 | 39 |
", 40 | afterGetState: function (key, value) {}, 41 | afterSetState: function (key, value) {}, 42 | afterRemoveState: function (key, value) {}, 43 | onStart: function (tour) {}, 44 | onEnd: function (tour) {}, 45 | onShow: function (tour) {}, 46 | onShown: function (tour) {}, 47 | onHide: function (tour) {}, 48 | onHidden: function (tour) {}, 49 | onNext: function (tour) {}, 50 | onPrev: function (tour) {}, 51 | onPause: function (tour, duration) {}, 52 | onResume: function (tour, duration) {}, 53 | onRedirectError: function (tour) {} 54 | }); 55 | {% endhighlight %} 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 72 | 73 | 74 | 75 | 76 | 77 | 79 | 80 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 114 | 115 | 116 | 117 | 118 | 119 | 121 | 122 | 123 | 124 | 125 | 126 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 141 | 142 | 143 | 144 | 145 | 146 | 149 | 150 | 151 | 152 | 153 | 154 | 157 | 158 | 159 | 160 | 161 | 162 | 165 | 166 | 167 | 168 | 169 | 170 | 178 | 179 | 180 | 181 | 182 | 186 | 187 | 188 | 189 | 190 | 191 | 203 | 218 | 219 | 220 | 221 | 222 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 307 | 308 | 309 | 310 | 311 | 312 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 322 | 323 | 324 | 325 |
NameTypeDescriptionDefault
nameStringThis option is used to build the name of the storage item where the tour state is stored. 70 | The name should contain only alphanumerics, underscores and hyphens. 71 | You can initialize several tours with different names in the same page and application.'tour'
stepsArrayA list of object representing the steps to be included in the tour. Jump to 78 | Step options for the single step API.[]
containerStringAppends the step popover to a specific element.
85 | See Usage section of 86 | Popover. 87 | 88 |
'body'
autoscrollBooleanAutoscrolls the window when the step popover is out of view.true
keyboardBooleanThis option set the left and right arrow navigation.true
storageObjectThe storage system you want to use. Could be the objects window.localStorage, 107 | window.sessionStorage or your own object.
108 | You can set this option as 109 | false to disable storage 110 | persistence (the tour starts from beginning every time the page is 111 | loaded).
112 | Read more about DOM Storage interfaces. 113 |
window.localStorage
debugBooleanSet this option to true to have some useful informations printed in the 120 | console.false
backdropBooleanShow a dark backdrop behind the popover and its element, 127 | highlighting the current step.false
backdropContainer NEWString (jQuery selector)HTML element on which the backdrop should be shown.'body'
backdropPadding NEWNumber|ObjectAdd padding to the backdrop element that highlights the step element.
140 | It can be a number or a object containing optional top, right, bottom and left numbers.
0
redirectBoolean|FunctionSet a custom function to execute as redirect function. 147 | The default redirect relies on the traditional 148 | document.location.hreftrue
orphanBoolean|String|functionAllow to show the step regardless whether its element is not set, is 155 | not present in the page or is hidden. The step is fixed 156 | positioned in the middle of the page.false
duration NEWBoolean|NumberSet a expiration time for the steps. When the current step expires, 163 | the next step is automatically shown. See it as a sort of guided, automatized tour 164 | functionality. The value is specified in millisecondsfalse
delay NEWBoolean|NumberSpecifies a delay for the showing and hiding the tour steps. 171 | It can be: 172 |
    173 |
  • a falsy - there is no delay
  • 174 |
  • a number - used as a delay for both showing and hiding. In milliseconds
  • 175 |
  • a object containing optional show and hide numbers - defines the delays for showing and hiding respectively
  • 176 |
177 |
0
basePathStringSpecify a default base path prepended to the 183 | path option of every single 184 | step. Very useful if you need to reuse the same tour on different 185 | environments or sub-projects.''
templateString|FunctionString or function that returns a string of the HTML template for 192 | the popovers. If you pass a Function, two parameters are available: 193 | i is the position of step in the tour and 194 | step is the an object that contains all the other step 195 | options.
196 | From version 0.5, the navigation template is included inside the 197 | template so you can easily rewrite it. However, Bootstrap Tour maps the 198 | previous, next and end logics to the elements 199 | which have the related data-role 200 | attribute. Therefore, you can also have multiple elements with the same 201 | data-role attribute. 202 |
204 | {% highlight javascript %} 205 | "
206 |
207 |

208 |
209 |
210 | 211 | | 212 | 213 | 214 |
215 |
" 216 | {% endhighlight %} 217 |
afterGetState, afterSetState, afterRemoveStateFunctionYou may want to do something right after Bootstrap Tour read, write or remove 223 | the state. Just pass functions to these.
224 | Your functions can have two parameters: 225 |
    226 |
  • 227 | key 228 | Contains the name of the state being saved. It can be 229 | current_step (for the state where the 230 | latest step the visitor viewed is saved) or 231 | end (for the state which is saved when 232 | the user complete the tour). Note that Bootstrap Tour prepends the key with 233 | tour_ when saving the state. 234 |
  • 235 |
  • 236 | value 237 | The value of the state been saved. Can be the index of the 238 | current step if the key is current_step, or 239 | yes if the key is end. 240 |
  • 241 |
242 |

A simple example if to send a post request to your server each 243 | time there is a change:

244 | {% highlight javascript %} 245 | var tour = new Tour({ 246 | afterSetState: function (key, value) { 247 | $.post("/some/path", value); 248 | } 249 | }); 250 | {% endhighlight %} 251 |
function (key, value) { }
onStartFunctionFunction to execute when the tour starts.function (tour) { }
onEndFunctionFunction to execute when the tour ends.function (tour) { }
onShowFunctionFunction to execute right before each step is shown.function (tour) { }
onShownFunctionFunction to execute right after each step is shown.function (tour) { }
onHideFunctionFunction to execute right before each step is hidden.function (tour) { }
onHiddenFunctionFunction to execute right after each step is hidden.function (tour) { }
onNextFunctionFunction to execute when next step is called.function (tour) { }
onPrevFunctionFunction to execute when prev step is called.function (tour) { }
onPause NEWFunctionFunction to execute when pause is called. The second argument refers to the 306 | remaining duration.function (tour, duration) { }
onResume NEWFunctionFunction to execute when resume is called. The second argument refers to the 313 | remaining duration.function (tour, duration) { }
onRedirectError NEWFunctionFunction to execute when there is a redirection error. This 321 | happens when bootstrap tour cannot redirect to the path of the stepfunction (tour) { }
326 | 327 |

Step Options

328 | {% highlight javascript %} 329 | tour.addStep({ 330 | path: "", 331 | host: "", 332 | element: "", 333 | placement: "right", 334 | title: "", 335 | content: "", 336 | next: 0, 337 | prev: 0, 338 | animation: true, 339 | container: "body", 340 | backdrop: false, 341 | backdropContainer: 'body', 342 | backdropPadding: false, 343 | redirect: true, 344 | reflex: false, 345 | orphan: false, 346 | template: "", 347 | onShow: function (tour) {}, 348 | onShown: function (tour) {}, 349 | onHide: function (tour) {}, 350 | onHidden: function (tour) {}, 351 | onNext: function (tour) {}, 352 | onPrev: function (tour) {}, 353 | onPause: function (tour) {}, 354 | onResume: function (tour) {}, 355 | onRedirectError: function (tour) {} 356 | }); 357 | {% endhighlight %} 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 381 | 382 | 383 | 384 | 385 | 386 | 388 | 389 | 390 | 391 | 392 | 393 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 427 | 428 | 429 | 430 | 431 | 432 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 453 | 454 | 455 | 456 | 457 | 458 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 473 | 474 | 475 | 476 | 477 | 478 | 481 | 482 | 483 | 484 | 485 | 486 | 489 | 490 | 491 | 492 | 493 | 494 | 499 | 500 | 501 | 502 | 503 | 504 | 507 | 508 | 509 | 510 | 511 | 512 | 524 | 539 | 540 | 541 | 542 | 543 | 545 | 546 | 547 | 548 | 549 | 550 | 552 | 553 | 554 | 555 | 556 | 557 | 559 | 560 | 561 | 562 | 563 | 564 | 566 | 567 | 568 | 569 | 570 | 571 | 573 | 574 | 575 | 576 | 577 | 578 | 580 | 581 | 582 | 583 | 584 | 585 | 588 | 589 | 590 | 591 | 592 | 593 | 596 | 597 | 598 | 599 | 600 | 601 | 604 | 605 | 606 | 607 |
NameTypeDescriptionDefault
pathString or RegExpPath to the page on which the step should be shown. This 372 | allows you to build tours that span several pages!''
host NEWString or RegExpHost of the page on which the step should be shown. This 380 | allows you to build tours for several sub-domains''
elementString (jQuery selector)HTML element on which the step popover should be shown.
387 | If orphan is false, this option is required.
''
placementString|FunctionHow to position the popover. Possible choices: 394 | 'top', 395 | 'bottom', 396 | 'left', 397 | 'right', 398 | 'auto'. 399 | When "auto" is specified, it will dynamically reorient the popover. 400 | For example, if placement is "auto left", the popover will display to 401 | the left when possible, otherwise it will display right. 402 | 'right'
titleString|FunctionStep title''
contentString|FunctionStep content''
nextIntegerIndex of the step to show after this one, starting from 421 | 0 for the first step of the 422 | tour. -1 to not show the link 423 | to next step. By default, the next step (in the order you added 424 | them) will be shown.
425 | This option should be used in conjunction with 426 | prev.
0
prevIntegerIndex of the step to show before this one, starting from 433 | 0 for the first step of the 434 | tour. -1 to not show the link 435 | to previous step. By default, the previous step (in the order you added 436 | them) will be shown.
437 | This option should be used in conjunction with 438 | next.
0
animationBooleanApply a css fade transition to the tooltip.true
containerString (jQuery selector)Attachment of popover. Pass an element to append the popover 451 | to. By default the popover is appended after the 'element' above. 452 | This option is particularly helpful for Internet Explorer.'body'
backdropBooleanShow a dark backdrop behind the popover and its element, 459 | highlighting the current step.false
backdropContainer NEWString (jQuery selector)HTML element on which the backdrop should be shown.'body'
backdropPadding NEWBoolean|ObjectAdd padding to the backdrop element that highlights the step element.
472 | It can be a number or a object containing optional top, right, bottom and left numbers.
0
redirectBoolean|FunctionSet a custom function to execute as redirect function. 479 | The default redirect relies on the traditional 480 | document.location.hreftrue
reflex UPDATEDBoolean|StringEnable the reflex mode: attach an handler on click on the step element to continue the tour.
487 | In order to bind the handler to a custom event, you can pass a string with its name.
488 | Also, the class tour-step-element-reflex is added to the element, as hook for your custom style (e.g: cursor: pointer).
false
orphanBoolean|String|FunctionAllow to show the step regardless whether its element is not set, is 495 | not present in the page or is hidden. The step is fixed 496 | positioned in the middle of the page.
497 | You can use a string or function that returns a string of the HTML template for 498 | the orphan popovers
false
duration NEWBoolean|StringSet a expiration time for the steps. When the step expires, 505 | the next step is automatically shown. See it as a sort of guided, automatized tour 506 | functionality. The value is specified in millisecondsfalse
templateString|FunctionString or function that returns a string of the HTML template for 513 | the popovers. If you pass a Function, two parameters are available: 514 | i is the position of step in the tour and 515 | step is the object that contains all the other step 516 | options.
517 | From version 0.5, the navigation template is included inside the 518 | template so you can easily rewrite it. However, Bootstrap Tour maps the 519 | previous, next and end logics to the elements 520 | which have the related data-role 521 | attribute. Therefore, you can also have multiple elements with the same 522 | data-role attribute. 523 |
525 | {% highlight javascript %} 526 | "
527 |
528 |

529 |
530 |
531 | 532 | | 533 | 534 | 535 |
536 |
" 537 | {% endhighlight %} 538 |
onShowFunctionFunction to execute right before the step is shown. It overrides the 544 | global onShow option.function (tour) { }
onShownFunctionFunction to execute right after the step is shown. It overrides the 551 | global onShown option.function (tour) { }
onHideFunctionFunction to execute right before the step is hidden. It overrides 558 | the global onHide option.function (tour) { }
onHiddenFunctionFunction to execute right after the step is hidden. It overrides the 565 | global onHidden option.function (tour) { }
onNextFunctionFunction to execute when next step is called. It overrides the 572 | global onNext option.function (tour) { }
onPrevFunctionFunction to execute when prev step is called. It overrides the global 579 | onPrev option.function (tour) { }
onPause NEWFunctionFunction to execute when pause is called. The second argument refers to the 586 | remaining duration. It overrides the global the 587 | onPause optionfunction (tour, duration) { }
onResume NEWFunctionFunction to execute when resume is called. The second argument refers to the 594 | remaining duration. It overrides the global 595 | onResume optionfunction (tour, duration) { }
onRedirectError NEWFunctionFunction to execute when there is a redirection error. This 602 | happens when bootstrap tour cannot redirect to the path of the step. It overrides the global 603 | onRedirectError optionfunction (tour) { }
608 |
609 |
610 | 611 |
612 |
613 | 616 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 662 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 694 | 695 | 696 | 697 | 699 | 700 | 701 |
NameDescription
addSteps([])Add multiple steps to the tour. Pass an array of objects.
addStep({})Add single step to the tour. Pass an object.
init()Initialize the tour. You must do it before calling start.
start(true)Start the tour. Pass true to force the start.
restart()Restart the tour after it ended.
end()End the tour prematurely.
next()Skip to the next step.
prev()Go back to the previous step.
goTo(i) 661 | UPDATEDSkip to a specific step. Pass i as the 663 | index of the step in the tour (0-based).
664 | From version 0.7.0, the method has been renamed since some Javascript compilers 665 | are confused by the property name goto, which is a reserved word) 666 | . 667 |
pause()Pause the duration timer. It works only if tour or current step has duration.
resume()Resume the duration timer. It works only if tour or current step has duration.
ended()Verify if the tour ended. Returns boolean.
getStep(i)Get the step object. Pass i as the index 684 | of the step in the tour (0-based).
getCurrentStep()Get the index of the current step.
setCurrentStep(i)Override the current step. Pass i 693 | as the index of the step in the tour (0-based).
redraw()Triggers a redraw on the overlay element. Useful for 698 | Dynamically sized tour targets.
702 |
703 |
704 | 705 |
706 |
707 | 712 |

Bootstrap Tour can be used to create tours that span multiple pages. If you have URLs for each page that have 713 | unique paths, and the dependencies are loaded on each page, you can easily create a tour like so: 714 |

715 | {% highlight javascript %} 716 | var tour = new Tour({ 717 | steps: [ 718 | { 719 | element: "#my-element", 720 | title: "Title of my step", 721 | content: "Content of my step" 722 | }, 723 | { 724 | element: "#my-other-element", 725 | title: "Title of my step", 726 | content: "Content of my step", 727 | path: "/url/to/go/to/" 728 | } 729 | ] 730 | }); 731 | {% endhighlight %} 732 | 733 |

It's that simple.

734 | 735 |

If you do not know the URL you wish to go to because it contains a different value per user or per 736 | instance, you can use a regular expression as the path attribute and set the redirect 737 | attribute to a function that performs the redirect. 738 |

739 |

For example:

740 | 741 | {% highlight javascript %} 742 | var tour = new Tour({ 743 | steps: [ 744 | { 745 | element: "#my-element", 746 | title: "Title of my step", 747 | content: "Content of my step", 748 | redirect: function(){ 749 | document.location.href = '/url/' + userId; 750 | }; 751 | }, 752 | { 753 | element: "#my-other-element", 754 | title: "Title of my step", 755 | content: "Content of my step", 756 | path: RegExp("\/url\/[^/]+", "i") 757 | } 758 | ] 759 | }); 760 | {% endhighlight %} 761 | 762 |

Finally, if you are only using GET parameters to define different pages, and wish to redirect using those 763 | parameters, you may run into the problem that Bootstrap Tour will consider the path of the two steps to be 764 | identical. For example, you cannot use the path parameter to go from your homepage at / to a 765 | search results page at /?q=foo, because from Bootstrap Tour's perspective, those are the same 766 | location (/). 767 |

768 |

To work around this limitation, you can set the onNext attribute a function that returns a 769 | promise.

770 |

For example:

771 | 772 | {% highlight javascript %} 773 | var tour = new Tour({ 774 | steps: [ 775 | { 776 | element: "#my-element", 777 | title: "Title of my step", 778 | content: "Content of my step", 779 | onNext: function(){ 780 | document.location.href = '/?q=foo'; 781 | return (new jQuery.Deferred()).promise(); 782 | }; 783 | }, 784 | { 785 | element: "#my-other-element", 786 | title: "Title of my step", 787 | content: "Content of my step", 788 | } 789 | ] 790 | }); 791 | {% endhighlight %} 792 | 793 |

Doing this will prevent the next step from popping up while the redirect is being completed in the 794 | onNext function. 795 |

796 |
797 |
798 | -------------------------------------------------------------------------------- /src/docs/assets/css/bootstrap-tour.css: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-tour - v0.12.0 3 | * http://bootstraptour.com 4 | * ======================================================================== 5 | * Copyright 2012-2017 Ulrich Sossou 6 | * 7 | * ======================================================================== 8 | * Licensed under the MIT License (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://opensource.org/licenses/MIT 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | .tour-backdrop { 23 | background-color: #000; 24 | filter: alpha(opacity=80); 25 | opacity: .8; 26 | position: absolute; 27 | z-index: 1100; } 28 | 29 | .popover[class*="tour-"] { 30 | z-index: 1102; } 31 | .popover[class*="tour-"] .popover-navigation { 32 | overflow: hidden; 33 | padding: 9px 14px; } 34 | .popover[class*="tour-"] .popover-navigation *[data-role="end"] { 35 | float: right; } 36 | .popover[class*="tour-"] .popover-navigation *[data-role="prev"], 37 | .popover[class*="tour-"] .popover-navigation *[data-role="next"], 38 | .popover[class*="tour-"] .popover-navigation *[data-role="end"] { 39 | cursor: pointer; } 40 | .popover[class*="tour-"] .popover-navigation *[data-role="prev"].disabled, 41 | .popover[class*="tour-"] .popover-navigation *[data-role="next"].disabled, 42 | .popover[class*="tour-"] .popover-navigation *[data-role="end"].disabled { 43 | cursor: default; } 44 | .popover[class*="tour-"].orphan { 45 | left: 50%; 46 | margin-top: 0; 47 | position: fixed; 48 | top: 50%; 49 | transform: translate(-50%, -50%); } 50 | .popover[class*="tour-"].orphan .arrow { 51 | display: none; } 52 | -------------------------------------------------------------------------------- /src/docs/assets/css/bootstrap-tour.docs.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-bottom: 50px; 3 | } 4 | 5 | #github { 6 | display: block; 7 | position: absolute; 8 | width: 150px; 9 | height: 150px; 10 | right: 0; 11 | z-index: 1050; 12 | } 13 | 14 | .masthead { 15 | position: relative; 16 | padding: 30px 15px; 17 | color: #fff; 18 | text-align: center; 19 | text-shadow: 0 1px 0 rgba(0,0,0,.1); 20 | background-color: #3276b1; 21 | background-image: -webkit-linear-gradient(top, #285e8e 0%, #3276b1 100%); 22 | background-image: linear-gradient(to bottom, #285e8e 0%, #3276b1 100%); 23 | background-repeat: repeat-x; 24 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#285e8e', endColorstr='#3276b1', GradientType=0); 25 | } 26 | .masthead small { 27 | font-size: 80%; 28 | color: #ccc; 29 | } 30 | 31 | #demo { 32 | cursor: pointer; 33 | } 34 | 35 | .page-header { 36 | margin-top: 3rem; 37 | margin-bottom: 1rem; 38 | } 39 | 40 | /* 41 | * Social buttons 42 | * 43 | * Twitter and GitHub social action buttons (for homepage and footer). 44 | */ 45 | 46 | .social { 47 | display: inline-block; 48 | margin-top: 40px; 49 | margin-bottom: 0; 50 | padding-left: 0; 51 | list-style: none; 52 | } 53 | .social li { 54 | display: inline-block; 55 | line-height: 1; 56 | padding: 5px 8px; 57 | } 58 | .social .tweet-btn a { 59 | width: 98px !important; 60 | } 61 | /* Style the GitHub buttons via CSS instead of inline attributes */ 62 | .github-btn { 63 | border: 0; 64 | overflow: hidden; 65 | } 66 | -------------------------------------------------------------------------------- /src/docs/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorich87/bootstrap-tour/c501ce7ad1728050b0882bb9316f5c33df87f6a2/src/docs/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/docs/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorich87/bootstrap-tour/c501ce7ad1728050b0882bb9316f5c33df87f6a2/src/docs/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/docs/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorich87/bootstrap-tour/c501ce7ad1728050b0882bb9316f5c33df87f6a2/src/docs/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/docs/assets/img/apple-touch-icon-144-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorich87/bootstrap-tour/c501ce7ad1728050b0882bb9316f5c33df87f6a2/src/docs/assets/img/apple-touch-icon-144-precomposed.png -------------------------------------------------------------------------------- /src/docs/assets/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorich87/bootstrap-tour/c501ce7ad1728050b0882bb9316f5c33df87f6a2/src/docs/assets/img/favicon.png -------------------------------------------------------------------------------- /src/docs/assets/img/masthead-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorich87/bootstrap-tour/c501ce7ad1728050b0882bb9316f5c33df87f6a2/src/docs/assets/img/masthead-pattern.png -------------------------------------------------------------------------------- /src/docs/assets/vendor/pygments-manni.css: -------------------------------------------------------------------------------- 1 | .hll { background-color: #ffffcc } 2 | /*{ background: #f0f3f3; }*/ 3 | .c { color: #999; } /* Comment */ 4 | .err { color: #AA0000; background-color: #FFAAAA } /* Error */ 5 | .k { color: #006699; } /* Keyword */ 6 | .o { color: #555555 } /* Operator */ 7 | .cm { color: #0099FF; font-style: italic } /* Comment.Multiline */ 8 | .cp { color: #009999 } /* Comment.Preproc */ 9 | .c1 { color: #999; } /* Comment.Single */ 10 | .cs { color: #999; } /* Comment.Special */ 11 | .gd { background-color: #FFCCCC; border: 1px solid #CC0000 } /* Generic.Deleted */ 12 | .ge { font-style: italic } /* Generic.Emph */ 13 | .gr { color: #FF0000 } /* Generic.Error */ 14 | .gh { color: #003300; } /* Generic.Heading */ 15 | .gi { background-color: #CCFFCC; border: 1px solid #00CC00 } /* Generic.Inserted */ 16 | .go { color: #AAAAAA } /* Generic.Output */ 17 | .gp { color: #000099; } /* Generic.Prompt */ 18 | .gs { } /* Generic.Strong */ 19 | .gu { color: #003300; } /* Generic.Subheading */ 20 | .gt { color: #99CC66 } /* Generic.Traceback */ 21 | .kc { color: #006699; } /* Keyword.Constant */ 22 | .kd { color: #006699; } /* Keyword.Declaration */ 23 | .kn { color: #006699; } /* Keyword.Namespace */ 24 | .kp { color: #006699 } /* Keyword.Pseudo */ 25 | .kr { color: #006699; } /* Keyword.Reserved */ 26 | .kt { color: #007788; } /* Keyword.Type */ 27 | .m { color: #FF6600 } /* Literal.Number */ 28 | .s { color: #d44950 } /* Literal.String */ 29 | .na { color: #4f9fcf } /* Name.Attribute */ 30 | .nb { color: #336666 } /* Name.Builtin */ 31 | .nc { color: #00AA88; } /* Name.Class */ 32 | .no { color: #336600 } /* Name.Constant */ 33 | .nd { color: #9999FF } /* Name.Decorator */ 34 | .ni { color: #999999; } /* Name.Entity */ 35 | .ne { color: #CC0000; } /* Name.Exception */ 36 | .nf { color: #CC00FF } /* Name.Function */ 37 | .nl { color: #9999FF } /* Name.Label */ 38 | .nn { color: #00CCFF; } /* Name.Namespace */ 39 | .nt { color: #2f6f9f; } /* Name.Tag */ 40 | .nv { color: #003333 } /* Name.Variable */ 41 | .ow { color: #000000; } /* Operator.Word */ 42 | .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .mf { color: #FF6600 } /* Literal.Number.Float */ 44 | .mh { color: #FF6600 } /* Literal.Number.Hex */ 45 | .mi { color: #FF6600 } /* Literal.Number.Integer */ 46 | .mo { color: #FF6600 } /* Literal.Number.Oct */ 47 | .sb { color: #CC3300 } /* Literal.String.Backtick */ 48 | .sc { color: #CC3300 } /* Literal.String.Char */ 49 | .sd { color: #CC3300; font-style: italic } /* Literal.String.Doc */ 50 | .s2 { color: #CC3300 } /* Literal.String.Double */ 51 | .se { color: #CC3300; } /* Literal.String.Escape */ 52 | .sh { color: #CC3300 } /* Literal.String.Heredoc */ 53 | .si { color: #AA0000 } /* Literal.String.Interpol */ 54 | .sx { color: #CC3300 } /* Literal.String.Other */ 55 | .sr { color: #33AAAA } /* Literal.String.Regex */ 56 | .s1 { color: #CC3300 } /* Literal.String.Single */ 57 | .ss { color: #FFCC33 } /* Literal.String.Symbol */ 58 | .bp { color: #336666 } /* Name.Builtin.Pseudo */ 59 | .vc { color: #003333 } /* Name.Variable.Class */ 60 | .vg { color: #003333 } /* Name.Variable.Global */ 61 | .vi { color: #003333 } /* Name.Variable.Instance */ 62 | .il { color: #FF6600 } /* Literal.Number.Integer.Long */ 63 | 64 | .css .o, 65 | .css .o + .nt, 66 | .css .nt + .nt { color: #999; } 67 | -------------------------------------------------------------------------------- /src/docs/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Bootstrap Tour 4 | slug: home 5 | --- 6 | 7 |
8 |
9 |

Bootstrap Tour

10 |

11 | The easiest way to show people how to use your website. 12 |
Not using Bootstrap? It works anyway! 13 |

14 |

15 | 19 |

20 | 31 |
32 |
33 |
34 |
35 | 38 |

Add the dependencies

39 |

If you are using Bootstrap, include bootstrap-tour.min.css and bootstrap-tour.min.js:

40 | {% highlight html %} 41 | 42 | 43 | ... 44 | 45 | 46 | 47 | {% endhighlight %} 48 |

Otherwise, just include bootstrap-tour-standalone.min.css and bootstrap-tour-standalone.min.js:

49 | {% highlight html %} 50 | 51 | ... 52 | 53 | 54 | {% endhighlight %} 55 |

Setup your tour:

56 | {% highlight javascript %} 57 | // Instance the tour 58 | var tour = new Tour({ 59 | steps: [ 60 | { 61 | element: "#my-element", 62 | title: "Title of my step", 63 | content: "Content of my step" 64 | }, 65 | { 66 | element: "#my-other-element", 67 | title: "Title of my step", 68 | content: "Content of my step" 69 | } 70 | ]}); 71 | 72 | // Initialize the tour 73 | tour.init(); 74 | 75 | // Start the tour 76 | tour.start(); 77 | {% endhighlight %} 78 |

Do you want to do more? Read the full documentation.

79 |
80 |
81 |
82 |
83 | 86 |
87 |
88 | Ulrich Sossou 89 |
90 |
91 | Emanuele Marchi 92 |
93 |
94 | Nicola Molinari 95 |
96 |
97 |
98 |
99 |
100 |
101 | 104 |

Code licensed under the MIT license.
105 | Documentation licensed under CC BY 3.0.

106 |
107 |
108 | -------------------------------------------------------------------------------- /src/scss/bootstrap-tour-standalone.scss: -------------------------------------------------------------------------------- 1 | @import 'functions'; 2 | @import 'variables'; 3 | @import 'mixins'; 4 | @import 'buttons'; 5 | @import 'transitions'; 6 | @import 'button-group'; 7 | @import 'popover'; 8 | @import 'bootstrap-tour'; 9 | -------------------------------------------------------------------------------- /src/scss/bootstrap-tour.scss: -------------------------------------------------------------------------------- 1 | .tour-backdrop { 2 | background-color: #000; 3 | filter: alpha(opacity = 80); 4 | opacity: .8; 5 | position: absolute; 6 | z-index: 1100; 7 | } 8 | 9 | .popover[class*="tour-"] { 10 | z-index: 1102; 11 | 12 | .popover-navigation { 13 | overflow: hidden; 14 | padding: 9px 14px; 15 | 16 | *[data-role="end"] { 17 | float: right; 18 | } 19 | 20 | *[data-role="prev"], 21 | *[data-role="next"], 22 | *[data-role="end"] { 23 | cursor: pointer; 24 | 25 | &.disabled { 26 | cursor: default; 27 | } 28 | } 29 | } 30 | 31 | &.orphan { 32 | left: 50%; 33 | margin-top: 0; 34 | position: fixed; 35 | top: 50%; 36 | transform: translate(-50%, -50%); 37 | 38 | .arrow { 39 | display: none; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/bootstrap-tour.js: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-tour - v0.12.0 3 | * http://bootstraptour.com 4 | * ======================================================================== 5 | * Copyright 2012-2017 Ulrich Sossou 6 | * 7 | * ======================================================================== 8 | * Licensed under the MIT License (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * https://opensource.org/licenses/MIT 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 23 | 24 | (function(window, factory) { 25 | if (typeof define === 'function' && define.amd) { 26 | return define(['jquery'], function(jQuery) { 27 | return window.Tour = factory(jQuery); 28 | }); 29 | } else if (typeof exports === 'object') { 30 | return module.exports = factory(require('jquery')); 31 | } else { 32 | return window.Tour = factory(window.jQuery); 33 | } 34 | })(window, function($) { 35 | var Tour, document; 36 | document = window.document; 37 | Tour = (function() { 38 | function Tour(options) { 39 | this._showPopoverAndOverlay = bind(this._showPopoverAndOverlay, this); 40 | var storage; 41 | try { 42 | storage = window.localStorage; 43 | } catch (error) { 44 | storage = false; 45 | } 46 | this._options = $.extend({ 47 | name: 'tour', 48 | steps: [], 49 | container: 'body', 50 | autoscroll: true, 51 | keyboard: true, 52 | storage: storage, 53 | debug: false, 54 | backdrop: false, 55 | backdropContainer: 'body', 56 | backdropPadding: 0, 57 | redirect: true, 58 | orphan: false, 59 | duration: false, 60 | delay: false, 61 | basePath: '', 62 | template: '', 63 | afterSetState: function(key, value) {}, 64 | afterGetState: function(key, value) {}, 65 | afterRemoveState: function(key) {}, 66 | onStart: function(tour) {}, 67 | onEnd: function(tour) {}, 68 | onShow: function(tour) {}, 69 | onShown: function(tour) {}, 70 | onHide: function(tour) {}, 71 | onHidden: function(tour) {}, 72 | onNext: function(tour) {}, 73 | onPrev: function(tour) {}, 74 | onPause: function(tour, duration) {}, 75 | onResume: function(tour, duration) {}, 76 | onRedirectError: function(tour) {} 77 | }, options); 78 | this._force = false; 79 | this._inited = false; 80 | this._current = null; 81 | this.backdrops = []; 82 | this; 83 | } 84 | 85 | Tour.prototype.addSteps = function(steps) { 86 | var j, len, step; 87 | for (j = 0, len = steps.length; j < len; j++) { 88 | step = steps[j]; 89 | this.addStep(step); 90 | } 91 | return this; 92 | }; 93 | 94 | Tour.prototype.addStep = function(step) { 95 | this._options.steps.push(step); 96 | return this; 97 | }; 98 | 99 | Tour.prototype.getStep = function(i) { 100 | if (this._options.steps[i] != null) { 101 | return $.extend({ 102 | id: "step-" + i, 103 | path: '', 104 | host: '', 105 | placement: 'right', 106 | title: '', 107 | content: '

', 108 | next: i === this._options.steps.length - 1 ? -1 : i + 1, 109 | prev: i - 1, 110 | animation: true, 111 | container: this._options.container, 112 | autoscroll: this._options.autoscroll, 113 | backdrop: this._options.backdrop, 114 | backdropContainer: this._options.backdropContainer, 115 | backdropPadding: this._options.backdropPadding, 116 | redirect: this._options.redirect, 117 | reflexElement: this._options.steps[i].element, 118 | backdropElement: this._options.steps[i].element, 119 | orphan: this._options.orphan, 120 | duration: this._options.duration, 121 | delay: this._options.delay, 122 | template: this._options.template, 123 | onShow: this._options.onShow, 124 | onShown: this._options.onShown, 125 | onHide: this._options.onHide, 126 | onHidden: this._options.onHidden, 127 | onNext: this._options.onNext, 128 | onPrev: this._options.onPrev, 129 | onPause: this._options.onPause, 130 | onResume: this._options.onResume, 131 | onRedirectError: this._options.onRedirectError 132 | }, this._options.steps[i]); 133 | } 134 | }; 135 | 136 | Tour.prototype.init = function(force) { 137 | this._force = force; 138 | if (this.ended()) { 139 | this._debug('Tour ended, init prevented.'); 140 | return this; 141 | } 142 | this.setCurrentStep(); 143 | this._initMouseNavigation(); 144 | this._initKeyboardNavigation(); 145 | if (this._current !== null) { 146 | this.showStep(this._current); 147 | } 148 | this._inited = true; 149 | return this; 150 | }; 151 | 152 | Tour.prototype.start = function(force) { 153 | var promise; 154 | if (force == null) { 155 | force = false; 156 | } 157 | if (!this._inited) { 158 | this.init(force); 159 | } 160 | if (this._current === null) { 161 | promise = this._makePromise(this._options.onStart != null ? this._options.onStart(this) : void 0); 162 | this._callOnPromiseDone(promise, this.showStep, 0); 163 | } 164 | return this; 165 | }; 166 | 167 | Tour.prototype.next = function() { 168 | var promise; 169 | promise = this.hideStep(this._current, this._current + 1); 170 | return this._callOnPromiseDone(promise, this._showNextStep); 171 | }; 172 | 173 | Tour.prototype.prev = function() { 174 | var promise; 175 | promise = this.hideStep(this._current, this._current - 1); 176 | return this._callOnPromiseDone(promise, this._showPrevStep); 177 | }; 178 | 179 | Tour.prototype.goTo = function(i) { 180 | var promise; 181 | promise = this.hideStep(this._current, i); 182 | return this._callOnPromiseDone(promise, this.showStep, i); 183 | }; 184 | 185 | Tour.prototype.end = function() { 186 | var endHelper, promise; 187 | endHelper = (function(_this) { 188 | return function(e) { 189 | $(document).off("click.tour-" + _this._options.name); 190 | $(document).off("keyup.tour-" + _this._options.name); 191 | _this._setState('end', 'yes'); 192 | _this._inited = false; 193 | _this._force = false; 194 | _this._clearTimer(); 195 | if (_this._options.onEnd != null) { 196 | return _this._options.onEnd(_this); 197 | } 198 | }; 199 | })(this); 200 | promise = this.hideStep(this._current); 201 | return this._callOnPromiseDone(promise, endHelper); 202 | }; 203 | 204 | Tour.prototype.ended = function() { 205 | return !this._force && !!this._getState('end'); 206 | }; 207 | 208 | Tour.prototype.restart = function() { 209 | this._removeState('current_step'); 210 | this._removeState('end'); 211 | this._removeState('redirect_to'); 212 | return this.start(); 213 | }; 214 | 215 | Tour.prototype.pause = function() { 216 | var step; 217 | step = this.getStep(this._current); 218 | if (!(step && step.duration)) { 219 | return this; 220 | } 221 | this._paused = true; 222 | this._duration -= new Date().getTime() - this._start; 223 | window.clearTimeout(this._timer); 224 | this._debug("Paused/Stopped step " + (this._current + 1) + " timer (" + this._duration + " remaining)."); 225 | if (step.onPause != null) { 226 | return step.onPause(this, this._duration); 227 | } 228 | }; 229 | 230 | Tour.prototype.resume = function() { 231 | var step; 232 | step = this.getStep(this._current); 233 | if (!(step && step.duration)) { 234 | return this; 235 | } 236 | this._paused = false; 237 | this._start = new Date().getTime(); 238 | this._duration = this._duration || step.duration; 239 | this._timer = window.setTimeout((function(_this) { 240 | return function() { 241 | if (_this._isLast()) { 242 | return _this.next(); 243 | } else { 244 | return _this.end(); 245 | } 246 | }; 247 | })(this), this._duration); 248 | this._debug("Started step " + (this._current + 1) + " timer with duration " + this._duration); 249 | if ((step.onResume != null) && this._duration !== step.duration) { 250 | return step.onResume(this, this._duration); 251 | } 252 | }; 253 | 254 | Tour.prototype.hideStep = function(i, iNext) { 255 | var hideDelay, hideStepHelper, promise, step; 256 | step = this.getStep(i); 257 | if (!step) { 258 | return; 259 | } 260 | this._clearTimer(); 261 | promise = this._makePromise(step.onHide != null ? step.onHide(this, i) : void 0); 262 | hideStepHelper = (function(_this) { 263 | return function(e) { 264 | var $element, next_step; 265 | $element = $(step.element); 266 | if (!$element.data('bs.popover')) { 267 | $element = $('body'); 268 | } 269 | $element.popover('dispose').removeClass("tour-" + _this._options.name + "-element tour-" + _this._options.name + "-" + i + "-element").removeData('bs.popover'); 270 | if (step.reflex) { 271 | $(step.reflexElement).removeClass('tour-step-element-reflex').off((_this._reflexEvent(step.reflex)) + ".tour-" + _this._options.name); 272 | } 273 | if (step.backdrop) { 274 | next_step = (iNext != null) && _this.getStep(iNext); 275 | if (!next_step || !next_step.backdrop || next_step.backdropElement !== step.backdropElement) { 276 | _this._hideOverlayElement(step); 277 | } 278 | } 279 | if (step.onHidden != null) { 280 | return step.onHidden(_this); 281 | } 282 | }; 283 | })(this); 284 | hideDelay = step.delay.hide || step.delay; 285 | if ({}.toString.call(hideDelay) === '[object Number]' && hideDelay > 0) { 286 | this._debug("Wait " + hideDelay + " milliseconds to hide the step " + (this._current + 1)); 287 | window.setTimeout((function(_this) { 288 | return function() { 289 | return _this._callOnPromiseDone(promise, hideStepHelper); 290 | }; 291 | })(this), hideDelay); 292 | } else { 293 | this._callOnPromiseDone(promise, hideStepHelper); 294 | } 295 | return promise; 296 | }; 297 | 298 | Tour.prototype.showStep = function(i) { 299 | var path, promise, showDelay, showStepHelper, skipToPrevious, step; 300 | if (this.ended()) { 301 | this._debug('Tour ended, showStep prevented.'); 302 | return this; 303 | } 304 | step = this.getStep(i); 305 | if (!step) { 306 | return; 307 | } 308 | skipToPrevious = i < this._current; 309 | promise = this._makePromise(step.onShow != null ? step.onShow(this, i) : void 0); 310 | this.setCurrentStep(i); 311 | path = (function() { 312 | switch ({}.toString.call(step.path)) { 313 | case '[object Function]': 314 | return step.path(); 315 | case '[object String]': 316 | return this._options.basePath + step.path; 317 | default: 318 | return step.path; 319 | } 320 | }).call(this); 321 | if (step.redirect && this._isRedirect(step.host, path, document.location)) { 322 | this._redirect(step, i, path); 323 | if (!this._isJustPathHashDifferent(step.host, path, document.location)) { 324 | return; 325 | } 326 | } 327 | showStepHelper = (function(_this) { 328 | return function(e) { 329 | if (_this._isOrphan(step)) { 330 | if (step.orphan === false) { 331 | _this._debug("Skip the orphan step " + (_this._current + 1) + ".\nOrphan option is false and the element does not exist or is hidden."); 332 | if (skipToPrevious) { 333 | _this._showPrevStep(); 334 | } else { 335 | _this._showNextStep(); 336 | } 337 | return; 338 | } 339 | _this._debug("Show the orphan step " + (_this._current + 1) + ". Orphans option is true."); 340 | } 341 | if (step.autoscroll) { 342 | _this._scrollIntoView(i); 343 | } else { 344 | _this._showPopoverAndOverlay(i); 345 | } 346 | if (step.duration) { 347 | return _this.resume(); 348 | } 349 | }; 350 | })(this); 351 | showDelay = step.delay.show || step.delay; 352 | if ({}.toString.call(showDelay) === '[object Number]' && showDelay > 0) { 353 | this._debug("Wait " + showDelay + " milliseconds to show the step " + (this._current + 1)); 354 | window.setTimeout((function(_this) { 355 | return function() { 356 | return _this._callOnPromiseDone(promise, showStepHelper); 357 | }; 358 | })(this), showDelay); 359 | } else { 360 | this._callOnPromiseDone(promise, showStepHelper); 361 | } 362 | return promise; 363 | }; 364 | 365 | Tour.prototype.getCurrentStep = function() { 366 | return this._current; 367 | }; 368 | 369 | Tour.prototype.setCurrentStep = function(value) { 370 | if (value != null) { 371 | this._current = value; 372 | this._setState('current_step', value); 373 | } else { 374 | this._current = this._getState('current_step'); 375 | this._current = this._current === null ? null : parseInt(this._current, 10); 376 | } 377 | return this; 378 | }; 379 | 380 | Tour.prototype.redraw = function() { 381 | return this._showOverlayElement(this.getStep(this.getCurrentStep())); 382 | }; 383 | 384 | Tour.prototype._setState = function(key, value) { 385 | var e, keyName; 386 | if (this._options.storage) { 387 | keyName = this._options.name + "_" + key; 388 | try { 389 | this._options.storage.setItem(keyName, value); 390 | } catch (error) { 391 | e = error; 392 | if (e.code === DOMException.QUOTA_EXCEEDED_ERR) { 393 | this._debug('LocalStorage quota exceeded. State storage failed.'); 394 | } 395 | } 396 | return this._options.afterSetState(keyName, value); 397 | } else { 398 | if (this._state == null) { 399 | this._state = {}; 400 | } 401 | return this._state[key] = value; 402 | } 403 | }; 404 | 405 | Tour.prototype._removeState = function(key) { 406 | var keyName; 407 | if (this._options.storage) { 408 | keyName = this._options.name + "_" + key; 409 | this._options.storage.removeItem(keyName); 410 | return this._options.afterRemoveState(keyName); 411 | } else { 412 | if (this._state != null) { 413 | return delete this._state[key]; 414 | } 415 | } 416 | }; 417 | 418 | Tour.prototype._getState = function(key) { 419 | var keyName, value; 420 | if (this._options.storage) { 421 | keyName = this._options.name + "_" + key; 422 | value = this._options.storage.getItem(keyName); 423 | } else { 424 | if (this._state != null) { 425 | value = this._state[key]; 426 | } 427 | } 428 | if (value === void 0 || value === 'null') { 429 | value = null; 430 | } 431 | this._options.afterGetState(key, value); 432 | return value; 433 | }; 434 | 435 | Tour.prototype._showNextStep = function() { 436 | var promise, showNextStepHelper, step; 437 | step = this.getStep(this._current); 438 | showNextStepHelper = (function(_this) { 439 | return function(e) { 440 | return _this.showStep(step.next); 441 | }; 442 | })(this); 443 | promise = this._makePromise(step.onNext != null ? step.onNext(this) : void 0); 444 | return this._callOnPromiseDone(promise, showNextStepHelper); 445 | }; 446 | 447 | Tour.prototype._showPrevStep = function() { 448 | var promise, showPrevStepHelper, step; 449 | step = this.getStep(this._current); 450 | showPrevStepHelper = (function(_this) { 451 | return function(e) { 452 | return _this.showStep(step.prev); 453 | }; 454 | })(this); 455 | promise = this._makePromise(step.onPrev != null ? step.onPrev(this) : void 0); 456 | return this._callOnPromiseDone(promise, showPrevStepHelper); 457 | }; 458 | 459 | Tour.prototype._debug = function(text) { 460 | if (this._options.debug) { 461 | return window.console.log("Bootstrap Tour '" + this._options.name + "' | " + text); 462 | } 463 | }; 464 | 465 | Tour.prototype._isRedirect = function(host, path, location) { 466 | var currentPath; 467 | if ((host != null) && host !== '' && (({}.toString.call(host) === '[object RegExp]' && !host.test(location.origin)) || ({}.toString.call(host) === '[object String]' && this._isHostDifferent(host, location)))) { 468 | return true; 469 | } 470 | currentPath = [location.pathname, location.search, location.hash].join(''); 471 | return (path != null) && path !== '' && (({}.toString.call(path) === '[object RegExp]' && !path.test(currentPath)) || ({}.toString.call(path) === '[object String]' && this._isPathDifferent(path, currentPath))); 472 | }; 473 | 474 | Tour.prototype._isHostDifferent = function(host, location) { 475 | switch ({}.toString.call(host)) { 476 | case '[object RegExp]': 477 | return !host.test(location.origin); 478 | case '[object String]': 479 | return this._getProtocol(host) !== this._getProtocol(location.href) || this._getHost(host) !== this._getHost(location.href); 480 | default: 481 | return true; 482 | } 483 | }; 484 | 485 | Tour.prototype._isPathDifferent = function(path, currentPath) { 486 | return this._getPath(path) !== this._getPath(currentPath) || !this._equal(this._getQuery(path), this._getQuery(currentPath)) || !this._equal(this._getHash(path), this._getHash(currentPath)); 487 | }; 488 | 489 | Tour.prototype._isJustPathHashDifferent = function(host, path, location) { 490 | var currentPath; 491 | if ((host != null) && host !== '') { 492 | if (this._isHostDifferent(host, location)) { 493 | return false; 494 | } 495 | } 496 | currentPath = [location.pathname, location.search, location.hash].join(''); 497 | if ({}.toString.call(path) === '[object String]') { 498 | return this._getPath(path) === this._getPath(currentPath) && this._equal(this._getQuery(path), this._getQuery(currentPath)) && !this._equal(this._getHash(path), this._getHash(currentPath)); 499 | } 500 | return false; 501 | }; 502 | 503 | Tour.prototype._redirect = function(step, i, path) { 504 | var href; 505 | if ($.isFunction(step.redirect)) { 506 | return step.redirect.call(this, path); 507 | } else { 508 | href = {}.toString.call(step.host) === '[object String]' ? "" + step.host + path : path; 509 | this._debug("Redirect to " + href); 510 | if (this._getState('redirect_to') === ("" + i)) { 511 | this._debug("Error redirection loop to " + path); 512 | this._removeState('redirect_to'); 513 | if (step.onRedirectError != null) { 514 | return step.onRedirectError(this); 515 | } 516 | } else { 517 | this._setState('redirect_to', "" + i); 518 | return document.location.href = href; 519 | } 520 | } 521 | }; 522 | 523 | Tour.prototype._isOrphan = function(step) { 524 | return (step.element == null) || !$(step.element).length || $(step.element).is(':hidden') && ($(step.element)[0].namespaceURI !== 'http://www.w3.org/2000/svg'); 525 | }; 526 | 527 | Tour.prototype._isLast = function() { 528 | return this._current < this._options.steps.length - 1; 529 | }; 530 | 531 | Tour.prototype._showPopoverAndOverlay = function(i) { 532 | var step; 533 | if (this.getCurrentStep() !== i || this.ended()) { 534 | return; 535 | } 536 | step = this.getStep(i); 537 | if (step.backdrop) { 538 | this._showOverlayElement(step); 539 | } 540 | this._showPopover(step, i); 541 | if (step.onShown != null) { 542 | step.onShown(this); 543 | } 544 | return this._debug("Step " + (this._current + 1) + " of " + this._options.steps.length); 545 | }; 546 | 547 | Tour.prototype._showPopover = function(step, i) { 548 | var $element, $tip, isOrphan, options; 549 | $(".tour-" + this._options.name).remove(); 550 | options = $.extend({}, this._options); 551 | isOrphan = this._isOrphan(step); 552 | step.template = this._template(step, i); 553 | if (isOrphan) { 554 | step.element = 'body'; 555 | step.placement = 'top'; 556 | } 557 | $element = $(step.element); 558 | $element.addClass("tour-" + this._options.name + "-element tour-" + this._options.name + "-" + i + "-element"); 559 | if (step.options) { 560 | $.extend(options, step.options); 561 | } 562 | if (step.reflex && !isOrphan) { 563 | $(step.reflexElement).addClass('tour-step-element-reflex').off((this._reflexEvent(step.reflex)) + ".tour-" + this._options.name).on((this._reflexEvent(step.reflex)) + ".tour-" + this._options.name, (function(_this) { 564 | return function() { 565 | if (_this._isLast()) { 566 | return _this.next(); 567 | } else { 568 | return _this.end(); 569 | } 570 | }; 571 | })(this)); 572 | } 573 | $element.popover({ 574 | placement: step.placement, 575 | trigger: 'manual', 576 | title: step.title, 577 | content: step.content, 578 | html: true, 579 | animation: step.animation, 580 | container: step.container, 581 | template: step.template, 582 | selector: step.element 583 | }).popover('show'); 584 | $tip = $($element.data('bs.popover').getTipElement()); 585 | return $tip.attr('id', step.id); 586 | }; 587 | 588 | Tour.prototype._template = function(step, i) { 589 | var $navigation, $next, $prev, $resume, $template, template; 590 | template = step.template; 591 | if (this._isOrphan(step) && {}.toString.call(step.orphan) !== '[object Boolean]') { 592 | template = step.orphan; 593 | } 594 | $template = $.isFunction(template) ? $(template(i, step)) : $(template); 595 | $navigation = $template.find('.popover-navigation'); 596 | $prev = $navigation.find('[data-role="prev"]'); 597 | $next = $navigation.find('[data-role="next"]'); 598 | $resume = $navigation.find('[data-role="pause-resume"]'); 599 | if (this._isOrphan(step)) { 600 | $template.addClass('orphan'); 601 | } 602 | $template.addClass("tour-" + this._options.name + " tour-" + this._options.name + "-" + i); 603 | if (step.reflex) { 604 | $template.addClass("tour-" + this._options.name + "-reflex"); 605 | } 606 | if (step.prev < 0) { 607 | $prev.addClass('disabled').prop('disabled', true).prop('tabindex', -1); 608 | } 609 | if (step.next < 0) { 610 | $next.addClass('disabled').prop('disabled', true).prop('tabindex', -1); 611 | } 612 | if (!step.duration) { 613 | $resume.remove(); 614 | } 615 | return $template.clone().wrap('
').parent().html(); 616 | }; 617 | 618 | Tour.prototype._reflexEvent = function(reflex) { 619 | if ({}.toString.call(reflex) === '[object Boolean]') { 620 | return 'click'; 621 | } else { 622 | return reflex; 623 | } 624 | }; 625 | 626 | Tour.prototype._scrollIntoView = function(i) { 627 | var $element, $window, counter, height, offsetTop, scrollTop, step, windowHeight; 628 | step = this.getStep(i); 629 | $element = $(step.element); 630 | if (!$element.length) { 631 | return this._showPopoverAndOverlay(i); 632 | } 633 | $window = $(window); 634 | offsetTop = $element.offset().top; 635 | height = $element.outerHeight(); 636 | windowHeight = $window.height(); 637 | scrollTop = 0; 638 | switch (step.placement) { 639 | case 'top': 640 | scrollTop = Math.max(0, offsetTop - (windowHeight / 2)); 641 | break; 642 | case 'left': 643 | case 'right': 644 | scrollTop = Math.max(0, (offsetTop + height / 2) - (windowHeight / 2)); 645 | break; 646 | case 'bottom': 647 | scrollTop = Math.max(0, (offsetTop + height) - (windowHeight / 2)); 648 | } 649 | this._debug("Scroll into view. ScrollTop: " + scrollTop + ". Element offset: " + offsetTop + ". Window height: " + windowHeight + "."); 650 | counter = 0; 651 | return $('body, html').stop(true, true).animate({ 652 | scrollTop: Math.ceil(scrollTop) 653 | }, (function(_this) { 654 | return function() { 655 | if (++counter === 2) { 656 | _this._showPopoverAndOverlay(i); 657 | return _this._debug("Scroll into view.\nAnimation end element offset: " + ($element.offset().top) + ".\nWindow height: " + ($window.height()) + "."); 658 | } 659 | }; 660 | })(this)); 661 | }; 662 | 663 | Tour.prototype._initMouseNavigation = function() { 664 | var _this; 665 | _this = this; 666 | return $(document).off("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role='prev']").off("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role='next']").off("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role='end']").off("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role='pause-resume']").on("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role='next']", (function(_this) { 667 | return function(e) { 668 | e.preventDefault(); 669 | return _this.next(); 670 | }; 671 | })(this)).on("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role='prev']", (function(_this) { 672 | return function(e) { 673 | e.preventDefault(); 674 | if (_this._current > 0) { 675 | return _this.prev(); 676 | } 677 | }; 678 | })(this)).on("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role='end']", (function(_this) { 679 | return function(e) { 680 | e.preventDefault(); 681 | return _this.end(); 682 | }; 683 | })(this)).on("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role='pause-resume']", function(e) { 684 | var $this; 685 | e.preventDefault(); 686 | $this = $(this); 687 | $this.text(_this._paused ? $this.data('pause-text') : $this.data('resume-text')); 688 | if (_this._paused) { 689 | return _this.resume(); 690 | } else { 691 | return _this.pause(); 692 | } 693 | }); 694 | }; 695 | 696 | Tour.prototype._initKeyboardNavigation = function() { 697 | if (!this._options.keyboard) { 698 | return; 699 | } 700 | return $(document).on("keyup.tour-" + this._options.name, (function(_this) { 701 | return function(e) { 702 | if (!e.which) { 703 | return; 704 | } 705 | switch (e.which) { 706 | case 39: 707 | e.preventDefault(); 708 | if (_this._isLast()) { 709 | return _this.next(); 710 | } else { 711 | return _this.end(); 712 | } 713 | break; 714 | case 37: 715 | e.preventDefault(); 716 | if (_this._current > 0) { 717 | return _this.prev(); 718 | } 719 | } 720 | }; 721 | })(this)); 722 | }; 723 | 724 | Tour.prototype._makePromise = function(result) { 725 | if (result && $.isFunction(result.then)) { 726 | return result; 727 | } else { 728 | return null; 729 | } 730 | }; 731 | 732 | Tour.prototype._callOnPromiseDone = function(promise, cb, arg) { 733 | if (promise) { 734 | return promise.then((function(_this) { 735 | return function(e) { 736 | return cb.call(_this, arg); 737 | }; 738 | })(this)); 739 | } else { 740 | return cb.call(this, arg); 741 | } 742 | }; 743 | 744 | Tour.prototype._showBackground = function(step, data) { 745 | var $backdrop, base, height, j, len, pos, ref, results, width; 746 | height = $(document).height(); 747 | width = $(document).width(); 748 | ref = ['top', 'bottom', 'left', 'right']; 749 | results = []; 750 | for (j = 0, len = ref.length; j < len; j++) { 751 | pos = ref[j]; 752 | $backdrop = (base = this.backdrops)[pos] != null ? base[pos] : base[pos] = $('
', { 753 | "class": "tour-backdrop " + pos 754 | }); 755 | $(step.backdropContainer).append($backdrop); 756 | switch (pos) { 757 | case 'top': 758 | results.push($backdrop.height(data.offset.top > 0 ? data.offset.top : 0).width(width).offset({ 759 | top: 0, 760 | left: 0 761 | })); 762 | break; 763 | case 'bottom': 764 | results.push($backdrop.offset({ 765 | top: data.offset.top + data.height, 766 | left: 0 767 | }).height(height - (data.offset.top + data.height)).width(width)); 768 | break; 769 | case 'left': 770 | results.push($backdrop.offset({ 771 | top: data.offset.top, 772 | left: 0 773 | }).height(data.height).width(data.offset.left > 0 ? data.offset.left : 0)); 774 | break; 775 | case 'right': 776 | results.push($backdrop.offset({ 777 | top: data.offset.top, 778 | left: data.offset.left + data.width 779 | }).height(data.height).width(width - (data.offset.left + data.width))); 780 | break; 781 | default: 782 | results.push(void 0); 783 | } 784 | } 785 | return results; 786 | }; 787 | 788 | Tour.prototype._showOverlayElement = function(step) { 789 | var $backdropElement, elementData; 790 | $backdropElement = $(step.backdropElement); 791 | if ($backdropElement.length === 0) { 792 | elementData = { 793 | width: 0, 794 | height: 0, 795 | offset: { 796 | top: 0, 797 | left: 0 798 | } 799 | }; 800 | } else { 801 | elementData = { 802 | width: $backdropElement.innerWidth(), 803 | height: $backdropElement.innerHeight(), 804 | offset: $backdropElement.offset() 805 | }; 806 | $backdropElement.addClass('tour-step-backdrop'); 807 | if (step.backdropPadding) { 808 | elementData = this._applyBackdropPadding(step.backdropPadding, elementData); 809 | } 810 | } 811 | return this._showBackground(step, elementData); 812 | }; 813 | 814 | Tour.prototype._hideOverlayElement = function(step) { 815 | var $backdrop, pos, ref; 816 | $(step.backdropElement).removeClass('tour-step-backdrop'); 817 | ref = this.backdrops; 818 | for (pos in ref) { 819 | $backdrop = ref[pos]; 820 | if ($backdrop && $backdrop.remove !== void 0) { 821 | $backdrop.remove(); 822 | } 823 | } 824 | return this.backdrops = []; 825 | }; 826 | 827 | Tour.prototype._applyBackdropPadding = function(padding, data) { 828 | if (typeof padding === 'object') { 829 | if (padding.top == null) { 830 | padding.top = 0; 831 | } 832 | if (padding.right == null) { 833 | padding.right = 0; 834 | } 835 | if (padding.bottom == null) { 836 | padding.bottom = 0; 837 | } 838 | if (padding.left == null) { 839 | padding.left = 0; 840 | } 841 | data.offset.top = data.offset.top - padding.top; 842 | data.offset.left = data.offset.left - padding.left; 843 | data.width = data.width + padding.left + padding.right; 844 | data.height = data.height + padding.top + padding.bottom; 845 | } else { 846 | data.offset.top = data.offset.top - padding; 847 | data.offset.left = data.offset.left - padding; 848 | data.width = data.width + (padding * 2); 849 | data.height = data.height + (padding * 2); 850 | } 851 | return data; 852 | }; 853 | 854 | Tour.prototype._clearTimer = function() { 855 | window.clearTimeout(this._timer); 856 | this._timer = null; 857 | return this._duration = null; 858 | }; 859 | 860 | Tour.prototype._getProtocol = function(url) { 861 | url = url.split('://'); 862 | if (url.length > 1) { 863 | return url[0]; 864 | } else { 865 | return 'http'; 866 | } 867 | }; 868 | 869 | Tour.prototype._getHost = function(url) { 870 | url = url.split('//'); 871 | url = url.length > 1 ? url[1] : url[0]; 872 | return url.split('/')[0]; 873 | }; 874 | 875 | Tour.prototype._getPath = function(path) { 876 | return path.replace(/\/?$/, '').split('?')[0].split('#')[0]; 877 | }; 878 | 879 | Tour.prototype._getQuery = function(path) { 880 | return this._getParams(path, '?'); 881 | }; 882 | 883 | Tour.prototype._getHash = function(path) { 884 | return this._getParams(path, '#'); 885 | }; 886 | 887 | Tour.prototype._getParams = function(path, start) { 888 | var j, len, param, params, paramsObject; 889 | params = path.split(start); 890 | if (params.length === 1) { 891 | return {}; 892 | } 893 | params = params[1].split('&'); 894 | paramsObject = {}; 895 | for (j = 0, len = params.length; j < len; j++) { 896 | param = params[j]; 897 | param = param.split('='); 898 | paramsObject[param[0]] = param[1] || ''; 899 | } 900 | return paramsObject; 901 | }; 902 | 903 | Tour.prototype._equal = function(obj1, obj2) { 904 | var j, k, len, obj1Keys, obj2Keys, v; 905 | if ({}.toString.call(obj1) === '[object Object]' && {}.toString.call(obj2) === '[object Object]') { 906 | obj1Keys = Object.keys(obj1); 907 | obj2Keys = Object.keys(obj2); 908 | if (obj1Keys.length !== obj2Keys.length) { 909 | return false; 910 | } 911 | for (k in obj1) { 912 | v = obj1[k]; 913 | if (!this._equal(obj2[k], v)) { 914 | return false; 915 | } 916 | } 917 | return true; 918 | } else if ({}.toString.call(obj1) === '[object Array]' && {}.toString.call(obj2) === '[object Array]') { 919 | if (obj1.length !== obj2.length) { 920 | return false; 921 | } 922 | for (k = j = 0, len = obj1.length; j < len; k = ++j) { 923 | v = obj1[k]; 924 | if (!this._equal(v, obj2[k])) { 925 | return false; 926 | } 927 | } 928 | return true; 929 | } else { 930 | return obj1 === obj2; 931 | } 932 | }; 933 | 934 | return Tour; 935 | 936 | })(); 937 | return Tour; 938 | }); 939 | --------------------------------------------------------------------------------