roroacms 0.0.6.9 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (685) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -20
  3. data/Gemfile +14 -14
  4. data/Gemfile.lock +207 -207
  5. data/MIT-LICENSE +20 -20
  6. data/README.md +61 -62
  7. data/Rakefile +19 -19
  8. data/app/assets/javascripts/roroacms/admin/admin.js +95 -95
  9. data/app/assets/javascripts/roroacms/admin/application.js +8 -8
  10. data/app/assets/javascripts/roroacms/admin/menu.js +135 -135
  11. data/app/assets/javascripts/roroacms/admin/posts/posts.js +69 -69
  12. data/app/assets/javascripts/roroacms/admin/roroacms-tour.js +48 -48
  13. data/app/assets/javascripts/roroacms/admin/settings/settings.js +27 -27
  14. data/app/assets/javascripts/roroacms/admin/site.js +289 -289
  15. data/app/assets/javascripts/roroacms/application.js +9 -9
  16. data/app/assets/javascripts/roroacms/vendor/bootstrap/js/bootstrap.min.js +5 -5
  17. data/app/assets/javascripts/roroacms/vendor/chosen/chosen.jquery.min.js +1211 -1211
  18. data/app/assets/javascripts/roroacms/vendor/datatables/js/datatables.bootstrap.js +250 -250
  19. data/app/assets/javascripts/roroacms/vendor/datatables/js/jquery.datatables.js +150 -150
  20. data/app/assets/javascripts/roroacms/vendor/ghostdown/ghostdown.js +6425 -6425
  21. data/app/assets/javascripts/roroacms/vendor/ghostdown/jquery.ghostdown.js +88 -88
  22. data/app/assets/javascripts/roroacms/vendor/hopscotch.js +2409 -2409
  23. data/app/assets/javascripts/roroacms/vendor/html5shiv.js +8 -8
  24. data/app/assets/javascripts/roroacms/vendor/icheck/js/icheck.min.js +219 -219
  25. data/app/assets/javascripts/roroacms/vendor/jquery/jquery-1.10.2.min.js +6 -6
  26. data/app/assets/javascripts/roroacms/vendor/jquery/jquery-ui-1.10.4.min.js +6 -6
  27. data/app/assets/javascripts/roroacms/vendor/jquery/modernizr-2.6.2.min.js +4 -4
  28. data/app/assets/javascripts/roroacms/vendor/respond.min.js +4 -4
  29. data/app/assets/stylesheets/roroacms/admin/application.css +5 -5
  30. data/app/assets/stylesheets/roroacms/admin/main.css.scss +3676 -3676
  31. data/app/assets/stylesheets/roroacms/vendor/animate.css +2724 -2724
  32. data/app/assets/stylesheets/roroacms/vendor/bootstrap/css/bootstrap.min.css +6 -6
  33. data/app/assets/stylesheets/roroacms/vendor/chosen/chosen.css.erb +365 -365
  34. data/app/assets/stylesheets/roroacms/vendor/datatables/css/dataTables.css +226 -226
  35. data/app/assets/stylesheets/roroacms/vendor/ghostdown.css +584 -584
  36. data/app/assets/stylesheets/roroacms/vendor/hopscotch.css +464 -464
  37. data/app/assets/stylesheets/roroacms/vendor/icheck/css/_all.css.erb +559 -559
  38. data/app/assets/stylesheets/roroacms/vendor/icheck/css/grey.css.erb +55 -55
  39. data/app/assets/stylesheets/roroacms/vendor/jquery/jquery-ui.css +1177 -1177
  40. data/app/assets/stylesheets/roroacms/vendor/typeahead.css +182 -182
  41. data/app/controllers/roroacms/admin/administrators_controller.rb +149 -149
  42. data/app/controllers/roroacms/admin/articles_controller.rb +169 -169
  43. data/app/controllers/roroacms/admin/comments_controller.rb +103 -103
  44. data/app/controllers/roroacms/admin/dashboard_controller.rb +11 -11
  45. data/app/controllers/roroacms/admin/menus_controller.rb +90 -90
  46. data/app/controllers/roroacms/admin/pages_controller.rb +137 -137
  47. data/app/controllers/roroacms/admin/revisions_controller.rb +39 -39
  48. data/app/controllers/roroacms/admin/settings_controller.rb +72 -72
  49. data/app/controllers/roroacms/admin/terms_controller.rb +140 -140
  50. data/app/controllers/roroacms/admin/themes_controller.rb +45 -45
  51. data/app/controllers/roroacms/admin/trash_controller.rb +56 -56
  52. data/app/controllers/roroacms/admin_controller.rb +64 -64
  53. data/app/controllers/roroacms/application_controller.rb +154 -154
  54. data/app/controllers/roroacms/comments_controller.rb +42 -42
  55. data/app/controllers/roroacms/pages_controller.rb +55 -55
  56. data/app/controllers/roroacms/setup_controller.rb +104 -104
  57. data/app/helpers/roroacms/admin_menu_helper.rb +110 -110
  58. data/app/helpers/roroacms/admin_roroa_helper.rb +355 -355
  59. data/app/helpers/roroacms/admin_ui_helper.rb +22 -22
  60. data/app/helpers/roroacms/admin_view_helper.rb +24 -24
  61. data/app/helpers/roroacms/application_helper.rb +4 -4
  62. data/app/helpers/roroacms/comments_helper.rb +39 -39
  63. data/app/helpers/roroacms/general_helper.rb +144 -144
  64. data/app/helpers/roroacms/media_helper.rb +44 -44
  65. data/app/helpers/roroacms/menu_helper.rb +183 -183
  66. data/app/helpers/roroacms/prepcontent_helper.rb +27 -27
  67. data/app/helpers/roroacms/routing_helper.rb +392 -392
  68. data/app/helpers/roroacms/seo_helper.rb +257 -257
  69. data/app/helpers/roroacms/theme_helper.rb +6 -6
  70. data/app/helpers/roroacms/view_helper.rb +1229 -1229
  71. data/app/mailers/roroacms/emailer.rb +31 -31
  72. data/app/models/roroacms/admin.rb +86 -86
  73. data/app/models/roroacms/comment.rb +83 -83
  74. data/app/models/roroacms/menu.rb +18 -18
  75. data/app/models/roroacms/menu_option.rb +41 -41
  76. data/app/models/roroacms/post.rb +380 -380
  77. data/app/models/roroacms/setting.rb +190 -190
  78. data/app/models/roroacms/term.rb +157 -157
  79. data/app/models/roroacms/term_anatomy.rb +8 -8
  80. data/app/models/roroacms/term_relationship.rb +9 -9
  81. data/app/models/roroacms/trash.rb +67 -67
  82. data/app/views/devise/confirmations/new.html.erb +11 -11
  83. data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -5
  84. data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -8
  85. data/app/views/devise/mailer/unlock_instructions.html.erb +6 -6
  86. data/app/views/devise/passwords/edit.html.erb +33 -33
  87. data/app/views/devise/passwords/new.html.erb +22 -22
  88. data/app/views/devise/registrations/edit.html.erb +24 -24
  89. data/app/views/devise/registrations/new.html.erb +17 -17
  90. data/app/views/devise/sessions/new.html.erb +42 -42
  91. data/app/views/devise/shared/_links.erb +12 -12
  92. data/app/views/devise/unlocks/new.html.erb +11 -11
  93. data/app/views/layouts/roroacms/admin.html.erb +172 -172
  94. data/app/views/layouts/roroacms/login.html.erb +35 -35
  95. data/app/views/layouts/roroacms/setup.html.erb +36 -36
  96. data/app/views/roroacms/admin/administrators/_form.html.erb +135 -135
  97. data/app/views/roroacms/admin/administrators/edit.html.erb +5 -5
  98. data/app/views/roroacms/admin/administrators/index.html.erb +59 -59
  99. data/app/views/roroacms/admin/administrators/new.html.erb +5 -5
  100. data/app/views/roroacms/admin/articles/edit.html.erb +8 -8
  101. data/app/views/roroacms/admin/articles/index.html.erb +14 -14
  102. data/app/views/roroacms/admin/articles/new.html.erb +7 -7
  103. data/app/views/roroacms/admin/comments/_form.html.erb +89 -89
  104. data/app/views/roroacms/admin/comments/edit.html.erb +24 -24
  105. data/app/views/roroacms/admin/comments/index.html.erb +78 -78
  106. data/app/views/roroacms/admin/dashboard/index.html.erb +144 -144
  107. data/app/views/roroacms/admin/menus/_menu_form.html.erb +31 -31
  108. data/app/views/roroacms/admin/menus/_menu_list.html.erb +2 -2
  109. data/app/views/roroacms/admin/menus/edit.html.erb +29 -29
  110. data/app/views/roroacms/admin/menus/index.html.erb +42 -42
  111. data/app/views/roroacms/admin/menus/partials/_menu_content_accordion.html.erb +70 -70
  112. data/app/views/roroacms/admin/menus/partials/_menu_custom_accordion.html.erb +39 -39
  113. data/app/views/roroacms/admin/menus/partials/_menu_dropdown.html.erb +49 -49
  114. data/app/views/roroacms/admin/menus/partials/_menu_list_loop.html.erb +64 -64
  115. data/app/views/roroacms/admin/menus/partials/_menu_menu_groups.html.erb +18 -18
  116. data/app/views/roroacms/admin/pages/edit.html.erb +6 -6
  117. data/app/views/roroacms/admin/pages/index.html.erb +15 -15
  118. data/app/views/roroacms/admin/pages/new.html.erb +7 -7
  119. data/app/views/roroacms/admin/partials/_admin_comment.html.erb +18 -18
  120. data/app/views/roroacms/admin/partials/_append_sidebar_menu.html.erb +51 -51
  121. data/app/views/roroacms/admin/partials/_articles_table.html.erb +46 -46
  122. data/app/views/roroacms/admin/partials/_bulk_update_dropdown.html.erb +35 -35
  123. data/app/views/roroacms/admin/partials/_comment.html.erb +18 -18
  124. data/app/views/roroacms/admin/partials/_content_form.html.erb +316 -316
  125. data/app/views/roroacms/admin/partials/_cover_image.html.erb +12 -12
  126. data/app/views/roroacms/admin/partials/_editor.html.erb +21 -21
  127. data/app/views/roroacms/admin/partials/_markdown_modal.html.erb +57 -57
  128. data/app/views/roroacms/admin/partials/_pages_table.html.erb +25 -25
  129. data/app/views/roroacms/admin/partials/_post_additional_data_view.html.erb +7 -7
  130. data/app/views/roroacms/admin/partials/_revision_tree.html.erb +78 -78
  131. data/app/views/roroacms/admin/partials/_submit_bar.html.erb +30 -30
  132. data/app/views/roroacms/admin/partials/_table_header.html.erb +14 -14
  133. data/app/views/roroacms/admin/partials/_table_row.html.erb +34 -34
  134. data/app/views/roroacms/admin/partials/_term_table_row.html.erb +22 -22
  135. data/app/views/roroacms/admin/partials/_user_group_view.html.erb +19 -19
  136. data/app/views/roroacms/admin/revisions/_autosave_table.html.erb +36 -36
  137. data/app/views/roroacms/admin/revisions/edit.html.erb +91 -91
  138. data/app/views/roroacms/admin/settings/index.html.erb +398 -398
  139. data/app/views/roroacms/admin/terms/_form.html.erb +50 -50
  140. data/app/views/roroacms/admin/terms/_table.html.erb +21 -21
  141. data/app/views/roroacms/admin/terms/edit.html.erb +49 -49
  142. data/app/views/roroacms/admin/terms/view.html.erb +31 -31
  143. data/app/views/roroacms/admin/themes/index.html.erb +81 -81
  144. data/app/views/roroacms/admin/trash/index.html.erb +110 -110
  145. data/app/views/roroacms/devise/confirmations/new.html.erb +11 -11
  146. data/app/views/roroacms/devise/mailer/confirmation_instructions.html.erb +5 -5
  147. data/app/views/roroacms/devise/mailer/reset_password_instructions.html.erb +8 -8
  148. data/app/views/roroacms/devise/mailer/unlock_instructions.html.erb +6 -6
  149. data/app/views/roroacms/devise/passwords/edit.html.erb +33 -33
  150. data/app/views/roroacms/devise/passwords/new.html.erb +22 -22
  151. data/app/views/roroacms/devise/registrations/edit.html.erb +24 -24
  152. data/app/views/roroacms/devise/registrations/new.html.erb +17 -17
  153. data/app/views/roroacms/devise/sessions/new.html.erb +39 -39
  154. data/app/views/roroacms/devise/shared/_links.erb +12 -12
  155. data/app/views/roroacms/devise/unlocks/new.html.erb +11 -11
  156. data/app/views/roroacms/emailer/comment.html.erb +13 -13
  157. data/app/views/roroacms/emailer/comment.text.erb +12 -12
  158. data/app/views/roroacms/emailer/profile.html.erb +5 -5
  159. data/app/views/roroacms/emailer/profile.text.erb +5 -5
  160. data/app/views/roroacms/setup/_theme.html.erb +61 -61
  161. data/app/views/roroacms/setup/administrator.html.erb +72 -72
  162. data/app/views/roroacms/setup/index.html.erb +116 -116
  163. data/bin/rails +12 -12
  164. data/config/initializers/devise.rb +258 -258
  165. data/config/locales/devise.en.yml +115 -115
  166. data/config/routes.rb +96 -96
  167. data/db/migrate/20140801203556_add_admins.rb +44 -44
  168. data/db/migrate/20140802084604_add_roroacms_comments.rb +31 -31
  169. data/db/migrate/20140802084615_add_roroacms_menu_options.rb +24 -24
  170. data/db/migrate/20140802084624_add_roroacms_menus.rb +18 -18
  171. data/db/migrate/20140802084631_add_roroacms_posts.rb +42 -42
  172. data/db/migrate/20140802084638_add_roroacms_settings.rb +21 -21
  173. data/db/migrate/20140802084658_add_roroacms_term_anatomies.rb +20 -20
  174. data/db/migrate/20140802084707_add_roroacms_term_relationships.rb +23 -23
  175. data/db/migrate/20140802084717_add_roroacms_terms.rb +27 -27
  176. data/db/seeds.rb +60 -60
  177. data/lib/ext/string.rb +4 -4
  178. data/lib/generators/roroacms/install_generator.rb +16 -16
  179. data/lib/roroacms/engine.rb +83 -84
  180. data/lib/roroacms/version.rb +3 -3
  181. data/lib/roroacms.rb +5 -5
  182. data/lib/tasks/roroacms_tasks.rake +9 -9
  183. data/spec/controllers/roroacms/admin/administrators_controller_spec.rb +136 -0
  184. data/spec/controllers/roroacms/admin/articles_controller_spec.rb +135 -0
  185. data/spec/controllers/roroacms/admin/comments_controller_spec.rb +123 -0
  186. data/spec/controllers/roroacms/admin/dashboard_controller_spec.rb +24 -0
  187. data/spec/controllers/roroacms/admin/menus_controller_spec.rb +103 -0
  188. data/spec/controllers/roroacms/admin/pages_controller_spec.rb +163 -0
  189. data/spec/controllers/roroacms/admin/revisions_controller_spec.rb +44 -0
  190. data/spec/controllers/roroacms/admin/settings_controller_spec.rb +75 -0
  191. data/spec/controllers/roroacms/admin/terms_controller_spec.rb +177 -0
  192. data/spec/controllers/roroacms/admin/themes_controller_spec.rb +57 -0
  193. data/spec/controllers/roroacms/admin/trash_controller_spec.rb +62 -0
  194. data/spec/controllers/roroacms/comments_controller_spec.rb +24 -0
  195. data/spec/controllers/roroacms/pages_controller_spec.rb +34 -0
  196. data/spec/dummy/README.rdoc +28 -0
  197. data/spec/dummy/Rakefile +6 -0
  198. data/spec/dummy/app/assets/images/.keep +0 -0
  199. data/spec/dummy/app/assets/javascripts/application.js +1 -0
  200. data/spec/dummy/app/assets/stylesheets/application.css +1 -0
  201. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  202. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  203. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  204. data/spec/dummy/app/helpers/theme_helper.rb +13 -0
  205. data/spec/dummy/app/mailers/.keep +0 -0
  206. data/spec/dummy/app/models/.keep +0 -0
  207. data/spec/dummy/app/models/concerns/.keep +0 -0
  208. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  209. data/spec/dummy/app/views/theme/roroa-bootstrap-3/_sidebar.html.erb +33 -0
  210. data/spec/dummy/app/views/theme/roroa-bootstrap-3/archive.html.erb +60 -0
  211. data/spec/dummy/app/views/theme/roroa-bootstrap-3/article.html.erb +38 -0
  212. data/spec/dummy/app/views/theme/roroa-bootstrap-3/assets/font/glyphicons-halflings-regular.eot +0 -0
  213. data/spec/dummy/app/views/theme/roroa-bootstrap-3/assets/font/glyphicons-halflings-regular.svg +229 -0
  214. data/spec/dummy/app/views/theme/roroa-bootstrap-3/assets/font/glyphicons-halflings-regular.ttf +0 -0
  215. data/spec/dummy/app/views/theme/roroa-bootstrap-3/assets/font/glyphicons-halflings-regular.woff +0 -0
  216. data/spec/dummy/app/views/theme/roroa-bootstrap-3/assets/javascripts/roroacms-bs3-theme.js +17 -0
  217. data/spec/dummy/app/views/theme/roroa-bootstrap-3/assets/javascripts/theme.js +4 -0
  218. data/spec/dummy/app/views/theme/roroa-bootstrap-3/assets/javascripts/vendor/bootstrap.min.js +6 -0
  219. data/spec/dummy/app/views/theme/roroa-bootstrap-3/assets/stylesheets/roroacms-bs3-theme.css +222 -0
  220. data/spec/dummy/app/views/theme/roroa-bootstrap-3/assets/stylesheets/theme.css +4 -0
  221. data/spec/dummy/app/views/theme/roroa-bootstrap-3/assets/stylesheets/vendor/bootstrap.min.css +7 -0
  222. data/spec/dummy/app/views/theme/roroa-bootstrap-3/category.html.erb +66 -0
  223. data/spec/dummy/app/views/theme/roroa-bootstrap-3/comments_form.html.erb +43 -0
  224. data/spec/dummy/app/views/theme/roroa-bootstrap-3/error_404.html.erb +6 -0
  225. data/spec/dummy/app/views/theme/roroa-bootstrap-3/footer.html.erb +81 -0
  226. data/spec/dummy/app/views/theme/roroa-bootstrap-3/header.html.erb +129 -0
  227. data/spec/dummy/app/views/theme/roroa-bootstrap-3/home.html.erb +23 -0
  228. data/spec/dummy/app/views/theme/roroa-bootstrap-3/layout.html.erb +5 -0
  229. data/spec/dummy/app/views/theme/roroa-bootstrap-3/page.html.erb +27 -0
  230. data/spec/dummy/app/views/theme/roroa-bootstrap-3/search.html.erb +33 -0
  231. data/spec/dummy/app/views/theme/roroa-bootstrap-3/search_form.html.erb +9 -0
  232. data/spec/dummy/app/views/theme/roroa-bootstrap-3/template-contact.html.erb +81 -0
  233. data/spec/dummy/app/views/theme/roroa-bootstrap-3/theme.yml +6 -0
  234. data/spec/dummy/app/views/theme/roroa-bootstrap-3/theme_helper.rb +9 -0
  235. data/spec/dummy/app/views/theme/roroa1/_sidebar.html.erb +18 -0
  236. data/spec/dummy/app/views/theme/roroa1/archive.html.erb +33 -0
  237. data/spec/dummy/app/views/theme/roroa1/article.html.erb +23 -0
  238. data/spec/dummy/app/views/theme/roroa1/assets/javascripts/roroa-standard.js.coffee +10 -0
  239. data/spec/dummy/app/views/theme/roroa1/assets/javascripts/theme.js +1 -0
  240. data/spec/dummy/app/views/theme/roroa1/assets/stylesheets/style.css.scss +884 -0
  241. data/spec/dummy/app/views/theme/roroa1/assets/stylesheets/theme.css +3 -0
  242. data/spec/dummy/app/views/theme/roroa1/category.html.erb +37 -0
  243. data/spec/dummy/app/views/theme/roroa1/comments_form.html.erb +61 -0
  244. data/spec/dummy/app/views/theme/roroa1/error_404.html.erb +4 -0
  245. data/spec/dummy/app/views/theme/roroa1/home.html.erb +25 -0
  246. data/spec/dummy/app/views/theme/roroa1/layout.html.erb +95 -0
  247. data/spec/dummy/app/views/theme/roroa1/page.html.erb +7 -0
  248. data/spec/dummy/app/views/theme/roroa1/search.html.erb +31 -0
  249. data/spec/dummy/app/views/theme/roroa1/search_form.html.erb +6 -0
  250. data/spec/dummy/app/views/theme/roroa1/template-contact.html.erb +125 -0
  251. data/spec/dummy/app/views/theme/roroa1/template-news.html.erb +1 -0
  252. data/spec/dummy/app/views/theme/roroa1/theme.yml +5 -0
  253. data/spec/dummy/bin/bundle +3 -0
  254. data/spec/dummy/bin/rails +4 -0
  255. data/spec/dummy/bin/rake +4 -0
  256. data/spec/dummy/config/application.rb +23 -0
  257. data/spec/dummy/config/boot.rb +5 -0
  258. data/spec/dummy/config/database.yml +8 -0
  259. data/spec/dummy/config/environment.rb +5 -0
  260. data/spec/dummy/config/environments/development.rb +37 -0
  261. data/spec/dummy/config/environments/production.rb +82 -0
  262. data/spec/dummy/config/environments/test.rb +39 -0
  263. data/spec/dummy/config/initializers/assets.rb +8 -0
  264. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  265. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  266. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  267. data/spec/dummy/config/initializers/inflections.rb +16 -0
  268. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  269. data/spec/dummy/config/initializers/session_store.rb +3 -0
  270. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  271. data/spec/dummy/config/locales/en.yml +23 -0
  272. data/spec/dummy/config/routes.rb +3 -0
  273. data/spec/dummy/config/secrets.yml +22 -0
  274. data/spec/dummy/config.ru +4 -0
  275. data/spec/dummy/db/development.sqlite3 +0 -0
  276. data/spec/dummy/db/schema.rb +149 -0
  277. data/spec/dummy/lib/assets/.keep +0 -0
  278. data/spec/dummy/log/.keep +0 -0
  279. data/spec/dummy/public/404.html +67 -0
  280. data/spec/dummy/public/422.html +67 -0
  281. data/spec/dummy/public/500.html +66 -0
  282. data/spec/dummy/public/favicon.ico +0 -0
  283. data/spec/dummy/public/javascripts/translations.js +2 -0
  284. data/spec/dummy/tmp/cache/assets/development/sprockets/01b0bb90f5966d00bf38cc2e4c6dc61c +0 -0
  285. data/spec/dummy/tmp/cache/assets/development/sprockets/01c4f566937fc1135c9aac08aaed1d7f +0 -0
  286. data/spec/dummy/tmp/cache/assets/development/sprockets/02ac78c2224882185ca7d2180a010a02 +0 -0
  287. data/spec/dummy/tmp/cache/assets/development/sprockets/02dd9be3b289f56169f00da8d6e8cb63 +0 -0
  288. data/spec/dummy/tmp/cache/assets/development/sprockets/044f18e425557a4db28813e3d413b2ac +0 -0
  289. data/spec/dummy/tmp/cache/assets/development/sprockets/062c6b6c1fd8cc4dec9482e22b8bf15e +0 -0
  290. data/spec/dummy/tmp/cache/assets/development/sprockets/0671a937a155d760eb50f0a4673b361e +0 -0
  291. data/spec/dummy/tmp/cache/assets/development/sprockets/075f916e73522805629055d0ebc0a96f +0 -0
  292. data/spec/dummy/tmp/cache/assets/development/sprockets/07c31f29bd114c516fec34c12e23040b +0 -0
  293. data/spec/dummy/tmp/cache/assets/development/sprockets/07df29267aa56c3d60701d02593b42fe +0 -0
  294. data/spec/dummy/tmp/cache/assets/development/sprockets/07ed3087294a5538008469d14dad37ec +0 -0
  295. data/spec/dummy/tmp/cache/assets/development/sprockets/0915aabdb5273fb452531154011b1530 +0 -0
  296. data/spec/dummy/tmp/cache/assets/development/sprockets/096998b34babf611dd1fd30affcce1f8 +0 -0
  297. data/spec/dummy/tmp/cache/assets/development/sprockets/0a959d1d6d5addc548e6a7d72cda5513 +0 -0
  298. data/spec/dummy/tmp/cache/assets/development/sprockets/0aa0cf2c509791fae370e676145661e9 +0 -0
  299. data/spec/dummy/tmp/cache/assets/development/sprockets/0b82858a45f9840a67e62c589b49a30b +0 -0
  300. data/spec/dummy/tmp/cache/assets/development/sprockets/0c31dca9d5a7fe6fa13975f398bf6755 +0 -0
  301. data/spec/dummy/tmp/cache/assets/development/sprockets/0c7ddb3a4e6bd5f58621aafa9d8b268c +0 -0
  302. data/spec/dummy/tmp/cache/assets/development/sprockets/0ccfaad24ef387850a25da694a32fdeb +0 -0
  303. data/spec/dummy/tmp/cache/assets/development/sprockets/0d014c14229f4e6af12b9f5214c2c900 +0 -0
  304. data/spec/dummy/tmp/cache/assets/development/sprockets/0d2c92577a98a55e84279cf35fa47154 +0 -0
  305. data/spec/dummy/tmp/cache/assets/development/sprockets/0f1a8d8cb2d347d2c43e167b6e8dd772 +0 -0
  306. data/spec/dummy/tmp/cache/assets/development/sprockets/0f7739cbdf17c178ed8302927ab6f91a +0 -0
  307. data/spec/dummy/tmp/cache/assets/development/sprockets/0fd906d894053aa32d20273d272914a6 +0 -0
  308. data/spec/dummy/tmp/cache/assets/development/sprockets/108902bac7ebd1b233ad72bff14a3c6e +0 -0
  309. data/spec/dummy/tmp/cache/assets/development/sprockets/117c1540c942b9a30221507b7b545ac7 +0 -0
  310. data/spec/dummy/tmp/cache/assets/development/sprockets/120699e4b9a291742d32cc3f1d54a1c0 +0 -0
  311. data/spec/dummy/tmp/cache/assets/development/sprockets/12823874e85d7f4b0d0f24e75b6a606f +0 -0
  312. data/spec/dummy/tmp/cache/assets/development/sprockets/12d4f50804f6b2e95ae22d6240d5f251 +0 -0
  313. data/spec/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  314. data/spec/dummy/tmp/cache/assets/development/sprockets/14a07aa21f4b662c3a00906bb7fd96a1 +0 -0
  315. data/spec/dummy/tmp/cache/assets/development/sprockets/16246104f8ae735f81f309dff662f6ac +0 -0
  316. data/spec/dummy/tmp/cache/assets/development/sprockets/170c2bd9dd32f95b5ff94772e3046435 +0 -0
  317. data/spec/dummy/tmp/cache/assets/development/sprockets/175f517f3f99a11bea98f1b106cbad3e +0 -0
  318. data/spec/dummy/tmp/cache/assets/development/sprockets/182f982bc1069c944b0768c5fb8e70e8 +0 -0
  319. data/spec/dummy/tmp/cache/assets/development/sprockets/189afb43eebc6790a343d47e099b5b3b +0 -0
  320. data/spec/dummy/tmp/cache/assets/development/sprockets/18f00a43e59b4caf8cab8002225abb16 +0 -0
  321. data/spec/dummy/tmp/cache/assets/development/sprockets/19cc7d516e9c5c945152a00c520652a7 +0 -0
  322. data/spec/dummy/tmp/cache/assets/development/sprockets/1a49195845de370bd0390b666bf63002 +0 -0
  323. data/spec/dummy/tmp/cache/assets/development/sprockets/1aabd6297701193c87b371bb28423093 +0 -0
  324. data/spec/dummy/tmp/cache/assets/development/sprockets/1ad4574bbfb58c18cf27bde856cfb8e6 +0 -0
  325. data/spec/dummy/tmp/cache/assets/development/sprockets/1bbda697ecdf1dab2d9857c246167e61 +0 -0
  326. data/spec/dummy/tmp/cache/assets/development/sprockets/1c8fd5c201d2c1c04f0dfa7f84406549 +0 -0
  327. data/spec/dummy/tmp/cache/assets/development/sprockets/1e13b0d0d3273c81783675257ae416a5 +0 -0
  328. data/spec/dummy/tmp/cache/assets/development/sprockets/1ecda2df373750075af958a84e177443 +0 -0
  329. data/spec/dummy/tmp/cache/assets/development/sprockets/1ed030685f785031c518b9c084d2c562 +0 -0
  330. data/spec/dummy/tmp/cache/assets/development/sprockets/20a8ce6b001e3f5e000d83b03c39973a +0 -0
  331. data/spec/dummy/tmp/cache/assets/development/sprockets/22a1b56f54d66c951d0414589ae7e592 +0 -0
  332. data/spec/dummy/tmp/cache/assets/development/sprockets/22b316eb2ae844d2aa5b3d56609e7134 +0 -0
  333. data/spec/dummy/tmp/cache/assets/development/sprockets/23979d8c8de077d9c5aca204af6d1cae +0 -0
  334. data/spec/dummy/tmp/cache/assets/development/sprockets/2405f0ab32bb68958749badf5c0c5ab2 +0 -0
  335. data/spec/dummy/tmp/cache/assets/development/sprockets/24507a849b1f6f49e4d80806a2ae8f7f +0 -0
  336. data/spec/dummy/tmp/cache/assets/development/sprockets/2611a6793dffe5cf691fc66ef8a1d96f +0 -0
  337. data/spec/dummy/tmp/cache/assets/development/sprockets/2649e6f5622aa342a69fa0c56058f50e +0 -0
  338. data/spec/dummy/tmp/cache/assets/development/sprockets/266a1d6eaa1096958f5220dcd72fa818 +0 -0
  339. data/spec/dummy/tmp/cache/assets/development/sprockets/28257287bdb69e9da885553af4d784fd +0 -0
  340. data/spec/dummy/tmp/cache/assets/development/sprockets/283ae16090ef393308b47b6eac68a78d +0 -0
  341. data/spec/dummy/tmp/cache/assets/development/sprockets/2855a662a410acf5b8341d18ea08ad09 +0 -0
  342. data/spec/dummy/tmp/cache/assets/development/sprockets/29027878740c3347a585de84e2468cec +0 -0
  343. data/spec/dummy/tmp/cache/assets/development/sprockets/29b612a243d57e996aa25e6bd55c3ae0 +0 -0
  344. data/spec/dummy/tmp/cache/assets/development/sprockets/2a1ca299203dc52bfd48e9e990234288 +0 -0
  345. data/spec/dummy/tmp/cache/assets/development/sprockets/2a3ef4fee1ead82a2e020bd01e0f9492 +0 -0
  346. data/spec/dummy/tmp/cache/assets/development/sprockets/2b31fdf797816a4d7eeb501882522245 +0 -0
  347. data/spec/dummy/tmp/cache/assets/development/sprockets/2d14fddff832dffcfdc6b7c16543a37a +0 -0
  348. data/spec/dummy/tmp/cache/assets/development/sprockets/2d57904662e5b6b51010d683ea36f3aa +0 -0
  349. data/spec/dummy/tmp/cache/assets/development/sprockets/2ed37822c6f252a1bb9a2a4186d51b80 +0 -0
  350. data/spec/dummy/tmp/cache/assets/development/sprockets/2ed54505fb85c659a5d40fd42fdb8c82 +0 -0
  351. data/spec/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  352. data/spec/dummy/tmp/cache/assets/development/sprockets/2f7814e3b44a611d9cc5ba4838783cec +0 -0
  353. data/spec/dummy/tmp/cache/assets/development/sprockets/2f95938c43835c0d255c4d98c85130a2 +0 -0
  354. data/spec/dummy/tmp/cache/assets/development/sprockets/2ffa1a843006a5e108eb823936fc4b8d +0 -0
  355. data/spec/dummy/tmp/cache/assets/development/sprockets/30c74df35467423ca4f594c50ca972cb +0 -0
  356. data/spec/dummy/tmp/cache/assets/development/sprockets/31de52bfeb25a36a638e82c1d44b6110 +0 -0
  357. data/spec/dummy/tmp/cache/assets/development/sprockets/32189e4bb4be3499e1154394fe5fa5cf +0 -0
  358. data/spec/dummy/tmp/cache/assets/development/sprockets/3316f6752e709d7a6eb5a3781fced097 +0 -0
  359. data/spec/dummy/tmp/cache/assets/development/sprockets/33177563a7306549dc9684f9348ae6c6 +0 -0
  360. data/spec/dummy/tmp/cache/assets/development/sprockets/34fac393d91ac96fad0606d7d210212c +0 -0
  361. data/spec/dummy/tmp/cache/assets/development/sprockets/354bc78e74a0ae625ed0173f96ad5356 +0 -0
  362. data/spec/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  363. data/spec/dummy/tmp/cache/assets/development/sprockets/35ab8dc5331965047a6a48cfebf03234 +0 -0
  364. data/spec/dummy/tmp/cache/assets/development/sprockets/36264cd973b91fad389f3a7e83d358df +0 -0
  365. data/spec/dummy/tmp/cache/assets/development/sprockets/3633cb5a540f9da590a8be6b659357d9 +0 -0
  366. data/spec/dummy/tmp/cache/assets/development/sprockets/363ec8b1e6c0e4e28147577cd9aa1767 +0 -0
  367. data/spec/dummy/tmp/cache/assets/development/sprockets/36899e053ce425269daeefbc24afde74 +0 -0
  368. data/spec/dummy/tmp/cache/assets/development/sprockets/374a7e699349c3747ad0eb80df319ee5 +0 -0
  369. data/spec/dummy/tmp/cache/assets/development/sprockets/3792de44b7ef5883097d33bd02bb66a7 +0 -0
  370. data/spec/dummy/tmp/cache/assets/development/sprockets/37a02dc6a0d71db376ca89c53f25713b +0 -0
  371. data/spec/dummy/tmp/cache/assets/development/sprockets/385067d2a8c69821fc305178e1abec5d +0 -0
  372. data/spec/dummy/tmp/cache/assets/development/sprockets/389b05cb7df2beb5f9f7f7ae2983276a +0 -0
  373. data/spec/dummy/tmp/cache/assets/development/sprockets/39e74d6deb67d83562723310a49d2343 +0 -0
  374. data/spec/dummy/tmp/cache/assets/development/sprockets/3d7a543a402dfe7a63a4d4dfc2f02e35 +0 -0
  375. data/spec/dummy/tmp/cache/assets/development/sprockets/3dac43d31d5e2baae0882a187f995373 +0 -0
  376. data/spec/dummy/tmp/cache/assets/development/sprockets/3df24cb6966ced6c31468ea144303cba +0 -0
  377. data/spec/dummy/tmp/cache/assets/development/sprockets/3e9e1f77164027e372eab31e44bd792e +0 -0
  378. data/spec/dummy/tmp/cache/assets/development/sprockets/402809273e09d12a0d961e96277c93ff +0 -0
  379. data/spec/dummy/tmp/cache/assets/development/sprockets/40dcda7ab7c26916df1b499cf472b036 +0 -0
  380. data/spec/dummy/tmp/cache/assets/development/sprockets/415a7735417367ba5c89a10bda3c3664 +0 -0
  381. data/spec/dummy/tmp/cache/assets/development/sprockets/42875948b15702a8e6423bfcd55eb397 +0 -0
  382. data/spec/dummy/tmp/cache/assets/development/sprockets/4361292ee0e8da4ffba6417b67d107d7 +0 -0
  383. data/spec/dummy/tmp/cache/assets/development/sprockets/4416c7d372391892156a3ad7e796eea2 +0 -0
  384. data/spec/dummy/tmp/cache/assets/development/sprockets/44aff36650b10dfc65996edc7c5ba786 +0 -0
  385. data/spec/dummy/tmp/cache/assets/development/sprockets/45f19765f78bbeaf456e3e85d8eeaeea +0 -0
  386. data/spec/dummy/tmp/cache/assets/development/sprockets/460c01975695d2221ebfe45345c66c14 +0 -0
  387. data/spec/dummy/tmp/cache/assets/development/sprockets/474d3d268fe31fa85b9ad6a702106f89 +0 -0
  388. data/spec/dummy/tmp/cache/assets/development/sprockets/4818258c6899deb650e9b7a11472a5cb +0 -0
  389. data/spec/dummy/tmp/cache/assets/development/sprockets/485ba245fdff299bc3993a1dfae69830 +0 -0
  390. data/spec/dummy/tmp/cache/assets/development/sprockets/48f01ff4ef7bc5f6389a2481742a32cc +0 -0
  391. data/spec/dummy/tmp/cache/assets/development/sprockets/490efd99068b27bb2660dd5f97822f75 +0 -0
  392. data/spec/dummy/tmp/cache/assets/development/sprockets/49bc81ab46512983d6c99fe5ee89c87a +0 -0
  393. data/spec/dummy/tmp/cache/assets/development/sprockets/4a0b8c669e9c663a5d7a6fe73bf92961 +0 -0
  394. data/spec/dummy/tmp/cache/assets/development/sprockets/4aef0c0538c834078e6d8537889cc2a1 +0 -0
  395. data/spec/dummy/tmp/cache/assets/development/sprockets/4af8affa02b9ecd9051258755d4be60a +0 -0
  396. data/spec/dummy/tmp/cache/assets/development/sprockets/4b5d082bdfae4f56c090f7c10cb6647d +0 -0
  397. data/spec/dummy/tmp/cache/assets/development/sprockets/4b6f1eca9b2c126af3c3790129ba15e6 +0 -0
  398. data/spec/dummy/tmp/cache/assets/development/sprockets/4beeeb189ce51371e4edc7569eb4a53a +0 -0
  399. data/spec/dummy/tmp/cache/assets/development/sprockets/4c86f80454f717e7151747beb022d61d +0 -0
  400. data/spec/dummy/tmp/cache/assets/development/sprockets/4cffdc29cdafdcdf76ae764312573579 +0 -0
  401. data/spec/dummy/tmp/cache/assets/development/sprockets/4d26f6579d12e4f1d3c79fba613933fb +0 -0
  402. data/spec/dummy/tmp/cache/assets/development/sprockets/4e13d96aef7929980b0c503756cc0c6b +0 -0
  403. data/spec/dummy/tmp/cache/assets/development/sprockets/4f5ca293a17dc427299d712706be99d6 +0 -0
  404. data/spec/dummy/tmp/cache/assets/development/sprockets/4f6e11155789e81497dc94d22a2c9db8 +0 -0
  405. data/spec/dummy/tmp/cache/assets/development/sprockets/4f829c2dffc45c149adee4cd1b35f23b +0 -0
  406. data/spec/dummy/tmp/cache/assets/development/sprockets/5132d2c56e2db02645975ee877d5ecb9 +0 -0
  407. data/spec/dummy/tmp/cache/assets/development/sprockets/579cf218fbfa8b52dd892d7f9c39c65e +0 -0
  408. data/spec/dummy/tmp/cache/assets/development/sprockets/57a51996827da8be33316d6e513c1856 +0 -0
  409. data/spec/dummy/tmp/cache/assets/development/sprockets/581344a3bce36221a1fc5ee9d507cb78 +0 -0
  410. data/spec/dummy/tmp/cache/assets/development/sprockets/58f64a672004610fe63c125f3cd8fe7d +0 -0
  411. data/spec/dummy/tmp/cache/assets/development/sprockets/596d21a0e4b0e82491dd946147d0b509 +0 -0
  412. data/spec/dummy/tmp/cache/assets/development/sprockets/5ac8aaa5bee9489bc446a774e2e646b9 +0 -0
  413. data/spec/dummy/tmp/cache/assets/development/sprockets/5b79b1a35f5118fe38011ca429634834 +0 -0
  414. data/spec/dummy/tmp/cache/assets/development/sprockets/5c8ebcef7b93c3061021528083a680c6 +0 -0
  415. data/spec/dummy/tmp/cache/assets/development/sprockets/5cb8219e4c1db70267fb2a88ba7b599f +0 -0
  416. data/spec/dummy/tmp/cache/assets/development/sprockets/5d5b525c2315ad3fd4711a7ca14b9cc2 +0 -0
  417. data/spec/dummy/tmp/cache/assets/development/sprockets/5d6d4e3b38e87b2de5b9b9b4b2fddf53 +0 -0
  418. data/spec/dummy/tmp/cache/assets/development/sprockets/5de859ea92a0f8d5b1cf81233ff541ce +0 -0
  419. data/spec/dummy/tmp/cache/assets/development/sprockets/5df45e879827bae35f9c0af1d89d3484 +0 -0
  420. data/spec/dummy/tmp/cache/assets/development/sprockets/5ea7acc2a017fb91990f70b1c94413b5 +0 -0
  421. data/spec/dummy/tmp/cache/assets/development/sprockets/5ef3f544813f934df08810f79021eecb +0 -0
  422. data/spec/dummy/tmp/cache/assets/development/sprockets/5f52bb9c53ea90254f76b8d305b70373 +0 -0
  423. data/spec/dummy/tmp/cache/assets/development/sprockets/5f58ba12a115846181d5932f8ebe1108 +0 -0
  424. data/spec/dummy/tmp/cache/assets/development/sprockets/5f8bf71cbb460285f2151bac01ea7481 +0 -0
  425. data/spec/dummy/tmp/cache/assets/development/sprockets/6095811354e7dc1babcb682c0fdfd69e +0 -0
  426. data/spec/dummy/tmp/cache/assets/development/sprockets/60be3ded1b6e36fb0c87316b38156ae2 +0 -0
  427. data/spec/dummy/tmp/cache/assets/development/sprockets/622cbabaed3da4fff5fab28bbcf32bed +0 -0
  428. data/spec/dummy/tmp/cache/assets/development/sprockets/6250be42273c14b0aad242da2150d901 +0 -0
  429. data/spec/dummy/tmp/cache/assets/development/sprockets/631f072a6df346c42e13a97388ae3156 +0 -0
  430. data/spec/dummy/tmp/cache/assets/development/sprockets/634081a34eeeb1ff0f78e8a8cabcf12c +0 -0
  431. data/spec/dummy/tmp/cache/assets/development/sprockets/64563ba9a277596542f2bf4558a991e4 +0 -0
  432. data/spec/dummy/tmp/cache/assets/development/sprockets/65c1cea3dc9ac48c3fd086462bb01d43 +0 -0
  433. data/spec/dummy/tmp/cache/assets/development/sprockets/65caafdf3d25c108b8c44d7c616745d1 +0 -0
  434. data/spec/dummy/tmp/cache/assets/development/sprockets/674bfd6d5bf888941166039a3e65eb72 +0 -0
  435. data/spec/dummy/tmp/cache/assets/development/sprockets/675dcab958bbfb0582a7113840bbe35d +0 -0
  436. data/spec/dummy/tmp/cache/assets/development/sprockets/68a9f70d3be10ed9f50de29a1e300d27 +0 -0
  437. data/spec/dummy/tmp/cache/assets/development/sprockets/696323f68df35557d1dc046a9dbf1a81 +0 -0
  438. data/spec/dummy/tmp/cache/assets/development/sprockets/699417eba3487c8f71acd9d596662606 +0 -0
  439. data/spec/dummy/tmp/cache/assets/development/sprockets/699787446bc5facff1e55d8f4b31221f +0 -0
  440. data/spec/dummy/tmp/cache/assets/development/sprockets/69a29b2ad8cdf192be64b3c3f8b77d8c +0 -0
  441. data/spec/dummy/tmp/cache/assets/development/sprockets/69cf4fd612d34478f8065d84838e982c +0 -0
  442. data/spec/dummy/tmp/cache/assets/development/sprockets/6a9320eaa769c69aaae2231ba2f82fb6 +0 -0
  443. data/spec/dummy/tmp/cache/assets/development/sprockets/6aab73cf49337b2782e983900ad3a3ab +0 -0
  444. data/spec/dummy/tmp/cache/assets/development/sprockets/6affc41afde1d2df6ba129053c1babcd +0 -0
  445. data/spec/dummy/tmp/cache/assets/development/sprockets/6bcfde2d65e4e5d1903a5936de95a86e +0 -0
  446. data/spec/dummy/tmp/cache/assets/development/sprockets/6c927972735d32feb6ed10e7ef1f894e +0 -0
  447. data/spec/dummy/tmp/cache/assets/development/sprockets/6ca2d6d64a82723279eccd56b81e6755 +0 -0
  448. data/spec/dummy/tmp/cache/assets/development/sprockets/6d7dfd8665840288a937f61efa149aed +0 -0
  449. data/spec/dummy/tmp/cache/assets/development/sprockets/6e01bdf2376fcd0c30a6a79058c1e504 +0 -0
  450. data/spec/dummy/tmp/cache/assets/development/sprockets/6e9c00de1c41fdc6467ef2dadc20c591 +0 -0
  451. data/spec/dummy/tmp/cache/assets/development/sprockets/6f2c9037f8ccf7c2a4bca6a25fb598b9 +0 -0
  452. data/spec/dummy/tmp/cache/assets/development/sprockets/7066aee6f2d50aa1675ee592a4c336ba +0 -0
  453. data/spec/dummy/tmp/cache/assets/development/sprockets/7084e05e641b381bfff47c554afec2da +0 -0
  454. data/spec/dummy/tmp/cache/assets/development/sprockets/71d7f12a2022920f7a2a28dbdff79019 +0 -0
  455. data/spec/dummy/tmp/cache/assets/development/sprockets/723bcf3e6983a0559b9e0be394f10c01 +0 -0
  456. data/spec/dummy/tmp/cache/assets/development/sprockets/724781877ef4c3636943c3610499bf87 +0 -0
  457. data/spec/dummy/tmp/cache/assets/development/sprockets/73a26df4a7d7bdc16b247ccfade6121a +0 -0
  458. data/spec/dummy/tmp/cache/assets/development/sprockets/74340e2a21288361b5df1465dea4cc1f +0 -0
  459. data/spec/dummy/tmp/cache/assets/development/sprockets/74cc560b7e7c5a745659ec08176b13c5 +0 -0
  460. data/spec/dummy/tmp/cache/assets/development/sprockets/7507fec5587acab83f4625979da7c58b +0 -0
  461. data/spec/dummy/tmp/cache/assets/development/sprockets/754e347f3d36e2977fd81db154e80602 +0 -0
  462. data/spec/dummy/tmp/cache/assets/development/sprockets/75eb11c26a539bb390644b542a59a231 +0 -0
  463. data/spec/dummy/tmp/cache/assets/development/sprockets/76cd0ec15ae2bbfa35a9f78bef162b83 +0 -0
  464. data/spec/dummy/tmp/cache/assets/development/sprockets/7709ff603668ddf82318669448ae5364 +0 -0
  465. data/spec/dummy/tmp/cache/assets/development/sprockets/77190fb71aee455df365832e249ff2bd +0 -0
  466. data/spec/dummy/tmp/cache/assets/development/sprockets/77e6403a2258ce2966c037bc05d3d35f +0 -0
  467. data/spec/dummy/tmp/cache/assets/development/sprockets/78210e807010563b375b608f20ccefa1 +0 -0
  468. data/spec/dummy/tmp/cache/assets/development/sprockets/78f00a860f4c5ded036af4761397352d +0 -0
  469. data/spec/dummy/tmp/cache/assets/development/sprockets/79a104fd39f788070c7d9add71efa32c +0 -0
  470. data/spec/dummy/tmp/cache/assets/development/sprockets/79b65d2d7b7bab646f6c393bd9703bc3 +0 -0
  471. data/spec/dummy/tmp/cache/assets/development/sprockets/79bab032f54eb5a98a68a8bc06b57822 +0 -0
  472. data/spec/dummy/tmp/cache/assets/development/sprockets/7a4c12dd007420226c0bc78951585006 +0 -0
  473. data/spec/dummy/tmp/cache/assets/development/sprockets/7aeeee63a3e1037db68405ac86e30a4f +0 -0
  474. data/spec/dummy/tmp/cache/assets/development/sprockets/7b2bf6a64d27177512054df06580f092 +0 -0
  475. data/spec/dummy/tmp/cache/assets/development/sprockets/7cbd90392269c732b318fc901d004f43 +0 -0
  476. data/spec/dummy/tmp/cache/assets/development/sprockets/7cd7c56a479baddf925e01aec7848a4c +0 -0
  477. data/spec/dummy/tmp/cache/assets/development/sprockets/7cf77ddd2975dd6830a4880fb1fc1dbe +0 -0
  478. data/spec/dummy/tmp/cache/assets/development/sprockets/7def427cd79eb67621a05b2d5ad64a61 +0 -0
  479. data/spec/dummy/tmp/cache/assets/development/sprockets/7e5b5fd06d5df24ebb2221f640358492 +0 -0
  480. data/spec/dummy/tmp/cache/assets/development/sprockets/7f7f0e62735d0c01e2e3203eb47c2c3f +0 -0
  481. data/spec/dummy/tmp/cache/assets/development/sprockets/7fbff98235e61aaf8d4b230dd31e9c25 +0 -0
  482. data/spec/dummy/tmp/cache/assets/development/sprockets/7fdf03e57d42dfff59c41760e67fa65e +0 -0
  483. data/spec/dummy/tmp/cache/assets/development/sprockets/80b092dfcd9313068d17c057182f71bd +0 -0
  484. data/spec/dummy/tmp/cache/assets/development/sprockets/80c2f11f8bad645b74798a955ca8e89c +0 -0
  485. data/spec/dummy/tmp/cache/assets/development/sprockets/81c527c170a21781457f6cea4e7b27ce +0 -0
  486. data/spec/dummy/tmp/cache/assets/development/sprockets/81f727984d6b9c602f2b0932f06ebe25 +0 -0
  487. data/spec/dummy/tmp/cache/assets/development/sprockets/81f9c4e9d09403395a2e9d809b10fc1a +0 -0
  488. data/spec/dummy/tmp/cache/assets/development/sprockets/8224aeb6f917522433c9f3d235d83fd1 +0 -0
  489. data/spec/dummy/tmp/cache/assets/development/sprockets/83f5fffe46a2a0b83ef4b6efbe951083 +0 -0
  490. data/spec/dummy/tmp/cache/assets/development/sprockets/84961945b1044869ca1248d1828de33a +0 -0
  491. data/spec/dummy/tmp/cache/assets/development/sprockets/85684bab4511b98c766527cf9f03acd2 +0 -0
  492. data/spec/dummy/tmp/cache/assets/development/sprockets/85b350459ae7cc9bd89e9b8d52a61b75 +0 -0
  493. data/spec/dummy/tmp/cache/assets/development/sprockets/8601c7a96bd24a6af87741abd57e5e94 +0 -0
  494. data/spec/dummy/tmp/cache/assets/development/sprockets/86d499f85b615bdfa95f9f976f8ea3ad +0 -0
  495. data/spec/dummy/tmp/cache/assets/development/sprockets/8704d0a0e9f0ff5e5a2aa2eea9f14a5f +0 -0
  496. data/spec/dummy/tmp/cache/assets/development/sprockets/87c4b682fff94bf74e6b1438033b7c60 +0 -0
  497. data/spec/dummy/tmp/cache/assets/development/sprockets/88327adbd46a84276cfb290bbdb7dca3 +0 -0
  498. data/spec/dummy/tmp/cache/assets/development/sprockets/88eae1724e3694d2070c7155ea3021cc +0 -0
  499. data/spec/dummy/tmp/cache/assets/development/sprockets/89becf26df10662c7cab4b1adcdce227 +0 -0
  500. data/spec/dummy/tmp/cache/assets/development/sprockets/8c696e0458f6923f2458d7a44ad21211 +0 -0
  501. data/spec/dummy/tmp/cache/assets/development/sprockets/8f49105e73394ed3b12e18d79f7a4ff6 +0 -0
  502. data/spec/dummy/tmp/cache/assets/development/sprockets/9146fcb24656f70dfb5c4d8a0461a473 +0 -0
  503. data/spec/dummy/tmp/cache/assets/development/sprockets/9155c28c949a5bdfe776bf620dbc6dc3 +0 -0
  504. data/spec/dummy/tmp/cache/assets/development/sprockets/91b1792019998f509e7965b8fa75c378 +0 -0
  505. data/spec/dummy/tmp/cache/assets/development/sprockets/91c9b223dfe8a224fb6b451a51eb63dd +0 -0
  506. data/spec/dummy/tmp/cache/assets/development/sprockets/9232fe8335151d85d1a8aad7c8c0524b +0 -0
  507. data/spec/dummy/tmp/cache/assets/development/sprockets/9304a78db8748cd47489295ed4843152 +0 -0
  508. data/spec/dummy/tmp/cache/assets/development/sprockets/93845737b6f58a77e524653ce5d6926b +0 -0
  509. data/spec/dummy/tmp/cache/assets/development/sprockets/948e894982cb58176b6ee43037ccf460 +0 -0
  510. data/spec/dummy/tmp/cache/assets/development/sprockets/99002a27f7d6702b0ae6da7da7036dc6 +0 -0
  511. data/spec/dummy/tmp/cache/assets/development/sprockets/99354d91c97da4d779f609eb9935b8fe +0 -0
  512. data/spec/dummy/tmp/cache/assets/development/sprockets/9a37f19ccd71116845a63ff439dd5bf4 +0 -0
  513. data/spec/dummy/tmp/cache/assets/development/sprockets/9b38afb533f8d22148e93931754162c5 +0 -0
  514. data/spec/dummy/tmp/cache/assets/development/sprockets/9c358f08b8672c0d2fd6db7a16ed6c2b +0 -0
  515. data/spec/dummy/tmp/cache/assets/development/sprockets/9c3f89f42447b0c2bdc921ec9317fb38 +0 -0
  516. data/spec/dummy/tmp/cache/assets/development/sprockets/9d56b3e3616119ca42b546e0fb96b7c8 +0 -0
  517. data/spec/dummy/tmp/cache/assets/development/sprockets/9e120a10db5fa35fd3c421f933ebd01a +0 -0
  518. data/spec/dummy/tmp/cache/assets/development/sprockets/9f6baf784711e665ea8352685be3676b +0 -0
  519. data/spec/dummy/tmp/cache/assets/development/sprockets/9fe740ce6c837b3418e0972324ffdb7b +0 -0
  520. data/spec/dummy/tmp/cache/assets/development/sprockets/a1d664104b57e68ca75daa681364b1e4 +0 -0
  521. data/spec/dummy/tmp/cache/assets/development/sprockets/a1e099d69cea07186941a247f24a23e7 +0 -0
  522. data/spec/dummy/tmp/cache/assets/development/sprockets/a319221d824e8f5b647c0b5a6203a67d +0 -0
  523. data/spec/dummy/tmp/cache/assets/development/sprockets/a3ace8e3eb3d918414cf185e820e5b8d +0 -0
  524. data/spec/dummy/tmp/cache/assets/development/sprockets/a3f16a7b18a62765c0c73c95dc786de8 +0 -0
  525. data/spec/dummy/tmp/cache/assets/development/sprockets/a56cdc9b1cc0cd1372a15c410ce15f52 +0 -0
  526. data/spec/dummy/tmp/cache/assets/development/sprockets/a58a85a704454cee53c7a15541ed5e6a +0 -0
  527. data/spec/dummy/tmp/cache/assets/development/sprockets/a5f93f7d4c4dc37755df8de1011556d6 +0 -0
  528. data/spec/dummy/tmp/cache/assets/development/sprockets/a5fc7fe296e4ee928bf5454edc9c334b +0 -0
  529. data/spec/dummy/tmp/cache/assets/development/sprockets/a6902cd66f93fe0640c6d25ba7bbab74 +0 -0
  530. data/spec/dummy/tmp/cache/assets/development/sprockets/a751ef0c349119da69b47d3e8a5411e0 +0 -0
  531. data/spec/dummy/tmp/cache/assets/development/sprockets/a7d46bacc8876cb9f26574792a4eb602 +0 -0
  532. data/spec/dummy/tmp/cache/assets/development/sprockets/a7fda81a77165684dcaf76b8a5c0d98c +0 -0
  533. data/spec/dummy/tmp/cache/assets/development/sprockets/a8d295cec937ed4c43b627c1ec2ee3bf +0 -0
  534. data/spec/dummy/tmp/cache/assets/development/sprockets/a9070b0220974335806b38921350ae6f +0 -0
  535. data/spec/dummy/tmp/cache/assets/development/sprockets/aa04111e924cc573b669237eaa1459a7 +0 -0
  536. data/spec/dummy/tmp/cache/assets/development/sprockets/ab1955c741364b51eafef10d828fe3c0 +0 -0
  537. data/spec/dummy/tmp/cache/assets/development/sprockets/ab25f25883b53de5ea5aa934233b96fd +0 -0
  538. data/spec/dummy/tmp/cache/assets/development/sprockets/ac5eb83633eca54957093b7eacaed98a +0 -0
  539. data/spec/dummy/tmp/cache/assets/development/sprockets/acf6532dc4556d46cbb143a53c1c9d99 +0 -0
  540. data/spec/dummy/tmp/cache/assets/development/sprockets/ad3755af68762ce29bc54dc42b36c7d2 +0 -0
  541. data/spec/dummy/tmp/cache/assets/development/sprockets/ad644b75f8c65173f38014035baeac41 +0 -0
  542. data/spec/dummy/tmp/cache/assets/development/sprockets/adc76ee6c343c47fd7d666b54465af8a +0 -0
  543. data/spec/dummy/tmp/cache/assets/development/sprockets/aeb4f87af70343155cc847f44080a197 +0 -0
  544. data/spec/dummy/tmp/cache/assets/development/sprockets/af16c115b782a328caa529dc3d7ca063 +0 -0
  545. data/spec/dummy/tmp/cache/assets/development/sprockets/b01d038005ad822401663f5ec72fa6ef +0 -0
  546. data/spec/dummy/tmp/cache/assets/development/sprockets/b0ec8166abdcc705333dd67e4f4d2e52 +0 -0
  547. data/spec/dummy/tmp/cache/assets/development/sprockets/b22ac2c9e1f1284a84e42681ced6af62 +0 -0
  548. data/spec/dummy/tmp/cache/assets/development/sprockets/b3a92f2154a373b214e1c134c82aa35a +0 -0
  549. data/spec/dummy/tmp/cache/assets/development/sprockets/b522e8257bebed61822f79ef5b631af5 +0 -0
  550. data/spec/dummy/tmp/cache/assets/development/sprockets/b53c3ca1da3cc97ff2d061205c93f09f +0 -0
  551. data/spec/dummy/tmp/cache/assets/development/sprockets/b53f839c1435533b1f9f096792b75ba2 +0 -0
  552. data/spec/dummy/tmp/cache/assets/development/sprockets/b5679ed21250dca0e0afaccbc4dbea96 +0 -0
  553. data/spec/dummy/tmp/cache/assets/development/sprockets/b79572ce5447929b8752d55866019e5d +0 -0
  554. data/spec/dummy/tmp/cache/assets/development/sprockets/ba4cdbcfe8d56a527507346ec0b0aaa8 +0 -0
  555. data/spec/dummy/tmp/cache/assets/development/sprockets/bb82cf918d1f9deb18fd513203e12b65 +0 -0
  556. data/spec/dummy/tmp/cache/assets/development/sprockets/bde0358bf67ceb4b32068fa3c9bfb3b6 +0 -0
  557. data/spec/dummy/tmp/cache/assets/development/sprockets/be7e82e0b81f8ffb8f906ab45b453305 +0 -0
  558. data/spec/dummy/tmp/cache/assets/development/sprockets/bea40ce674c3fb117ef21e05fade75b1 +0 -0
  559. data/spec/dummy/tmp/cache/assets/development/sprockets/beabf9e96ebd3323d01ae3a39fb6babc +0 -0
  560. data/spec/dummy/tmp/cache/assets/development/sprockets/bed9255c41c5b2e5ff029fef9cde0f77 +0 -0
  561. data/spec/dummy/tmp/cache/assets/development/sprockets/bfd9eac23f2a5c80c63c2af546c6ccaa +0 -0
  562. data/spec/dummy/tmp/cache/assets/development/sprockets/bfdabecefcd035cd13ad6dd39628b98c +0 -0
  563. data/spec/dummy/tmp/cache/assets/development/sprockets/c07fbd81375ee01b6e5816e938a7c11b +0 -0
  564. data/spec/dummy/tmp/cache/assets/development/sprockets/c10616d9f47b2417f5182868178a348e +0 -0
  565. data/spec/dummy/tmp/cache/assets/development/sprockets/c1086ef26030d24fa0820807aa477ea9 +0 -0
  566. data/spec/dummy/tmp/cache/assets/development/sprockets/c2862a0368fa4de5e2f7d189f2301cf2 +0 -0
  567. data/spec/dummy/tmp/cache/assets/development/sprockets/c2d2edeabc3f4e1488bc3d2bc65aeeb5 +0 -0
  568. data/spec/dummy/tmp/cache/assets/development/sprockets/c31de5bdd33cb06b2f048832cc17bb33 +0 -0
  569. data/spec/dummy/tmp/cache/assets/development/sprockets/c392b9f02046ab5ac544b4b768d6c873 +0 -0
  570. data/spec/dummy/tmp/cache/assets/development/sprockets/c3f6d29f592a6debd961bf7090260a63 +0 -0
  571. data/spec/dummy/tmp/cache/assets/development/sprockets/c40f5cd3096b72ead5fbc1bbd3fae161 +0 -0
  572. data/spec/dummy/tmp/cache/assets/development/sprockets/c60c7de98dc1644323fe3e1042b9b6f7 +0 -0
  573. data/spec/dummy/tmp/cache/assets/development/sprockets/c99a79f0817c71898237ba09de527dee +0 -0
  574. data/spec/dummy/tmp/cache/assets/development/sprockets/c9a9d61d59425108b4b277185175e2a8 +0 -0
  575. data/spec/dummy/tmp/cache/assets/development/sprockets/c9b91475983f0835ba45ca2fe35d240d +0 -0
  576. data/spec/dummy/tmp/cache/assets/development/sprockets/c9e64896be951cc49773ac83696255d1 +0 -0
  577. data/spec/dummy/tmp/cache/assets/development/sprockets/ca142e718b036301f24dac4e3275e9b6 +0 -0
  578. data/spec/dummy/tmp/cache/assets/development/sprockets/caf60d972f82c0ee76b650f65b8fdcb1 +0 -0
  579. data/spec/dummy/tmp/cache/assets/development/sprockets/cbc2f7b8866f0684266e6ad2e7a30352 +0 -0
  580. data/spec/dummy/tmp/cache/assets/development/sprockets/cbd483d17ac0184f48a8509c18f979b7 +0 -0
  581. data/spec/dummy/tmp/cache/assets/development/sprockets/cc721a188ab3d7e83b31a1f04b713426 +0 -0
  582. data/spec/dummy/tmp/cache/assets/development/sprockets/cc914eaa2dce30f7d476190bd890061e +0 -0
  583. data/spec/dummy/tmp/cache/assets/development/sprockets/ce6b662fdbe7682b43ddc78b3b9cad70 +0 -0
  584. data/spec/dummy/tmp/cache/assets/development/sprockets/ce9ed001e3d4f0735c213877e8a81df2 +0 -0
  585. data/spec/dummy/tmp/cache/assets/development/sprockets/ceaf1a9beac2d5eeb6d34f1ec3b4ba18 +0 -0
  586. data/spec/dummy/tmp/cache/assets/development/sprockets/cee893d2db4350453a50c4e9ca1d232f +0 -0
  587. data/spec/dummy/tmp/cache/assets/development/sprockets/cf6435acb846ab2d7c5fa87f58813ebd +0 -0
  588. data/spec/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  589. data/spec/dummy/tmp/cache/assets/development/sprockets/d065c115c2649e84ceda18a5424c6a19 +0 -0
  590. data/spec/dummy/tmp/cache/assets/development/sprockets/d07e71e2ceaa8385c55f2d3b8fbf07b3 +0 -0
  591. data/spec/dummy/tmp/cache/assets/development/sprockets/d152ac74ddbbe9d32933a12ddfdde296 +0 -0
  592. data/spec/dummy/tmp/cache/assets/development/sprockets/d169f1441cbbc3af7e89e80362df3e76 +0 -0
  593. data/spec/dummy/tmp/cache/assets/development/sprockets/d1b84034e0429b81de91ed8fc1aaecaf +0 -0
  594. data/spec/dummy/tmp/cache/assets/development/sprockets/d253accae3b846232602bc6c995368f9 +0 -0
  595. data/spec/dummy/tmp/cache/assets/development/sprockets/d3104281c8b0826ddc1ac16ebcabf6d5 +0 -0
  596. data/spec/dummy/tmp/cache/assets/development/sprockets/d3f00f999f8970016ac14a517904a010 +0 -0
  597. data/spec/dummy/tmp/cache/assets/development/sprockets/d41a28273f01863d778343769f561a15 +0 -0
  598. data/spec/dummy/tmp/cache/assets/development/sprockets/d46338cdeab8149426f52310289fce9f +0 -0
  599. data/spec/dummy/tmp/cache/assets/development/sprockets/d51f7e1dd8a079a07dca6b54555b0619 +0 -0
  600. data/spec/dummy/tmp/cache/assets/development/sprockets/d7168a34b2c50ddfedd98542037fa67e +0 -0
  601. data/spec/dummy/tmp/cache/assets/development/sprockets/d72b5c6c396050709ed7a61795624bcf +0 -0
  602. data/spec/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  603. data/spec/dummy/tmp/cache/assets/development/sprockets/d8d0ad6c27cfbaf3dc08f4b89c943aee +0 -0
  604. data/spec/dummy/tmp/cache/assets/development/sprockets/da00d054a700ca1243a14fe7ed80f957 +0 -0
  605. data/spec/dummy/tmp/cache/assets/development/sprockets/db5a3f247b11ff272a640ca13b7e4282 +0 -0
  606. data/spec/dummy/tmp/cache/assets/development/sprockets/dbe16788dcd2defa93482f68099b0d93 +0 -0
  607. data/spec/dummy/tmp/cache/assets/development/sprockets/dce3bb78b49b624e0ef121de61c90eb5 +0 -0
  608. data/spec/dummy/tmp/cache/assets/development/sprockets/dcf33d27a8bc879ea6dc1460a6896572 +0 -0
  609. data/spec/dummy/tmp/cache/assets/development/sprockets/dd4661c809be283f24af745cd471fef2 +0 -0
  610. data/spec/dummy/tmp/cache/assets/development/sprockets/ddd024b41556433726d54ab75efc4cf6 +0 -0
  611. data/spec/dummy/tmp/cache/assets/development/sprockets/de76378a179dbe23a8b6938fecaeaead +0 -0
  612. data/spec/dummy/tmp/cache/assets/development/sprockets/dec6c0affea84dfd7c9509d3b82cb67a +0 -0
  613. data/spec/dummy/tmp/cache/assets/development/sprockets/df9f17599b8555c7e5f33f3096d0908d +0 -0
  614. data/spec/dummy/tmp/cache/assets/development/sprockets/e0b8689ae5bba59b9f14acd1f9f2e2e1 +0 -0
  615. data/spec/dummy/tmp/cache/assets/development/sprockets/e1b60cab102fc77a9250c3cec9005f2f +0 -0
  616. data/spec/dummy/tmp/cache/assets/development/sprockets/e20b05c5de673581bbaa330f6f5a8e26 +0 -0
  617. data/spec/dummy/tmp/cache/assets/development/sprockets/e2bf8a1dcf48c25b3c508c348b77d073 +0 -0
  618. data/spec/dummy/tmp/cache/assets/development/sprockets/e33f8e486061b22bb4e64bbda4fd264e +0 -0
  619. data/spec/dummy/tmp/cache/assets/development/sprockets/e395be2c63ec3c4d214bbaf44fb03973 +0 -0
  620. data/spec/dummy/tmp/cache/assets/development/sprockets/e5357bc5a31b7a2fb6e078aaaa4d913b +0 -0
  621. data/spec/dummy/tmp/cache/assets/development/sprockets/e5c14e1961152bd7edd7743b7d623416 +0 -0
  622. data/spec/dummy/tmp/cache/assets/development/sprockets/e6433186bf62e32f40d2aa957885e12b +0 -0
  623. data/spec/dummy/tmp/cache/assets/development/sprockets/e647b5d9467ba8f6bea35247e2c08f38 +0 -0
  624. data/spec/dummy/tmp/cache/assets/development/sprockets/e7a0f449e890f16fe0114f946c3c2e23 +0 -0
  625. data/spec/dummy/tmp/cache/assets/development/sprockets/e9512b851cc002cb6564586e198bff22 +0 -0
  626. data/spec/dummy/tmp/cache/assets/development/sprockets/ea53643fc53873e31950c304a2dd81bf +0 -0
  627. data/spec/dummy/tmp/cache/assets/development/sprockets/ea7dd58d64f762e53736715fbef1379d +0 -0
  628. data/spec/dummy/tmp/cache/assets/development/sprockets/eaad17bfad25bffeaa285383f55b6683 +0 -0
  629. data/spec/dummy/tmp/cache/assets/development/sprockets/eb6ce605b7a9878f16ce34a3d47043a3 +0 -0
  630. data/spec/dummy/tmp/cache/assets/development/sprockets/eb757c948334f75d17bbf23e8c48ef43 +0 -0
  631. data/spec/dummy/tmp/cache/assets/development/sprockets/ebea8f383de1fdfd76ff7f1095ab95ec +0 -0
  632. data/spec/dummy/tmp/cache/assets/development/sprockets/ec65ed1571852474143593894f051fda +0 -0
  633. data/spec/dummy/tmp/cache/assets/development/sprockets/ee379911da706e69963a52263eab4755 +0 -0
  634. data/spec/dummy/tmp/cache/assets/development/sprockets/f0c46205ec11714608a5716a6b6867df +0 -0
  635. data/spec/dummy/tmp/cache/assets/development/sprockets/f1161e3366336f110844aee9ed699af6 +0 -0
  636. data/spec/dummy/tmp/cache/assets/development/sprockets/f12044c9db79b466f15257e01c41932b +0 -0
  637. data/spec/dummy/tmp/cache/assets/development/sprockets/f13ba0b59341c6ffd42d680c6d6a57af +0 -0
  638. data/spec/dummy/tmp/cache/assets/development/sprockets/f3c8f45ef41694b747be31b658829a4a +0 -0
  639. data/spec/dummy/tmp/cache/assets/development/sprockets/f3dd8a99ff713f718a8a2b197d5dd469 +0 -0
  640. data/spec/dummy/tmp/cache/assets/development/sprockets/f471c0922a75832dec078946cb0a1086 +0 -0
  641. data/spec/dummy/tmp/cache/assets/development/sprockets/f54740d21191b3939d47b6e87b5d6ef2 +0 -0
  642. data/spec/dummy/tmp/cache/assets/development/sprockets/f566a6d534c7e8e6f4cd1e5ca907df2c +0 -0
  643. data/spec/dummy/tmp/cache/assets/development/sprockets/f5f4f0999132979f9f325a78e0a6fd47 +0 -0
  644. data/spec/dummy/tmp/cache/assets/development/sprockets/f61dc0dc55f73dca2c580f5914074a9a +0 -0
  645. data/spec/dummy/tmp/cache/assets/development/sprockets/f6dc9afc56ceced12c16a1f7022d6623 +0 -0
  646. data/spec/dummy/tmp/cache/assets/development/sprockets/f702a7d63bc9bbaa4b9da177d4220dfb +0 -0
  647. data/spec/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  648. data/spec/dummy/tmp/cache/assets/development/sprockets/f7f67c6fdf257bb9b675445ad3ca2aca +0 -0
  649. data/spec/dummy/tmp/cache/assets/development/sprockets/fb942ec0bc8d87080136d1eb2abcfa33 +0 -0
  650. data/spec/dummy/tmp/cache/assets/development/sprockets/fbb623d8d4700f3e7eccae88700e5e73 +0 -0
  651. data/spec/dummy/tmp/cache/assets/development/sprockets/fbee64fb1d31d4687a8068c38ff0889e +0 -0
  652. data/spec/dummy/tmp/cache/assets/development/sprockets/fbfcf6fdc335e01d8f7f0c7a26e9a9fb +0 -0
  653. data/spec/dummy/tmp/cache/assets/development/sprockets/fc33f62b017026ea2cb55de4fe0786c5 +0 -0
  654. data/spec/dummy/tmp/cache/assets/development/sprockets/fd5afb63c8690c4e0df71ff9498d049f +0 -0
  655. data/spec/dummy/tmp/cache/assets/development/sprockets/fdb60665342a3a54a59aa9b7b77a135b +0 -0
  656. data/spec/dummy/tmp/cache/assets/development/sprockets/fdc31d89f3b051d66029a28b59cc7e63 +0 -0
  657. data/spec/dummy/tmp/cache/assets/development/sprockets/fe0dc53c09ac52e5b9255086e0ffb328 +0 -0
  658. data/spec/dummy/tmp/cache/assets/development/sprockets/fe1e7428179101b62bdf21409049badd +0 -0
  659. data/spec/dummy/tmp/cache/assets/development/sprockets/fee99bdbec260ac672198e956afd0698 +0 -0
  660. data/spec/dummy/tmp/cache/i18n-js.yml +36 -0
  661. data/spec/models/roroacms/admin_spec.rb +67 -0
  662. data/spec/models/roroacms/comment_spec.rb +76 -0
  663. data/spec/models/roroacms/menu_spec.rb +17 -0
  664. data/spec/models/roroacms/post_spec.rb +121 -0
  665. data/spec/models/roroacms/setting_spec.rb +66 -0
  666. data/spec/models/roroacms/term_spec.rb +67 -0
  667. data/spec/models/roroacms/trash_spec.rb +31 -0
  668. data/spec/requests/roroacms/admin/administration_spec.rb +71 -0
  669. data/spec/requests/roroacms/admin/article_spec.rb +113 -0
  670. data/spec/requests/roroacms/admin/comment_spec.rb +85 -0
  671. data/spec/requests/roroacms/admin/dashboard_spec.rb +55 -0
  672. data/spec/requests/roroacms/admin/menu_spec.rb +67 -0
  673. data/spec/requests/roroacms/admin/page_spec.rb +102 -0
  674. data/spec/requests/roroacms/admin/revision_spec.rb +41 -0
  675. data/spec/requests/roroacms/admin/setting_spec.rb +113 -0
  676. data/spec/requests/roroacms/admin/term_spec.rb +194 -0
  677. data/spec/requests/roroacms/admin/theme_spec.rb +33 -0
  678. data/spec/requests/roroacms/admin/trash_spec.rb +70 -0
  679. data/spec/requests/roroacms/pages_spec.rb +28 -0
  680. data/spec/spec_helper.rb +36 -0
  681. data/spec/support/devise.rb +3 -0
  682. data/spec/support/factories.rb +79 -0
  683. data/spec/support/selectors.rb +5 -0
  684. data/spec/support/utilities.rb +6 -0
  685. metadata +1027 -301
