Chris@0: /** Chris@0: * @file Chris@0: * Attaches behaviors for the Tour module's toolbar tab. Chris@0: */ Chris@0: Chris@17: (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@17: $('body') Chris@17: .once('tour') Chris@17: .each(() => { Chris@17: const model = new Drupal.tour.models.StateModel(); Chris@17: new Drupal.tour.views.ToggleTourView({ Chris@17: el: $(context).find('#toolbar-tab-tour'), Chris@17: model, Chris@17: }); Chris@17: Chris@17: model Chris@17: // Allow other scripts to respond to tour events. Chris@17: .on('change:isActive', (model, isActive) => { Chris@17: $(document).trigger( Chris@17: isActive ? 'drupalTourStarted' : 'drupalTourStopped', Chris@17: ); Chris@17: }) Chris@17: // Initialization: check whether a tour is available on the current Chris@17: // page. Chris@17: .set('tour', $(context).find('ol#tour')); Chris@17: Chris@17: // Start the tour immediately if toggled via query string. Chris@17: if (/tour=?/i.test(queryString)) { Chris@17: model.set('isActive', true); Chris@17: } 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: * @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@17: Drupal.tour.models.StateModel = Backbone.Model.extend( Chris@17: /** @lends Drupal.tour.models.StateModel# */ { Chris@17: /** Chris@17: * @type {object} Chris@17: */ Chris@17: defaults: /** @lends Drupal.tour.models.StateModel# */ { Chris@17: /** Chris@17: * Indicates whether the Drupal root window has a tour. Chris@17: * Chris@17: * @type {Array} Chris@17: */ Chris@17: tour: [], Chris@0: Chris@17: /** Chris@17: * Indicates whether the tour is currently running. Chris@17: * Chris@17: * @type {bool} Chris@17: */ Chris@17: isActive: false, Chris@17: Chris@17: /** Chris@17: * Indicates which tour is the active one (necessary to cleanly stop). Chris@17: * Chris@17: * @type {Array} Chris@17: */ Chris@17: activeTour: [], Chris@17: }, Chris@17: }, Chris@17: ); Chris@17: Chris@17: Drupal.tour.views.ToggleTourView = Backbone.View.extend( Chris@17: /** @lends Drupal.tour.views.ToggleTourView# */ { Chris@17: /** Chris@17: * @type {object} Chris@17: */ Chris@17: events: { click: 'onClick' }, Chris@0: Chris@0: /** Chris@17: * Handles edit mode toggle interactions. Chris@0: * Chris@17: * @constructs Chris@17: * Chris@17: * @augments Backbone.View Chris@0: */ Chris@17: initialize() { Chris@17: this.listenTo(this.model, 'change:tour change:isActive', this.render); Chris@17: this.listenTo(this.model, 'change:isActive', this.toggleTour); Chris@17: }, Chris@0: Chris@0: /** Chris@17: * @inheritdoc Chris@0: * Chris@17: * @return {Drupal.tour.views.ToggleTourView} Chris@17: * The `ToggleTourView` view. Chris@0: */ Chris@17: render() { Chris@17: // Render the visibility. Chris@17: this.$el.toggleClass('hidden', this._getTour().length === 0); Chris@17: // Render the state. Chris@17: const isActive = this.model.get('isActive'); Chris@17: this.$el Chris@17: .find('button') Chris@17: .toggleClass('is-active', isActive) Chris@17: .prop('aria-pressed', isActive); Chris@17: return this; Chris@17: }, Chris@0: Chris@0: /** Chris@17: * Model change handler; starts or stops the tour. Chris@17: */ Chris@17: toggleTour() { Chris@17: if (this.model.get('isActive')) { Chris@17: const $tour = this._getTour(); Chris@17: this._removeIrrelevantTourItems($tour, this._getDocument()); Chris@17: const that = this; Chris@17: const close = Drupal.t('Close'); Chris@17: if ($tour.find('li').length) { Chris@17: $tour.joyride({ Chris@17: autoStart: true, Chris@17: postRideCallback() { Chris@17: that.model.set('isActive', false); Chris@17: }, Chris@17: // HTML segments for tip layout. Chris@17: template: { Chris@17: link: `×`, Chris@17: button: Chris@17: '', Chris@17: }, Chris@17: }); Chris@17: this.model.set({ isActive: true, activeTour: $tour }); Chris@17: } Chris@17: } else { Chris@17: this.model.get('activeTour').joyride('destroy'); Chris@17: this.model.set({ isActive: false, activeTour: [] }); Chris@17: } Chris@17: }, Chris@17: Chris@17: /** Chris@17: * Toolbar tab click event handler; toggles isActive. Chris@0: * Chris@17: * @param {jQuery.Event} event Chris@17: * The click event. Chris@0: */ Chris@17: onClick(event) { Chris@17: this.model.set('isActive', !this.model.get('isActive')); Chris@17: event.preventDefault(); Chris@17: event.stopPropagation(); Chris@17: }, Chris@0: Chris@17: /** Chris@17: * Gets the tour. Chris@17: * Chris@17: * @return {jQuery} Chris@17: * A jQuery element pointing to a `