Chris@0: /** Chris@0: * @file Chris@0: * Attaches behaviors for the Tour module's toolbar tab. Chris@0: */ Chris@0: Chris@0: (function ($, Backbone, Drupal, document) { Chris@0: const queryString = decodeURI(window.location.search); Chris@0: Chris@0: /** Chris@0: * Attaches the tour's toolbar tab behavior. Chris@0: * Chris@0: * It uses the query string for: Chris@0: * - tour: When ?tour=1 is present, the tour will start automatically after Chris@0: * the page has loaded. Chris@0: * - tips: Pass ?tips=class in the url to filter the available tips to the Chris@0: * subset which match the given class. Chris@0: * Chris@0: * @example Chris@0: * http://example.com/foo?tour=1&tips=bar Chris@0: * Chris@0: * @type {Drupal~behavior} Chris@0: * Chris@0: * @prop {Drupal~behaviorAttach} attach Chris@0: * Attach tour functionality on `tour` events. Chris@0: */ Chris@0: Drupal.behaviors.tour = { Chris@0: attach(context) { Chris@0: $('body').once('tour').each(() => { Chris@0: const model = new Drupal.tour.models.StateModel(); Chris@0: new Drupal.tour.views.ToggleTourView({ Chris@0: el: $(context).find('#toolbar-tab-tour'), Chris@0: model, Chris@0: }); Chris@0: Chris@0: model Chris@0: // Allow other scripts to respond to tour events. Chris@0: .on('change:isActive', (model, isActive) => { Chris@0: $(document).trigger((isActive) ? 'drupalTourStarted' : 'drupalTourStopped'); Chris@0: }) Chris@0: // Initialization: check whether a tour is available on the current Chris@0: // page. Chris@0: .set('tour', $(context).find('ol#tour')); Chris@0: Chris@0: // Start the tour immediately if toggled via query string. Chris@0: if (/tour=?/i.test(queryString)) { Chris@0: model.set('isActive', true); Chris@0: } Chris@0: }); Chris@0: }, Chris@0: }; Chris@0: Chris@0: /** Chris@0: * @namespace Chris@0: */ Chris@0: Drupal.tour = Drupal.tour || { Chris@0: Chris@0: /** Chris@0: * @namespace Drupal.tour.models Chris@0: */ Chris@0: models: {}, Chris@0: Chris@0: /** Chris@0: * @namespace Drupal.tour.views Chris@0: */ Chris@0: views: {}, Chris@0: }; Chris@0: Chris@0: /** Chris@0: * Backbone Model for tours. Chris@0: * Chris@0: * @constructor Chris@0: * Chris@0: * @augments Backbone.Model Chris@0: */ Chris@0: Drupal.tour.models.StateModel = Backbone.Model.extend(/** @lends Drupal.tour.models.StateModel# */{ Chris@0: Chris@0: /** Chris@0: * @type {object} Chris@0: */ Chris@0: defaults: /** @lends Drupal.tour.models.StateModel# */{ Chris@0: Chris@0: /** Chris@0: * Indicates whether the Drupal root window has a tour. Chris@0: * Chris@0: * @type {Array} Chris@0: */ Chris@0: tour: [], Chris@0: Chris@0: /** Chris@0: * Indicates whether the tour is currently running. Chris@0: * Chris@0: * @type {bool} Chris@0: */ Chris@0: isActive: false, Chris@0: Chris@0: /** Chris@0: * Indicates which tour is the active one (necessary to cleanly stop). Chris@0: * Chris@0: * @type {Array} Chris@0: */ Chris@0: activeTour: [], Chris@0: }, Chris@0: }); Chris@0: Chris@0: Drupal.tour.views.ToggleTourView = Backbone.View.extend(/** @lends Drupal.tour.views.ToggleTourView# */{ Chris@0: Chris@0: /** Chris@0: * @type {object} Chris@0: */ Chris@0: events: { click: 'onClick' }, Chris@0: Chris@0: /** Chris@0: * Handles edit mode toggle interactions. Chris@0: * Chris@0: * @constructs Chris@0: * Chris@0: * @augments Backbone.View Chris@0: */ Chris@0: initialize() { Chris@0: this.listenTo(this.model, 'change:tour change:isActive', this.render); Chris@0: this.listenTo(this.model, 'change:isActive', this.toggleTour); Chris@0: }, Chris@0: Chris@0: /** Chris@0: * @inheritdoc Chris@0: * Chris@0: * @return {Drupal.tour.views.ToggleTourView} Chris@0: * The `ToggleTourView` view. Chris@0: */ Chris@0: render() { Chris@0: // Render the visibility. Chris@0: this.$el.toggleClass('hidden', this._getTour().length === 0); Chris@0: // Render the state. Chris@0: const isActive = this.model.get('isActive'); Chris@0: this.$el.find('button') Chris@0: .toggleClass('is-active', isActive) Chris@0: .prop('aria-pressed', isActive); Chris@0: return this; Chris@0: }, Chris@0: Chris@0: /** Chris@0: * Model change handler; starts or stops the tour. Chris@0: */ Chris@0: toggleTour() { Chris@0: if (this.model.get('isActive')) { Chris@0: const $tour = this._getTour(); Chris@0: this._removeIrrelevantTourItems($tour, this._getDocument()); Chris@0: const that = this; Chris@14: const close = Drupal.t('Close'); Chris@0: if ($tour.find('li').length) { Chris@0: $tour.joyride({ Chris@0: autoStart: true, Chris@0: postRideCallback() { Chris@0: that.model.set('isActive', false); Chris@0: }, Chris@0: // HTML segments for tip layout. Chris@0: template: { Chris@14: link: `×`, Chris@14: button: '', Chris@0: }, Chris@0: }); Chris@0: this.model.set({ isActive: true, activeTour: $tour }); Chris@0: } Chris@0: } Chris@0: else { Chris@0: this.model.get('activeTour').joyride('destroy'); Chris@0: this.model.set({ isActive: false, activeTour: [] }); Chris@0: } Chris@0: }, Chris@0: Chris@0: /** Chris@0: * Toolbar tab click event handler; toggles isActive. Chris@0: * Chris@0: * @param {jQuery.Event} event Chris@0: * The click event. Chris@0: */ Chris@0: onClick(event) { Chris@0: this.model.set('isActive', !this.model.get('isActive')); Chris@0: event.preventDefault(); Chris@0: event.stopPropagation(); Chris@0: }, Chris@0: Chris@0: /** Chris@0: * Gets the tour. Chris@0: * Chris@0: * @return {jQuery} Chris@0: * A jQuery element pointing to a `