@@ -1,2409 +1,2409 @@
1
- (function(context, namespace) {
2
- var Hopscotch,
3
- HopscotchBubble,
4
- HopscotchCalloutManager,
5
- HopscotchI18N,
6
- customI18N,
7
- customRenderer,
8
- customEscape,
9
- templateToUse = 'bubble_default',
10
- Sizzle = window.Sizzle || null,
11
- utils,
12
- callbacks,
13
- helpers,
14
- winLoadHandler,
15
- defaultOpts,
16
- winHopscotch = context[namespace],
17
- undefinedStr = 'undefined',
18
- waitingToStart = false, // is a tour waiting for the document to finish
19
- // loading so that it can start?
20
- hasJquery = (typeof window.jQuery !== undefinedStr),
21
- hasSessionStorage = false,
22
- document = window.document;
23
-
24
- // If cookies are disabled, accessing sessionStorage can throw an error.
25
- try {
26
- hasSessionStorage = (typeof window.sessionStorage !== undefinedStr);
27
- } catch (err) {}
28
-
29
- defaultOpts = {
30
- smoothScroll: true,
31
- scrollDuration: 1000,
32
- scrollTopMargin: 200,
33
- showCloseButton: true,
34
- showPrevButton: false,
35
- showNextButton: true,
36
- bubbleWidth: 280,
37
- bubblePadding: 15,
38
- arrowWidth: 20,
39
- skipIfNoElement: true,
40
- cookieName: 'hopscotch.tour.state',
41
- highlight: false,
42
- highlightMargin: 0
43
- };
44
-
45
- if (winHopscotch) {
46
- // Hopscotch already exists.
47
- return;
48
- }
49
-
50
- if (!Array.isArray) {
51
- Array.isArray = function(obj) {
52
- return Object.prototype.toString.call(obj) === '[object Array]';
53
- };
54
- }
55
-
56
- /**
57
- * Called when the page is done loading.
58
- *
59
- * @private
60
- */
61
- winLoadHandler = function() {
62
- if (waitingToStart) {
63
- winHopscotch.startTour();
64
- }
65
- };
66
-
67
- /**
68
- * utils
69
- * =====
70
- * A set of utility functions, mostly for standardizing to manipulate
71
- * and extract information from the DOM. Basically these are things I
72
- * would normally use jQuery for, but I don't want to require it for
73
- * this framework.
74
- *
75
- * @private
76
- */
77
- utils = {
78
- /**
79
- * addClass
80
- * ========
81
- * Adds one or more classes to a DOM element.
82
- *
83
- * @private
84
- */
85
- addClass: function(domEl, classToAdd) {
86
- var domClasses,
87
- classToAddArr,
88
- setClass,
89
- i,
90
- len;
91
-
92
- if (!domEl.className) {
93
- domEl.className = classToAdd;
94
- }
95
- else {
96
- classToAddArr = classToAdd.split(/\s+/);
97
- domClasses = ' ' + domEl.className + ' ';
98
- for (i = 0, len = classToAddArr.length; i < len; ++i) {
99
- if (domClasses.indexOf(' ' + classToAddArr[i] + ' ') < 0) {
100
- domClasses += classToAddArr[i] + ' ';
101
- }
102
- }
103
- domEl.className = domClasses.replace(/^\s+|\s+$/g,'');
104
- }
105
- },
106
-
107
- /**
108
- * removeClass
109
- * ===========
110
- * Remove one or more classes from a DOM element.
111
- *
112
- * @private
113
- */
114
- removeClass: function(domEl, classToRemove) {
115
- var domClasses,
116
- classToRemoveArr,
117
- currClass,
118
- i,
119
- len;
120
-
121
- classToRemoveArr = classToRemove.split(/\s+/);
122
- domClasses = ' ' + domEl.className + ' ';
123
- for (i = 0, len = classToRemoveArr.length; i < len; ++i) {
124
- domClasses = domClasses.replace(' ' + classToRemoveArr[i] + ' ', ' ');
125
- }
126
- domEl.className = domClasses.replace(/^\s+|\s+$/g,'');
127
- },
128
-
129
- /**
130
- * hasClass
131
- * ========
132
- * Determine if a given DOM element has a class.
133
- */
134
- hasClass: function(domEl, classToCheck){
135
- var classes;
136
-
137
- if(!domEl.className){ return false; }
138
- classes = ' ' + domEl.className + ' ';
139
- return (classes.indexOf(' ' + classToCheck + ' ') !== -1);
140
- },
141
-
142
- /**
143
- * @private
144
- */
145
- getPixelValue: function(val) {
146
- var valType = typeof val;
147
- if (valType === 'number') { return val; }
148
- if (valType === 'string') { return parseInt(val, 10); }
149
- return 0;
150
- },
151
-
152
- /**
153
- * Inspired by Python... returns val if it's defined, otherwise returns the default.
154
- *
155
- * @private
156
- */
157
- valOrDefault: function(val, valDefault) {
158
- return typeof val !== undefinedStr ? val : valDefault;
159
- },
160
-
161
- /**
162
- * Invokes a single callback represented by an array.
163
- * Example input: ["my_fn", "arg1", 2, "arg3"]
164
- * @private
165
- */
166
- invokeCallbackArrayHelper: function(arr) {
167
- // Logic for a single callback
168
- var fn;
169
- if (Array.isArray(arr)) {
170
- fn = helpers[arr[0]];
171
- if (typeof fn === 'function') {
172
- return fn.apply(this, arr.slice(1));
173
- }
174
- }
175
- },
176
-
177
- /**
178
- * Invokes one or more callbacks. Array should have at most one level of nesting.
179
- * Example input:
180
- * ["my_fn", "arg1", 2, "arg3"]
181
- * [["my_fn_1", "arg1", "arg2"], ["my_fn_2", "arg2-1", "arg2-2"]]
182
- * [["my_fn_1", "arg1", "arg2"], function() { ... }]
183
- * @private
184
- */
185
- invokeCallbackArray: function(arr) {
186
- var i, len;
187
-
188
- if (Array.isArray(arr)) {
189
- if (typeof arr[0] === 'string') {
190
- // Assume there are no nested arrays. This is the one and only callback.
191
- return utils.invokeCallbackArrayHelper(arr);
192
- }
193
- else { // assume an array
194
- for (i = 0, len = arr.length; i < len; ++i) {
195
- utils.invokeCallback(arr[i]);
196
- }
197
- }
198
- }
199
- },
200
-
201
- /**
202
- * Helper function for invoking a callback, whether defined as a function literal
203
- * or an array that references a registered helper function.
204
- * @private
205
- */
206
- invokeCallback: function(cb) {
207
- if (typeof cb === 'function') {
208
- return cb();
209
- }
210
- if (typeof cb === 'string' && helpers[cb]) { // name of a helper
211
- return helpers[cb]();
212
- }
213
- else { // assuming array
214
- return utils.invokeCallbackArray(cb);
215
- }
216
- },
217
-
218
- /**
219
- * If stepCb (the step-specific helper callback) is passed in, then invoke
220
- * it first. Then invoke tour-wide helper.
221
- *
222
- * @private
223
- */
224
- invokeEventCallbacks: function(evtType, stepCb) {
225
- var cbArr = callbacks[evtType],
226
- callback,
227
- fn,
228
- i,
229
- len;
230
-
231
- if (stepCb) {
232
- return this.invokeCallback(stepCb);
233
- }
234
-
235
- for (i=0, len=cbArr.length; i<len; ++i) {
236
- this.invokeCallback(cbArr[i].cb);
237
- }
238
- },
239
-
240
- /**
241
- * @private
242
- */
243
- getScrollTop: function() {
244
- var scrollTop;
245
- if (typeof window.pageYOffset !== undefinedStr) {
246
- scrollTop = window.pageYOffset;
247
- }
248
- else {
249
- // Most likely IE <=8, which doesn't support pageYOffset
250
- scrollTop = document.documentElement.scrollTop;
251
- }
252
- return scrollTop;
253
- },
254
-
255
- /**
256
- * @private
257
- */
258
- getScrollLeft: function() {
259
- var scrollLeft;
260
- if (typeof window.pageXOffset !== undefinedStr) {
261
- scrollLeft = window.pageXOffset;
262
- }
263
- else {
264
- // Most likely IE <=8, which doesn't support pageXOffset
265
- scrollLeft = document.documentElement.scrollLeft;
266
- }
267
- return scrollLeft;
268
- },
269
-
270
- /**
271
- * @private
272
- */
273
- getWindowHeight: function() {
274
- return window.innerHeight || document.documentElement.clientHeight;
275
- },
276
-
277
- /**
278
- * @private
279
- */
280
- getWindowWidth: function() {
281
- return window.innerWidth || document.documentElement.clientWidth;
282
- },
283
-
284
- /**
285
- * @private
286
- */
287
- addEvtListener: function(el, evtName, fn) {
288
- return el.addEventListener ? el.addEventListener(evtName, fn, false) : el.attachEvent('on' + evtName, fn);
289
- },
290
-
291
- /**
292
- * @private
293
- */
294
- removeEvtListener: function(el, evtName, fn) {
295
- return el.removeEventListener ? el.removeEventListener(evtName, fn, false) : el.detachEvent('on' + evtName, fn);
296
- },
297
-
298
- documentIsReady: function() {
299
- return document.readyState === 'complete' || document.readyState === 'interactive';
300
- },
301
-
302
- /**
303
- * @private
304
- */
305
- evtPreventDefault: function(evt) {
306
- if (evt.preventDefault) {
307
- evt.preventDefault();
308
- }
309
- else if (event) {
310
- event.returnValue = false;
311
- }
312
- },
313
-
314
- /**
315
- * @private
316
- */
317
- extend: function(obj1, obj2) {
318
- var prop;
319
- for (prop in obj2) {
320
- if (obj2.hasOwnProperty(prop)) {
321
- obj1[prop] = obj2[prop];
322
- }
323
- }
324
- },
325
-
326
- /**
327
- * Helper function to get a single target DOM element. We will try to
328
- * locate the DOM element through several ways, in the following order:
329
- *
330
- * 1) Passing the string into document.querySelector
331
- * 2) Passing the string to jQuery, if it exists
332
- * 3) Passing the string to Sizzle, if it exists
333
- * 4) Calling document.getElementById if it is a plain id
334
- *
335
- * Default case is to assume the string is a plain id and call
336
- * document.getElementById on it.
337
- *
338
- * @private
339
- */
340
- getStepTargetHelper: function(target){
341
- var result = document.getElementById(target);
342
-
343
- //Backwards compatibility: assume the string is an id
344
- if (result) {
345
- return result;
346
- }
347
- if (document.querySelector) {
348
- return document.querySelector(target);
349
- }
350
- if (hasJquery) {
351
- result = jQuery(target);
352
- return result.length ? result[0] : null;
353
- }
354
- if (Sizzle) {
355
- result = new Sizzle(target);
356
- return result.length ? result[0] : null;
357
- }
358
- // Regex test for id. Following the HTML 4 spec for valid id formats.
359
- // (http://www.w3.org/TR/html4/types.html#type-id)
360
- if (/^#[a-zA-Z][\w-_:.]*$/.test(target)) {
361
- return document.getElementById(target.substring(1));
362
- }
363
-
364
- return null;
365
- },
366
-
367
- /**
368
- * Given a step, returns the target DOM element associated with it. It is
369
- * recommended to only assign one target per step. However, there are
370
- * some use cases which require multiple step targets to be supplied. In
371
- * this event, we will use the first target in the array that we can
372
- * locate on the page. See the comments for getStepTargetHelper for more
373
- * information.
374
- *
375
- * @private
376
- */
377
- getStepTarget: function(step) {
378
- var queriedTarget;
379
-
380
- if (!step || !step.target) {
381
- return null;
382
- }
383
-
384
- if (typeof step.target === 'string') {
385
- //Just one target to test. Check, cache, and return its results.
386
- step.target = utils.getStepTargetHelper(step.target);
387
- return step.target;
388
- }
389
- else if (Array.isArray(step.target)) {
390
- // Multiple items to check. Check each and return the first success.
391
- // Assuming they are all strings.
392
- var i,
393
- len;
394
-
395
- for (i = 0, len = step.target.length; i < len; i++){
396
- if (typeof step.target[i] === 'string') {
397
- queriedTarget = utils.getStepTargetHelper(step.target[i]);
398
-
399
- if (queriedTarget) {
400
- // Replace step.target with result so we don't have to look it up again.
401
- step.target = queriedTarget;
402
- return queriedTarget;
403
- }
404
- }
405
- }
406
- return null;
407
- }
408
-
409
- // Assume that the step.target is a DOM element
410
- return step.target;
411
- },
412
-
413
- /**
414
- * Convenience method for getting an i18n string. Returns custom i18n value
415
- * or the default i18n value if no custom value exists.
416
- *
417
- * @private
418
- */
419
- getI18NString: function(key) {
420
- return customI18N[key] || HopscotchI18N[key];
421
- },
422
-
423
- // Tour session persistence for multi-page tours. Uses HTML5 sessionStorage if available, then
424
- // falls back to using cookies.
425
- //
426
- // The following cookie-related logic is borrowed from:
427
- // http://www.quirksmode.org/js/cookies.html
428
-
429
- /**
430
- * @private
431
- */
432
- setState: function(name,value,days) {
433
- var expires = '',
434
- date;
435
-
436
- if (hasSessionStorage) {
437
- sessionStorage.setItem(name, value);
438
- }
439
- else {
440
- if (days) {
441
- date = new Date();
442
- date.setTime(date.getTime()+(days*24*60*60*1000));
443
- expires = '; expires='+date.toGMTString();
444
- }
445
- document.cookie = name+'='+value+expires+'; path=/';
446
- }
447
- },
448
-
449
- /**
450
- * @private
451
- */
452
- getState: function(name) {
453
- var nameEQ = name + '=',
454
- ca = document.cookie.split(';'),
455
- i,
456
- c,
457
- state;
458
-
459
- if (hasSessionStorage) {
460
- state = sessionStorage.getItem(name);
461
- }
462
- else {
463
- for(i=0;i < ca.length;i++) {
464
- c = ca[i];
465
- while (c.charAt(0)===' ') {c = c.substring(1,c.length);}
466
- if (c.indexOf(nameEQ) === 0) {
467
- state = c.substring(nameEQ.length,c.length);
468
- break;
469
- }
470
- }
471
- }
472
-
473
- return state;
474
- },
475
-
476
- /**
477
- * @private
478
- */
479
- clearState: function(name) {
480
- if (hasSessionStorage) {
481
- sessionStorage.removeItem(name);
482
- }
483
- else {
484
- this.setState(name,'',-1);
485
- }
486
- }
487
- };
488
-
489
- utils.addEvtListener(window, 'load', winLoadHandler);
490
-
491
- callbacks = {
492
- next: [],
493
- prev: [],
494
- start: [],
495
- end: [],
496
- show: [],
497
- error: [],
498
- close: []
499
- };
500
-
501
- /**
502
- * helpers
503
- * =======
504
- * A map of functions to be used as callback listeners. Functions are
505
- * added to and removed from the map using the functions
506
- * Hopscotch.registerHelper() and Hopscotch.unregisterHelper().
507
- */
508
- helpers = {};
509
-
510
- HopscotchI18N = {
511
- stepNums: null,
512
- nextBtn: 'Next',
513
- prevBtn: 'Back',
514
- doneBtn: 'Done',
515
- skipBtn: 'Skip',
516
- closeTooltip: 'Close'
517
- };
518
-
519
- customI18N = {}; // Developer's custom i18n strings goes here.
520
-
521
- /**
522
- * HopscotchBubble
523
- *
524
- * @class The HopscotchBubble class represents the view of a bubble. This class is also used for Hopscotch callouts.
525
- */
526
- HopscotchBubble = function(opt) {
527
- this.init(opt);
528
- };
529
-
530
- HopscotchBubble.prototype = {
531
- isShowing: false,
532
-
533
- currStep: undefined,
534
-
535
- /**
536
- * setPosition
537
- *
538
- * Sets the position of the bubble using the bounding rectangle of the
539
- * target element and the orientation and offset information specified by
540
- * the JSON.
541
- */
542
- setPosition: function(step) {
543
- var bubbleBoundingHeight,
544
- bubbleBoundingWidth,
545
- boundingRect,
546
- top,
547
- left,
548
- arrowOffset,
549
- self = this,
550
- targetEl = utils.getStepTarget(step),
551
- el = this.element,
552
- arrowEl = this.arrowEl;
553
-
554
- bubbleBoundingWidth = el.offsetWidth;
555
- bubbleBoundingHeight = el.offsetHeight;
556
- utils.removeClass(el, 'fade-in-down fade-in-up fade-in-left fade-in-right');
557
-
558
- // Originally called it orientation, but placement is more intuitive.
559
- // Allowing both for now for backwards compatibility.
560
- if (!step.placement && step.orientation) {
561
- step.placement = step.orientation;
562
- }
563
-
564
- // SET POSITION
565
- boundingRect = targetEl.getBoundingClientRect();
566
- if (step.placement === 'top') {
567
- top = (boundingRect.top - bubbleBoundingHeight) - this.opt.arrowWidth;
568
- left = boundingRect.left;
569
- }
570
- else if (step.placement === 'bottom') {
571
- top = boundingRect.bottom + this.opt.arrowWidth;
572
- left = boundingRect.left;
573
- }
574
- else if (step.placement === 'left') {
575
- top = boundingRect.top;
576
- left = boundingRect.left - bubbleBoundingWidth - this.opt.arrowWidth;
577
- }
578
- else if (step.placement === 'right') {
579
- top = boundingRect.top;
580
- left = boundingRect.right + this.opt.arrowWidth;
581
- }
582
- else {
583
- throw 'Bubble placement failed because step.placement is invalid or undefined!';
584
- }
585
-
586
- // UPDATE HIGHLIGHT
587
- self.highlight.setPosition(step, boundingRect);
588
-
589
-
590
- // SET (OR RESET) ARROW OFFSETS
591
- if (step.arrowOffset !== 'center') {
592
- arrowOffset = utils.getPixelValue(step.arrowOffset);
593
- }
594
- else {
595
- arrowOffset = step.arrowOffset;
596
- }
597
- if (!arrowOffset) {
598
- arrowEl.style.top = '';
599
- arrowEl.style.left = '';
600
- }
601
- else if (step.placement === 'top' || step.placement === 'bottom') {
602
- arrowEl.style.top = '';
603
- if (arrowOffset === 'center') {
604
- arrowEl.style.left = Math.floor((bubbleBoundingWidth / 2) - arrowEl.offsetWidth/2) + 'px';
605
- }
606
- else {
607
- // Numeric pixel value
608
- arrowEl.style.left = arrowOffset + 'px';
609
- }
610
- }
611
- else if (step.placement === 'left' || step.placement === 'right') {
612
- arrowEl.style.left = '';
613
- if (arrowOffset === 'center') {
614
- arrowEl.style.top = Math.floor((bubbleBoundingHeight / 2) - arrowEl.offsetHeight/2) + 'px';
615
- }
616
- else {
617
- // Numeric pixel value
618
- arrowEl.style.top = arrowOffset + 'px';
619
- }
620
- }
621
-
622
- // HORIZONTAL OFFSET
623
- if (step.xOffset === 'center') {
624
- left = (boundingRect.left + targetEl.offsetWidth/2) - (bubbleBoundingWidth / 2);
625
- }
626
- else {
627
- left += utils.getPixelValue(step.xOffset);
628
- }
629
- // VERTICAL OFFSET
630
- if (step.yOffset === 'center') {
631
- top = (boundingRect.top + targetEl.offsetHeight/2) - (bubbleBoundingHeight / 2);
632
- }
633
- else {
634
- top += utils.getPixelValue(step.yOffset);
635
- }
636
-
637
- // ADJUST TOP FOR SCROLL POSITION
638
- if (!step.fixedElement) {
639
- top += utils.getScrollTop();
640
- left += utils.getScrollLeft();
641
- }
642
-
643
- // ACCOUNT FOR FIXED POSITION ELEMENTS
644
- el.style.position = (step.fixedElement ? 'fixed' : 'absolute');
645
-
646
- el.style.top = top + 'px';
647
- el.style.left = left + 'px';
648
- },
649
-
650
- /**
651
- * Renders the bubble according to the step JSON.
652
- *
653
- * @param {Object} step Information defining how the bubble should look.
654
- * @param {Number} idx The index of the step in the tour. Not used for callouts.
655
- * @param {Function} callback Function to be invoked after rendering is finished.
656
- */
657
- render: function(step, idx, callback) {
658
- var el = this.element,
659
- tourSpecificRenderer,
660
- customTourData,
661
- unsafe,
662
- currTour,
663
- totalSteps,
664
- nextBtnText,
665
- isLast,
666
- opts;
667
-
668
- // Cache current step information.
669
- if (step) {
670
- this.currStep = step;
671
- }
672
- else if (this.currStep) {
673
- step = this.currStep;
674
- }
675
-
676
- // update highlight with current step information
677
- this.highlight.render(step);
678
-
679
- // Check current tour for total number of steps and custom render data
680
- if(this.opt.isTourBubble){
681
- currTour = winHopscotch.getCurrTour();
682
- if(currTour){
683
- customTourData = currTour.customData;
684
- tourSpecificRenderer = currTour.customRenderer;
685
- unsafe = currTour.unsafe;
686
- if(Array.isArray(currTour.steps)){
687
- totalSteps = currTour.steps.length;
688
- isLast = (idx === totalSteps - 1);
689
- }
690
- }
691
- }else{
692
- customTourData = step.customData;
693
- tourSpecificRenderer = step.customRenderer;
694
- unsafe = step.unsafe;
695
- }
696
-
697
- // Determine label for next button
698
- if(isLast){
699
- nextBtnText = utils.getI18NString('doneBtn');
700
- } else if(step.showSkip) {
701
- nextBtnText = utils.getI18NString('skipBtn');
702
- } else {
703
- nextBtnText = utils.getI18NString('nextBtn');
704
- }
705
-
706
- // Originally called it orientation, but placement is more intuitive.
707
- // Allowing both for now for backwards compatibility.
708
- if (!step.placement && step.orientation) {
709
- step.placement = step.orientation;
710
- }
711
- this.placement = step.placement;
712
-
713
- // Setup the configuration options we want to pass along to the template
714
- opts = {
715
- i18n: {
716
- prevBtn: utils.getI18NString('prevBtn'),
717
- nextBtn: nextBtnText,
718
- closeTooltip: utils.getI18NString('closeTooltip'),
719
- stepNum: this._getStepI18nNum(idx),
720
- },
721
- buttons:{
722
- showPrev: (utils.valOrDefault(step.showPrevButton, this.opt.showPrevButton) && (idx > 0)),
723
- showNext: utils.valOrDefault(step.showNextButton, this.opt.showNextButton),
724
- showCTA: utils.valOrDefault((step.showCTAButton && step.ctaLabel), false),
725
- ctaLabel: step.ctaLabel,
726
- showClose: utils.valOrDefault(this.opt.showCloseButton, true)
727
- },
728
- step:{
729
- num: idx,
730
- isLast: utils.valOrDefault(isLast, false),
731
- title: (step.title || ''),
732
- content: (step.content || ''),
733
- placement: step.placement,
734
- padding: utils.valOrDefault(step.padding, this.opt.bubblePadding),
735
- width: utils.getPixelValue(step.width) || this.opt.bubbleWidth,
736
- customData: (step.customData || {})
737
- },
738
- tour:{
739
- isTour: this.opt.isTourBubble,
740
- numSteps: totalSteps,
741
- unsafe: utils.valOrDefault(unsafe, false),
742
- customData: (customTourData || {})
743
- }
744
- };
745
-
746
- // Render the bubble's content.
747
- // Use tour renderer if available, then the global customRenderer if defined.
748
- if(typeof tourSpecificRenderer === 'function'){
749
- el.innerHTML = tourSpecificRenderer(opts);
750
- }
751
- else if(typeof tourSpecificRenderer === 'string'){
752
- if(!hopscotch.templates || (typeof hopscotch.templates[tourSpecificRenderer] !== 'function')){
753
- throw 'Bubble rendering failed - template "' + tourSpecificRenderer + '" is not a function.';
754
- }
755
- el.innerHTML = hopscotch.templates[tourSpecificRenderer](opts);
756
- }
757
- else if(customRenderer){
758
- el.innerHTML = customRenderer(opts);
759
- }
760
- else{
761
- if(!hopscotch.templates || (typeof hopscotch.templates[templateToUse] !== 'function')){
762
- throw 'Bubble rendering failed - template "' + templateToUse + '" is not a function.';
763
- }
764
- el.innerHTML = hopscotch.templates[templateToUse](opts);
765
- }
766
-
767
- // Find arrow among new child elements.
768
- children = el.children;
769
- numChildren = children.length;
770
- for (i = 0; i < numChildren; i++){
771
- node = children[i];
772
-
773
- if(utils.hasClass(node, 'hopscotch-arrow')){
774
- this.arrowEl = node;
775
- }
776
- }
777
-
778
- // Set z-index and arrow placement
779
- el.style.zIndex = step.zindex || '';
780
- this._setArrow(step.placement);
781
-
782
- // Set bubble positioning
783
- // Make sure we're using visibility:hidden instead of display:none for height/width calculations.
784
- this.hide(false);
785
- this.setPosition(step);
786
-
787
- // only want to adjust window scroll for non-fixed elements
788
- if (callback) {
789
- callback(!step.fixedElement);
790
- }
791
-
792
- return this;
793
- },
794
-
795
- /**
796
- * Get the I18N step number for the current step.
797
- *
798
- * @private
799
- */
800
- _getStepI18nNum: function(idx) {
801
- var stepNumI18N = utils.getI18NString('stepNums');
802
- if (stepNumI18N && idx < stepNumI18N.length) {
803
- idx = stepNumI18N[idx];
804
- }
805
- else {
806
- idx = idx + 1;
807
- }
808
- return idx;
809
- },
810
-
811
- /**
812
- * Sets which side the arrow is on.
813
- *
814
- * @private
815
- */
816
- _setArrow: function(orientation) {
817
- utils.removeClass(this.arrowEl, 'down up right left');
818
-
819
- // Whatever the orientation is, we want to arrow to appear
820
- // "opposite" of the orientation. E.g., a top orientation
821
- // requires a bottom arrow.
822
- if (orientation === 'top') {
823
- utils.addClass(this.arrowEl, 'down');
824
- }
825
- else if (orientation === 'bottom') {
826
- utils.addClass(this.arrowEl, 'up');
827
- }
828
- else if (orientation === 'left') {
829
- utils.addClass(this.arrowEl, 'right');
830
- }
831
- else if (orientation === 'right') {
832
- utils.addClass(this.arrowEl, 'left');
833
- }
834
- },
835
-
836
- /**
837
- * @private
838
- */
839
- _getArrowDirection: function() {
840
- if (this.placement === 'top') {
841
- return 'down';
842
- }
843
- if (this.placement === 'bottom') {
844
- return 'up';
845
- }
846
- if (this.placement === 'left') {
847
- return 'right';
848
- }
849
- if (this.placement === 'right') {
850
- return 'left';
851
- }
852
- },
853
-
854
- show: function() {
855
- var self = this,
856
- fadeClass = 'fade-in-' + this._getArrowDirection(),
857
- fadeDur = 1000;
858
-
859
- utils.removeClass(this.element, 'hide');
860
- utils.addClass(this.element, fadeClass);
861
- setTimeout(function() {
862
- utils.removeClass(self.element, 'invisible');
863
- }, 50);
864
- setTimeout(function() {
865
- utils.removeClass(self.element, fadeClass);
866
- }, fadeDur);
867
- this.isShowing = true;
868
- this.highlight.show();
869
-
870
- return this;
871
- },
872
-
873
- hide: function(remove) {
874
- var el = this.element;
875
-
876
- remove = utils.valOrDefault(remove, true);
877
- el.style.top = '';
878
- el.style.left = '';
879
-
880
- // display: none
881
- if (remove) {
882
- utils.addClass(el, 'hide');
883
- utils.removeClass(el, 'invisible');
884
- }
885
- // opacity: 0
886
- else {
887
- utils.removeClass(el, 'hide');
888
- utils.addClass(el, 'invisible');
889
- }
890
- utils.removeClass(el, 'animate fade-in-up fade-in-down fade-in-right fade-in-left');
891
- this.isShowing = false;
892
- this.highlight.hide();
893
-
894
- return this;
895
- },
896
-
897
- destroy: function() {
898
- var el = this.element;
899
-
900
- if (el) {
901
- el.parentNode.removeChild(el);
902
- }
903
- utils.removeEvtListener(el, 'click', this.clickCb);
904
- },
905
-
906
- _handleBubbleClick: function(evt){
907
- var action;
908
-
909
- //Recursively look up the parent tree until we find a match
910
- //with one of the classes we're looking for, or the triggering element.
911
- function findMatchRecur(el){
912
- /* We're going to make the assumption that we're not binding
913
- * multiple event classes to the same element.
914
- * (next + previous = wait... err... what?)
915
- *
916
- * In the odd event we end up with an element with multiple
917
- * possible matches, the following priority order is applied:
918
- * hopscotch-cta, hopscotch-next, hopscotch-prev, hopscotch-close
919
- */
920
- if(el === evt.currentTarget){ return null; }
921
- if(utils.hasClass(el, 'hopscotch-cta')){ return 'cta'; }
922
- if(utils.hasClass(el, 'hopscotch-next')){ return 'next'; }
923
- if(utils.hasClass(el, 'hopscotch-prev')){ return 'prev'; }
924
- if(utils.hasClass(el, 'hopscotch-close')){ return 'close'; }
925
- /*else*/ return findMatchRecur(el.parentElement);
926
- }
927
-
928
- action = findMatchRecur(evt.target);
929
-
930
- //Now that we know what action we should take, let's take it.
931
- if (action === 'cta'){
932
- if (!this.opt.isTourBubble) {
933
- // This is a callout. Close the callout when CTA is clicked.
934
- winHopscotch.getCalloutManager().removeCallout(this.currStep.id);
935
- }
936
- // Call onCTA callback if one is provided
937
- if (this.currStep.onCTA) {
938
- utils.invokeCallback(this.currStep.onCTA);
939
- }
940
- }
941
- else if (action === 'next'){
942
- winHopscotch.nextStep(true);
943
- }
944
- else if (action === 'prev'){
945
- winHopscotch.prevStep(true);
946
- }
947
- else if (action === 'close'){
948
- if (this.opt.isTourBubble){
949
- var currStepNum = winHopscotch.getCurrStepNum(),
950
- currTour = winHopscotch.getCurrTour(),
951
- doEndCallback = (currStepNum === currTour.steps.length-1);
952
-
953
- utils.invokeEventCallbacks('close');
954
-
955
- winHopscotch.endTour(true, doEndCallback);
956
- } else {
957
- if (this.opt.onClose) {
958
- utils.invokeCallback(this.opt.onClose);
959
- }
960
- if (this.opt.id && !this.opt.isTourBubble) {
961
- // Remove via the HopscotchCalloutManager.
962
- // removeCallout() calls HopscotchBubble.destroy internally.
963
- winHopscotch.getCalloutManager().removeCallout(this.opt.id);
964
- }
965
- else {
966
- this.destroy();
967
- }
968
- }
969
-
970
- utils.evtPreventDefault(evt);
971
- }
972
- //Otherwise, do nothing. We didn't click on anything relevant.
973
- },
974
-
975
- init: function(initOpt) {
976
- var el = document.createElement('div'),
977
- self = this,
978
- resizeCooldown = false, // for updating after window resize
979
- onWinResize,
980
- appendToBody,
981
- children,
982
- numChildren,
983
- node,
984
- i,
985
- opt;
986
-
987
- //Register DOM element for this bubble.
988
- this.element = el;
989
-
990
- //Merge bubble options with defaults.
991
- opt = {
992
- showPrevButton: defaultOpts.showPrevButton,
993
- showNextButton: defaultOpts.showNextButton,
994
- bubbleWidth: defaultOpts.bubbleWidth,
995
- bubblePadding: defaultOpts.bubblePadding,
996
- arrowWidth: defaultOpts.arrowWidth,
997
- showNumber: true,
998
- isTourBubble: true
999
- };
1000
- initOpt = (typeof initOpt === undefinedStr ? {} : initOpt);
1001
- utils.extend(opt, initOpt);
1002
- this.opt = opt;
1003
-
1004
- //Apply classes to bubble. Add "animated" for fade css animation
1005
- el.className = 'hopscotch-bubble animated';
1006
- if (!opt.isTourBubble) {
1007
- utils.addClass(el, 'hopscotch-callout no-number');
1008
- }
1009
-
1010
- self.highlight = new HopscotchHighlight(initOpt);
1011
-
1012
- /**
1013
- * Not pretty, but IE8 doesn't support Function.bind(), so I'm
1014
- * relying on closures to keep a handle of "this".
1015
- * Reset position of bubble when window is resized
1016
- *
1017
- * @private
1018
- */
1019
- onWinResize = function() {
1020
- if (resizeCooldown || !self.isShowing) {
1021
- return;
1022
- }
1023
-
1024
- resizeCooldown = true;
1025
- setTimeout(function() {
1026
- self.setPosition(self.currStep);
1027
- resizeCooldown = false;
1028
- }, 100);
1029
- };
1030
-
1031
- //Add listener to reset bubble position on window resize
1032
- utils.addEvtListener(window, 'resize', onWinResize);
1033
-
1034
- //Create our click callback handler and keep a
1035
- //reference to it for later.
1036
- this.clickCb = function(evt){
1037
- self._handleBubbleClick(evt);
1038
- };
1039
- utils.addEvtListener(el, 'click', this.clickCb);
1040
-
1041
- //Hide the bubble by default
1042
- this.hide();
1043
-
1044
- //Finally, append our new bubble to body once the DOM is ready.
1045
- if (utils.documentIsReady()) {
1046
- document.body.appendChild(el);
1047
- self.highlight.addToDom();
1048
- }
1049
- else {
1050
- // Moz, webkit, Opera
1051
- if (document.addEventListener) {
1052
- appendToBody = function() {
1053
- document.removeEventListener('DOMContentLoaded', appendToBody);
1054
- window.removeEventListener('load', appendToBody);
1055
-
1056
- document.body.appendChild(el);
1057
- self.highlight.addToDom();
1058
- };
1059
-
1060
- document.addEventListener('DOMContentLoaded', appendToBody, false);
1061
- }
1062
- // IE
1063
- else {
1064
- appendToBody = function() {
1065
- if (document.readyState === 'complete') {
1066
- document.detachEvent('onreadystatechange', appendToBody);
1067
- window.detachEvent('onload', appendToBody);
1068
- document.body.appendChild(el);
1069
- self.highlight.addToDom();
1070
- }
1071
- };
1072
-
1073
- document.attachEvent('onreadystatechange', appendToBody);
1074
- }
1075
- utils.addEvtListener(window, 'load', appendToBody);
1076
- }
1077
- }
1078
- };
1079
-
1080
- /**
1081
- * HopscotchCalloutManager
1082
- *
1083
- * @class Manages the creation and destruction of single callouts.
1084
- * @constructor
1085
- */
1086
- HopscotchCalloutManager = function() {
1087
- var callouts = {};
1088
-
1089
- /**
1090
- * createCallout
1091
- *
1092
- * Creates a standalone callout. This callout has the same API
1093
- * as a Hopscotch tour bubble.
1094
- *
1095
- * @param {Object} opt The options for the callout. For the most
1096
- * part, these are the same options as you would find in a tour
1097
- * step.
1098
- */
1099
- this.createCallout = function(opt) {
1100
- var callout;
1101
-
1102
- if (opt.id) {
1103
- if (callouts[opt.id]) {
1104
- throw 'Callout by that id already exists. Please choose a unique id.';
1105
- }
1106
- opt.showNextButton = opt.showPrevButton = false;
1107
- opt.isTourBubble = false;
1108
- callout = new HopscotchBubble(opt);
1109
- callouts[opt.id] = callout;
1110
- if (opt.target) {
1111
- callout.render(opt, null, function() {
1112
- callout.show();
1113
- });
1114
- }
1115
- }
1116
- else {
1117
- throw 'Must specify a callout id.';
1118
- }
1119
- return callout;
1120
- };
1121
-
1122
- /**
1123
- * getCallout
1124
- *
1125
- * Returns a callout by its id.
1126
- *
1127
- * @param {String} id The id of the callout to fetch.
1128
- * @returns {Object} HopscotchBubble
1129
- */
1130
- this.getCallout = function(id) {
1131
- return callouts[id];
1132
- };
1133
-
1134
- /**
1135
- * removeAllCallouts
1136
- *
1137
- * Removes all existing callouts.
1138
- */
1139
- this.removeAllCallouts = function() {
1140
- var calloutId,
1141
- callout;
1142
-
1143
- for (calloutId in callouts) {
1144
- if (callouts.hasOwnProperty(calloutId)) {
1145
- this.removeCallout(calloutId);
1146
- }
1147
- }
1148
- };
1149
-
1150
- /**
1151
- * removeAllCallout
1152
- *
1153
- * Removes an existing callout by id.
1154
- *
1155
- * @param {String} id The id of the callout to remove.
1156
- */
1157
- this.removeCallout = function(id) {
1158
- var callout = callouts[id];
1159
-
1160
- callouts[id] = null;
1161
- if (!callout) { return; }
1162
-
1163
- callout.destroy();
1164
- };
1165
- };
1166
-
1167
- /**
1168
- * Hopscotch
1169
- *
1170
- * @class Creates the Hopscotch object. Used to manage tour progress and configurations.
1171
- * @constructor
1172
- * @param {Object} initOptions Options to be passed to `configure()`.
1173
- */
1174
- Hopscotch = function(initOptions) {
1175
- var self = this, // for targetClickNextFn
1176
- bubble,
1177
- calloutMgr,
1178
- opt,
1179
- currTour,
1180
- currStepNum,
1181
- cookieTourId,
1182
- cookieTourStep,
1183
- _configure,
1184
-
1185
- /**
1186
- * getBubble
1187
- *
1188
- * Singleton accessor function for retrieving or creating bubble object.
1189
- *
1190
- * @private
1191
- * @param setOptions {Boolean} when true, transfers configuration options to the bubble
1192
- * @returns {Object} HopscotchBubble
1193
- */
1194
- getBubble = function(setOptions) {
1195
- if (!bubble) {
1196
- bubble = new HopscotchBubble(opt);
1197
- }
1198
- if (setOptions) {
1199
- utils.extend(bubble.opt, {
1200
- bubblePadding: getOption('bubblePadding'),
1201
- bubbleWidth: getOption('bubbleWidth'),
1202
- showNextButton: getOption('showNextButton'),
1203
- showPrevButton: getOption('showPrevButton'),
1204
- showCloseButton: getOption('showCloseButton'),
1205
- arrowWidth: getOption('arrowWidth')
1206
- });
1207
- }
1208
- return bubble;
1209
- },
1210
-
1211
- /**
1212
- * Convenience method for getting an option. Returns custom config option
1213
- * or the default config option if no custom value exists.
1214
- *
1215
- * @private
1216
- * @param name {String} config option name
1217
- * @returns {Object} config option value
1218
- */
1219
- getOption = function(name) {
1220
- if (typeof opt === 'undefined') {
1221
- return defaultOpts[name];
1222
- }
1223
- return utils.valOrDefault(opt[name], defaultOpts[name]);
1224
- },
1225
-
1226
- /**
1227
- * getCurrStep
1228
- *
1229
- * @private
1230
- * @returns {Object} the step object corresponding to the current value of currStepNum
1231
- */
1232
- getCurrStep = function() {
1233
- var step;
1234
-
1235
- if (currStepNum < 0 || currStepNum >= currTour.steps.length) {
1236
- step = null;
1237
- }
1238
- else {
1239
- step = currTour.steps[currStepNum];
1240
- }
1241
-
1242
- return step;
1243
- },
1244
-
1245
- /**
1246
- * Used for nextOnTargetClick
1247
- *
1248
- * @private
1249
- */
1250
- targetClickNextFn = function() {
1251
- self.nextStep();
1252
- },
1253
-
1254
- /**
1255
- * adjustWindowScroll
1256
- *
1257
- * Checks if the bubble or target element is partially or completely
1258
- * outside of the viewport. If it is, adjust the window scroll position
1259
- * to bring it back into the viewport.
1260
- *
1261
- * @private
1262
- * @param {Function} cb Callback to invoke after done scrolling.
1263
- */
1264
- adjustWindowScroll = function(cb) {
1265
- var bubble = getBubble(),
1266
-
1267
- // Calculate the bubble element top and bottom position
1268
- bubbleEl = bubble.element,
1269
- bubbleTop = utils.getPixelValue(bubbleEl.style.top),
1270
- bubbleBottom = bubbleTop + utils.getPixelValue(bubbleEl.offsetHeight),
1271
-
1272
- // Calculate the target element top and bottom position
1273
- targetEl = utils.getStepTarget(getCurrStep()),
1274
- targetBounds = targetEl.getBoundingClientRect(),
1275
- targetElTop = targetBounds.top + utils.getScrollTop(),
1276
- targetElBottom = targetBounds.bottom + utils.getScrollTop(),
1277
-
1278
- // The higher of the two: bubble or target
1279
- targetTop = (bubbleTop < targetElTop) ? bubbleTop : targetElTop,
1280
- // The lower of the two: bubble or target
1281
- targetBottom = (bubbleBottom > targetElBottom) ? bubbleBottom : targetElBottom,
1282
-
1283
- // Calculate the current viewport top and bottom
1284
- windowTop = utils.getScrollTop(),
1285
- windowBottom = windowTop + utils.getWindowHeight(),
1286
-
1287
- // This is our final target scroll value.
1288
- scrollToVal = targetTop - getOption('scrollTopMargin'),
1289
-
1290
- scrollEl,
1291
- yuiAnim,
1292
- yuiEase,
1293
- direction,
1294
- scrollIncr,
1295
- scrollTimeout,
1296
- scrollTimeoutFn;
1297
-
1298
- // Target and bubble are both visible in viewport
1299
- if (targetTop >= windowTop && (targetTop <= windowTop + getOption('scrollTopMargin') || targetBottom <= windowBottom)) {
1300
- if (cb) { cb(); } // HopscotchBubble.show
1301
- }
1302
-
1303
- // Abrupt scroll to scroll target
1304
- else if (!getOption('smoothScroll')) {
1305
- window.scrollTo(0, scrollToVal);
1306
-
1307
- if (cb) { cb(); } // HopscotchBubble.show
1308
- }
1309
-
1310
- // Smooth scroll to scroll target
1311
- else {
1312
- // Use YUI if it exists
1313
- if (typeof YAHOO !== undefinedStr &&
1314
- typeof YAHOO.env !== undefinedStr &&
1315
- typeof YAHOO.env.ua !== undefinedStr &&
1316
- typeof YAHOO.util !== undefinedStr &&
1317
- typeof YAHOO.util.Scroll !== undefinedStr) {
1318
- scrollEl = YAHOO.env.ua.webkit ? document.body : document.documentElement;
1319
- yuiEase = YAHOO.util.Easing ? YAHOO.util.Easing.easeOut : undefined;
1320
- yuiAnim = new YAHOO.util.Scroll(scrollEl, {
1321
- scroll: { to: [0, scrollToVal] }
1322
- }, getOption('scrollDuration')/1000, yuiEase);
1323
- yuiAnim.onComplete.subscribe(cb);
1324
- yuiAnim.animate();
1325
- }
1326
-
1327
- // Use jQuery if it exists
1328
- else if (hasJquery) {
1329
- jQuery('body, html').animate({ scrollTop: scrollToVal }, getOption('scrollDuration'), cb);
1330
- }
1331
-
1332
- // Use my crummy setInterval scroll solution if we're using plain, vanilla Javascript.
1333
- else {
1334
- if (scrollToVal < 0) {
1335
- scrollToVal = 0;
1336
- }
1337
-
1338
- // 48 * 10 == 480ms scroll duration
1339
- // make it slightly less than CSS transition duration because of
1340
- // setInterval overhead.
1341
- // To increase or decrease duration, change the divisor of scrollIncr.
1342
- direction = (windowTop > targetTop) ? -1 : 1; // -1 means scrolling up, 1 means down
1343
- scrollIncr = Math.abs(windowTop - scrollToVal) / (getOption('scrollDuration')/10);
1344
- scrollTimeoutFn = function() {
1345
- var scrollTop = utils.getScrollTop(),
1346
- scrollTarget = scrollTop + (direction * scrollIncr);
1347
-
1348
- if ((direction > 0 && scrollTarget >= scrollToVal) ||
1349
- (direction < 0 && scrollTarget <= scrollToVal)) {
1350
- // Overshot our target. Just manually set to equal the target
1351
- // and clear the interval
1352
- scrollTarget = scrollToVal;
1353
- if (cb) { cb(); } // HopscotchBubble.show
1354
- window.scrollTo(0, scrollTarget);
1355
- return;
1356
- }
1357
-
1358
- window.scrollTo(0, scrollTarget);
1359
-
1360
- if (utils.getScrollTop() === scrollTop) {
1361
- // Couldn't scroll any further.
1362
- if (cb) { cb(); } // HopscotchBubble.show
1363
- return;
1364
- }
1365
-
1366
- // If we reached this point, that means there's still more to scroll.
1367
- setTimeout(scrollTimeoutFn, 10);
1368
- };
1369
-
1370
- scrollTimeoutFn();
1371
- }
1372
- }
1373
- },
1374
-
1375
- /**
1376
- * goToStepWithTarget
1377
- *
1378
- * Helper function to increment the step number until a step is found where
1379
- * the step target exists or until we reach the end/beginning of the tour.
1380
- *
1381
- * @private
1382
- * @param {Number} direction Either 1 for incrementing or -1 for decrementing
1383
- * @param {Function} cb The callback function to be invoked when the step has been found
1384
- */
1385
- goToStepWithTarget = function(direction, cb) {
1386
- var target,
1387
- step,
1388
- goToStepFn;
1389
-
1390
- if (currStepNum + direction >= 0 &&
1391
- currStepNum + direction < currTour.steps.length) {
1392
-
1393
- currStepNum += direction;
1394
- step = getCurrStep();
1395
-
1396
- goToStepFn = function() {
1397
- target = utils.getStepTarget(step);
1398
-
1399
- if (target) {
1400
- // We're done! Return the step number via the callback.
1401
- cb(currStepNum);
1402
- }
1403
- else {
1404
- // Haven't found a valid target yet. Recursively call
1405
- // goToStepWithTarget.
1406
- utils.invokeEventCallbacks('error');
1407
- goToStepWithTarget(direction, cb);
1408
- }
1409
- };
1410
-
1411
- if (step.delay) {
1412
- setTimeout(goToStepFn, step.delay);
1413
- }
1414
- else {
1415
- goToStepFn();
1416
- }
1417
- }
1418
- else {
1419
- cb(-1); // signal that we didn't find any step with a valid target
1420
- }
1421
- },
1422
-
1423
- /**
1424
- * changeStep
1425
- *
1426
- * Helper function to change step by going forwards or backwards 1.
1427
- * nextStep and prevStep are publicly accessible wrappers for this function.
1428
- *
1429
- * @private
1430
- * @param {Boolean} doCallbacks Flag for invoking onNext or onPrev callbacks
1431
- * @param {Number} direction Either 1 for "next" or -1 for "prev"
1432
- */
1433
- changeStep = function(doCallbacks, direction) {
1434
- var bubble = getBubble(),
1435
- self = this,
1436
- step,
1437
- origStep,
1438
- wasMultiPage,
1439
- changeStepCb;
1440
-
1441
- bubble.hide();
1442
-
1443
- doCallbacks = utils.valOrDefault(doCallbacks, true);
1444
- step = getCurrStep();
1445
- origStep = step;
1446
- if (direction > 0) {
1447
- wasMultiPage = origStep.multipage;
1448
- }
1449
- else {
1450
- wasMultiPage = (currStepNum > 0 && currTour.steps[currStepNum-1].multipage);
1451
- }
1452
-
1453
- /**
1454
- * Callback for goToStepWithTarget
1455
- *
1456
- * @private
1457
- */
1458
- changeStepCb = function(stepNum) {
1459
- var doShowFollowingStep;
1460
-
1461
- if (stepNum === -1) {
1462
- // Wasn't able to find a step with an existing element. End tour.
1463
- return this.endTour(true);
1464
- }
1465
-
1466
- if (doCallbacks) {
1467
- if (direction > 0) {
1468
- doShowFollowingStep = utils.invokeEventCallbacks('next', origStep.onNext);
1469
- }
1470
- else {
1471
- doShowFollowingStep = utils.invokeEventCallbacks('prev', origStep.onPrev);
1472
- }
1473
- }
1474
-
1475
- // If the state of the tour is updated in a callback, assume the client
1476
- // doesn't want to go to next step since they specifically updated.
1477
- if (stepNum !== currStepNum) {
1478
- return;
1479
- }
1480
-
1481
- if (wasMultiPage) {
1482
- // Update state for the next page
1483
- utils.setState(getOption('cookieName'), currTour.id + ':' + currStepNum, 1);
1484
-
1485
- // Next step is on a different page, so no need to attempt to render it.
1486
- return;
1487
- }
1488
-
1489
- doShowFollowingStep = utils.valOrDefault(doShowFollowingStep, true);
1490
-
1491
- // If the onNext/onPrev callback returned false, halt the tour and
1492
- // don't show the next step.
1493
- if (doShowFollowingStep) {
1494
- this.showStep(stepNum);
1495
- }
1496
- else {
1497
- // Halt tour (but don't clear state)
1498
- this.endTour(false);
1499
- }
1500
- };
1501
-
1502
- if (!wasMultiPage && getOption('skipIfNoElement')) {
1503
- goToStepWithTarget(direction, function(stepNum) {
1504
- changeStepCb.call(self, stepNum);
1505
- });
1506
- }
1507
- else if (currStepNum + direction >= 0 && currStepNum + direction < currTour.steps.length) {
1508
- // only try incrementing once, and invoke error callback if no target is found
1509
- currStepNum += direction;
1510
- step = getCurrStep();
1511
- if (!utils.getStepTarget(step) && !wasMultiPage) {
1512
- utils.invokeEventCallbacks('error');
1513
- return this.endTour(true, false);
1514
- }
1515
- changeStepCb.call(this, currStepNum);
1516
- }
1517
-
1518
- return this;
1519
- },
1520
-
1521
- /**
1522
- * loadTour
1523
- *
1524
- * Loads, but does not display, tour.
1525
- *
1526
- * @private
1527
- * @param tour The tour JSON object
1528
- */
1529
- loadTour = function(tour) {
1530
- var tmpOpt = {},
1531
- prop,
1532
- tourState,
1533
- tourPair;
1534
-
1535
- // Set tour-specific configurations
1536
- for (prop in tour) {
1537
- if (tour.hasOwnProperty(prop) &&
1538
- prop !== 'id' &&
1539
- prop !== 'steps') {
1540
- tmpOpt[prop] = tour[prop];
1541
- }
1542
- }
1543
-
1544
- //this.resetDefaultOptions(); // reset all options so there are no surprises
1545
- // TODO check number of config properties of tour
1546
- _configure.call(this, tmpOpt, true);
1547
-
1548
- // Get existing tour state, if it exists.
1549
- tourState = utils.getState(getOption('cookieName'));
1550
- if (tourState) {
1551
- tourPair = tourState.split(':');
1552
- cookieTourId = tourPair[0]; // selecting tour is not supported by this framework.
1553
- cookieTourStep = tourPair[1];
1554
-
1555
- cookieTourStep = parseInt(cookieTourStep, 10);
1556
- }
1557
-
1558
- return this;
1559
- },
1560
-
1561
- /**
1562
- * Find the first step to show for a tour. (What is the first step with a
1563
- * target on the page?)
1564
- */
1565
- findStartingStep = function(startStepNum, cb) {
1566
- var step,
1567
- target,
1568
- stepNum;
1569
-
1570
- currStepNum = startStepNum || 0;
1571
- step = getCurrStep();
1572
- target = utils.getStepTarget(step);
1573
-
1574
- if (target) {
1575
- // First step had an existing target.
1576
- cb(currStepNum);
1577
- return;
1578
- }
1579
-
1580
- if (!target) {
1581
- // Previous target doesn't exist either. The user may have just
1582
- // clicked on a link that wasn't part of the tour. Another possibility is that
1583
- // the user clicked on the correct link, but the target is just missing for
1584
- // whatever reason. In either case, we should just advance until we find a step
1585
- // that has a target on the page or end the tour if we can't find such a step.
1586
- utils.invokeEventCallbacks('error');
1587
-
1588
- if (getOption('skipIfNoElement')) {
1589
- goToStepWithTarget(1, cb);
1590
- return;
1591
- }
1592
- else {
1593
- currStepNum = -1;
1594
- cb(currStepNum);
1595
- }
1596
- }
1597
- },
1598
-
1599
- showStepHelper = function(stepNum) {
1600
- var step = currTour.steps[stepNum],
1601
- tourSteps = currTour.steps,
1602
- numTourSteps = tourSteps.length,
1603
- cookieVal = currTour.id + ':' + stepNum,
1604
- bubble = getBubble(),
1605
- targetEl = utils.getStepTarget(step),
1606
- isLast,
1607
- showBubble;
1608
-
1609
- showBubble = function() {
1610
- bubble.show();
1611
- utils.invokeEventCallbacks('show', step.onShow);
1612
- };
1613
-
1614
- // Update bubble for current step
1615
- currStepNum = stepNum;
1616
-
1617
- bubble.hide(false);
1618
-
1619
- isLast = (stepNum === numTourSteps - 1);
1620
- bubble.render(step, stepNum, function(adjustScroll) {
1621
- // when done adjusting window scroll, call showBubble helper fn
1622
- if (adjustScroll) {
1623
- adjustWindowScroll(showBubble);
1624
- }
1625
- else {
1626
- showBubble();
1627
- }
1628
-
1629
- // If we want to advance to next step when user clicks on target.
1630
- if (step.nextOnTargetClick) {
1631
- utils.addEvtListener(targetEl, 'click', targetClickNextFn);
1632
- }
1633
- });
1634
-
1635
- utils.setState(getOption('cookieName'), cookieVal, 1);
1636
- },
1637
-
1638
- /**
1639
- * init
1640
- *
1641
- * Initializes the Hopscotch object.
1642
- *
1643
- * @private
1644
- */
1645
- init = function(initOptions) {
1646
- if (initOptions) {
1647
- //initOptions.cookieName = initOptions.cookieName || 'hopscotch.tour.state';
1648
- this.configure(initOptions);
1649
- }
1650
- };
1651
-
1652
- /**
1653
- * getCalloutManager
1654
- *
1655
- * Gets the callout manager.
1656
- *
1657
- * @returns {Object} HopscotchCalloutManager
1658
- *
1659
- */
1660
- this.getCalloutManager = function() {
1661
- if (typeof calloutMgr === undefinedStr) {
1662
- calloutMgr = new HopscotchCalloutManager();
1663
- }
1664
-
1665
- return calloutMgr;
1666
- };
1667
-
1668
- /**
1669
- * startTour
1670
- *
1671
- * Begins the tour.
1672
- *
1673
- * @param {Object} tour The tour JSON object
1674
- * @stepNum {Number} stepNum __Optional__ The step number to start from
1675
- * @returns {Object} Hopscotch
1676
- *
1677
- */
1678
- this.startTour = function(tour, stepNum) {
1679
- var bubble,
1680
- currStepNum,
1681
- self = this;
1682
-
1683
- // loadTour if we are calling startTour directly. (When we call startTour
1684
- // from window onLoad handler, we'll use currTour)
1685
- if (!currTour) {
1686
- currTour = tour;
1687
- loadTour.call(this, tour);
1688
- }
1689
-
1690
- if (typeof stepNum !== undefinedStr) {
1691
- if (stepNum >= currTour.steps.length) {
1692
- throw 'Specified step number out of bounds.';
1693
- }
1694
- currStepNum = stepNum;
1695
- }
1696
-
1697
- // If document isn't ready, wait for it to finish loading.
1698
- // (so that we can calculate positioning accurately)
1699
- if (!utils.documentIsReady()) {
1700
- waitingToStart = true;
1701
- return this;
1702
- }
1703
-
1704
- if (typeof currStepNum === "undefined" && currTour.id === cookieTourId && typeof cookieTourStep !== undefinedStr) {
1705
- currStepNum = cookieTourStep;
1706
- }
1707
- else if (!currStepNum) {
1708
- currStepNum = 0;
1709
- }
1710
-
1711
- // Find the current step we should begin the tour on, and then actually start the tour.
1712
- findStartingStep(currStepNum, function(stepNum) {
1713
- var target = (stepNum !== -1) && utils.getStepTarget(currTour.steps[stepNum]);
1714
-
1715
- if (!target) {
1716
- // Should we trigger onEnd callback? Let's err on the side of caution
1717
- // and not trigger it. Don't want weird stuff happening on a page that
1718
- // wasn't meant for the tour. Up to the developer to fix their tour.
1719
- self.endTour(false, false);
1720
- return;
1721
- }
1722
-
1723
- utils.invokeEventCallbacks('start');
1724
-
1725
- bubble = getBubble();
1726
- // TODO: do we still need this call to .hide()? No longer using opt.animate...
1727
- // Leaving it in for now to play it safe
1728
- bubble.hide(false); // make invisible for boundingRect calculations when opt.animate == true
1729
-
1730
- self.isActive = true;
1731
-
1732
- if (!utils.getStepTarget(getCurrStep())) {
1733
- // First step element doesn't exist
1734
- utils.invokeEventCallbacks('error');
1735
- if (getOption('skipIfNoElement')) {
1736
- self.nextStep(false);
1737
- }
1738
- }
1739
- else {
1740
- self.showStep(stepNum);
1741
- }
1742
- });
1743
-
1744
- return this;
1745
- };
1746
-
1747
- /**
1748
- * showStep
1749
- *
1750
- * Skips to a specific step and renders the corresponding bubble.
1751
- *
1752
- * @stepNum {Number} stepNum The step number to show
1753
- * @returns {Object} Hopscotch
1754
- */
1755
- this.showStep = function(stepNum) {
1756
- var step = currTour.steps[stepNum];
1757
- if (step.delay) {
1758
- setTimeout(function() {
1759
- showStepHelper(stepNum);
1760
- }, step.delay);
1761
- }
1762
- else {
1763
- showStepHelper(stepNum);
1764
- }
1765
- return this;
1766
- };
1767
-
1768
- /**
1769
- * prevStep
1770
- *
1771
- * Jump to the previous step.
1772
- *
1773
- * @param {Boolean} doCallbacks Flag for invoking onPrev callback. Defaults to true.
1774
- * @returns {Object} Hopscotch
1775
- */
1776
- this.prevStep = function(doCallbacks) {
1777
- changeStep.call(this, doCallbacks, -1);
1778
- return this;
1779
- };
1780
-
1781
- /**
1782
- * nextStep
1783
- *
1784
- * Jump to the next step.
1785
- *
1786
- * @param {Boolean} doCallbacks Flag for invoking onNext callback. Defaults to true.
1787
- * @returns {Object} Hopscotch
1788
- */
1789
- this.nextStep = function(doCallbacks) {
1790
- var step = getCurrStep(),
1791
- targetEl = utils.getStepTarget(step);
1792
-
1793
- if (step.nextOnTargetClick) {
1794
- // Detach the listener after we've clicked on the target OR the next button.
1795
- utils.removeEvtListener(targetEl, 'click', targetClickNextFn);
1796
- }
1797
- changeStep.call(this, doCallbacks, 1);
1798
- return this;
1799
- };
1800
-
1801
- /**
1802
- * endTour
1803
- *
1804
- * Cancels out of an active tour.
1805
- *
1806
- * @param {Boolean} clearState Flag for clearing state. Defaults to true.
1807
- * @param {Boolean} doCallbacks Flag for invoking 'onEnd' callbacks. Defaults to true.
1808
- * @returns {Object} Hopscotch
1809
- */
1810
- this.endTour = function(clearState, doCallbacks) {
1811
- var bubble = getBubble();
1812
- clearState = utils.valOrDefault(clearState, true);
1813
- doCallbacks = utils.valOrDefault(doCallbacks, true);
1814
- currStepNum = 0;
1815
- cookieTourStep = undefined;
1816
-
1817
- bubble.hide();
1818
- if (clearState) {
1819
- utils.clearState(getOption('cookieName'));
1820
- }
1821
- if (this.isActive) {
1822
- this.isActive = false;
1823
-
1824
- if (currTour && doCallbacks) {
1825
- utils.invokeEventCallbacks('end');
1826
- }
1827
- }
1828
-
1829
- this.removeCallbacks(null, true);
1830
- this.resetDefaultOptions();
1831
-
1832
- currTour = null;
1833
-
1834
- return this;
1835
- };
1836
-
1837
- /**
1838
- * getCurrTour
1839
- *
1840
- * @return {Object} The currently loaded tour.
1841
- */
1842
- this.getCurrTour = function() {
1843
- return currTour;
1844
- };
1845
-
1846
- /**
1847
- * getCurrTarget
1848
- *
1849
- * @return {Object} The currently visible target.
1850
- */
1851
- this.getCurrTarget = function() {
1852
- return utils.getStepTarget(getCurrStep());
1853
- };
1854
-
1855
- /**
1856
- * getCurrStepNum
1857
- *
1858
- * @return {number} The current zero-based step number.
1859
- */
1860
- this.getCurrStepNum = function() {
1861
- return currStepNum;
1862
- };
1863
-
1864
- /**
1865
- * refreshBubblePosition
1866
- *
1867
- * Tell hopscotch that the position of the current tour element changed
1868
- * and the bubble therefore needs to be redrawn
1869
- *
1870
- * @returns {Object} Hopscotch
1871
- */
1872
- this.refreshBubblePosition = function() {
1873
- bubble.setPosition(getCurrStep());
1874
- return this;
1875
- };
1876
-
1877
- /**
1878
- * listen
1879
- *
1880
- * Adds a callback for one of the event types. Valid event types are:
1881
- *
1882
- * @param {string} evtType "start", "end", "next", "prev", "show", "close", or "error"
1883
- * @param {Function} cb The callback to add.
1884
- * @param {Boolean} isTourCb Flag indicating callback is from a tour definition.
1885
- * For internal use only!
1886
- * @returns {Object} Hopscotch
1887
- */
1888
- this.listen = function(evtType, cb, isTourCb) {
1889
- if (evtType) {
1890
- callbacks[evtType].push({ cb: cb, fromTour: isTourCb });
1891
- }
1892
- return this;
1893
- };
1894
-
1895
- /**
1896
- * unlisten
1897
- *
1898
- * Removes a callback for one of the event types, e.g. 'start', 'next', etc.
1899
- *
1900
- * @param {string} evtType "start", "end", "next", "prev", "show", "close", or "error"
1901
- * @param {Function} cb The callback to remove.
1902
- * @returns {Object} Hopscotch
1903
- */
1904
- this.unlisten = function(evtType, cb) {
1905
- var evtCallbacks = callbacks[evtType],
1906
- i,
1907
- len;
1908
-
1909
- for (i = 0, len = evtCallbacks.length; i < len; ++i) {
1910
- if (evtCallbacks[i] === cb) {
1911
- evtCallbacks.splice(i, 1);
1912
- }
1913
- }
1914
- return this;
1915
- };
1916
-
1917
- /**
1918
- * removeCallbacks
1919
- *
1920
- * Remove callbacks for hopscotch events. If tourOnly is set to true, only
1921
- * removes callbacks specified by a tour (callbacks set by external calls
1922
- * to hopscotch.configure or hopscotch.listen will not be removed). If
1923
- * evtName is null or undefined, callbacks for all events will be removed.
1924
- *
1925
- * @param {string} evtName Optional Event name for which we should remove callbacks
1926
- * @param {boolean} tourOnly Optional flag to indicate we should only remove callbacks added
1927
- * by a tour. Defaults to false.
1928
- * @returns {Object} Hopscotch
1929
- */
1930
- this.removeCallbacks = function(evtName, tourOnly) {
1931
- var cbArr,
1932
- i,
1933
- len,
1934
- evt;
1935
-
1936
- // If evtName is null or undefined, remove callbacks for all events.
1937
- for (evt in callbacks) {
1938
- if (!evtName || evtName === evt) {
1939
- if (tourOnly) {
1940
- cbArr = callbacks[evt];
1941
- for (i=0, len=cbArr.length; i < len; ++i) {
1942
- if (cbArr[i].fromTour) {
1943
- cbArr.splice(i--, 1);
1944
- --len;
1945
- }
1946
- }
1947
- }
1948
- else {
1949
- callbacks[evt] = [];
1950
- }
1951
- }
1952
- }
1953
- return this;
1954
- };
1955
-
1956
- /**
1957
- * registerHelper
1958
- * ==============
1959
- * Registers a helper function to be used as a callback function.
1960
- *
1961
- * @param {String} id The id of the function.
1962
- * @param {Function} id The callback function.
1963
- */
1964
- this.registerHelper = function(id, fn) {
1965
- if (typeof id === 'string' && typeof fn === 'function') {
1966
- helpers[id] = fn;
1967
- }
1968
- };
1969
-
1970
- this.unregisterHelper = function(id) {
1971
- helpers[id] = null;
1972
- };
1973
-
1974
- this.invokeHelper = function(id) {
1975
- var args = [],
1976
- i,
1977
- len;
1978
-
1979
- for (i = 1, len = arguments.length; i < len; ++i) {
1980
- args.push(arguments[i]);
1981
- }
1982
- if (helpers[id]) {
1983
- helpers[id].call(null, args);
1984
- }
1985
- };
1986
-
1987
- /**
1988
- * setCookieName
1989
- *
1990
- * Sets the cookie name (or sessionStorage name, if supported) used for multi-page
1991
- * tour persistence.
1992
- *
1993
- * @param {String} name The cookie name
1994
- * @returns {Object} Hopscotch
1995
- */
1996
- this.setCookieName = function(name) {
1997
- opt.cookieName = name;
1998
- return this;
1999
- };
2000
-
2001
- /**
2002
- * resetDefaultOptions
2003
- *
2004
- * Resets all configuration options to default.
2005
- *
2006
- * @returns {Object} Hopscotch
2007
- */
2008
- this.resetDefaultOptions = function() {
2009
- opt = {};
2010
- return this;
2011
- };
2012
-
2013
- /**
2014
- * resetDefaultI18N
2015
- *
2016
- * Resets all i18n.
2017
- *
2018
- * @returns {Object} Hopscotch
2019
- */
2020
- this.resetDefaultI18N = function() {
2021
- customI18N = {};
2022
- return this;
2023
- };
2024
-
2025
- /**
2026
- * hasState
2027
- *
2028
- * Returns state from a previous tour run, if it exists.
2029
- *
2030
- * @returns {String} State of previous tour run, or empty string if none exists.
2031
- */
2032
- this.getState = function() {
2033
- return utils.getState(getOption('cookieName'));
2034
- };
2035
-
2036
- /**
2037
- * _configure
2038
- *
2039
- * @see this.configure
2040
- * @private
2041
- * @param options
2042
- * @param {Boolean} isTourOptions Should be set to true when setting options from a tour definition.
2043
- */
2044
- _configure = function(options, isTourOptions) {
2045
- var bubble,
2046
- events = ['next', 'prev', 'start', 'end', 'show', 'error', 'close'],
2047
- eventPropName,
2048
- callbackProp,
2049
- i,
2050
- len;
2051
-
2052
- if (!opt) {
2053
- this.resetDefaultOptions();
2054
- }
2055
-
2056
- utils.extend(opt, options);
2057
-
2058
- if (options) {
2059
- utils.extend(customI18N, options.i18n);
2060
- }
2061
-
2062
- for (i = 0, len = events.length; i < len; ++i) {
2063
- // At this point, options[eventPropName] may have changed from an array
2064
- // to a function.
2065
- eventPropName = 'on' + events[i].charAt(0).toUpperCase() + events[i].substring(1);
2066
- if (options[eventPropName]) {
2067
- this.listen(events[i],
2068
- options[eventPropName],
2069
- isTourOptions);
2070
- }
2071
- }
2072
-
2073
- bubble = getBubble(true);
2074
-
2075
- return this;
2076
- };
2077
-
2078
- /**
2079
- * configure
2080
- *
2081
- * <pre>
2082
- * VALID OPTIONS INCLUDE...
2083
- *
2084
- * - bubbleWidth: Number - Default bubble width. Defaults to 280.
2085
- * - bubblePadding: Number - DEPRECATED. Default bubble padding. Defaults to 15.
2086
- * - smoothScroll: Boolean - should the page scroll smoothly to the next
2087
- * step? Defaults to TRUE.
2088
- * - scrollDuration: Number - Duration of page scroll. Only relevant when
2089
- * smoothScroll is set to true. Defaults to
2090
- * 1000ms.
2091
- * - scrollTopMargin: NUMBER - When the page scrolls, how much space should there
2092
- * be between the bubble/targetElement and the top
2093
- * of the viewport? Defaults to 200.
2094
- * - showCloseButton: Boolean - should the tour bubble show a close (X) button?
2095
- * Defaults to TRUE.
2096
- * - showPrevButton: Boolean - should the bubble have the Previous button?
2097
- * Defaults to FALSE.
2098
- * - showNextButton: Boolean - should the bubble have the Next button?
2099
- * Defaults to TRUE.
2100
- * - arrowWidth: Number - Default arrow width. (space between the bubble
2101
- * and the targetEl) Used for bubble position
2102
- * calculation. Only use this option if you are
2103
- * using your own custom CSS. Defaults to 20.
2104
- * - skipIfNoElement Boolean - If a specified target element is not found,
2105
- * should we skip to the next step? Defaults to
2106
- * TRUE.
2107
- * - onNext: Function - A callback to be invoked after every click on
2108
- * a "Next" button.
2109
- *
2110
- * - i18n: Object - For i18n purposes. Allows you to change the
2111
- * text of button labels and step numbers.
2112
- * - i18n.stepNums: Array\<String\> - Provide a list of strings to be shown as
2113
- * the step number, based on index of array. Unicode
2114
- * characters are supported. (e.g., ['&#x4e00;',
2115
- * '&#x4e8c;', '&#x4e09;']) If there are more steps
2116
- * than provided numbers, Arabic numerals
2117
- * ('4', '5', '6', etc.) will be used as default.
2118
- * - highlight: Boolean - Shows an overlay that highlights the selected element
2119
- * Defaults to FALSE.
2120
- * - highlightMargin: Number - Amount of margin around the selected element to show
2121
- * Defaults to 0
2122
- *
2123
- * // =========
2124
- * // CALLBACKS
2125
- * // =========
2126
- * - onNext: Function - Invoked after every click on a "Next" button.
2127
- * - onPrev: Function - Invoked after every click on a "Prev" button.
2128
- * - onStart: Function - Invoked when the tour is started.
2129
- * - onEnd: Function - Invoked when the tour ends.
2130
- * - onClose: Function - Invoked when the user closes the tour before finishing.
2131
- * - onError: Function - Invoked when the specified target element doesn't exist on the page.
2132
- *
2133
- * // ====
2134
- * // I18N
2135
- * // ====
2136
- * i18n: OBJECT - For i18n purposes. Allows you to change the text
2137
- * of button labels and step numbers.
2138
- * i18n.nextBtn: STRING - Label for next button
2139
- * i18n.prevBtn: STRING - Label for prev button
2140
- * i18n.doneBtn: STRING - Label for done button
2141
- * i18n.skipBtn: STRING - Label for skip button
2142
- * i18n.closeTooltip: STRING - Text for close button tooltip
2143
- * i18n.stepNums: ARRAY<STRING> - Provide a list of strings to be shown as
2144
- * the step number, based on index of array. Unicode
2145
- * characters are supported. (e.g., ['&#x4e00;',
2146
- * '&#x4e8c;', '&#x4e09;']) If there are more steps
2147
- * than provided numbers, Arabic numerals
2148
- * ('4', '5', '6', etc.) will be used as default.
2149
- * </pre>
2150
- *
2151
- * @example hopscotch.configure({ scrollDuration: 1000, scrollTopMargin: 150 });
2152
- * @example
2153
- * hopscotch.configure({
2154
- * scrollTopMargin: 150,
2155
- * onStart: function() {
2156
- * alert("Have fun!");
2157
- * },
2158
- * i18n: {
2159
- * nextBtn: 'Forward',
2160
- * prevBtn: 'Previous'
2161
- * closeTooltip: 'Quit'
2162
- * }
2163
- * });
2164
- *
2165
- * @param {Object} options A hash of configuration options.
2166
- * @returns {Object} Hopscotch
2167
- */
2168
- this.configure = function(options) {
2169
- return _configure.call(this, options, false);
2170
- };
2171
-
2172
- /**
2173
- * Set the template that should be used for rendering Hopscotch bubbles.
2174
- * If a string, it's assumed your template is available in the
2175
- * hopscotch.templates namespace.
2176
- *
2177
- * @param {String|Function(obj)} The template to use for rendering.
2178
- * @returns {Object} The Hopscotch object (for chaining).
2179
- */
2180
- this.setRenderer = function(render){
2181
- var typeOfRender = typeof render;
2182
-
2183
- if(typeOfRender === 'string'){
2184
- templateToUse = render;
2185
- customRenderer = undefined;
2186
- }
2187
- else if(typeOfRender === 'function'){
2188
- customRenderer = render;
2189
- }
2190
- return this;
2191
- };
2192
-
2193
- /**
2194
- * Sets the escaping method to be used by JST templates.
2195
- *
2196
- * @param {Function} - The escape method to use.
2197
- * @returns {Object} The Hopscotch object (for chaining).
2198
- */
2199
- this.setEscaper = function(esc){
2200
- if (typeof esc === 'function'){
2201
- customEscape = esc;
2202
- }
2203
- return this;
2204
- };
2205
-
2206
- init.call(this, initOptions);
2207
- };
2208
-
2209
-
2210
-
2211
- HopscotchHighlight = function(opt) {
2212
- this.init(opt);
2213
- };
2214
-
2215
- HopscotchHighlight.prototype = {
2216
- init: function(initOpt) {
2217
- var opt;
2218
- var el = {
2219
- top: document.createElement('div'),
2220
- left: document.createElement('div'),
2221
- right: document.createElement('div'),
2222
- bottom: document.createElement('div')
2223
- };
2224
-
2225
- this.element = el;
2226
-
2227
-
2228
- //Merge highlight options with defaults.
2229
- opt = {
2230
- highlight: defaultOpts.highlight,
2231
- highlightMargin: defaultOpts.highlightMargin
2232
- };
2233
-
2234
- initOpt = (typeof initOpt === undefinedStr ? {} : initOpt);
2235
- utils.extend(opt, initOpt);
2236
- this.opt = opt;
2237
-
2238
- for (var e in this.element){
2239
- utils.addClass(this.element[e], 'hopscotch-overlay');
2240
- }
2241
- },
2242
- addToDom: function(){
2243
- for (var e in this.element){
2244
- document.body.appendChild(this.element[e]);
2245
- }
2246
- },
2247
- show: function(){
2248
- // check if step has disabled the highlight:
2249
- if (!this.stepOpts.highlight){
2250
- return;
2251
- }
2252
-
2253
- for (var e in this.element){
2254
- utils.removeClass(this.element[e], 'hide');
2255
- }
2256
- },
2257
- hide: function(){
2258
- for (var e in this.element){
2259
- utils.addClass(this.element[e], 'hide');
2260
- }
2261
- },
2262
- setPosition: function(step, targetBounds){
2263
- // check if step has disabled the highlight:
2264
- if (!this.stepOpts.highlight){
2265
- return;
2266
- }
2267
-
2268
- var margin = this.stepOpts.highlightMargin;
2269
-
2270
- var body = document.body,
2271
- html = document.documentElement;
2272
-
2273
- var documentHeight = Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight );
2274
-
2275
- // top div:
2276
- el = this.element.top;
2277
- el.style.top = '0px';
2278
- el.style.left = '0px';
2279
- el.style.width = window.screen.width + 'px';
2280
- el.style.height = targetBounds.top + utils.getScrollTop() - margin + 'px';
2281
-
2282
- // right div:
2283
- el = this.element.right;
2284
- el.style.top = targetBounds.top + utils.getScrollTop() - margin + 'px';
2285
- el.style.left = targetBounds.left + targetBounds.width + utils.getScrollLeft() + margin + 'px';
2286
- el.style.width = window.screen.width - (targetBounds.left + targetBounds.width + utils.getScrollLeft() + margin) + 'px';
2287
- el.style.height = targetBounds.height + margin * 2 + 'px';
2288
-
2289
- // bottom div:
2290
- el = this.element.bottom;
2291
- el.style.top = targetBounds.top + utils.getScrollTop() + targetBounds.height + margin + 'px';
2292
- el.style.left = '0px';
2293
- el.style.width = window.screen.width + 'px';
2294
- el.style.height = documentHeight - (targetBounds.top + utils.getScrollTop() + targetBounds.height + margin) + 'px';
2295
-
2296
- // left div:
2297
- el = this.element.left;
2298
- el.style.top = targetBounds.top + utils.getScrollTop() + - margin + 'px';
2299
- el.style.left = '0px';
2300
- el.style.width = targetBounds.left + utils.getScrollLeft() - margin + 'px';
2301
- el.style.height = targetBounds.height + margin * 2 + 'px';
2302
- },
2303
- render: function(step){
2304
- // set options for current step:
2305
- this.stepOpts = {};
2306
-
2307
- utils.extend(this.stepOpts, this.opt);
2308
- utils.extend(this.stepOpts, step);
2309
- }
2310
- };
2311
-
2312
-
2313
-
2314
- winHopscotch = new Hopscotch();
2315
- context[namespace] = winHopscotch;
2316
-
2317
- // Template includes, placed inside a closure to ensure we don't
2318
- // end up declaring our shim globally.
2319
- (function(){
2320
- var _ = {};
2321
- /*
2322
- * Adapted from the Underscore.js framework. Check it out at
2323
- * https://github.com/jashkenas/underscore
2324
- */
2325
- _.escape = function(str){
2326
- if(customEscape){ return customEscape(str); }
2327
-
2328
- if(str == null) return '';
2329
- return ('' + str).replace(new RegExp('[&<>"\']', 'g'), function(match){
2330
- if(match == '&'){ return '&amp;' }
2331
- if(match == '<'){ return '&lt;' }
2332
- if(match == '>'){ return '&gt;' }
2333
- if(match == '"'){ return '&quot;' }
2334
- if(match == "'"){ return '&#x27;' }
2335
- });
2336
- }
2337
- this["hopscotch"] = this["hopscotch"] || {};
2338
- this["hopscotch"]["templates"] = this["hopscotch"]["templates"] || {};
2339
-
2340
- this["hopscotch"]["templates"]["bubble_default"] = function(obj) {
2341
- obj || (obj = {});
2342
- var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
2343
- function print() { __p += __j.call(arguments, '') }
2344
- with (obj) {
2345
-
2346
-
2347
- function optEscape(str, unsafe){
2348
- if(unsafe){
2349
- return _.escape(str);
2350
- }
2351
- return str;
2352
- }
2353
- ;
2354
- __p += '\n<div class="hopscotch-bubble-container" style="width: ' +
2355
- ((__t = ( step.width )) == null ? '' : __t) +
2356
- 'px; padding: ' +
2357
- ((__t = ( step.padding )) == null ? '' : __t) +
2358
- 'px;">\n ';
2359
- if(tour.isTour){ ;
2360
- __p += '<span class="hopscotch-bubble-number">' +
2361
- ((__t = ( i18n.stepNum )) == null ? '' : __t) +
2362
- '</span>';
2363
- } ;
2364
- __p += '\n <div class="hopscotch-bubble-content">\n ';
2365
- if(step.title !== ''){ ;
2366
- __p += '<h3 class="hopscotch-title">' +
2367
- ((__t = ( optEscape(step.title, tour.unsafe) )) == null ? '' : __t) +
2368
- '</h3>';
2369
- } ;
2370
- __p += '\n ';
2371
- if(step.content !== ''){ ;
2372
- __p += '<div class="hopscotch-content">' +
2373
- ((__t = ( optEscape(step.content, tour.unsafe) )) == null ? '' : __t) +
2374
- '</div>';
2375
- } ;
2376
- __p += '\n </div>\n <div class="hopscotch-actions">\n ';
2377
- if(buttons.showPrev){ ;
2378
- __p += '<button class="hopscotch-nav-button prev hopscotch-prev">' +
2379
- ((__t = ( i18n.prevBtn )) == null ? '' : __t) +
2380
- '</button>';
2381
- } ;
2382
- __p += '\n ';
2383
- if(buttons.showCTA){ ;
2384
- __p += '<button class="hopscotch-nav-button next hopscotch-cta">' +
2385
- ((__t = ( buttons.ctaLabel )) == null ? '' : __t) +
2386
- '</button>';
2387
- } ;
2388
- __p += '\n ';
2389
- if(buttons.showNext){ ;
2390
- __p += '<button class="next hopscotch-next btn btn-primary btn-block btn-sm">' +
2391
- ((__t = ( i18n.nextBtn )) == null ? '' : __t) +
2392
- '</button>';
2393
- } ;
2394
- __p += '\n </div>\n ';
2395
- if(buttons.showClose){ ;
2396
- __p += '<a title="' +
2397
- ((__t = ( i18n.closeTooltip )) == null ? '' : __t) +
2398
- '" href="#" class="hopscotch-bubble-close hopscotch-close">' +
2399
- ((__t = ( i18n.closeTooltip )) == null ? '' : __t) +
2400
- '</a>';
2401
- } ;
2402
- __p += '\n</div>\n<div class="hopscotch-bubble-arrow-container hopscotch-arrow">\n <div class="hopscotch-bubble-arrow-border"></div>\n <div class="hopscotch-bubble-arrow"></div>\n</div>';
2403
-
2404
- }
2405
- return __p
2406
- };
2407
- }());
2408
-
2409
- }(window, 'hopscotch'));
1
+ (function(context, namespace) {
2
+ var Hopscotch,
3
+ HopscotchBubble,
4
+ HopscotchCalloutManager,
5
+ HopscotchI18N,
6
+ customI18N,
7
+ customRenderer,
8
+ customEscape,
9
+ templateToUse = 'bubble_default',
10
+ Sizzle = window.Sizzle || null,
11
+ utils,
12
+ callbacks,
13
+ helpers,
14
+ winLoadHandler,
15
+ defaultOpts,
16
+ winHopscotch = context[namespace],
17
+ undefinedStr = 'undefined',
18
+ waitingToStart = false, // is a tour waiting for the document to finish
19
+ // loading so that it can start?
20
+ hasJquery = (typeof window.jQuery !== undefinedStr),
21
+ hasSessionStorage = false,
22
+ document = window.document;
23
+
24
+ // If cookies are disabled, accessing sessionStorage can throw an error.
25
+ try {
26
+ hasSessionStorage = (typeof window.sessionStorage !== undefinedStr);
27
+ } catch (err) {}
28
+
29
+ defaultOpts = {
30
+ smoothScroll: true,
31
+ scrollDuration: 1000,
32
+ scrollTopMargin: 200,
33
+ showCloseButton: true,
34
+ showPrevButton: false,
35
+ showNextButton: true,
36
+ bubbleWidth: 280,
37
+ bubblePadding: 15,
38
+ arrowWidth: 20,
39
+ skipIfNoElement: true,
40
+ cookieName: 'hopscotch.tour.state',
41
+ highlight: false,
42
+ highlightMargin: 0
43
+ };
44
+
45
+ if (winHopscotch) {
46
+ // Hopscotch already exists.
47
+ return;
48
+ }
49
+
50
+ if (!Array.isArray) {
51
+ Array.isArray = function(obj) {
52
+ return Object.prototype.toString.call(obj) === '[object Array]';
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Called when the page is done loading.
58
+ *
59
+ * @private
60
+ */
61
+ winLoadHandler = function() {
62
+ if (waitingToStart) {
63
+ winHopscotch.startTour();
64
+ }
65
+ };
66
+
67
+ /**
68
+ * utils
69
+ * =====
70
+ * A set of utility functions, mostly for standardizing to manipulate
71
+ * and extract information from the DOM. Basically these are things I
72
+ * would normally use jQuery for, but I don't want to require it for
73
+ * this framework.
74
+ *
75
+ * @private
76
+ */
77
+ utils = {
78
+ /**
79
+ * addClass
80
+ * ========
81
+ * Adds one or more classes to a DOM element.
82
+ *
83
+ * @private
84
+ */
85
+ addClass: function(domEl, classToAdd) {
86
+ var domClasses,
87
+ classToAddArr,
88
+ setClass,
89
+ i,
90
+ len;
91
+
92
+ if (!domEl.className) {
93
+ domEl.className = classToAdd;
94
+ }
95
+ else {
96
+ classToAddArr = classToAdd.split(/\s+/);
97
+ domClasses = ' ' + domEl.className + ' ';
98
+ for (i = 0, len = classToAddArr.length; i < len; ++i) {
99
+ if (domClasses.indexOf(' ' + classToAddArr[i] + ' ') < 0) {
100
+ domClasses += classToAddArr[i] + ' ';
101
+ }
102
+ }
103
+ domEl.className = domClasses.replace(/^\s+|\s+$/g,'');
104
+ }
105
+ },
106
+
107
+ /**
108
+ * removeClass
109
+ * ===========
110
+ * Remove one or more classes from a DOM element.
111
+ *
112
+ * @private
113
+ */
114
+ removeClass: function(domEl, classToRemove) {
115
+ var domClasses,
116
+ classToRemoveArr,
117
+ currClass,
118
+ i,
119
+ len;
120
+
121
+ classToRemoveArr = classToRemove.split(/\s+/);
122
+ domClasses = ' ' + domEl.className + ' ';
123
+ for (i = 0, len = classToRemoveArr.length; i < len; ++i) {
124
+ domClasses = domClasses.replace(' ' + classToRemoveArr[i] + ' ', ' ');
125
+ }
126
+ domEl.className = domClasses.replace(/^\s+|\s+$/g,'');
127
+ },
128
+
129
+ /**
130
+ * hasClass
131
+ * ========
132
+ * Determine if a given DOM element has a class.
133
+ */
134
+ hasClass: function(domEl, classToCheck){
135
+ var classes;
136
+
137
+ if(!domEl.className){ return false; }
138
+ classes = ' ' + domEl.className + ' ';
139
+ return (classes.indexOf(' ' + classToCheck + ' ') !== -1);
140
+ },
141
+
142
+ /**
143
+ * @private
144
+ */
145
+ getPixelValue: function(val) {
146
+ var valType = typeof val;
147
+ if (valType === 'number') { return val; }
148
+ if (valType === 'string') { return parseInt(val, 10); }
149
+ return 0;
150
+ },
151
+
152
+ /**
153
+ * Inspired by Python... returns val if it's defined, otherwise returns the default.
154
+ *
155
+ * @private
156
+ */
157
+ valOrDefault: function(val, valDefault) {
158
+ return typeof val !== undefinedStr ? val : valDefault;
159
+ },
160
+
161
+ /**
162
+ * Invokes a single callback represented by an array.
163
+ * Example input: ["my_fn", "arg1", 2, "arg3"]
164
+ * @private
165
+ */
166
+ invokeCallbackArrayHelper: function(arr) {
167
+ // Logic for a single callback
168
+ var fn;
169
+ if (Array.isArray(arr)) {
170
+ fn = helpers[arr[0]];
171
+ if (typeof fn === 'function') {
172
+ return fn.apply(this, arr.slice(1));
173
+ }
174
+ }
175
+ },
176
+
177
+ /**
178
+ * Invokes one or more callbacks. Array should have at most one level of nesting.
179
+ * Example input:
180
+ * ["my_fn", "arg1", 2, "arg3"]
181
+ * [["my_fn_1", "arg1", "arg2"], ["my_fn_2", "arg2-1", "arg2-2"]]
182
+ * [["my_fn_1", "arg1", "arg2"], function() { ... }]
183
+ * @private
184
+ */
185
+ invokeCallbackArray: function(arr) {
186
+ var i, len;
187
+
188
+ if (Array.isArray(arr)) {
189
+ if (typeof arr[0] === 'string') {
190
+ // Assume there are no nested arrays. This is the one and only callback.
191
+ return utils.invokeCallbackArrayHelper(arr);
192
+ }
193
+ else { // assume an array
194
+ for (i = 0, len = arr.length; i < len; ++i) {
195
+ utils.invokeCallback(arr[i]);
196
+ }
197
+ }
198
+ }
199
+ },
200
+
201
+ /**
202
+ * Helper function for invoking a callback, whether defined as a function literal
203
+ * or an array that references a registered helper function.
204
+ * @private
205
+ */
206
+ invokeCallback: function(cb) {
207
+ if (typeof cb === 'function') {
208
+ return cb();
209
+ }
210
+ if (typeof cb === 'string' && helpers[cb]) { // name of a helper
211
+ return helpers[cb]();
212
+ }
213
+ else { // assuming array
214
+ return utils.invokeCallbackArray(cb);
215
+ }
216
+ },
217
+
218
+ /**
219
+ * If stepCb (the step-specific helper callback) is passed in, then invoke
220
+ * it first. Then invoke tour-wide helper.
221
+ *
222
+ * @private
223
+ */
224
+ invokeEventCallbacks: function(evtType, stepCb) {
225
+ var cbArr = callbacks[evtType],
226
+ callback,
227
+ fn,
228
+ i,
229
+ len;
230
+
231
+ if (stepCb) {
232
+ return this.invokeCallback(stepCb);
233
+ }
234
+
235
+ for (i=0, len=cbArr.length; i<len; ++i) {
236
+ this.invokeCallback(cbArr[i].cb);
237
+ }
238
+ },
239
+
240
+ /**
241
+ * @private
242
+ */
243
+ getScrollTop: function() {
244
+ var scrollTop;
245
+ if (typeof window.pageYOffset !== undefinedStr) {
246
+ scrollTop = window.pageYOffset;
247
+ }
248
+ else {
249
+ // Most likely IE <=8, which doesn't support pageYOffset
250
+ scrollTop = document.documentElement.scrollTop;
251
+ }
252
+ return scrollTop;
253
+ },
254
+
255
+ /**
256
+ * @private
257
+ */
258
+ getScrollLeft: function() {
259
+ var scrollLeft;
260
+ if (typeof window.pageXOffset !== undefinedStr) {
261
+ scrollLeft = window.pageXOffset;
262
+ }
263
+ else {
264
+ // Most likely IE <=8, which doesn't support pageXOffset
265
+ scrollLeft = document.documentElement.scrollLeft;
266
+ }
267
+ return scrollLeft;
268
+ },
269
+
270
+ /**
271
+ * @private
272
+ */
273
+ getWindowHeight: function() {
274
+ return window.innerHeight || document.documentElement.clientHeight;
275
+ },
276
+
277
+ /**
278
+ * @private
279
+ */
280
+ getWindowWidth: function() {
281
+ return window.innerWidth || document.documentElement.clientWidth;
282
+ },
283
+
284
+ /**
285
+ * @private
286
+ */
287
+ addEvtListener: function(el, evtName, fn) {
288
+ return el.addEventListener ? el.addEventListener(evtName, fn, false) : el.attachEvent('on' + evtName, fn);
289
+ },
290
+
291
+ /**
292
+ * @private
293
+ */
294
+ removeEvtListener: function(el, evtName, fn) {
295
+ return el.removeEventListener ? el.removeEventListener(evtName, fn, false) : el.detachEvent('on' + evtName, fn);
296
+ },
297
+
298
+ documentIsReady: function() {
299
+ return document.readyState === 'complete' || document.readyState === 'interactive';
300
+ },
301
+
302
+ /**
303
+ * @private
304
+ */
305
+ evtPreventDefault: function(evt) {
306
+ if (evt.preventDefault) {
307
+ evt.preventDefault();
308
+ }
309
+ else if (event) {
310
+ event.returnValue = false;
311
+ }
312
+ },
313
+
314
+ /**
315
+ * @private
316
+ */
317
+ extend: function(obj1, obj2) {
318
+ var prop;
319
+ for (prop in obj2) {
320
+ if (obj2.hasOwnProperty(prop)) {
321
+ obj1[prop] = obj2[prop];
322
+ }
323
+ }
324
+ },
325
+
326
+ /**
327
+ * Helper function to get a single target DOM element. We will try to
328
+ * locate the DOM element through several ways, in the following order:
329
+ *
330
+ * 1) Passing the string into document.querySelector
331
+ * 2) Passing the string to jQuery, if it exists
332
+ * 3) Passing the string to Sizzle, if it exists
333
+ * 4) Calling document.getElementById if it is a plain id
334
+ *
335
+ * Default case is to assume the string is a plain id and call
336
+ * document.getElementById on it.
337
+ *
338
+ * @private
339
+ */
340
+ getStepTargetHelper: function(target){
341
+ var result = document.getElementById(target);
342
+
343
+ //Backwards compatibility: assume the string is an id
344
+ if (result) {
345
+ return result;
346
+ }
347
+ if (document.querySelector) {
348
+ return document.querySelector(target);
349
+ }
350
+ if (hasJquery) {
351
+ result = jQuery(target);
352
+ return result.length ? result[0] : null;
353
+ }
354
+ if (Sizzle) {
355
+ result = new Sizzle(target);
356
+ return result.length ? result[0] : null;
357
+ }
358
+ // Regex test for id. Following the HTML 4 spec for valid id formats.
359
+ // (http://www.w3.org/TR/html4/types.html#type-id)
360
+ if (/^#[a-zA-Z][\w-_:.]*$/.test(target)) {
361
+ return document.getElementById(target.substring(1));
362
+ }
363
+
364
+ return null;
365
+ },
366
+
367
+ /**
368
+ * Given a step, returns the target DOM element associated with it. It is
369
+ * recommended to only assign one target per step. However, there are
370
+ * some use cases which require multiple step targets to be supplied. In
371
+ * this event, we will use the first target in the array that we can
372
+ * locate on the page. See the comments for getStepTargetHelper for more
373
+ * information.
374
+ *
375
+ * @private
376
+ */
377
+ getStepTarget: function(step) {
378
+ var queriedTarget;
379
+
380
+ if (!step || !step.target) {
381
+ return null;
382
+ }
383
+
384
+ if (typeof step.target === 'string') {
385
+ //Just one target to test. Check, cache, and return its results.
386
+ step.target = utils.getStepTargetHelper(step.target);
387
+ return step.target;
388
+ }
389
+ else if (Array.isArray(step.target)) {
390
+ // Multiple items to check. Check each and return the first success.
391
+ // Assuming they are all strings.
392
+ var i,
393
+ len;
394
+
395
+ for (i = 0, len = step.target.length; i < len; i++){
396
+ if (typeof step.target[i] === 'string') {
397
+ queriedTarget = utils.getStepTargetHelper(step.target[i]);
398
+
399
+ if (queriedTarget) {
400
+ // Replace step.target with result so we don't have to look it up again.
401
+ step.target = queriedTarget;
402
+ return queriedTarget;
403
+ }
404
+ }
405
+ }
406
+ return null;
407
+ }
408
+
409
+ // Assume that the step.target is a DOM element
410
+ return step.target;
411
+ },
412
+
413
+ /**
414
+ * Convenience method for getting an i18n string. Returns custom i18n value
415
+ * or the default i18n value if no custom value exists.
416
+ *
417
+ * @private
418
+ */
419
+ getI18NString: function(key) {
420
+ return customI18N[key] || HopscotchI18N[key];
421
+ },
422
+
423
+ // Tour session persistence for multi-page tours. Uses HTML5 sessionStorage if available, then
424
+ // falls back to using cookies.
425
+ //
426
+ // The following cookie-related logic is borrowed from:
427
+ // http://www.quirksmode.org/js/cookies.html
428
+
429
+ /**
430
+ * @private
431
+ */
432
+ setState: function(name,value,days) {
433
+ var expires = '',
434
+ date;
435
+
436
+ if (hasSessionStorage) {
437
+ sessionStorage.setItem(name, value);
438
+ }
439
+ else {
440
+ if (days) {
441
+ date = new Date();
442
+ date.setTime(date.getTime()+(days*24*60*60*1000));
443
+ expires = '; expires='+date.toGMTString();
444
+ }
445
+ document.cookie = name+'='+value+expires+'; path=/';
446
+ }
447
+ },
448
+
449
+ /**
450
+ * @private
451
+ */
452
+ getState: function(name) {
453
+ var nameEQ = name + '=',
454
+ ca = document.cookie.split(';'),
455
+ i,
456
+ c,
457
+ state;
458
+
459
+ if (hasSessionStorage) {
460
+ state = sessionStorage.getItem(name);
461
+ }
462
+ else {
463
+ for(i=0;i < ca.length;i++) {
464
+ c = ca[i];
465
+ while (c.charAt(0)===' ') {c = c.substring(1,c.length);}
466
+ if (c.indexOf(nameEQ) === 0) {
467
+ state = c.substring(nameEQ.length,c.length);
468
+ break;
469
+ }
470
+ }
471
+ }
472
+
473
+ return state;
474
+ },
475
+
476
+ /**
477
+ * @private
478
+ */
479
+ clearState: function(name) {
480
+ if (hasSessionStorage) {
481
+ sessionStorage.removeItem(name);
482
+ }
483
+ else {
484
+ this.setState(name,'',-1);
485
+ }
486
+ }
487
+ };
488
+
489
+ utils.addEvtListener(window, 'load', winLoadHandler);
490
+
491
+ callbacks = {
492
+ next: [],
493
+ prev: [],
494
+ start: [],
495
+ end: [],
496
+ show: [],
497
+ error: [],
498
+ close: []
499
+ };
500
+
501
+ /**
502
+ * helpers
503
+ * =======
504
+ * A map of functions to be used as callback listeners. Functions are
505
+ * added to and removed from the map using the functions
506
+ * Hopscotch.registerHelper() and Hopscotch.unregisterHelper().
507
+ */
508
+ helpers = {};
509
+
510
+ HopscotchI18N = {
511
+ stepNums: null,
512
+ nextBtn: 'Next',
513
+ prevBtn: 'Back',
514
+ doneBtn: 'Done',
515
+ skipBtn: 'Skip',
516
+ closeTooltip: 'Close'
517
+ };
518
+
519
+ customI18N = {}; // Developer's custom i18n strings goes here.
520
+
521
+ /**
522
+ * HopscotchBubble
523
+ *
524
+ * @class The HopscotchBubble class represents the view of a bubble. This class is also used for Hopscotch callouts.
525
+ */
526
+ HopscotchBubble = function(opt) {
527
+ this.init(opt);
528
+ };
529
+
530
+ HopscotchBubble.prototype = {
531
+ isShowing: false,
532
+
533
+ currStep: undefined,
534
+
535
+ /**
536
+ * setPosition
537
+ *
538
+ * Sets the position of the bubble using the bounding rectangle of the
539
+ * target element and the orientation and offset information specified by
540
+ * the JSON.
541
+ */
542
+ setPosition: function(step) {
543
+ var bubbleBoundingHeight,
544
+ bubbleBoundingWidth,
545
+ boundingRect,
546
+ top,
547
+ left,
548
+ arrowOffset,
549
+ self = this,
550
+ targetEl = utils.getStepTarget(step),
551
+ el = this.element,
552
+ arrowEl = this.arrowEl;
553
+
554
+ bubbleBoundingWidth = el.offsetWidth;
555
+ bubbleBoundingHeight = el.offsetHeight;
556
+ utils.removeClass(el, 'fade-in-down fade-in-up fade-in-left fade-in-right');
557
+
558
+ // Originally called it orientation, but placement is more intuitive.
559
+ // Allowing both for now for backwards compatibility.
560
+ if (!step.placement && step.orientation) {
561
+ step.placement = step.orientation;
562
+ }
563
+
564
+ // SET POSITION
565
+ boundingRect = targetEl.getBoundingClientRect();
566
+ if (step.placement === 'top') {
567
+ top = (boundingRect.top - bubbleBoundingHeight) - this.opt.arrowWidth;
568
+ left = boundingRect.left;
569
+ }
570
+ else if (step.placement === 'bottom') {
571
+ top = boundingRect.bottom + this.opt.arrowWidth;
572
+ left = boundingRect.left;
573
+ }
574
+ else if (step.placement === 'left') {
575
+ top = boundingRect.top;
576
+ left = boundingRect.left - bubbleBoundingWidth - this.opt.arrowWidth;
577
+ }
578
+ else if (step.placement === 'right') {
579
+ top = boundingRect.top;
580
+ left = boundingRect.right + this.opt.arrowWidth;
581
+ }
582
+ else {
583
+ throw 'Bubble placement failed because step.placement is invalid or undefined!';
584
+ }
585
+
586
+ // UPDATE HIGHLIGHT
587
+ self.highlight.setPosition(step, boundingRect);
588
+
589
+
590
+ // SET (OR RESET) ARROW OFFSETS
591
+ if (step.arrowOffset !== 'center') {
592
+ arrowOffset = utils.getPixelValue(step.arrowOffset);
593
+ }
594
+ else {
595
+ arrowOffset = step.arrowOffset;
596
+ }
597
+ if (!arrowOffset) {
598
+ arrowEl.style.top = '';
599
+ arrowEl.style.left = '';
600
+ }
601
+ else if (step.placement === 'top' || step.placement === 'bottom') {
602
+ arrowEl.style.top = '';
603
+ if (arrowOffset === 'center') {
604
+ arrowEl.style.left = Math.floor((bubbleBoundingWidth / 2) - arrowEl.offsetWidth/2) + 'px';
605
+ }
606
+ else {
607
+ // Numeric pixel value
608
+ arrowEl.style.left = arrowOffset + 'px';
609
+ }
610
+ }
611
+ else if (step.placement === 'left' || step.placement === 'right') {
612
+ arrowEl.style.left = '';
613
+ if (arrowOffset === 'center') {
614
+ arrowEl.style.top = Math.floor((bubbleBoundingHeight / 2) - arrowEl.offsetHeight/2) + 'px';
615
+ }
616
+ else {
617
+ // Numeric pixel value
618
+ arrowEl.style.top = arrowOffset + 'px';
619
+ }
620
+ }
621
+
622
+ // HORIZONTAL OFFSET
623
+ if (step.xOffset === 'center') {
624
+ left = (boundingRect.left + targetEl.offsetWidth/2) - (bubbleBoundingWidth / 2);
625
+ }
626
+ else {
627
+ left += utils.getPixelValue(step.xOffset);
628
+ }
629
+ // VERTICAL OFFSET
630
+ if (step.yOffset === 'center') {
631
+ top = (boundingRect.top + targetEl.offsetHeight/2) - (bubbleBoundingHeight / 2);
632
+ }
633
+ else {
634
+ top += utils.getPixelValue(step.yOffset);
635
+ }
636
+
637
+ // ADJUST TOP FOR SCROLL POSITION
638
+ if (!step.fixedElement) {
639
+ top += utils.getScrollTop();
640
+ left += utils.getScrollLeft();
641
+ }
642
+
643
+ // ACCOUNT FOR FIXED POSITION ELEMENTS
644
+ el.style.position = (step.fixedElement ? 'fixed' : 'absolute');
645
+
646
+ el.style.top = top + 'px';
647
+ el.style.left = left + 'px';
648
+ },
649
+
650
+ /**
651
+ * Renders the bubble according to the step JSON.
652
+ *
653
+ * @param {Object} step Information defining how the bubble should look.
654
+ * @param {Number} idx The index of the step in the tour. Not used for callouts.
655
+ * @param {Function} callback Function to be invoked after rendering is finished.
656
+ */
657
+ render: function(step, idx, callback) {
658
+ var el = this.element,
659
+ tourSpecificRenderer,
660
+ customTourData,
661
+ unsafe,
662
+ currTour,
663
+ totalSteps,
664
+ nextBtnText,
665
+ isLast,
666
+ opts;
667
+
668
+ // Cache current step information.
669
+ if (step) {
670
+ this.currStep = step;
671
+ }
672
+ else if (this.currStep) {
673
+ step = this.currStep;
674
+ }
675
+
676
+ // update highlight with current step information
677
+ this.highlight.render(step);
678
+
679
+ // Check current tour for total number of steps and custom render data
680
+ if(this.opt.isTourBubble){
681
+ currTour = winHopscotch.getCurrTour();
682
+ if(currTour){
683
+ customTourData = currTour.customData;
684
+ tourSpecificRenderer = currTour.customRenderer;
685
+ unsafe = currTour.unsafe;
686
+ if(Array.isArray(currTour.steps)){
687
+ totalSteps = currTour.steps.length;
688
+ isLast = (idx === totalSteps - 1);
689
+ }
690
+ }
691
+ }else{
692
+ customTourData = step.customData;
693
+ tourSpecificRenderer = step.customRenderer;
694
+ unsafe = step.unsafe;
695
+ }
696
+
697
+ // Determine label for next button
698
+ if(isLast){
699
+ nextBtnText = utils.getI18NString('doneBtn');
700
+ } else if(step.showSkip) {
701
+ nextBtnText = utils.getI18NString('skipBtn');
702
+ } else {
703
+ nextBtnText = utils.getI18NString('nextBtn');
704
+ }
705
+
706
+ // Originally called it orientation, but placement is more intuitive.
707
+ // Allowing both for now for backwards compatibility.
708
+ if (!step.placement && step.orientation) {
709
+ step.placement = step.orientation;
710
+ }
711
+ this.placement = step.placement;
712
+
713
+ // Setup the configuration options we want to pass along to the template
714
+ opts = {
715
+ i18n: {
716
+ prevBtn: utils.getI18NString('prevBtn'),
717
+ nextBtn: nextBtnText,
718
+ closeTooltip: utils.getI18NString('closeTooltip'),
719
+ stepNum: this._getStepI18nNum(idx),
720
+ },
721
+ buttons:{
722
+ showPrev: (utils.valOrDefault(step.showPrevButton, this.opt.showPrevButton) && (idx > 0)),
723
+ showNext: utils.valOrDefault(step.showNextButton, this.opt.showNextButton),
724
+ showCTA: utils.valOrDefault((step.showCTAButton && step.ctaLabel), false),
725
+ ctaLabel: step.ctaLabel,
726
+ showClose: utils.valOrDefault(this.opt.showCloseButton, true)
727
+ },
728
+ step:{
729
+ num: idx,
730
+ isLast: utils.valOrDefault(isLast, false),
731
+ title: (step.title || ''),
732
+ content: (step.content || ''),
733
+ placement: step.placement,
734
+ padding: utils.valOrDefault(step.padding, this.opt.bubblePadding),
735
+ width: utils.getPixelValue(step.width) || this.opt.bubbleWidth,
736
+ customData: (step.customData || {})
737
+ },
738
+ tour:{
739
+ isTour: this.opt.isTourBubble,
740
+ numSteps: totalSteps,
741
+ unsafe: utils.valOrDefault(unsafe, false),
742
+ customData: (customTourData || {})
743
+ }
744
+ };
745
+
746
+ // Render the bubble's content.
747
+ // Use tour renderer if available, then the global customRenderer if defined.
748
+ if(typeof tourSpecificRenderer === 'function'){
749
+ el.innerHTML = tourSpecificRenderer(opts);
750
+ }
751
+ else if(typeof tourSpecificRenderer === 'string'){
752
+ if(!hopscotch.templates || (typeof hopscotch.templates[tourSpecificRenderer] !== 'function')){
753
+ throw 'Bubble rendering failed - template "' + tourSpecificRenderer + '" is not a function.';
754
+ }
755
+ el.innerHTML = hopscotch.templates[tourSpecificRenderer](opts);
756
+ }
757
+ else if(customRenderer){
758
+ el.innerHTML = customRenderer(opts);
759
+ }
760
+ else{
761
+ if(!hopscotch.templates || (typeof hopscotch.templates[templateToUse] !== 'function')){
762
+ throw 'Bubble rendering failed - template "' + templateToUse + '" is not a function.';
763
+ }
764
+ el.innerHTML = hopscotch.templates[templateToUse](opts);
765
+ }
766
+
767
+ // Find arrow among new child elements.
768
+ children = el.children;
769
+ numChildren = children.length;
770
+ for (i = 0; i < numChildren; i++){
771
+ node = children[i];
772
+
773
+ if(utils.hasClass(node, 'hopscotch-arrow')){
774
+ this.arrowEl = node;
775
+ }
776
+ }
777
+
778
+ // Set z-index and arrow placement
779
+ el.style.zIndex = step.zindex || '';
780
+ this._setArrow(step.placement);
781
+
782
+ // Set bubble positioning
783
+ // Make sure we're using visibility:hidden instead of display:none for height/width calculations.
784
+ this.hide(false);
785
+ this.setPosition(step);
786
+
787
+ // only want to adjust window scroll for non-fixed elements
788
+ if (callback) {
789
+ callback(!step.fixedElement);
790
+ }
791
+
792
+ return this;
793
+ },
794
+
795
+ /**
796
+ * Get the I18N step number for the current step.
797
+ *
798
+ * @private
799
+ */
800
+ _getStepI18nNum: function(idx) {
801
+ var stepNumI18N = utils.getI18NString('stepNums');
802
+ if (stepNumI18N && idx < stepNumI18N.length) {
803
+ idx = stepNumI18N[idx];
804
+ }
805
+ else {
806
+ idx = idx + 1;
807
+ }
808
+ return idx;
809
+ },
810
+
811
+ /**
812
+ * Sets which side the arrow is on.
813
+ *
814
+ * @private
815
+ */
816
+ _setArrow: function(orientation) {
817
+ utils.removeClass(this.arrowEl, 'down up right left');
818
+
819
+ // Whatever the orientation is, we want to arrow to appear
820
+ // "opposite" of the orientation. E.g., a top orientation
821
+ // requires a bottom arrow.
822
+ if (orientation === 'top') {
823
+ utils.addClass(this.arrowEl, 'down');
824
+ }
825
+ else if (orientation === 'bottom') {
826
+ utils.addClass(this.arrowEl, 'up');
827
+ }
828
+ else if (orientation === 'left') {
829
+ utils.addClass(this.arrowEl, 'right');
830
+ }
831
+ else if (orientation === 'right') {
832
+ utils.addClass(this.arrowEl, 'left');
833
+ }
834
+ },
835
+
836
+ /**
837
+ * @private
838
+ */
839
+ _getArrowDirection: function() {
840
+ if (this.placement === 'top') {
841
+ return 'down';
842
+ }
843
+ if (this.placement === 'bottom') {
844
+ return 'up';
845
+ }
846
+ if (this.placement === 'left') {
847
+ return 'right';
848
+ }
849
+ if (this.placement === 'right') {
850
+ return 'left';
851
+ }
852
+ },
853
+
854
+ show: function() {
855
+ var self = this,
856
+ fadeClass = 'fade-in-' + this._getArrowDirection(),
857
+ fadeDur = 1000;
858
+
859
+ utils.removeClass(this.element, 'hide');
860
+ utils.addClass(this.element, fadeClass);
861
+ setTimeout(function() {
862
+ utils.removeClass(self.element, 'invisible');
863
+ }, 50);
864
+ setTimeout(function() {
865
+ utils.removeClass(self.element, fadeClass);
866
+ }, fadeDur);
867
+ this.isShowing = true;
868
+ this.highlight.show();
869
+
870
+ return this;
871
+ },
872
+
873
+ hide: function(remove) {
874
+ var el = this.element;
875
+
876
+ remove = utils.valOrDefault(remove, true);
877
+ el.style.top = '';
878
+ el.style.left = '';
879
+
880
+ // display: none
881
+ if (remove) {
882
+ utils.addClass(el, 'hide');
883
+ utils.removeClass(el, 'invisible');
884
+ }
885
+ // opacity: 0
886
+ else {
887
+ utils.removeClass(el, 'hide');
888
+ utils.addClass(el, 'invisible');
889
+ }
890
+ utils.removeClass(el, 'animate fade-in-up fade-in-down fade-in-right fade-in-left');
891
+ this.isShowing = false;
892
+ this.highlight.hide();
893
+
894
+ return this;
895
+ },
896
+
897
+ destroy: function() {
898
+ var el = this.element;
899
+
900
+ if (el) {
901
+ el.parentNode.removeChild(el);
902
+ }
903
+ utils.removeEvtListener(el, 'click', this.clickCb);
904
+ },
905
+
906
+ _handleBubbleClick: function(evt){
907
+ var action;
908
+
909
+ //Recursively look up the parent tree until we find a match
910
+ //with one of the classes we're looking for, or the triggering element.
911
+ function findMatchRecur(el){
912
+ /* We're going to make the assumption that we're not binding
913
+ * multiple event classes to the same element.
914
+ * (next + previous = wait... err... what?)
915
+ *
916
+ * In the odd event we end up with an element with multiple
917
+ * possible matches, the following priority order is applied:
918
+ * hopscotch-cta, hopscotch-next, hopscotch-prev, hopscotch-close
919
+ */
920
+ if(el === evt.currentTarget){ return null; }
921
+ if(utils.hasClass(el, 'hopscotch-cta')){ return 'cta'; }
922
+ if(utils.hasClass(el, 'hopscotch-next')){ return 'next'; }
923
+ if(utils.hasClass(el, 'hopscotch-prev')){ return 'prev'; }
924
+ if(utils.hasClass(el, 'hopscotch-close')){ return 'close'; }
925
+ /*else*/ return findMatchRecur(el.parentElement);
926
+ }
927
+
928
+ action = findMatchRecur(evt.target);
929
+
930
+ //Now that we know what action we should take, let's take it.
931
+ if (action === 'cta'){
932
+ if (!this.opt.isTourBubble) {
933
+ // This is a callout. Close the callout when CTA is clicked.
934
+ winHopscotch.getCalloutManager().removeCallout(this.currStep.id);
935
+ }
936
+ // Call onCTA callback if one is provided
937
+ if (this.currStep.onCTA) {
938
+ utils.invokeCallback(this.currStep.onCTA);
939
+ }
940
+ }
941
+ else if (action === 'next'){
942
+ winHopscotch.nextStep(true);
943
+ }
944
+ else if (action === 'prev'){
945
+ winHopscotch.prevStep(true);
946
+ }
947
+ else if (action === 'close'){
948
+ if (this.opt.isTourBubble){
949
+ var currStepNum = winHopscotch.getCurrStepNum(),
950
+ currTour = winHopscotch.getCurrTour(),
951
+ doEndCallback = (currStepNum === currTour.steps.length-1);
952
+
953
+ utils.invokeEventCallbacks('close');
954
+
955
+ winHopscotch.endTour(true, doEndCallback);
956
+ } else {
957
+ if (this.opt.onClose) {
958
+ utils.invokeCallback(this.opt.onClose);
959
+ }
960
+ if (this.opt.id && !this.opt.isTourBubble) {
961
+ // Remove via the HopscotchCalloutManager.
962
+ // removeCallout() calls HopscotchBubble.destroy internally.
963
+ winHopscotch.getCalloutManager().removeCallout(this.opt.id);
964
+ }
965
+ else {
966
+ this.destroy();
967
+ }
968
+ }
969
+
970
+ utils.evtPreventDefault(evt);
971
+ }
972
+ //Otherwise, do nothing. We didn't click on anything relevant.
973
+ },
974
+
975
+ init: function(initOpt) {
976
+ var el = document.createElement('div'),
977
+ self = this,
978
+ resizeCooldown = false, // for updating after window resize
979
+ onWinResize,
980
+ appendToBody,
981
+ children,
982
+ numChildren,
983
+ node,
984
+ i,
985
+ opt;
986
+
987
+ //Register DOM element for this bubble.
988
+ this.element = el;
989
+
990
+ //Merge bubble options with defaults.
991
+ opt = {
992
+ showPrevButton: defaultOpts.showPrevButton,
993
+ showNextButton: defaultOpts.showNextButton,
994
+ bubbleWidth: defaultOpts.bubbleWidth,
995
+ bubblePadding: defaultOpts.bubblePadding,
996
+ arrowWidth: defaultOpts.arrowWidth,
997
+ showNumber: true,
998
+ isTourBubble: true
999
+ };
1000
+ initOpt = (typeof initOpt === undefinedStr ? {} : initOpt);
1001
+ utils.extend(opt, initOpt);
1002
+ this.opt = opt;
1003
+
1004
+ //Apply classes to bubble. Add "animated" for fade css animation
1005
+ el.className = 'hopscotch-bubble animated';
1006
+ if (!opt.isTourBubble) {
1007
+ utils.addClass(el, 'hopscotch-callout no-number');
1008
+ }
1009
+
1010
+ self.highlight = new HopscotchHighlight(initOpt);
1011
+
1012
+ /**
1013
+ * Not pretty, but IE8 doesn't support Function.bind(), so I'm
1014
+ * relying on closures to keep a handle of "this".
1015
+ * Reset position of bubble when window is resized
1016
+ *
1017
+ * @private
1018
+ */
1019
+ onWinResize = function() {
1020
+ if (resizeCooldown || !self.isShowing) {
1021
+ return;
1022
+ }
1023
+
1024
+ resizeCooldown = true;
1025
+ setTimeout(function() {
1026
+ self.setPosition(self.currStep);
1027
+ resizeCooldown = false;
1028
+ }, 100);
1029
+ };
1030
+
1031
+ //Add listener to reset bubble position on window resize
1032
+ utils.addEvtListener(window, 'resize', onWinResize);
1033
+
1034
+ //Create our click callback handler and keep a
1035
+ //reference to it for later.
1036
+ this.clickCb = function(evt){
1037
+ self._handleBubbleClick(evt);
1038
+ };
1039
+ utils.addEvtListener(el, 'click', this.clickCb);
1040
+
1041
+ //Hide the bubble by default
1042
+ this.hide();
1043
+
1044
+ //Finally, append our new bubble to body once the DOM is ready.
1045
+ if (utils.documentIsReady()) {
1046
+ document.body.appendChild(el);
1047
+ self.highlight.addToDom();
1048
+ }
1049
+ else {
1050
+ // Moz, webkit, Opera
1051
+ if (document.addEventListener) {
1052
+ appendToBody = function() {
1053
+ document.removeEventListener('DOMContentLoaded', appendToBody);
1054
+ window.removeEventListener('load', appendToBody);
1055
+
1056
+ document.body.appendChild(el);
1057
+ self.highlight.addToDom();
1058
+ };
1059
+
1060
+ document.addEventListener('DOMContentLoaded', appendToBody, false);
1061
+ }
1062
+ // IE
1063
+ else {
1064
+ appendToBody = function() {
1065
+ if (document.readyState === 'complete') {
1066
+ document.detachEvent('onreadystatechange', appendToBody);
1067
+ window.detachEvent('onload', appendToBody);
1068
+ document.body.appendChild(el);
1069
+ self.highlight.addToDom();
1070
+ }
1071
+ };
1072
+
1073
+ document.attachEvent('onreadystatechange', appendToBody);
1074
+ }
1075
+ utils.addEvtListener(window, 'load', appendToBody);
1076
+ }
1077
+ }
1078
+ };
1079
+
1080
+ /**
1081
+ * HopscotchCalloutManager
1082
+ *
1083
+ * @class Manages the creation and destruction of single callouts.
1084
+ * @constructor
1085
+ */
1086
+ HopscotchCalloutManager = function() {
1087
+ var callouts = {};
1088
+
1089
+ /**
1090
+ * createCallout
1091
+ *
1092
+ * Creates a standalone callout. This callout has the same API
1093
+ * as a Hopscotch tour bubble.
1094
+ *
1095
+ * @param {Object} opt The options for the callout. For the most
1096
+ * part, these are the same options as you would find in a tour
1097
+ * step.
1098
+ */
1099
+ this.createCallout = function(opt) {
1100
+ var callout;
1101
+
1102
+ if (opt.id) {
1103
+ if (callouts[opt.id]) {
1104
+ throw 'Callout by that id already exists. Please choose a unique id.';
1105
+ }
1106
+ opt.showNextButton = opt.showPrevButton = false;
1107
+ opt.isTourBubble = false;
1108
+ callout = new HopscotchBubble(opt);
1109
+ callouts[opt.id] = callout;
1110
+ if (opt.target) {
1111
+ callout.render(opt, null, function() {
1112
+ callout.show();
1113
+ });
1114
+ }
1115
+ }
1116
+ else {
1117
+ throw 'Must specify a callout id.';
1118
+ }
1119
+ return callout;
1120
+ };
1121
+
1122
+ /**
1123
+ * getCallout
1124
+ *
1125
+ * Returns a callout by its id.
1126
+ *
1127
+ * @param {String} id The id of the callout to fetch.
1128
+ * @returns {Object} HopscotchBubble
1129
+ */
1130
+ this.getCallout = function(id) {
1131
+ return callouts[id];
1132
+ };
1133
+
1134
+ /**
1135
+ * removeAllCallouts
1136
+ *
1137
+ * Removes all existing callouts.
1138
+ */
1139
+ this.removeAllCallouts = function() {
1140
+ var calloutId,
1141
+ callout;
1142
+
1143
+ for (calloutId in callouts) {
1144
+ if (callouts.hasOwnProperty(calloutId)) {
1145
+ this.removeCallout(calloutId);
1146
+ }
1147
+ }
1148
+ };
1149
+
1150
+ /**
1151
+ * removeAllCallout
1152
+ *
1153
+ * Removes an existing callout by id.
1154
+ *
1155
+ * @param {String} id The id of the callout to remove.
1156
+ */
1157
+ this.removeCallout = function(id) {
1158
+ var callout = callouts[id];
1159
+
1160
+ callouts[id] = null;
1161
+ if (!callout) { return; }
1162
+
1163
+ callout.destroy();
1164
+ };
1165
+ };
1166
+
1167
+ /**
1168
+ * Hopscotch
1169
+ *
1170
+ * @class Creates the Hopscotch object. Used to manage tour progress and configurations.
1171
+ * @constructor
1172
+ * @param {Object} initOptions Options to be passed to `configure()`.
1173
+ */
1174
+ Hopscotch = function(initOptions) {
1175
+ var self = this, // for targetClickNextFn
1176
+ bubble,
1177
+ calloutMgr,
1178
+ opt,
1179
+ currTour,
1180
+ currStepNum,
1181
+ cookieTourId,
1182
+ cookieTourStep,
1183
+ _configure,
1184
+
1185
+ /**
1186
+ * getBubble
1187
+ *
1188
+ * Singleton accessor function for retrieving or creating bubble object.
1189
+ *
1190
+ * @private
1191
+ * @param setOptions {Boolean} when true, transfers configuration options to the bubble
1192
+ * @returns {Object} HopscotchBubble
1193
+ */
1194
+ getBubble = function(setOptions) {
1195
+ if (!bubble) {
1196
+ bubble = new HopscotchBubble(opt);
1197
+ }
1198
+ if (setOptions) {
1199
+ utils.extend(bubble.opt, {
1200
+ bubblePadding: getOption('bubblePadding'),
1201
+ bubbleWidth: getOption('bubbleWidth'),
1202
+ showNextButton: getOption('showNextButton'),
1203
+ showPrevButton: getOption('showPrevButton'),
1204
+ showCloseButton: getOption('showCloseButton'),
1205
+ arrowWidth: getOption('arrowWidth')
1206
+ });
1207
+ }
1208
+ return bubble;
1209
+ },
1210
+
1211
+ /**
1212
+ * Convenience method for getting an option. Returns custom config option
1213
+ * or the default config option if no custom value exists.
1214
+ *
1215
+ * @private
1216
+ * @param name {String} config option name
1217
+ * @returns {Object} config option value
1218
+ */
1219
+ getOption = function(name) {
1220
+ if (typeof opt === 'undefined') {
1221
+ return defaultOpts[name];
1222
+ }
1223
+ return utils.valOrDefault(opt[name], defaultOpts[name]);
1224
+ },
1225
+
1226
+ /**
1227
+ * getCurrStep
1228
+ *
1229
+ * @private
1230
+ * @returns {Object} the step object corresponding to the current value of currStepNum
1231
+ */
1232
+ getCurrStep = function() {
1233
+ var step;
1234
+
1235
+ if (currStepNum < 0 || currStepNum >= currTour.steps.length) {
1236
+ step = null;
1237
+ }
1238
+ else {
1239
+ step = currTour.steps[currStepNum];
1240
+ }
1241
+
1242
+ return step;
1243
+ },
1244
+
1245
+ /**
1246
+ * Used for nextOnTargetClick
1247
+ *
1248
+ * @private
1249
+ */
1250
+ targetClickNextFn = function() {
1251
+ self.nextStep();
1252
+ },
1253
+
1254
+ /**
1255
+ * adjustWindowScroll
1256
+ *
1257
+ * Checks if the bubble or target element is partially or completely
1258
+ * outside of the viewport. If it is, adjust the window scroll position
1259
+ * to bring it back into the viewport.
1260
+ *
1261
+ * @private
1262
+ * @param {Function} cb Callback to invoke after done scrolling.
1263
+ */
1264
+ adjustWindowScroll = function(cb) {
1265
+ var bubble = getBubble(),
1266
+
1267
+ // Calculate the bubble element top and bottom position
1268
+ bubbleEl = bubble.element,
1269
+ bubbleTop = utils.getPixelValue(bubbleEl.style.top),
1270
+ bubbleBottom = bubbleTop + utils.getPixelValue(bubbleEl.offsetHeight),
1271
+
1272
+ // Calculate the target element top and bottom position
1273
+ targetEl = utils.getStepTarget(getCurrStep()),
1274
+ targetBounds = targetEl.getBoundingClientRect(),
1275
+ targetElTop = targetBounds.top + utils.getScrollTop(),
1276
+ targetElBottom = targetBounds.bottom + utils.getScrollTop(),
1277
+
1278
+ // The higher of the two: bubble or target
1279
+ targetTop = (bubbleTop < targetElTop) ? bubbleTop : targetElTop,
1280
+ // The lower of the two: bubble or target
1281
+ targetBottom = (bubbleBottom > targetElBottom) ? bubbleBottom : targetElBottom,
1282
+
1283
+ // Calculate the current viewport top and bottom
1284
+ windowTop = utils.getScrollTop(),
1285
+ windowBottom = windowTop + utils.getWindowHeight(),
1286
+
1287
+ // This is our final target scroll value.
1288
+ scrollToVal = targetTop - getOption('scrollTopMargin'),
1289
+
1290
+ scrollEl,
1291
+ yuiAnim,
1292
+ yuiEase,
1293
+ direction,
1294
+ scrollIncr,
1295
+ scrollTimeout,
1296
+ scrollTimeoutFn;
1297
+
1298
+ // Target and bubble are both visible in viewport
1299
+ if (targetTop >= windowTop && (targetTop <= windowTop + getOption('scrollTopMargin') || targetBottom <= windowBottom)) {
1300
+ if (cb) { cb(); } // HopscotchBubble.show
1301
+ }
1302
+
1303
+ // Abrupt scroll to scroll target
1304
+ else if (!getOption('smoothScroll')) {
1305
+ window.scrollTo(0, scrollToVal);
1306
+
1307
+ if (cb) { cb(); } // HopscotchBubble.show
1308
+ }
1309
+
1310
+ // Smooth scroll to scroll target
1311
+ else {
1312
+ // Use YUI if it exists
1313
+ if (typeof YAHOO !== undefinedStr &&
1314
+ typeof YAHOO.env !== undefinedStr &&
1315
+ typeof YAHOO.env.ua !== undefinedStr &&
1316
+ typeof YAHOO.util !== undefinedStr &&
1317
+ typeof YAHOO.util.Scroll !== undefinedStr) {
1318
+ scrollEl = YAHOO.env.ua.webkit ? document.body : document.documentElement;
1319
+ yuiEase = YAHOO.util.Easing ? YAHOO.util.Easing.easeOut : undefined;
1320
+ yuiAnim = new YAHOO.util.Scroll(scrollEl, {
1321
+ scroll: { to: [0, scrollToVal] }
1322
+ }, getOption('scrollDuration')/1000, yuiEase);
1323
+ yuiAnim.onComplete.subscribe(cb);
1324
+ yuiAnim.animate();
1325
+ }
1326
+
1327
+ // Use jQuery if it exists
1328
+ else if (hasJquery) {
1329
+ jQuery('body, html').animate({ scrollTop: scrollToVal }, getOption('scrollDuration'), cb);
1330
+ }
1331
+
1332
+ // Use my crummy setInterval scroll solution if we're using plain, vanilla Javascript.
1333
+ else {
1334
+ if (scrollToVal < 0) {
1335
+ scrollToVal = 0;
1336
+ }
1337
+
1338
+ // 48 * 10 == 480ms scroll duration
1339
+ // make it slightly less than CSS transition duration because of
1340
+ // setInterval overhead.
1341
+ // To increase or decrease duration, change the divisor of scrollIncr.
1342
+ direction = (windowTop > targetTop) ? -1 : 1; // -1 means scrolling up, 1 means down
1343
+ scrollIncr = Math.abs(windowTop - scrollToVal) / (getOption('scrollDuration')/10);
1344
+ scrollTimeoutFn = function() {
1345
+ var scrollTop = utils.getScrollTop(),
1346
+ scrollTarget = scrollTop + (direction * scrollIncr);
1347
+
1348
+ if ((direction > 0 && scrollTarget >= scrollToVal) ||
1349
+ (direction < 0 && scrollTarget <= scrollToVal)) {
1350
+ // Overshot our target. Just manually set to equal the target
1351
+ // and clear the interval
1352
+ scrollTarget = scrollToVal;
1353
+ if (cb) { cb(); } // HopscotchBubble.show
1354
+ window.scrollTo(0, scrollTarget);
1355
+ return;
1356
+ }
1357
+
1358
+ window.scrollTo(0, scrollTarget);
1359
+
1360
+ if (utils.getScrollTop() === scrollTop) {
1361
+ // Couldn't scroll any further.
1362
+ if (cb) { cb(); } // HopscotchBubble.show
1363
+ return;
1364
+ }
1365
+
1366
+ // If we reached this point, that means there's still more to scroll.
1367
+ setTimeout(scrollTimeoutFn, 10);
1368
+ };
1369
+
1370
+ scrollTimeoutFn();
1371
+ }
1372
+ }
1373
+ },
1374
+
1375
+ /**
1376
+ * goToStepWithTarget
1377
+ *
1378
+ * Helper function to increment the step number until a step is found where
1379
+ * the step target exists or until we reach the end/beginning of the tour.
1380
+ *
1381
+ * @private
1382
+ * @param {Number} direction Either 1 for incrementing or -1 for decrementing
1383
+ * @param {Function} cb The callback function to be invoked when the step has been found
1384
+ */
1385
+ goToStepWithTarget = function(direction, cb) {
1386
+ var target,
1387
+ step,
1388
+ goToStepFn;
1389
+
1390
+ if (currStepNum + direction >= 0 &&
1391
+ currStepNum + direction < currTour.steps.length) {
1392
+
1393
+ currStepNum += direction;
1394
+ step = getCurrStep();
1395
+
1396
+ goToStepFn = function() {
1397
+ target = utils.getStepTarget(step);
1398
+
1399
+ if (target) {
1400
+ // We're done! Return the step number via the callback.
1401
+ cb(currStepNum);
1402
+ }
1403
+ else {
1404
+ // Haven't found a valid target yet. Recursively call
1405
+ // goToStepWithTarget.
1406
+ utils.invokeEventCallbacks('error');
1407
+ goToStepWithTarget(direction, cb);
1408
+ }
1409
+ };
1410
+
1411
+ if (step.delay) {
1412
+ setTimeout(goToStepFn, step.delay);
1413
+ }
1414
+ else {
1415
+ goToStepFn();
1416
+ }
1417
+ }
1418
+ else {
1419
+ cb(-1); // signal that we didn't find any step with a valid target
1420
+ }
1421
+ },
1422
+
1423
+ /**
1424
+ * changeStep
1425
+ *
1426
+ * Helper function to change step by going forwards or backwards 1.
1427
+ * nextStep and prevStep are publicly accessible wrappers for this function.
1428
+ *
1429
+ * @private
1430
+ * @param {Boolean} doCallbacks Flag for invoking onNext or onPrev callbacks
1431
+ * @param {Number} direction Either 1 for "next" or -1 for "prev"
1432
+ */
1433
+ changeStep = function(doCallbacks, direction) {
1434
+ var bubble = getBubble(),
1435
+ self = this,
1436
+ step,
1437
+ origStep,
1438
+ wasMultiPage,
1439
+ changeStepCb;
1440
+
1441
+ bubble.hide();
1442
+
1443
+ doCallbacks = utils.valOrDefault(doCallbacks, true);
1444
+ step = getCurrStep();
1445
+ origStep = step;
1446
+ if (direction > 0) {
1447
+ wasMultiPage = origStep.multipage;
1448
+ }
1449
+ else {
1450
+ wasMultiPage = (currStepNum > 0 && currTour.steps[currStepNum-1].multipage);
1451
+ }
1452
+
1453
+ /**
1454
+ * Callback for goToStepWithTarget
1455
+ *
1456
+ * @private
1457
+ */
1458
+ changeStepCb = function(stepNum) {
1459
+ var doShowFollowingStep;
1460
+
1461
+ if (stepNum === -1) {
1462
+ // Wasn't able to find a step with an existing element. End tour.
1463
+ return this.endTour(true);
1464
+ }
1465
+
1466
+ if (doCallbacks) {
1467
+ if (direction > 0) {
1468
+ doShowFollowingStep = utils.invokeEventCallbacks('next', origStep.onNext);
1469
+ }
1470
+ else {
1471
+ doShowFollowingStep = utils.invokeEventCallbacks('prev', origStep.onPrev);
1472
+ }
1473
+ }
1474
+
1475
+ // If the state of the tour is updated in a callback, assume the client
1476
+ // doesn't want to go to next step since they specifically updated.
1477
+ if (stepNum !== currStepNum) {
1478
+ return;
1479
+ }
1480
+
1481
+ if (wasMultiPage) {
1482
+ // Update state for the next page
1483
+ utils.setState(getOption('cookieName'), currTour.id + ':' + currStepNum, 1);
1484
+
1485
+ // Next step is on a different page, so no need to attempt to render it.
1486
+ return;
1487
+ }
1488
+
1489
+ doShowFollowingStep = utils.valOrDefault(doShowFollowingStep, true);
1490
+
1491
+ // If the onNext/onPrev callback returned false, halt the tour and
1492
+ // don't show the next step.
1493
+ if (doShowFollowingStep) {
1494
+ this.showStep(stepNum);
1495
+ }
1496
+ else {
1497
+ // Halt tour (but don't clear state)
1498
+ this.endTour(false);
1499
+ }
1500
+ };
1501
+
1502
+ if (!wasMultiPage && getOption('skipIfNoElement')) {
1503
+ goToStepWithTarget(direction, function(stepNum) {
1504
+ changeStepCb.call(self, stepNum);
1505
+ });
1506
+ }
1507
+ else if (currStepNum + direction >= 0 && currStepNum + direction < currTour.steps.length) {
1508
+ // only try incrementing once, and invoke error callback if no target is found
1509
+ currStepNum += direction;
1510
+ step = getCurrStep();
1511
+ if (!utils.getStepTarget(step) && !wasMultiPage) {
1512
+ utils.invokeEventCallbacks('error');
1513
+ return this.endTour(true, false);
1514
+ }
1515
+ changeStepCb.call(this, currStepNum);
1516
+ }
1517
+
1518
+ return this;
1519
+ },
1520
+
1521
+ /**
1522
+ * loadTour
1523
+ *
1524
+ * Loads, but does not display, tour.
1525
+ *
1526
+ * @private
1527
+ * @param tour The tour JSON object
1528
+ */
1529
+ loadTour = function(tour) {
1530
+ var tmpOpt = {},
1531
+ prop,
1532
+ tourState,
1533
+ tourPair;
1534
+
1535
+ // Set tour-specific configurations
1536
+ for (prop in tour) {
1537
+ if (tour.hasOwnProperty(prop) &&
1538
+ prop !== 'id' &&
1539
+ prop !== 'steps') {
1540
+ tmpOpt[prop] = tour[prop];
1541
+ }
1542
+ }
1543
+
1544
+ //this.resetDefaultOptions(); // reset all options so there are no surprises
1545
+ // TODO check number of config properties of tour
1546
+ _configure.call(this, tmpOpt, true);
1547
+
1548
+ // Get existing tour state, if it exists.
1549
+ tourState = utils.getState(getOption('cookieName'));
1550
+ if (tourState) {
1551
+ tourPair = tourState.split(':');
1552
+ cookieTourId = tourPair[0]; // selecting tour is not supported by this framework.
1553
+ cookieTourStep = tourPair[1];
1554
+
1555
+ cookieTourStep = parseInt(cookieTourStep, 10);
1556
+ }
1557
+
1558
+ return this;
1559
+ },
1560
+
1561
+ /**
1562
+ * Find the first step to show for a tour. (What is the first step with a
1563
+ * target on the page?)
1564
+ */
1565
+ findStartingStep = function(startStepNum, cb) {
1566
+ var step,
1567
+ target,
1568
+ stepNum;
1569
+
1570
+ currStepNum = startStepNum || 0;
1571
+ step = getCurrStep();
1572
+ target = utils.getStepTarget(step);
1573
+
1574
+ if (target) {
1575
+ // First step had an existing target.
1576
+ cb(currStepNum);
1577
+ return;
1578
+ }
1579
+
1580
+ if (!target) {
1581
+ // Previous target doesn't exist either. The user may have just
1582
+ // clicked on a link that wasn't part of the tour. Another possibility is that
1583
+ // the user clicked on the correct link, but the target is just missing for
1584
+ // whatever reason. In either case, we should just advance until we find a step
1585
+ // that has a target on the page or end the tour if we can't find such a step.
1586
+ utils.invokeEventCallbacks('error');
1587
+
1588
+ if (getOption('skipIfNoElement')) {
1589
+ goToStepWithTarget(1, cb);
1590
+ return;
1591
+ }
1592
+ else {
1593
+ currStepNum = -1;
1594
+ cb(currStepNum);
1595
+ }
1596
+ }
1597
+ },
1598
+
1599
+ showStepHelper = function(stepNum) {
1600
+ var step = currTour.steps[stepNum],
1601
+ tourSteps = currTour.steps,
1602
+ numTourSteps = tourSteps.length,
1603
+ cookieVal = currTour.id + ':' + stepNum,
1604
+ bubble = getBubble(),
1605
+ targetEl = utils.getStepTarget(step),
1606
+ isLast,
1607
+ showBubble;
1608
+
1609
+ showBubble = function() {
1610
+ bubble.show();
1611
+ utils.invokeEventCallbacks('show', step.onShow);
1612
+ };
1613
+
1614
+ // Update bubble for current step
1615
+ currStepNum = stepNum;
1616
+
1617
+ bubble.hide(false);
1618
+
1619
+ isLast = (stepNum === numTourSteps - 1);
1620
+ bubble.render(step, stepNum, function(adjustScroll) {
1621
+ // when done adjusting window scroll, call showBubble helper fn
1622
+ if (adjustScroll) {
1623
+ adjustWindowScroll(showBubble);
1624
+ }
1625
+ else {
1626
+ showBubble();
1627
+ }
1628
+
1629
+ // If we want to advance to next step when user clicks on target.
1630
+ if (step.nextOnTargetClick) {
1631
+ utils.addEvtListener(targetEl, 'click', targetClickNextFn);
1632
+ }
1633
+ });
1634
+
1635
+ utils.setState(getOption('cookieName'), cookieVal, 1);
1636
+ },
1637
+
1638
+ /**
1639
+ * init
1640
+ *
1641
+ * Initializes the Hopscotch object.
1642
+ *
1643
+ * @private
1644
+ */
1645
+ init = function(initOptions) {
1646
+ if (initOptions) {
1647
+ //initOptions.cookieName = initOptions.cookieName || 'hopscotch.tour.state';
1648
+ this.configure(initOptions);
1649
+ }
1650
+ };
1651
+
1652
+ /**
1653
+ * getCalloutManager
1654
+ *
1655
+ * Gets the callout manager.
1656
+ *
1657
+ * @returns {Object} HopscotchCalloutManager
1658
+ *
1659
+ */
1660
+ this.getCalloutManager = function() {
1661
+ if (typeof calloutMgr === undefinedStr) {
1662
+ calloutMgr = new HopscotchCalloutManager();
1663
+ }
1664
+
1665
+ return calloutMgr;
1666
+ };
1667
+
1668
+ /**
1669
+ * startTour
1670
+ *
1671
+ * Begins the tour.
1672
+ *
1673
+ * @param {Object} tour The tour JSON object
1674
+ * @stepNum {Number} stepNum __Optional__ The step number to start from
1675
+ * @returns {Object} Hopscotch
1676
+ *
1677
+ */
1678
+ this.startTour = function(tour, stepNum) {
1679
+ var bubble,
1680
+ currStepNum,
1681
+ self = this;
1682
+
1683
+ // loadTour if we are calling startTour directly. (When we call startTour
1684
+ // from window onLoad handler, we'll use currTour)
1685
+ if (!currTour) {
1686
+ currTour = tour;
1687
+ loadTour.call(this, tour);
1688
+ }
1689
+
1690
+ if (typeof stepNum !== undefinedStr) {
1691
+ if (stepNum >= currTour.steps.length) {
1692
+ throw 'Specified step number out of bounds.';
1693
+ }
1694
+ currStepNum = stepNum;
1695
+ }
1696
+
1697
+ // If document isn't ready, wait for it to finish loading.
1698
+ // (so that we can calculate positioning accurately)
1699
+ if (!utils.documentIsReady()) {
1700
+ waitingToStart = true;
1701
+ return this;
1702
+ }
1703
+
1704
+ if (typeof currStepNum === "undefined" && currTour.id === cookieTourId && typeof cookieTourStep !== undefinedStr) {
1705
+ currStepNum = cookieTourStep;
1706
+ }
1707
+ else if (!currStepNum) {
1708
+ currStepNum = 0;
1709
+ }
1710
+
1711
+ // Find the current step we should begin the tour on, and then actually start the tour.
1712
+ findStartingStep(currStepNum, function(stepNum) {
1713
+ var target = (stepNum !== -1) && utils.getStepTarget(currTour.steps[stepNum]);
1714
+
1715
+ if (!target) {
1716
+ // Should we trigger onEnd callback? Let's err on the side of caution
1717
+ // and not trigger it. Don't want weird stuff happening on a page that
1718
+ // wasn't meant for the tour. Up to the developer to fix their tour.
1719
+ self.endTour(false, false);
1720
+ return;
1721
+ }
1722
+
1723
+ utils.invokeEventCallbacks('start');
1724
+
1725
+ bubble = getBubble();
1726
+ // TODO: do we still need this call to .hide()? No longer using opt.animate...
1727
+ // Leaving it in for now to play it safe
1728
+ bubble.hide(false); // make invisible for boundingRect calculations when opt.animate == true
1729
+
1730
+ self.isActive = true;
1731
+
1732
+ if (!utils.getStepTarget(getCurrStep())) {
1733
+ // First step element doesn't exist
1734
+ utils.invokeEventCallbacks('error');
1735
+ if (getOption('skipIfNoElement')) {
1736
+ self.nextStep(false);
1737
+ }
1738
+ }
1739
+ else {
1740
+ self.showStep(stepNum);
1741
+ }
1742
+ });
1743
+
1744
+ return this;
1745
+ };
1746
+
1747
+ /**
1748
+ * showStep
1749
+ *
1750
+ * Skips to a specific step and renders the corresponding bubble.
1751
+ *
1752
+ * @stepNum {Number} stepNum The step number to show
1753
+ * @returns {Object} Hopscotch
1754
+ */
1755
+ this.showStep = function(stepNum) {
1756
+ var step = currTour.steps[stepNum];
1757
+ if (step.delay) {
1758
+ setTimeout(function() {
1759
+ showStepHelper(stepNum);
1760
+ }, step.delay);
1761
+ }
1762
+ else {
1763
+ showStepHelper(stepNum);
1764
+ }
1765
+ return this;
1766
+ };
1767
+
1768
+ /**
1769
+ * prevStep
1770
+ *
1771
+ * Jump to the previous step.
1772
+ *
1773
+ * @param {Boolean} doCallbacks Flag for invoking onPrev callback. Defaults to true.
1774
+ * @returns {Object} Hopscotch
1775
+ */
1776
+ this.prevStep = function(doCallbacks) {
1777
+ changeStep.call(this, doCallbacks, -1);
1778
+ return this;
1779
+ };
1780
+
1781
+ /**
1782
+ * nextStep
1783
+ *
1784
+ * Jump to the next step.
1785
+ *
1786
+ * @param {Boolean} doCallbacks Flag for invoking onNext callback. Defaults to true.
1787
+ * @returns {Object} Hopscotch
1788
+ */
1789
+ this.nextStep = function(doCallbacks) {
1790
+ var step = getCurrStep(),
1791
+ targetEl = utils.getStepTarget(step);
1792
+
1793
+ if (step.nextOnTargetClick) {
1794
+ // Detach the listener after we've clicked on the target OR the next button.
1795
+ utils.removeEvtListener(targetEl, 'click', targetClickNextFn);
1796
+ }
1797
+ changeStep.call(this, doCallbacks, 1);
1798
+ return this;
1799
+ };
1800
+
1801
+ /**
1802
+ * endTour
1803
+ *
1804
+ * Cancels out of an active tour.
1805
+ *
1806
+ * @param {Boolean} clearState Flag for clearing state. Defaults to true.
1807
+ * @param {Boolean} doCallbacks Flag for invoking 'onEnd' callbacks. Defaults to true.
1808
+ * @returns {Object} Hopscotch
1809
+ */
1810
+ this.endTour = function(clearState, doCallbacks) {
1811
+ var bubble = getBubble();
1812
+ clearState = utils.valOrDefault(clearState, true);
1813
+ doCallbacks = utils.valOrDefault(doCallbacks, true);
1814
+ currStepNum = 0;
1815
+ cookieTourStep = undefined;
1816
+
1817
+ bubble.hide();
1818
+ if (clearState) {
1819
+ utils.clearState(getOption('cookieName'));
1820
+ }
1821
+ if (this.isActive) {
1822
+ this.isActive = false;
1823
+
1824
+ if (currTour && doCallbacks) {
1825
+ utils.invokeEventCallbacks('end');
1826
+ }
1827
+ }
1828
+
1829
+ this.removeCallbacks(null, true);
1830
+ this.resetDefaultOptions();
1831
+
1832
+ currTour = null;
1833
+
1834
+ return this;
1835
+ };
1836
+
1837
+ /**
1838
+ * getCurrTour
1839
+ *
1840
+ * @return {Object} The currently loaded tour.
1841
+ */
1842
+ this.getCurrTour = function() {
1843
+ return currTour;
1844
+ };
1845
+
1846
+ /**
1847
+ * getCurrTarget
1848
+ *
1849
+ * @return {Object} The currently visible target.
1850
+ */
1851
+ this.getCurrTarget = function() {
1852
+ return utils.getStepTarget(getCurrStep());
1853
+ };
1854
+
1855
+ /**
1856
+ * getCurrStepNum
1857
+ *
1858
+ * @return {number} The current zero-based step number.
1859
+ */
1860
+ this.getCurrStepNum = function() {
1861
+ return currStepNum;
1862
+ };
1863
+
1864
+ /**
1865
+ * refreshBubblePosition
1866
+ *
1867
+ * Tell hopscotch that the position of the current tour element changed
1868
+ * and the bubble therefore needs to be redrawn
1869
+ *
1870
+ * @returns {Object} Hopscotch
1871
+ */
1872
+ this.refreshBubblePosition = function() {
1873
+ bubble.setPosition(getCurrStep());
1874
+ return this;
1875
+ };
1876
+
1877
+ /**
1878
+ * listen
1879
+ *
1880
+ * Adds a callback for one of the event types. Valid event types are:
1881
+ *
1882
+ * @param {string} evtType "start", "end", "next", "prev", "show", "close", or "error"
1883
+ * @param {Function} cb The callback to add.
1884
+ * @param {Boolean} isTourCb Flag indicating callback is from a tour definition.
1885
+ * For internal use only!
1886
+ * @returns {Object} Hopscotch
1887
+ */
1888
+ this.listen = function(evtType, cb, isTourCb) {
1889
+ if (evtType) {
1890
+ callbacks[evtType].push({ cb: cb, fromTour: isTourCb });
1891
+ }
1892
+ return this;
1893
+ };
1894
+
1895
+ /**
1896
+ * unlisten
1897
+ *
1898
+ * Removes a callback for one of the event types, e.g. 'start', 'next', etc.
1899
+ *
1900
+ * @param {string} evtType "start", "end", "next", "prev", "show", "close", or "error"
1901
+ * @param {Function} cb The callback to remove.
1902
+ * @returns {Object} Hopscotch
1903
+ */
1904
+ this.unlisten = function(evtType, cb) {
1905
+ var evtCallbacks = callbacks[evtType],
1906
+ i,
1907
+ len;
1908
+
1909
+ for (i = 0, len = evtCallbacks.length; i < len; ++i) {
1910
+ if (evtCallbacks[i] === cb) {
1911
+ evtCallbacks.splice(i, 1);
1912
+ }
1913
+ }
1914
+ return this;
1915
+ };
1916
+
1917
+ /**
1918
+ * removeCallbacks
1919
+ *
1920
+ * Remove callbacks for hopscotch events. If tourOnly is set to true, only
1921
+ * removes callbacks specified by a tour (callbacks set by external calls
1922
+ * to hopscotch.configure or hopscotch.listen will not be removed). If
1923
+ * evtName is null or undefined, callbacks for all events will be removed.
1924
+ *
1925
+ * @param {string} evtName Optional Event name for which we should remove callbacks
1926
+ * @param {boolean} tourOnly Optional flag to indicate we should only remove callbacks added
1927
+ * by a tour. Defaults to false.
1928
+ * @returns {Object} Hopscotch
1929
+ */
1930
+ this.removeCallbacks = function(evtName, tourOnly) {
1931
+ var cbArr,
1932
+ i,
1933
+ len,
1934
+ evt;
1935
+
1936
+ // If evtName is null or undefined, remove callbacks for all events.
1937
+ for (evt in callbacks) {
1938
+ if (!evtName || evtName === evt) {
1939
+ if (tourOnly) {
1940
+ cbArr = callbacks[evt];
1941
+ for (i=0, len=cbArr.length; i < len; ++i) {
1942
+ if (cbArr[i].fromTour) {
1943
+ cbArr.splice(i--, 1);
1944
+ --len;
1945
+ }
1946
+ }
1947
+ }
1948
+ else {
1949
+ callbacks[evt] = [];
1950
+ }
1951
+ }
1952
+ }
1953
+ return this;
1954
+ };
1955
+
1956
+ /**
1957
+ * registerHelper
1958
+ * ==============
1959
+ * Registers a helper function to be used as a callback function.
1960
+ *
1961
+ * @param {String} id The id of the function.
1962
+ * @param {Function} id The callback function.
1963
+ */
1964
+ this.registerHelper = function(id, fn) {
1965
+ if (typeof id === 'string' && typeof fn === 'function') {
1966
+ helpers[id] = fn;
1967
+ }
1968
+ };
1969
+
1970
+ this.unregisterHelper = function(id) {
1971
+ helpers[id] = null;
1972
+ };
1973
+
1974
+ this.invokeHelper = function(id) {
1975
+ var args = [],
1976
+ i,
1977
+ len;
1978
+
1979
+ for (i = 1, len = arguments.length; i < len; ++i) {
1980
+ args.push(arguments[i]);
1981
+ }
1982
+ if (helpers[id]) {
1983
+ helpers[id].call(null, args);
1984
+ }
1985
+ };
1986
+
1987
+ /**
1988
+ * setCookieName
1989
+ *
1990
+ * Sets the cookie name (or sessionStorage name, if supported) used for multi-page
1991
+ * tour persistence.
1992
+ *
1993
+ * @param {String} name The cookie name
1994
+ * @returns {Object} Hopscotch
1995
+ */
1996
+ this.setCookieName = function(name) {
1997
+ opt.cookieName = name;
1998
+ return this;
1999
+ };
2000
+
2001
+ /**
2002
+ * resetDefaultOptions
2003
+ *
2004
+ * Resets all configuration options to default.
2005
+ *
2006
+ * @returns {Object} Hopscotch
2007
+ */
2008
+ this.resetDefaultOptions = function() {
2009
+ opt = {};
2010
+ return this;
2011
+ };
2012
+
2013
+ /**
2014
+ * resetDefaultI18N
2015
+ *
2016
+ * Resets all i18n.
2017
+ *
2018
+ * @returns {Object} Hopscotch
2019
+ */
2020
+ this.resetDefaultI18N = function() {
2021
+ customI18N = {};
2022
+ return this;
2023
+ };
2024
+
2025
+ /**
2026
+ * hasState
2027
+ *
2028
+ * Returns state from a previous tour run, if it exists.
2029
+ *
2030
+ * @returns {String} State of previous tour run, or empty string if none exists.
2031
+ */
2032
+ this.getState = function() {
2033
+ return utils.getState(getOption('cookieName'));
2034
+ };
2035
+
2036
+ /**
2037
+ * _configure
2038
+ *
2039
+ * @see this.configure
2040
+ * @private
2041
+ * @param options
2042
+ * @param {Boolean} isTourOptions Should be set to true when setting options from a tour definition.
2043
+ */
2044
+ _configure = function(options, isTourOptions) {
2045
+ var bubble,
2046
+ events = ['next', 'prev', 'start', 'end', 'show', 'error', 'close'],
2047
+ eventPropName,
2048
+ callbackProp,
2049
+ i,
2050
+ len;
2051
+
2052
+ if (!opt) {
2053
+ this.resetDefaultOptions();
2054
+ }
2055
+
2056
+ utils.extend(opt, options);
2057
+
2058
+ if (options) {
2059
+ utils.extend(customI18N, options.i18n);
2060
+ }
2061
+
2062
+ for (i = 0, len = events.length; i < len; ++i) {
2063
+ // At this point, options[eventPropName] may have changed from an array
2064
+ // to a function.
2065
+ eventPropName = 'on' + events[i].charAt(0).toUpperCase() + events[i].substring(1);
2066
+ if (options[eventPropName]) {
2067
+ this.listen(events[i],
2068
+ options[eventPropName],
2069
+ isTourOptions);
2070
+ }
2071
+ }
2072
+
2073
+ bubble = getBubble(true);
2074
+
2075
+ return this;
2076
+ };
2077
+
2078
+ /**
2079
+ * configure
2080
+ *
2081
+ * <pre>
2082
+ * VALID OPTIONS INCLUDE...
2083
+ *
2084
+ * - bubbleWidth: Number - Default bubble width. Defaults to 280.
2085
+ * - bubblePadding: Number - DEPRECATED. Default bubble padding. Defaults to 15.
2086
+ * - smoothScroll: Boolean - should the page scroll smoothly to the next
2087
+ * step? Defaults to TRUE.
2088
+ * - scrollDuration: Number - Duration of page scroll. Only relevant when
2089
+ * smoothScroll is set to true. Defaults to
2090
+ * 1000ms.
2091
+ * - scrollTopMargin: NUMBER - When the page scrolls, how much space should there
2092
+ * be between the bubble/targetElement and the top
2093
+ * of the viewport? Defaults to 200.
2094
+ * - showCloseButton: Boolean - should the tour bubble show a close (X) button?
2095
+ * Defaults to TRUE.
2096
+ * - showPrevButton: Boolean - should the bubble have the Previous button?
2097
+ * Defaults to FALSE.
2098
+ * - showNextButton: Boolean - should the bubble have the Next button?
2099
+ * Defaults to TRUE.
2100
+ * - arrowWidth: Number - Default arrow width. (space between the bubble
2101
+ * and the targetEl) Used for bubble position
2102
+ * calculation. Only use this option if you are
2103
+ * using your own custom CSS. Defaults to 20.
2104
+ * - skipIfNoElement Boolean - If a specified target element is not found,
2105
+ * should we skip to the next step? Defaults to
2106
+ * TRUE.
2107
+ * - onNext: Function - A callback to be invoked after every click on
2108
+ * a "Next" button.
2109
+ *
2110
+ * - i18n: Object - For i18n purposes. Allows you to change the
2111
+ * text of button labels and step numbers.
2112
+ * - i18n.stepNums: Array\<String\> - Provide a list of strings to be shown as
2113
+ * the step number, based on index of array. Unicode
2114
+ * characters are supported. (e.g., ['&#x4e00;',
2115
+ * '&#x4e8c;', '&#x4e09;']) If there are more steps
2116
+ * than provided numbers, Arabic numerals
2117
+ * ('4', '5', '6', etc.) will be used as default.
2118
+ * - highlight: Boolean - Shows an overlay that highlights the selected element
2119
+ * Defaults to FALSE.
2120
+ * - highlightMargin: Number - Amount of margin around the selected element to show
2121
+ * Defaults to 0
2122
+ *
2123
+ * // =========
2124
+ * // CALLBACKS
2125
+ * // =========
2126
+ * - onNext: Function - Invoked after every click on a "Next" button.
2127
+ * - onPrev: Function - Invoked after every click on a "Prev" button.
2128
+ * - onStart: Function - Invoked when the tour is started.
2129
+ * - onEnd: Function - Invoked when the tour ends.
2130
+ * - onClose: Function - Invoked when the user closes the tour before finishing.
2131
+ * - onError: Function - Invoked when the specified target element doesn't exist on the page.
2132
+ *
2133
+ * // ====
2134
+ * // I18N
2135
+ * // ====
2136
+ * i18n: OBJECT - For i18n purposes. Allows you to change the text
2137
+ * of button labels and step numbers.
2138
+ * i18n.nextBtn: STRING - Label for next button
2139
+ * i18n.prevBtn: STRING - Label for prev button
2140
+ * i18n.doneBtn: STRING - Label for done button
2141
+ * i18n.skipBtn: STRING - Label for skip button
2142
+ * i18n.closeTooltip: STRING - Text for close button tooltip
2143
+ * i18n.stepNums: ARRAY<STRING> - Provide a list of strings to be shown as
2144
+ * the step number, based on index of array. Unicode
2145
+ * characters are supported. (e.g., ['&#x4e00;',
2146
+ * '&#x4e8c;', '&#x4e09;']) If there are more steps
2147
+ * than provided numbers, Arabic numerals
2148
+ * ('4', '5', '6', etc.) will be used as default.
2149
+ * </pre>
2150
+ *
2151
+ * @example hopscotch.configure({ scrollDuration: 1000, scrollTopMargin: 150 });
2152
+ * @example
2153
+ * hopscotch.configure({
2154
+ * scrollTopMargin: 150,
2155
+ * onStart: function() {
2156
+ * alert("Have fun!");
2157
+ * },
2158
+ * i18n: {
2159
+ * nextBtn: 'Forward',
2160
+ * prevBtn: 'Previous'
2161
+ * closeTooltip: 'Quit'
2162
+ * }
2163
+ * });
2164
+ *
2165
+ * @param {Object} options A hash of configuration options.
2166
+ * @returns {Object} Hopscotch
2167
+ */
2168
+ this.configure = function(options) {
2169
+ return _configure.call(this, options, false);
2170
+ };
2171
+
2172
+ /**
2173
+ * Set the template that should be used for rendering Hopscotch bubbles.
2174
+ * If a string, it's assumed your template is available in the
2175
+ * hopscotch.templates namespace.
2176
+ *
2177
+ * @param {String|Function(obj)} The template to use for rendering.
2178
+ * @returns {Object} The Hopscotch object (for chaining).
2179
+ */
2180
+ this.setRenderer = function(render){
2181
+ var typeOfRender = typeof render;
2182
+
2183
+ if(typeOfRender === 'string'){
2184
+ templateToUse = render;
2185
+ customRenderer = undefined;
2186
+ }
2187
+ else if(typeOfRender === 'function'){
2188
+ customRenderer = render;
2189
+ }
2190
+ return this;
2191
+ };
2192
+
2193
+ /**
2194
+ * Sets the escaping method to be used by JST templates.
2195
+ *
2196
+ * @param {Function} - The escape method to use.
2197
+ * @returns {Object} The Hopscotch object (for chaining).
2198
+ */
2199
+ this.setEscaper = function(esc){
2200
+ if (typeof esc === 'function'){
2201
+ customEscape = esc;
2202
+ }
2203
+ return this;
2204
+ };
2205
+
2206
+ init.call(this, initOptions);
2207
+ };
2208
+
2209
+
2210
+
2211
+ HopscotchHighlight = function(opt) {
2212
+ this.init(opt);
2213
+ };
2214
+
2215
+ HopscotchHighlight.prototype = {
2216
+ init: function(initOpt) {
2217
+ var opt;
2218
+ var el = {
2219
+ top: document.createElement('div'),
2220
+ left: document.createElement('div'),
2221
+ right: document.createElement('div'),
2222
+ bottom: document.createElement('div')
2223
+ };
2224
+
2225
+ this.element = el;
2226
+
2227
+
2228
+ //Merge highlight options with defaults.
2229
+ opt = {
2230
+ highlight: defaultOpts.highlight,
2231
+ highlightMargin: defaultOpts.highlightMargin
2232
+ };
2233
+
2234
+ initOpt = (typeof initOpt === undefinedStr ? {} : initOpt);
2235
+ utils.extend(opt, initOpt);
2236
+ this.opt = opt;
2237
+
2238
+ for (var e in this.element){
2239
+ utils.addClass(this.element[e], 'hopscotch-overlay');
2240
+ }
2241
+ },
2242
+ addToDom: function(){
2243
+ for (var e in this.element){
2244
+ document.body.appendChild(this.element[e]);
2245
+ }
2246
+ },
2247
+ show: function(){
2248
+ // check if step has disabled the highlight:
2249
+ if (!this.stepOpts.highlight){
2250
+ return;
2251
+ }
2252
+
2253
+ for (var e in this.element){
2254
+ utils.removeClass(this.element[e], 'hide');
2255
+ }
2256
+ },
2257
+ hide: function(){
2258
+ for (var e in this.element){
2259
+ utils.addClass(this.element[e], 'hide');
2260
+ }
2261
+ },
2262
+ setPosition: function(step, targetBounds){
2263
+ // check if step has disabled the highlight:
2264
+ if (!this.stepOpts.highlight){
2265
+ return;
2266
+ }
2267
+
2268
+ var margin = this.stepOpts.highlightMargin;
2269
+
2270
+ var body = document.body,
2271
+ html = document.documentElement;
2272
+
2273
+ var documentHeight = Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight );
2274
+
2275
+ // top div:
2276
+ el = this.element.top;
2277
+ el.style.top = '0px';
2278
+ el.style.left = '0px';
2279
+ el.style.width = window.screen.width + 'px';
2280
+ el.style.height = targetBounds.top + utils.getScrollTop() - margin + 'px';
2281
+
2282
+ // right div:
2283
+ el = this.element.right;
2284
+ el.style.top = targetBounds.top + utils.getScrollTop() - margin + 'px';
2285
+ el.style.left = targetBounds.left + targetBounds.width + utils.getScrollLeft() + margin + 'px';
2286
+ el.style.width = window.screen.width - (targetBounds.left + targetBounds.width + utils.getScrollLeft() + margin) + 'px';
2287
+ el.style.height = targetBounds.height + margin * 2 + 'px';
2288
+
2289
+ // bottom div:
2290
+ el = this.element.bottom;
2291
+ el.style.top = targetBounds.top + utils.getScrollTop() + targetBounds.height + margin + 'px';
2292
+ el.style.left = '0px';
2293
+ el.style.width = window.screen.width + 'px';
2294
+ el.style.height = documentHeight - (targetBounds.top + utils.getScrollTop() + targetBounds.height + margin) + 'px';
2295
+
2296
+ // left div:
2297
+ el = this.element.left;
2298
+ el.style.top = targetBounds.top + utils.getScrollTop() + - margin + 'px';
2299
+ el.style.left = '0px';
2300
+ el.style.width = targetBounds.left + utils.getScrollLeft() - margin + 'px';
2301
+ el.style.height = targetBounds.height + margin * 2 + 'px';
2302
+ },
2303
+ render: function(step){
2304
+ // set options for current step:
2305
+ this.stepOpts = {};
2306
+
2307
+ utils.extend(this.stepOpts, this.opt);
2308
+ utils.extend(this.stepOpts, step);
2309
+ }
2310
+ };
2311
+
2312
+
2313
+
2314
+ winHopscotch = new Hopscotch();
2315
+ context[namespace] = winHopscotch;
2316
+
2317
+ // Template includes, placed inside a closure to ensure we don't
2318
+ // end up declaring our shim globally.
2319
+ (function(){
2320
+ var _ = {};
2321
+ /*
2322
+ * Adapted from the Underscore.js framework. Check it out at
2323
+ * https://github.com/jashkenas/underscore
2324
+ */
2325
+ _.escape = function(str){
2326
+ if(customEscape){ return customEscape(str); }
2327
+
2328
+ if(str == null) return '';
2329
+ return ('' + str).replace(new RegExp('[&<>"\']', 'g'), function(match){
2330
+ if(match == '&'){ return '&amp;' }
2331
+ if(match == '<'){ return '&lt;' }
2332
+ if(match == '>'){ return '&gt;' }
2333
+ if(match == '"'){ return '&quot;' }
2334
+ if(match == "'"){ return '&#x27;' }
2335
+ });
2336
+ }
2337
+ this["hopscotch"] = this["hopscotch"] || {};
2338
+ this["hopscotch"]["templates"] = this["hopscotch"]["templates"] || {};
2339
+
2340
+ this["hopscotch"]["templates"]["bubble_default"] = function(obj) {
2341
+ obj || (obj = {});
2342
+ var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
2343
+ function print() { __p += __j.call(arguments, '') }
2344
+ with (obj) {
2345
+
2346
+
2347
+ function optEscape(str, unsafe){
2348
+ if(unsafe){
2349
+ return _.escape(str);
2350
+ }
2351
+ return str;
2352
+ }
2353
+ ;
2354
+ __p += '\n<div class="hopscotch-bubble-container" style="width: ' +
2355
+ ((__t = ( step.width )) == null ? '' : __t) +
2356
+ 'px; padding: ' +
2357
+ ((__t = ( step.padding )) == null ? '' : __t) +
2358
+ 'px;">\n ';
2359
+ if(tour.isTour){ ;
2360
+ __p += '<span class="hopscotch-bubble-number">' +
2361
+ ((__t = ( i18n.stepNum )) == null ? '' : __t) +
2362
+ '</span>';
2363
+ } ;
2364
+ __p += '\n <div class="hopscotch-bubble-content">\n ';
2365
+ if(step.title !== ''){ ;
2366
+ __p += '<h3 class="hopscotch-title">' +
2367
+ ((__t = ( optEscape(step.title, tour.unsafe) )) == null ? '' : __t) +
2368
+ '</h3>';
2369
+ } ;
2370
+ __p += '\n ';
2371
+ if(step.content !== ''){ ;
2372
+ __p += '<div class="hopscotch-content">' +
2373
+ ((__t = ( optEscape(step.content, tour.unsafe) )) == null ? '' : __t) +
2374
+ '</div>';
2375
+ } ;
2376
+ __p += '\n </div>\n <div class="hopscotch-actions">\n ';
2377
+ if(buttons.showPrev){ ;
2378
+ __p += '<button class="hopscotch-nav-button prev hopscotch-prev">' +
2379
+ ((__t = ( i18n.prevBtn )) == null ? '' : __t) +
2380
+ '</button>';
2381
+ } ;
2382
+ __p += '\n ';
2383
+ if(buttons.showCTA){ ;
2384
+ __p += '<button class="hopscotch-nav-button next hopscotch-cta">' +
2385
+ ((__t = ( buttons.ctaLabel )) == null ? '' : __t) +
2386
+ '</button>';
2387
+ } ;
2388
+ __p += '\n ';
2389
+ if(buttons.showNext){ ;
2390
+ __p += '<button class="next hopscotch-next btn btn-primary btn-block btn-sm">' +
2391
+ ((__t = ( i18n.nextBtn )) == null ? '' : __t) +
2392
+ '</button>';
2393
+ } ;
2394
+ __p += '\n </div>\n ';
2395
+ if(buttons.showClose){ ;
2396
+ __p += '<a title="' +
2397
+ ((__t = ( i18n.closeTooltip )) == null ? '' : __t) +
2398
+ '" href="#" class="hopscotch-bubble-close hopscotch-close">' +
2399
+ ((__t = ( i18n.closeTooltip )) == null ? '' : __t) +
2400
+ '</a>';
2401
+ } ;
2402
+ __p += '\n</div>\n<div class="hopscotch-bubble-arrow-container hopscotch-arrow">\n <div class="hopscotch-bubble-arrow-border"></div>\n <div class="hopscotch-bubble-arrow"></div>\n</div>';
2403
+
2404
+ }
2405
+ return __p
2406
+ };
2407
+ }());
2408
+
2409
+ }(window, 'hopscotch'));