annotate core/modules/tour/js/tour.es6.js @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
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@17 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@17 28 $('body')
Chris@17 29 .once('tour')
Chris@17 30 .each(() => {
Chris@17 31 const model = new Drupal.tour.models.StateModel();
Chris@17 32 new Drupal.tour.views.ToggleTourView({
Chris@17 33 el: $(context).find('#toolbar-tab-tour'),
Chris@17 34 model,
Chris@17 35 });
Chris@17 36
Chris@17 37 model
Chris@17 38 // Allow other scripts to respond to tour events.
Chris@17 39 .on('change:isActive', (model, isActive) => {
Chris@17 40 $(document).trigger(
Chris@17 41 isActive ? 'drupalTourStarted' : 'drupalTourStopped',
Chris@17 42 );
Chris@17 43 })
Chris@17 44 // Initialization: check whether a tour is available on the current
Chris@17 45 // page.
Chris@17 46 .set('tour', $(context).find('ol#tour'));
Chris@17 47
Chris@17 48 // Start the tour immediately if toggled via query string.
Chris@17 49 if (/tour=?/i.test(queryString)) {
Chris@17 50 model.set('isActive', true);
Chris@17 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@17 78 Drupal.tour.models.StateModel = Backbone.Model.extend(
Chris@17 79 /** @lends Drupal.tour.models.StateModel# */ {
Chris@17 80 /**
Chris@17 81 * @type {object}
Chris@17 82 */
Chris@17 83 defaults: /** @lends Drupal.tour.models.StateModel# */ {
Chris@17 84 /**
Chris@17 85 * Indicates whether the Drupal root window has a tour.
Chris@17 86 *
Chris@17 87 * @type {Array}
Chris@17 88 */
Chris@17 89 tour: [],
Chris@0 90
Chris@17 91 /**
Chris@17 92 * Indicates whether the tour is currently running.
Chris@17 93 *
Chris@17 94 * @type {bool}
Chris@17 95 */
Chris@17 96 isActive: false,
Chris@17 97
Chris@17 98 /**
Chris@17 99 * Indicates which tour is the active one (necessary to cleanly stop).
Chris@17 100 *
Chris@17 101 * @type {Array}
Chris@17 102 */
Chris@17 103 activeTour: [],
Chris@17 104 },
Chris@17 105 },
Chris@17 106 );
Chris@17 107
Chris@17 108 Drupal.tour.views.ToggleTourView = Backbone.View.extend(
Chris@17 109 /** @lends Drupal.tour.views.ToggleTourView# */ {
Chris@17 110 /**
Chris@17 111 * @type {object}
Chris@17 112 */
Chris@17 113 events: { click: 'onClick' },
Chris@0 114
Chris@0 115 /**
Chris@17 116 * Handles edit mode toggle interactions.
Chris@0 117 *
Chris@17 118 * @constructs
Chris@17 119 *
Chris@17 120 * @augments Backbone.View
Chris@0 121 */
Chris@17 122 initialize() {
Chris@17 123 this.listenTo(this.model, 'change:tour change:isActive', this.render);
Chris@17 124 this.listenTo(this.model, 'change:isActive', this.toggleTour);
Chris@17 125 },
Chris@0 126
Chris@0 127 /**
Chris@17 128 * @inheritdoc
Chris@0 129 *
Chris@17 130 * @return {Drupal.tour.views.ToggleTourView}
Chris@17 131 * The `ToggleTourView` view.
Chris@0 132 */
Chris@17 133 render() {
Chris@17 134 // Render the visibility.
Chris@17 135 this.$el.toggleClass('hidden', this._getTour().length === 0);
Chris@17 136 // Render the state.
Chris@17 137 const isActive = this.model.get('isActive');
Chris@17 138 this.$el
Chris@17 139 .find('button')
Chris@17 140 .toggleClass('is-active', isActive)
Chris@17 141 .prop('aria-pressed', isActive);
Chris@17 142 return this;
Chris@17 143 },
Chris@0 144
Chris@0 145 /**
Chris@17 146 * Model change handler; starts or stops the tour.
Chris@17 147 */
Chris@17 148 toggleTour() {
Chris@17 149 if (this.model.get('isActive')) {
Chris@17 150 const $tour = this._getTour();
Chris@17 151 this._removeIrrelevantTourItems($tour, this._getDocument());
Chris@17 152 const that = this;
Chris@17 153 const close = Drupal.t('Close');
Chris@17 154 if ($tour.find('li').length) {
Chris@17 155 $tour.joyride({
Chris@17 156 autoStart: true,
Chris@17 157 postRideCallback() {
Chris@17 158 that.model.set('isActive', false);
Chris@17 159 },
Chris@17 160 // HTML segments for tip layout.
Chris@17 161 template: {
Chris@17 162 link: `<a href="#close" class="joyride-close-tip" aria-label="${close}">&times;</a>`,
Chris@17 163 button:
Chris@17 164 '<a href="#" class="button button--primary joyride-next-tip"></a>',
Chris@17 165 },
Chris@17 166 });
Chris@17 167 this.model.set({ isActive: true, activeTour: $tour });
Chris@17 168 }
Chris@17 169 } else {
Chris@17 170 this.model.get('activeTour').joyride('destroy');
Chris@17 171 this.model.set({ isActive: false, activeTour: [] });
Chris@17 172 }
Chris@17 173 },
Chris@17 174
Chris@17 175 /**
Chris@17 176 * Toolbar tab click event handler; toggles isActive.
Chris@0 177 *
Chris@17 178 * @param {jQuery.Event} event
Chris@17 179 * The click event.
Chris@0 180 */
Chris@17 181 onClick(event) {
Chris@17 182 this.model.set('isActive', !this.model.get('isActive'));
Chris@17 183 event.preventDefault();
Chris@17 184 event.stopPropagation();
Chris@17 185 },
Chris@0 186
Chris@17 187 /**
Chris@17 188 * Gets the tour.
Chris@17 189 *
Chris@17 190 * @return {jQuery}
Chris@17 191 * A jQuery element pointing to a `<ol>` containing tour items.
Chris@17 192 */
Chris@17 193 _getTour() {
Chris@17 194 return this.model.get('tour');
Chris@17 195 },
Chris@0 196
Chris@17 197 /**
Chris@17 198 * Gets the relevant document as a jQuery element.
Chris@17 199 *
Chris@17 200 * @return {jQuery}
Chris@17 201 * A jQuery element pointing to the document within which a tour would be
Chris@17 202 * started given the current state.
Chris@17 203 */
Chris@17 204 _getDocument() {
Chris@17 205 return $(document);
Chris@17 206 },
Chris@0 207
Chris@17 208 /**
Chris@17 209 * Removes tour items for elements that don't have matching page elements.
Chris@17 210 *
Chris@17 211 * Or that are explicitly filtered out via the 'tips' query string.
Chris@17 212 *
Chris@17 213 * @example
Chris@17 214 * <caption>This will filter out tips that do not have a matching
Chris@17 215 * page element or don't have the "bar" class.</caption>
Chris@17 216 * http://example.com/foo?tips=bar
Chris@17 217 *
Chris@17 218 * @param {jQuery} $tour
Chris@17 219 * A jQuery element pointing to a `<ol>` containing tour items.
Chris@17 220 * @param {jQuery} $document
Chris@17 221 * A jQuery element pointing to the document within which the elements
Chris@17 222 * should be sought.
Chris@17 223 *
Chris@17 224 * @see Drupal.tour.views.ToggleTourView#_getDocument
Chris@17 225 */
Chris@17 226 _removeIrrelevantTourItems($tour, $document) {
Chris@17 227 let removals = false;
Chris@17 228 const tips = /tips=([^&]+)/.exec(queryString);
Chris@17 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@17 241 if (
Chris@17 242 (!itemId && !itemClass) ||
Chris@0 243 (itemId && $document.find(`#${itemId}`).length) ||
Chris@17 244 (itemClass && $document.find(`.${itemClass}`).length)
Chris@17 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@17 252 // If there were removals, we'll have to do some clean-up.
Chris@17 253 if (removals) {
Chris@17 254 const total = $tour.find('li').length;
Chris@17 255 if (!total) {
Chris@17 256 this.model.set({ tour: [] });
Chris@17 257 }
Chris@17 258
Chris@17 259 $tour
Chris@17 260 .find('li')
Chris@17 261 // Rebuild the progress data.
Chris@17 262 .each(function(index) {
Chris@17 263 const progress = Drupal.t('!tour_item of !total', {
Chris@17 264 '!tour_item': index + 1,
Chris@17 265 '!total': total,
Chris@17 266 });
Chris@17 267 $(this)
Chris@17 268 .find('.tour-progress')
Chris@17 269 .text(progress);
Chris@17 270 })
Chris@17 271 // Update the last item to have "End tour" as the button.
Chris@17 272 .eq(-1)
Chris@17 273 .attr('data-text', Drupal.t('End tour'));
Chris@0 274 }
Chris@17 275 },
Chris@0 276 },
Chris@17 277 );
Chris@17 278 })(jQuery, Backbone, Drupal, document);