annotate core/modules/tour/js/tour.es6.js @ 5:12f9dff5fda9 tip

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