*
* @namespace
*/
-(function ($, Drupal, drupalSettings) {
+(function (_, $, Drupal, drupalSettings) {
'use strict';
- Drupal.bootstrap = {
+ var Bootstrap = {
+ processedOnce: {},
settings: drupalSettings.bootstrap || {}
};
*
* @ingroup sanitization
*/
- Drupal.bootstrap.checkPlain = function (str) {
+ Bootstrap.checkPlain = function (str) {
return str && Drupal.checkPlain(str) || '';
};
/**
- * Extends a Bootstrap plugin constructor.
+ * Creates a jQuery plugin.
*
- * @param {string} id
- * A Bootstrap plugin identifier located in $.fn.
- * @param {function} [callback]
- * A callback to extend the plugin constructor.
+ * @param {String} id
+ * A jQuery plugin identifier located in $.fn.
+ * @param {Function} plugin
+ * A constructor function used to initialize the for the jQuery plugin.
+ * @param {Boolean} [noConflict]
+ * Flag indicating whether or not to create a ".noConflict()" helper method
+ * for the plugin.
+ */
+ Bootstrap.createPlugin = function (id, plugin, noConflict) {
+ // Immediately return if plugin doesn't exist.
+ if ($.fn[id] !== void 0) {
+ return this.fatal('Specified jQuery plugin identifier already exists: @id. Use Drupal.bootstrap.replacePlugin() instead.', {'@id': id});
+ }
+
+ // Immediately return if plugin isn't a function.
+ if (typeof plugin !== 'function') {
+ return this.fatal('You must provide a constructor function to create a jQuery plugin "@id": @plugin', {'@id': id, '@plugin': plugin});
+ }
+
+ // Add a ".noConflict()" helper method.
+ this.pluginNoConflict(id, plugin, noConflict);
+
+ $.fn[id] = plugin;
+ };
+
+ /**
+ * Diff object properties.
+ *
+ * @param {...Object} objects
+ * Two or more objects. The first object will be used to return properties
+ * values.
*
- * @return {function|boolean}
- * The Bootstrap plugin or FALSE if the plugin does not exist.
+ * @return {Object}
+ * Returns the properties of the first passed object that are not present
+ * in all other passed objects.
*/
- Drupal.bootstrap.extendPlugin = function (id, callback) {
- // Immediately return if the plugin does not exist.
- if (!$.fn[id] || !$.fn[id].Constructor) return false;
-
- // Extend the plugin if a callback was provided.
- if ($.isFunction(callback)) {
- var ret = callback.apply($.fn[id].Constructor, [this.settings]);
- if ($.isPlainObject(ret)) {
- $.extend(true, $.fn[id].Constructor, ret);
- }
+ Bootstrap.diffObjects = function (objects) {
+ var args = Array.prototype.slice.call(arguments);
+ return _.pick(args[0], _.difference.apply(_, _.map(args, function (obj) {
+ return Object.keys(obj);
+ })));
+ };
+
+ /**
+ * Map of supported events by regular expression.
+ *
+ * @type {Object<Event|MouseEvent|KeyboardEvent|TouchEvent,RegExp>}
+ */
+ Bootstrap.eventMap = {
+ Event: /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,
+ MouseEvent: /^(?:click|dblclick|mouse(?:down|enter|leave|up|over|move|out))$/,
+ KeyboardEvent: /^(?:key(?:down|press|up))$/,
+ TouchEvent: /^(?:touch(?:start|end|move|cancel))$/
+ };
+
+ /**
+ * Extends a jQuery Plugin.
+ *
+ * @param {String} id
+ * A jQuery plugin identifier located in $.fn.
+ * @param {Function} callback
+ * A constructor function used to initialize the for the jQuery plugin.
+ *
+ * @return {Function|Boolean}
+ * The jQuery plugin constructor or FALSE if the plugin does not exist.
+ */
+ Bootstrap.extendPlugin = function (id, callback) {
+ // Immediately return if plugin doesn't exist.
+ if (typeof $.fn[id] !== 'function') {
+ return this.fatal('Specified jQuery plugin identifier does not exist: @id', {'@id': id});
+ }
+
+ // Immediately return if callback isn't a function.
+ if (typeof callback !== 'function') {
+ return this.fatal('You must provide a callback function to extend the jQuery plugin "@id": @callback', {'@id': id, '@callback': callback});
+ }
+
+ // Determine existing plugin constructor.
+ var constructor = $.fn[id] && $.fn[id].Constructor || $.fn[id];
+ var proto = constructor.prototype;
+
+ var obj = callback.apply(constructor, [this.settings]);
+ if (!$.isPlainObject(obj)) {
+ return this.fatal('Returned value from callback is not a plain object that can be used to extend the jQuery plugin "@id": @obj', {'@obj': obj});
}
// Add a jQuery UI like option getter/setter method.
- if ($.fn[id].Constructor.prototype.option === void(0)) {
- $.fn[id].Constructor.prototype.option = this.option;
+ var option = this.option;
+ if (proto.option === void(0)) {
+ proto.option = function () {
+ return option.apply(this, arguments);
+ };
+ }
+
+ // Handle prototype properties separately.
+ if (obj.prototype !== void 0) {
+ for (var key in obj.prototype) {
+ if (!obj.prototype.hasOwnProperty(key)) continue;
+ var value = obj.prototype[key];
+ if (typeof value === 'function') {
+ proto[key] = this.superWrapper(proto[key] || function () {}, value);
+ }
+ else {
+ proto[key] = $.isPlainObject(value) ? $.extend(true, {}, proto[key], value) : value;
+ }
+ }
+ }
+ delete obj.prototype;
+
+ // Handle static properties.
+ for (key in obj) {
+ if (!obj.hasOwnProperty(key)) continue;
+ value = obj[key];
+ if (typeof value === 'function') {
+ constructor[key] = this.superWrapper(constructor[key] || function () {}, value);
+ }
+ else {
+ constructor[key] = $.isPlainObject(value) ? $.extend(true, {}, constructor[key], value) : value;
+ }
}
- return $.fn[id].Constructor;
+ return $.fn[id];
+ };
+
+ Bootstrap.superWrapper = function (parent, fn) {
+ return function () {
+ var previousSuper = this.super;
+ this.super = parent;
+ var ret = fn.apply(this, arguments);
+ if (previousSuper) {
+ this.super = previousSuper;
+ }
+ else {
+ delete this.super;
+ }
+ return ret;
+ };
};
/**
- * Replaces a Bootstrap jQuery plugin definition.
+ * Provide a helper method for displaying when something is went wrong.
*
- * @param {string} id
- * A Bootstrap plugin identifier located in $.fn.
- * @param {function} [callback]
- * A callback to replace the jQuery plugin definition. The callback must
- * return a function that is used to construct a jQuery plugin.
+ * @param {String} message
+ * The message to display.
+ * @param {Object} [args]
+ * An arguments to use in message.
*
- * @return {function|boolean}
- * The Bootstrap jQuery plugin definition or FALSE if the plugin does not
- * exist.
+ * @return {Boolean}
+ * Always returns FALSE.
*/
- Drupal.bootstrap.replacePlugin = function (id, callback) {
- // Immediately return if plugin does not exist or not a valid callback.
- if (!$.fn[id] || !$.fn[id].Constructor || !$.isFunction(callback)) return false;
- var constructor = $.fn[id].Constructor;
+ Bootstrap.fatal = function (message, args) {
+ if (this.settings.dev && console.warn) {
+ for (var name in args) {
+ if (args.hasOwnProperty(name) && typeof args[name] === 'object') {
+ args[name] = JSON.stringify(args[name]);
+ }
+ }
+ Drupal.throwError(new Error(Drupal.formatString(message, args)));
+ }
+ return false;
+ };
+
+ /**
+ * Intersects object properties.
+ *
+ * @param {...Object} objects
+ * Two or more objects. The first object will be used to return properties
+ * values.
+ *
+ * @return {Object}
+ * Returns the properties of first passed object that intersects with all
+ * other passed objects.
+ */
+ Bootstrap.intersectObjects = function (objects) {
+ var args = Array.prototype.slice.call(arguments);
+ return _.pick(args[0], _.intersection.apply(_, _.map(args, function (obj) {
+ return Object.keys(obj);
+ })));
+ };
- var plugin = callback.apply(constructor);
- if ($.isFunction(plugin)) {
- plugin.Constructor = constructor;
+ /**
+ * An object based once plugin (similar to jquery.once, but without the DOM).
+ *
+ * @param {String} id
+ * A unique identifier.
+ * @param {Function} callback
+ * The callback to invoke if the identifier has not yet been seen.
+ *
+ * @return {Bootstrap}
+ */
+ Bootstrap.once = function (id, callback) {
+ // Immediately return if identifier has already been processed.
+ if (this.processedOnce[id]) {
+ return this;
+ }
+ callback.call(this, this.settings);
+ this.processedOnce[id] = true;
+ return this;
+ };
+ /**
+ * Provide jQuery UI like ability to get/set options for Bootstrap plugins.
+ *
+ * @param {string|object} key
+ * A string value of the option to set, can be dot like to a nested key.
+ * An object of key/value pairs.
+ * @param {*} [value]
+ * (optional) A value to set for key.
+ *
+ * @returns {*}
+ * - Returns nothing if key is an object or both key and value parameters
+ * were provided to set an option.
+ * - Returns the a value for a specific setting if key was provided.
+ * - Returns an object of key/value pairs of all the options if no key or
+ * value parameter was provided.
+ *
+ * @see https://github.com/jquery/jquery-ui/blob/master/ui/widget.js
+ */
+ Bootstrap.option = function (key, value) {
+ var options = $.isPlainObject(key) ? $.extend({}, key) : {};
+
+ // Get all options (clone so it doesn't reference the internal object).
+ if (arguments.length === 0) {
+ return $.extend({}, this.options);
+ }
+
+ // Get/set single option.
+ if (typeof key === "string") {
+ // Handle nested keys in dot notation.
+ // e.g., "foo.bar" => { foo: { bar: true } }
+ var parts = key.split('.');
+ key = parts.shift();
+ var obj = options;
+ if (parts.length) {
+ for (var i = 0; i < parts.length - 1; i++) {
+ obj[parts[i]] = obj[parts[i]] || {};
+ obj = obj[parts[i]];
+ }
+ key = parts.pop();
+ }
+
+ // Get.
+ if (arguments.length === 1) {
+ return obj[key] === void 0 ? null : obj[key];
+ }
+
+ // Set.
+ obj[key] = value;
+ }
+
+ // Set multiple options.
+ $.extend(true, this.options, options);
+ };
+
+ /**
+ * Adds a ".noConflict()" helper method if needed.
+ *
+ * @param {String} id
+ * A jQuery plugin identifier located in $.fn.
+ * @param {Function} plugin
+ * @param {Function} plugin
+ * A constructor function used to initialize the for the jQuery plugin.
+ * @param {Boolean} [noConflict]
+ * Flag indicating whether or not to create a ".noConflict()" helper method
+ * for the plugin.
+ */
+ Bootstrap.pluginNoConflict = function (id, plugin, noConflict) {
+ if (plugin.noConflict === void 0 && (noConflict === void 0 || noConflict)) {
var old = $.fn[id];
plugin.noConflict = function () {
$.fn[id] = old;
return this;
};
- $.fn[id] = plugin;
}
};
/**
- * Map of supported events by regular expression.
+ * Replaces a Bootstrap jQuery plugin definition.
*
- * @type {Object<Event|MouseEvent|KeyboardEvent|TouchEvent,RegExp>}
+ * @param {String} id
+ * A jQuery plugin identifier located in $.fn.
+ * @param {Function} callback
+ * A callback function that is immediately invoked and must return a
+ * function that will be used as the plugin constructor.
+ * @param {Boolean} [noConflict]
+ * Flag indicating whether or not to create a ".noConflict()" helper method
+ * for the plugin.
*/
- Drupal.bootstrap.eventMap = {
- Event: /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,
- MouseEvent: /^(?:click|dblclick|mouse(?:down|enter|leave|up|over|move|out))$/,
- KeyboardEvent: /^(?:key(?:down|press|up))$/,
- TouchEvent: /^(?:touch(?:start|end|move|cancel))$/
+ Bootstrap.replacePlugin = function (id, callback, noConflict) {
+ // Immediately return if plugin doesn't exist.
+ if (typeof $.fn[id] !== 'function') {
+ return this.fatal('Specified jQuery plugin identifier does not exist: @id', {'@id': id});
+ }
+
+ // Immediately return if callback isn't a function.
+ if (typeof callback !== 'function') {
+ return this.fatal('You must provide a valid callback function to replace a jQuery plugin: @callback', {'@callback': callback});
+ }
+
+ // Determine existing plugin constructor.
+ var constructor = $.fn[id] && $.fn[id].Constructor || $.fn[id];
+ var plugin = callback.apply(constructor, [this.settings]);
+
+ // Immediately return if plugin isn't a function.
+ if (typeof plugin !== 'function') {
+ return this.fatal('Returned value from callback is not a usable function to replace a jQuery plugin "@id": @plugin', {'@id': id, '@plugin': plugin});
+ }
+
+ // Add a ".noConflict()" helper method.
+ this.pluginNoConflict(id, plugin, noConflict);
+
+ $.fn[id] = plugin;
};
/**
* object here. This allows, if the browser supports it, to be a truly
* simulated event.
*/
- Drupal.bootstrap.simulate = function (element, type, options) {
+ Bootstrap.simulate = function (element, type, options) {
// Defer to the jQuery.simulate plugin, if it's available.
if (typeof $.simulate === 'function') {
new $.simulate(element, type, options);
}
var event;
var ctor;
- for (var name in Drupal.bootstrap.eventMap) {
- if (Drupal.bootstrap.eventMap[name].test(type)) {
+ for (var name in this.eventMap) {
+ if (this.eventMap[name].test(type)) {
ctor = name;
break;
}
};
/**
- * Provide jQuery UI like ability to get/set options for Bootstrap plugins.
+ * Provide a helper method for displaying when something is unsupported.
*
- * @param {string|object} key
- * A string value of the option to set, can be dot like to a nested key.
- * An object of key/value pairs.
+ * @param {String} type
+ * The type of unsupported object, e.g. method or option.
+ * @param {String} name
+ * The name of the unsupported object.
* @param {*} [value]
- * (optional) A value to set for key.
- *
- * @returns {*}
- * - Returns nothing if key is an object or both key and value parameters
- * were provided to set an option.
- * - Returns the a value for a specific setting if key was provided.
- * - Returns an object of key/value pairs of all the options if no key or
- * value parameter was provided.
- *
- * @see https://github.com/jquery/jquery-ui/blob/master/ui/widget.js
- *
- * @todo This isn't fully working since Bootstrap plugins don't allow
- * methods to return values.
+ * The value of the unsupported object.
*/
- Drupal.bootstrap.option = function (key, value) {
- var options = key;
- var parts, curOption, i;
-
- // Don't return a reference to the internal hash.
- if (arguments.length === 0) {
- return $.extend({}, this.options);
+ Bootstrap.unsupported = function (type, name, value) {
+ if (this.settings.dev && console.warn) {
+ console.warn(Drupal.formatString('Unsupported Drupal Bootstrap Modal @type: @name -> @value', {
+ '@type': type,
+ '@name': name,
+ '@value': typeof value === 'object' ? JSON.stringify(value) : value
+ }));
}
-
- // Handle a specific option.
- if (typeof key === "string") {
- // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
- options = {};
- parts = key.split(".");
- key = parts.shift();
- if (parts.length) {
- curOption = options[key] = $.extend({}, this.options[key]);
- for (i = 0; i < parts.length - 1; i++) {
- curOption[parts[i]] = curOption[parts[i]] || {};
- curOption = curOption[parts[i]];
- }
- key = parts.pop();
- if (arguments.length === 1) {
- return curOption[key] === undefined ? null : curOption[key];
- }
- curOption[key] = value;
- }
- else {
- if (arguments.length === 1) {
- return this.options[key] === undefined ? null : this.options[key];
- }
- options[key] = value;
- }
- }
-
- // Set the new option(s).
- for (key in options) {
- if (!options.hasOwnProperty(key)) continue;
- this.options[key] = options[key];
- }
- return this;
};
-})(window.jQuery, window.Drupal, window.drupalSettings);
+ /**
+ * Add Bootstrap to the global Drupal object.
+ *
+ * @type {Bootstrap}
+ */
+ Drupal.bootstrap = Drupal.bootstrap || Bootstrap;
+
+})(window._, window.jQuery, window.Drupal, window.drupalSettings);