tour.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. /**
  2. * @file
  3. * Attaches behaviors for the Tour module's toolbar tab.
  4. */
  5. (function ($, Backbone, Drupal, document) {
  6. 'use strict';
  7. var queryString = decodeURI(window.location.search);
  8. /**
  9. * Attaches the tour's toolbar tab behavior.
  10. *
  11. * It uses the query string for:
  12. * - tour: When ?tour=1 is present, the tour will start automatically after
  13. * the page has loaded.
  14. * - tips: Pass ?tips=class in the url to filter the available tips to the
  15. * subset which match the given class.
  16. *
  17. * @example
  18. * http://example.com/foo?tour=1&tips=bar
  19. *
  20. * @type {Drupal~behavior}
  21. *
  22. * @prop {Drupal~behaviorAttach} attach
  23. * Attach tour functionality on `tour` events.
  24. */
  25. Drupal.behaviors.tour = {
  26. attach: function (context) {
  27. $('body').once('tour').each(function () {
  28. var model = new Drupal.tour.models.StateModel();
  29. new Drupal.tour.views.ToggleTourView({
  30. el: $(context).find('#toolbar-tab-tour'),
  31. model: model
  32. });
  33. model
  34. // Allow other scripts to respond to tour events.
  35. .on('change:isActive', function (model, isActive) {
  36. $(document).trigger((isActive) ? 'drupalTourStarted' : 'drupalTourStopped');
  37. })
  38. // Initialization: check whether a tour is available on the current
  39. // page.
  40. .set('tour', $(context).find('ol#tour'));
  41. // Start the tour immediately if toggled via query string.
  42. if (/tour=?/i.test(queryString)) {
  43. model.set('isActive', true);
  44. }
  45. });
  46. }
  47. };
  48. /**
  49. * @namespace
  50. */
  51. Drupal.tour = Drupal.tour || {
  52. /**
  53. * @namespace Drupal.tour.models
  54. */
  55. models: {},
  56. /**
  57. * @namespace Drupal.tour.views
  58. */
  59. views: {}
  60. };
  61. /**
  62. * Backbone Model for tours.
  63. *
  64. * @constructor
  65. *
  66. * @augments Backbone.Model
  67. */
  68. Drupal.tour.models.StateModel = Backbone.Model.extend(/** @lends Drupal.tour.models.StateModel# */{
  69. /**
  70. * @type {object}
  71. */
  72. defaults: /** @lends Drupal.tour.models.StateModel# */{
  73. /**
  74. * Indicates whether the Drupal root window has a tour.
  75. *
  76. * @type {Array}
  77. */
  78. tour: [],
  79. /**
  80. * Indicates whether the tour is currently running.
  81. *
  82. * @type {bool}
  83. */
  84. isActive: false,
  85. /**
  86. * Indicates which tour is the active one (necessary to cleanly stop).
  87. *
  88. * @type {Array}
  89. */
  90. activeTour: []
  91. }
  92. });
  93. Drupal.tour.views.ToggleTourView = Backbone.View.extend(/** @lends Drupal.tour.views.ToggleTourView# */{
  94. /**
  95. * @type {object}
  96. */
  97. events: {click: 'onClick'},
  98. /**
  99. * Handles edit mode toggle interactions.
  100. *
  101. * @constructs
  102. *
  103. * @augments Backbone.View
  104. */
  105. initialize: function () {
  106. this.listenTo(this.model, 'change:tour change:isActive', this.render);
  107. this.listenTo(this.model, 'change:isActive', this.toggleTour);
  108. },
  109. /**
  110. * @inheritdoc
  111. *
  112. * @return {Drupal.tour.views.ToggleTourView}
  113. * The `ToggleTourView` view.
  114. */
  115. render: function () {
  116. // Render the visibility.
  117. this.$el.toggleClass('hidden', this._getTour().length === 0);
  118. // Render the state.
  119. var isActive = this.model.get('isActive');
  120. this.$el.find('button')
  121. .toggleClass('is-active', isActive)
  122. .prop('aria-pressed', isActive);
  123. return this;
  124. },
  125. /**
  126. * Model change handler; starts or stops the tour.
  127. */
  128. toggleTour: function () {
  129. if (this.model.get('isActive')) {
  130. var $tour = this._getTour();
  131. this._removeIrrelevantTourItems($tour, this._getDocument());
  132. var that = this;
  133. if ($tour.find('li').length) {
  134. $tour.joyride({
  135. autoStart: true,
  136. postRideCallback: function () { that.model.set('isActive', false); },
  137. // HTML segments for tip layout.
  138. template: {
  139. link: '<a href=\"#close\" class=\"joyride-close-tip\">&times;</a>',
  140. button: '<a href=\"#\" class=\"button button--primary joyride-next-tip\"></a>'
  141. }
  142. });
  143. this.model.set({isActive: true, activeTour: $tour});
  144. }
  145. }
  146. else {
  147. this.model.get('activeTour').joyride('destroy');
  148. this.model.set({isActive: false, activeTour: []});
  149. }
  150. },
  151. /**
  152. * Toolbar tab click event handler; toggles isActive.
  153. *
  154. * @param {jQuery.Event} event
  155. * The click event.
  156. */
  157. onClick: function (event) {
  158. this.model.set('isActive', !this.model.get('isActive'));
  159. event.preventDefault();
  160. event.stopPropagation();
  161. },
  162. /**
  163. * Gets the tour.
  164. *
  165. * @return {jQuery}
  166. * A jQuery element pointing to a `<ol>` containing tour items.
  167. */
  168. _getTour: function () {
  169. return this.model.get('tour');
  170. },
  171. /**
  172. * Gets the relevant document as a jQuery element.
  173. *
  174. * @return {jQuery}
  175. * A jQuery element pointing to the document within which a tour would be
  176. * started given the current state.
  177. */
  178. _getDocument: function () {
  179. return $(document);
  180. },
  181. /**
  182. * Removes tour items for elements that don't have matching page elements.
  183. *
  184. * Or that are explicitly filtered out via the 'tips' query string.
  185. *
  186. * @example
  187. * <caption>This will filter out tips that do not have a matching
  188. * page element or don't have the "bar" class.</caption>
  189. * http://example.com/foo?tips=bar
  190. *
  191. * @param {jQuery} $tour
  192. * A jQuery element pointing to a `<ol>` containing tour items.
  193. * @param {jQuery} $document
  194. * A jQuery element pointing to the document within which the elements
  195. * should be sought.
  196. *
  197. * @see Drupal.tour.views.ToggleTourView#_getDocument
  198. */
  199. _removeIrrelevantTourItems: function ($tour, $document) {
  200. var removals = false;
  201. var tips = /tips=([^&]+)/.exec(queryString);
  202. $tour
  203. .find('li')
  204. .each(function () {
  205. var $this = $(this);
  206. var itemId = $this.attr('data-id');
  207. var itemClass = $this.attr('data-class');
  208. // If the query parameter 'tips' is set, remove all tips that don't
  209. // have the matching class.
  210. if (tips && !$(this).hasClass(tips[1])) {
  211. removals = true;
  212. $this.remove();
  213. return;
  214. }
  215. // Remove tip from the DOM if there is no corresponding page element.
  216. if ((!itemId && !itemClass) ||
  217. (itemId && $document.find('#' + itemId).length) ||
  218. (itemClass && $document.find('.' + itemClass).length)) {
  219. return;
  220. }
  221. removals = true;
  222. $this.remove();
  223. });
  224. // If there were removals, we'll have to do some clean-up.
  225. if (removals) {
  226. var total = $tour.find('li').length;
  227. if (!total) {
  228. this.model.set({tour: []});
  229. }
  230. $tour
  231. .find('li')
  232. // Rebuild the progress data.
  233. .each(function (index) {
  234. var progress = Drupal.t('!tour_item of !total', {'!tour_item': index + 1, '!total': total});
  235. $(this).find('.tour-progress').text(progress);
  236. })
  237. // Update the last item to have "End tour" as the button.
  238. .eq(-1)
  239. .attr('data-text', Drupal.t('End tour'));
  240. }
  241. }
  242. });
  243. })(jQuery, Backbone, Drupal, document);