Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / web / themes / contrib / bootstrap / js / modal.js
index 8f32e5baafe312051b78abf0059fff40be05b134..5bcce17846499037bd7ecb86c3403f81f160872d 100644 (file)
  * @file
  * Bootstrap Modals.
  */
-(function ($, Drupal, Bootstrap) {
-  "use strict";
+(function ($, Drupal, Bootstrap, Attributes) {
+  'use strict';
 
   /**
-   * Extend the Bootstrap Modal plugin constructor class.
+   * Only process this once.
    */
-  Bootstrap.extendPlugin('modal', function (settings) {
-    return {
-      DEFAULTS: {
+  Bootstrap.once('modal', function (settings) {
+
+    /**
+     * Replace the Bootstrap Modal jQuery plugin definition.
+     *
+     * This adds a little bit of functionality so it works better with Drupal.
+     */
+    Bootstrap.replacePlugin('modal', function () {
+      var BootstrapModal = this;
+
+      // Override the Modal constructor.
+      var Modal = function (element, options) {
+        this.options             = options;
+        this.$body               = $(document.body);
+        this.$element            = $(element);
+        this.$dialog             = this.$element.find('.modal-dialog');
+        this.$header             = this.$dialog.find('.modal-header');
+        this.$close              = this.$header.find('.close');
+        this.$footer             = this.$dialog.find('.modal-footer');
+        this.$content            = this.$dialog.find('.modal-content');
+        this.$dialogBody         = this.$content.find('.modal-body');
+        this.$backdrop           = null;
+        this.isShown             = null;
+        this.originalBodyPad     = null;
+        this.scrollbarWidth      = 0;
+        this.ignoreBackdropClick = false;
+      };
+
+      // Extend defaults to take into account for theme settings.
+      Modal.DEFAULTS = $.extend({}, BootstrapModal.DEFAULTS, {
         animation: !!settings.modal_animation,
         backdrop: settings.modal_backdrop === 'static' ? 'static' : !!settings.modal_backdrop,
         keyboard: !!settings.modal_keyboard,
         show: !!settings.modal_show,
         size: settings.modal_size
-      }
-    };
-  });
-
-  /**
-   * Replace the Bootstrap Modal jQuery plugin definition.
-   *
-   * Replacing this is needed so that the "option" method can return values.
-   */
-  Bootstrap.replacePlugin('modal', function () {
-    var Modal = this;
-
-    // Extract the arguments.
-    var args = Array.prototype.slice.call(arguments, 1);
-
-    // Modal jQuery Plugin Definition.
-    return function (option, _relatedTarget) {
-      var ret = void(0);
-      this.each(function () {
-        var $this   = $(this);
-        var data    = $this.data('bs.modal');
-        var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option);
-
-        if (!data) $this.data('bs.modal', (data = new Modal(this, options)));
-        if (typeof option == 'string') ret = data[option].apply(data, args);
-        else if (options.show) data.show(_relatedTarget);
       });
 
-      // If just one element and there was a result returned for the option passed,
-      // then return the result. Otherwise, just return the jQuery object.
-      return this.length === 1 && ret !== void(0) ? ret : this;
-    }
-  });
+      // Copy over the original prototype methods.
+      Modal.prototype = BootstrapModal.prototype;
 
-  /**
-   * Extend Drupal theming functions.
-   */
-  $.extend(Drupal.theme, /** @lend Drupal.theme */ {
-    /**
-     * Theme function for a Bootstrap Modal.
-     *
-     * @param {object}[variables]
-     *   An object with the following keys:
-     *   - title: The name of the tab.
-     *
-     * @return {string}
-     *   The HTML for the modal.
-     */
-    bootstrapModal: function (variables) {
-      var settings = drupalSettings.bootstrap || {};
-      var defaults = {
-        body: '',
-        closeButton: true,
-        description: {
-          content: null,
-          position: 'before'
-        },
-        footer: '',
-        id: 'drupal-modal',
-        size: settings.modal_size ? settings.modal_size : '',
-        title: Drupal.t('Loading...')
+      /**
+       * Handler for $.fn.modal('destroy').
+       */
+      Modal.prototype.destroy = function () {
+        this.hide();
+        Drupal.detachBehaviors(this.$element[0]);
+        this.$element.removeData('bs.modal').remove();
       };
-      variables = $.extend(true, {}, defaults, variables);
-      var output = '';
 
-      // Build the modal wrapper.
-      var classes = ['modal'];
-      if (settings.modal_animation) {
-        classes.push('fade');
-      }
-      output += '<div id="' + variables.id + '" class="' + classes.join(' ') + '" tabindex="-1" role="dialog">';
+      /**
+       * Initialize the modal.
+       */
+      Modal.prototype.init = function () {
+        if (this.options.remote) {
+          this.$content.load(this.options.remote, $.proxy(function () {
+            this.$element.trigger('loaded.bs.modal');
+          }, this));
+        }
+      };
 
-      // Build the modal-dialog wrapper.
-      var dialogClasses = ['modal-dialog'];
-      if (variables.size) {
-        // @todo This should really be a clean CSS class method instead.
-        dialogClasses.push(Drupal.checkPlain(variables.size));
-      }
-      output += '<div class="' + dialogClasses.join(' ') + '" role="document">';
+      // Modal jQuery Plugin Definition.
+      var Plugin = function () {
+        // Extract the arguments.
+        var args = Array.prototype.slice.call(arguments);
+        var method = args.shift();
+        var options = {};
+        if ($.isPlainObject(method)) {
+          options = method;
+          method = null;
+        }
+        var ret = void 0;
+        this.each(function () {
+          var $this   = $(this);
+          var data    = $this.data('bs.modal');
+          var initialize = false;
 
-      // Build the modal-content wrapper.
-      output += '<div class="modal-content">';
+          options = $.extend({}, Modal.DEFAULTS, data && data.options, $this.data(), options);
+          if (!data) {
+            // When initializing the Bootstrap Modal, only pass the "supported"
+            // options by intersecting the default options. This allows plugins
+            // like the jQuery UI bridge to properly detect when options have
+            // changed when they're set below as a global "option" method.
+            $this.data('bs.modal', (data = new Modal(this, Bootstrap.intersectObjects(options, Modal.DEFAULTS))));
+            initialize = true;
+          }
 
-      // Build the header wrapper and title.
-      output += Drupal.theme.bootstrapModalHeader(variables.title, variables.closeButton);
+          // If no method or arguments, treat it like it's initializing the modal.
+          if (!method && !args.length) {
+            data.option(options);
+            initialize = true;
+          }
 
-      // Build the body.
-      output += Drupal.theme.bootstrapModalBody(variables.id + '--body', variables.body, variables.description);
+          // Initialize the modal.
+          if (initialize) {
+            data.init();
+          }
 
-      // Build the footer.
-      output += Drupal.theme.bootstrapModalFooter(variables.footer);
+          if (method) {
+            if (typeof data[method] === 'function') {
+              try {
+                ret = data[method].apply(data, args);
+              }
+              catch (e) {
+                Drupal.throwError(e);
+              }
+            }
+            else {
+              Bootstrap.unsupported('method', method);
+            }
+          }
+        });
 
-      // Close the modal-content wrapper.
-      output += '</div>';
+        // If just one element and there was a result returned for the option passed,
+        // then return the result. Otherwise, just return the jQuery object.
+        return this.length === 1 && ret !== void 0 ? ret : this;
+      };
 
-      // Close the modal-dialog wrapper.
-      output += '</div>';
+      // Replace the plugin constructor with the new Modal constructor.
+      Plugin.Constructor = Modal;
 
-      // Close the modal wrapper.
-      output += '</div>';
+      // Replace the data API so that it calls $.fn.modal rather than Plugin.
+      // This allows sub-themes to replace the jQuery Plugin if they like with
+      // out having to redo all this boilerplate.
+      $(document)
+        .off('click.bs.modal.data-api')
+        .on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
+          var $this   = $(this);
+          var href    = $this.attr('href');
+          var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))); // strip for ie7
+          var option  = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data());
 
-      // Return the constructed modal.
-      return output;
-    },
+          if ($this.is('a')) e.preventDefault();
 
-    /**
-     * Theme function for a Bootstrap Modal body markup.
-     *
-     * @param {string} id
-     *   A unique ID for the modal body div.
-     * @param {string} body
-     *   The HTML markup to place in the body.
-     * @param {string|object} description
-     *   A description to show. Can either be a string or an object with the
-     *   following key/value pairs:
-     *   - content: The description value.
-     *   - position: (optional) A display setting that can have these values:
-     *     - before: The description is displayed before the body. This is the
-     *       default value.
-     *     - after: The description is display after the body.
-     *     - invisible: The description is displayed after the element, hidden
-     *       visually but available to screen readers.
-     *
-     * @return {string}
-     *   The HTML for the modal close button.
-     */
-    bootstrapModalBody: function (id, body, description) {
-      var output = '';
-      output += '<div id="' + id + '" class="modal-body">';
-      if (!description || !$.isPlainObject(description)) {
-        description = { content: description};
-      }
-      description = $.extend({ position: 'before' }, description);
+          $target.one('show.bs.modal', function (showEvent) {
+            // Only register focus restorer if modal will actually get shown.
+            if (showEvent.isDefaultPrevented()) return;
+            $target.one('hidden.bs.modal', function () {
+              $this.is(':visible') && $this.trigger('focus');
+            });
+          });
+          $target.modal(option, this);
+        });
 
-      var descriptionClasses = ['help-block'];
-      if (description.content && description.position === 'invisible') {
-        descriptionClasses.push('sr-only');
-      }
-      if (description.content && description.position === 'before') {
-        output += '<p class="' + descriptionClasses.join(' ') + '">' + description.content + '</p>';
-      }
-      output += body;
-      if (description.content && (description.position === 'after' || description.position === 'invisible')) {
-        output += '<p class="' + descriptionClasses.join(' ') + '">' + description.content + '</p>';
-      }
-      output += '</div>';
-      return output;
-    },
+      return Plugin;
+    });
 
     /**
-     * Theme function for a Bootstrap Modal close button.
-     *
-     * @return {string}
-     *   The HTML for the modal close button.
+     * Extend Drupal theming functions.
      */
-    bootstrapModalClose: function () {
-      return '<button type="button" class="close" data-dismiss="modal" aria-label="' + Drupal.t('Close') + '"><span aria-hidden="true">&times;</span></button>';
-    },
+    $.extend(Drupal.theme, /** @lend Drupal.theme */ {
+      /**
+       * Theme function for a Bootstrap Modal.
+       *
+       * @param {Object} [variables]
+       *   An object containing key/value pairs of variables.
+       *
+       * @return {string}
+       *   The HTML for the modal.
+       */
+      bootstrapModal: function (variables) {
+        var output = '';
+        var settings = drupalSettings.bootstrap || {};
+        var defaults = {
+          attributes: {
+            class: ['modal'],
+            tabindex: -1,
+            role: 'dialog'
+          },
+          body: '',
+          closeButton: true,
+          description: {
+            attributes: {
+              class: ['help-block']
+            },
+            content: null,
+            position: 'before'
+          },
+          footer: '',
+          id: 'drupal-modal',
+          size: settings.modal_size ? settings.modal_size : '',
+          title: {
+            attributes: {
+              class: ['modal-title']
+            },
+            content: Drupal.t('Loading...'),
+            html: false,
+            tag: 'h4'
+          }
+        };
+        variables = $.extend(true, {}, defaults, variables);
 
-    /**
-     * Theme function for a Bootstrap Modal footer.
-     *
-     * @param {string} [footer]
-     *   The HTML markup to place in the footer.
-     * @param {boolean} [force]
-     *   Flag to force the rendering of the footer.
-     *
-     * @return {string}
-     *   The HTML for the modal footer.
-     */
-    bootstrapModalFooter: function (footer, force) {
-      return footer || force ? '<div class="modal-footer">' + (footer || '') + '</div>' : '';
-    },
+        var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
+        attributes.set('id', attributes.get('id', variables.id));
 
-    /**
-     * Theme function for a Bootstrap Modal header.
-     *
-     * @param {string} [title]
-     *   The title for the header.
-     * @param {boolean} [closeButton]
-     *   Flag indicating whether or not to show the close button in the header.
-     *
-     * @return {string}
-     *   The HTML for the modal header.
-     */
-    bootstrapModalHeader: function (title, closeButton) {
-      var output = '';
-      if (title) {
-        closeButton = closeButton !== void(0) ? closeButton : true;
-        output += '<div class="modal-header">';
-        if (closeButton) {
-          output += Drupal.theme.bootstrapModalClose();
+        if (settings.modal_animation) {
+          attributes.addClass('fade');
         }
-        output += '<h4 class="modal-title">' + Drupal.checkPlain(title) + '</h4>';
+
+        // Build the modal wrapper.
+        output += '<div' + attributes + '>';
+
+        // Build the modal-dialog wrapper.
+        output += Drupal.theme('bootstrapModalDialog', _.omit(variables, 'attributes'));
+
+        // Close the modal wrapper.
         output += '</div>';
+
+        // Return the constructed modal.
+        return output;
+      },
+
+      /**
+       * Theme function for a Bootstrap Modal dialog markup.
+       *
+       * @param {Object} [variables]
+       *   An object containing key/value pairs of variables.
+       *
+       * @return {string}
+       *   The HTML for the modal close button.
+       */
+      bootstrapModalDialog: function (variables) {
+        var output = '';
+
+        var defaults = {
+          attributes: {
+            class: ['modal-dialog'],
+            role: 'document'
+          },
+          id: 'drupal-modal'
+        };
+        variables = $.extend(true, {}, defaults, variables);
+
+        var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
+        attributes.set('id', attributes.get('id', variables.id + '--dialog'));
+
+        if (variables.size) {
+          attributes.addClass(variables.size);
+        }
+        output += '<div' + attributes + '>';
+
+        // Build the modal-content wrapper.
+        output += Drupal.theme('bootstrapModalContent', _.omit(variables, 'attributes'));
+
+        // Close the modal-dialog wrapper.
+        output += '</div>';
+        return output;
+      },
+
+      /**
+       * Theme function for a Bootstrap Modal content markup.
+       *
+       * @param {Object} [variables]
+       *   An object containing key/value pairs of variables.
+       *
+       * @return {string}
+       *   The HTML for the modal close button.
+       */
+      bootstrapModalContent: function (variables) {
+        var output = '';
+
+        var defaults = {
+          attributes: {
+            class: ['modal-content']
+          },
+          id: 'drupal-modal'
+        };
+        variables = $.extend(true, {}, defaults, variables);
+
+        var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
+        attributes.set('id', attributes.get('id', variables.id + '--content'));
+
+        // Build the modal-content wrapper.
+        output += '<div' + attributes + '>';
+        variables = _.omit(variables, 'attributes');
+
+        // Build the header wrapper and title.
+        output += Drupal.theme('bootstrapModalHeader', variables);
+
+        // Build the body.
+        output += Drupal.theme('bootstrapModalBody', variables);
+
+        // Build the footer.
+        output += Drupal.theme('bootstrapModalFooter', variables);
+
+        // Close the modal-content wrapper.
+        output += '</div>';
+
+        return output;
+      },
+
+      /**
+       * Theme function for a Bootstrap Modal body markup.
+       *
+       * @param {Object} [variables]
+       *   An object containing key/value pairs of variables.
+       *
+       * @return {string}
+       *   The HTML for the modal close button.
+       */
+      bootstrapModalBody: function (variables) {
+        var output = '';
+
+        var defaults = {
+          attributes: {
+            class: ['modal-body']
+          },
+          body: '',
+          description: {
+            attributes: {
+              class: ['help-block']
+            },
+            content: null,
+            position: 'before'
+          },
+          id: 'drupal-modal'
+        };
+        variables = $.extend(true, {}, defaults, variables);
+
+        var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
+        attributes.set('id', attributes.get('id', variables.id + '--body'));
+
+        output += '<div' + attributes + '>';
+
+        if (typeof variables.description === 'string') {
+          variables.description = $.extend({}, defaults.description, { content: variables.description });
+        }
+
+        var description = variables.description;
+        description.attributes = Attributes.create(defaults.description.attributes).merge(description.attributes);
+
+        if (description.content && description.position === 'invisible') {
+          description.attributes.addClass('sr-only');
+        }
+
+        if (description.content && description.position === 'before') {
+          output += '<p' + description.attributes + '>' + description.content + '</p>';
+        }
+
+        output += variables.body;
+
+        if (description.content && (description.position === 'after' || description.position === 'invisible')) {
+          output += '<p' + description.attributes + '>' + description.content + '</p>';
+        }
+
+        output += '</div>';
+
+        return output;
+      },
+
+      /**
+       * Theme function for a Bootstrap Modal close button.
+       *
+       * @param {Object} [variables]
+       *   An object containing key/value pairs of variables.
+       *
+       * @return {string}
+       *   The HTML for the modal close button.
+       */
+      bootstrapModalClose: function (variables) {
+        var defaults = {
+          attributes: {
+            'aria-label': Drupal.t('Close'),
+            class: ['close'],
+            'data-dismiss': 'modal',
+            type: 'button'
+          }
+        };
+        variables = $.extend(true, {}, defaults, variables);
+        var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
+        return '<button' + attributes + '><span aria-hidden="true">&times;</span></button>';
+      },
+
+      /**
+       * Theme function for a Bootstrap Modal footer.
+       *
+       * @param {Object} [variables]
+       *   An object containing key/value pairs of variables.
+       * @param {boolean} [force]
+       *   Flag to force rendering the footer, regardless if there's content.
+       *
+       * @return {string}
+       *   The HTML for the modal footer.
+       */
+      bootstrapModalFooter: function (variables, force) {
+        var output = '';
+        var defaults = {
+          attributes: {
+            class: ['modal-footer']
+          },
+          footer: '',
+          id: 'drupal-modal'
+        };
+
+        variables = $.extend(true, {}, defaults, variables);
+
+        if (force || variables.footer) {
+          var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
+          attributes.set('id', attributes.get('id', variables.id + '--footer'));
+          output += '<div' + attributes + '>';
+          output += variables.footer;
+          output += '</div>';
+        }
+
+        return output;
+      },
+
+      /**
+       * Theme function for a Bootstrap Modal header.
+       *
+       * @param {Object} [variables]
+       *   An object containing key/value pairs of variables.
+       *
+       * @return {string}
+       *   The HTML for the modal header.
+       */
+      bootstrapModalHeader: function (variables) {
+        var output = '';
+
+        var defaults = {
+          attributes: {
+            class: ['modal-header']
+          },
+          closeButton: true,
+          id: 'drupal-modal',
+          title: {
+            attributes: {
+              class: ['modal-title']
+            },
+            content: Drupal.t('Loading...'),
+            html: false,
+            tag: 'h4'
+          }
+        };
+        variables = $.extend(true, {}, defaults, variables);
+
+        var title = variables.title;
+        if (title) {
+          var attributes = Attributes.create(defaults.attributes).merge(variables.attributes);
+          attributes.set('id', attributes.get('id', variables.id + '--header'));
+
+          if (typeof title === 'string') {
+            title = $.extend({}, defaults.title, { content: title });
+          }
+
+          output += '<div' + attributes + '>';
+
+          if (variables.closeButton) {
+            output += Drupal.theme('bootstrapModalClose', _.omit(variables, 'attributes'));
+          }
+
+          output += '<' + Drupal.checkPlain(title.tag) + Attributes.create(defaults.title.attributes).merge(title.attributes) + '>' + (title.html ? title.content : Drupal.checkPlain(title.content)) + '</' + Drupal.checkPlain(title.tag) + '>';
+
+          output += '</div>';
+        }
+
+        return output;
       }
-      return output;
-    }
-  })
+    })
+
+  });
 
-})(window.jQuery, window.Drupal, window.Drupal.bootstrap);
+})(window.jQuery, window.Drupal, window.Drupal.bootstrap, window.Attributes);