comparison core/modules/tour/js/tour.es6.js @ 0:4c8ae668cc8c

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