601 lines
18 KiB
JavaScript
601 lines
18 KiB
JavaScript
/**
|
|
* @file
|
|
* Drupal Bootstrap object.
|
|
*/
|
|
|
|
/**
|
|
* All Drupal Bootstrap JavaScript APIs are contained in this namespace.
|
|
*
|
|
* @param {underscore} _
|
|
* @param {jQuery} $
|
|
* @param {Drupal} Drupal
|
|
* @param {drupalSettings} drupalSettings
|
|
*/
|
|
(function (_, $, Drupal, drupalSettings) {
|
|
'use strict';
|
|
|
|
/**
|
|
* @typedef Drupal.bootstrap
|
|
*/
|
|
var Bootstrap = {
|
|
processedOnce: {},
|
|
settings: drupalSettings.bootstrap || {}
|
|
};
|
|
|
|
/**
|
|
* Wraps Drupal.checkPlain() to ensure value passed isn't empty.
|
|
*
|
|
* Encodes special characters in a plain-text string for display as HTML.
|
|
*
|
|
* @param {string} str
|
|
* The string to be encoded.
|
|
*
|
|
* @return {string}
|
|
* The encoded string.
|
|
*
|
|
* @ingroup sanitization
|
|
*/
|
|
Bootstrap.checkPlain = function (str) {
|
|
return str && Drupal.checkPlain(str) || '';
|
|
};
|
|
|
|
/**
|
|
* Creates a jQuery plugin.
|
|
*
|
|
* @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 {Object}
|
|
* Returns the properties of the first passed object that are not present
|
|
* in all other passed objects.
|
|
*/
|
|
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 plugin = callback.apply(constructor, [this.settings]);
|
|
if (!$.isPlainObject(plugin)) {
|
|
return this.fatal('Returned value from callback is not a plain object that can be used to extend the jQuery plugin "@id": @obj', {'@obj': plugin});
|
|
}
|
|
|
|
this.wrapPluginConstructor(constructor, plugin, true);
|
|
|
|
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;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Provide a helper method for displaying when something is went wrong.
|
|
*
|
|
* @param {String} message
|
|
* The message to display.
|
|
* @param {Object} [args]
|
|
* An arguments to use in message.
|
|
*
|
|
* @return {Boolean}
|
|
* Always returns FALSE.
|
|
*/
|
|
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);
|
|
})));
|
|
};
|
|
|
|
/**
|
|
* Normalizes an object's values.
|
|
*
|
|
* @param {Object} obj
|
|
* The object to normalize.
|
|
*
|
|
* @return {Object}
|
|
* The normalized object.
|
|
*/
|
|
Bootstrap.normalizeObject = function (obj) {
|
|
if (!$.isPlainObject(obj)) {
|
|
return obj;
|
|
}
|
|
|
|
for (var k in obj) {
|
|
if (typeof obj[k] === 'string') {
|
|
if (obj[k] === 'true') {
|
|
obj[k] = true;
|
|
}
|
|
else if (obj[k] === 'false') {
|
|
obj[k] = false;
|
|
}
|
|
else if (obj[k].match(/^[\d-.]$/)) {
|
|
obj[k] = parseFloat(obj[k]);
|
|
}
|
|
}
|
|
else if ($.isPlainObject(obj[k])) {
|
|
obj[k] = Bootstrap.normalizeObject(obj[k]);
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
};
|
|
|
|
/**
|
|
* 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;
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Creates a handler that relays to another event name.
|
|
*
|
|
* @param {HTMLElement|jQuery} target
|
|
* A target element.
|
|
* @param {String} name
|
|
* The name of the event to trigger.
|
|
* @param {Boolean} [stopPropagation=true]
|
|
* Flag indicating whether to stop the propagation of the event, defaults
|
|
* to true.
|
|
*
|
|
* @return {Function}
|
|
* An even handler callback function.
|
|
*/
|
|
Bootstrap.relayEvent = function (target, name, stopPropagation) {
|
|
return function (e) {
|
|
if (stopPropagation === void 0 || stopPropagation) {
|
|
e.stopPropagation();
|
|
}
|
|
var $target = $(target);
|
|
var parts = name.split('.').filter(Boolean);
|
|
var type = parts.shift();
|
|
e.target = $target[0];
|
|
e.currentTarget = $target[0];
|
|
e.namespace = parts.join('.');
|
|
e.type = type;
|
|
$target.trigger(e);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Replaces a Bootstrap jQuery plugin definition.
|
|
*
|
|
* @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.
|
|
*/
|
|
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});
|
|
}
|
|
|
|
this.wrapPluginConstructor(constructor, plugin);
|
|
|
|
// Add a ".noConflict()" helper method.
|
|
this.pluginNoConflict(id, plugin, noConflict);
|
|
|
|
$.fn[id] = plugin;
|
|
};
|
|
|
|
/**
|
|
* Simulates a native event on an element in the browser.
|
|
*
|
|
* Note: This is a fairly complete modern implementation. If things aren't
|
|
* working quite the way you intend (in older browsers), you may wish to use
|
|
* the jQuery.simulate plugin. If it's available, this method will defer to
|
|
* that plugin.
|
|
*
|
|
* @see https://github.com/jquery/jquery-simulate
|
|
*
|
|
* @param {HTMLElement|jQuery} element
|
|
* A DOM element to dispatch event on. Note: this may be a jQuery object,
|
|
* however be aware that this will trigger the same event for each element
|
|
* inside the jQuery collection; use with caution.
|
|
* @param {String|String[]} type
|
|
* The type(s) of event to simulate.
|
|
* @param {Object} [options]
|
|
* An object of options to pass to the event constructor. Typically, if
|
|
* an event is being proxied, you should just pass the original event
|
|
* object here. This allows, if the browser supports it, to be a truly
|
|
* simulated event.
|
|
*
|
|
* @return {Boolean}
|
|
* The return value is false if event is cancelable and at least one of the
|
|
* event handlers which handled this event called Event.preventDefault().
|
|
* Otherwise it returns true.
|
|
*/
|
|
Bootstrap.simulate = function (element, type, options) {
|
|
// Handle jQuery object wrappers so it triggers on each element.
|
|
var ret = true;
|
|
if (element instanceof $) {
|
|
element.each(function () {
|
|
if (!Bootstrap.simulate(this, type, options)) {
|
|
ret = false;
|
|
}
|
|
});
|
|
return ret;
|
|
}
|
|
|
|
if (!(element instanceof HTMLElement)) {
|
|
this.fatal('Passed element must be an instance of HTMLElement, got "@type" instead.', {
|
|
'@type': typeof element,
|
|
});
|
|
}
|
|
|
|
// Defer to the jQuery.simulate plugin, if it's available.
|
|
if (typeof $.simulate === 'function') {
|
|
new $.simulate(element, type, options);
|
|
return true;
|
|
}
|
|
|
|
var event;
|
|
var ctor;
|
|
var types = [].concat(type);
|
|
for (var i = 0, l = types.length; i < l; i++) {
|
|
type = types[i];
|
|
for (var name in this.eventMap) {
|
|
if (this.eventMap[name].test(type)) {
|
|
ctor = name;
|
|
break;
|
|
}
|
|
}
|
|
if (!ctor) {
|
|
throw new SyntaxError('Only rudimentary HTMLEvents, KeyboardEvents and MouseEvents are supported: ' + type);
|
|
}
|
|
var opts = {bubbles: true, cancelable: true};
|
|
if (ctor === 'KeyboardEvent' || ctor === 'MouseEvent') {
|
|
$.extend(opts, {ctrlKey: !1, altKey: !1, shiftKey: !1, metaKey: !1});
|
|
}
|
|
if (ctor === 'MouseEvent') {
|
|
$.extend(opts, {button: 0, pointerX: 0, pointerY: 0, view: window});
|
|
}
|
|
if (options) {
|
|
$.extend(opts, options);
|
|
}
|
|
if (typeof window[ctor] === 'function') {
|
|
event = new window[ctor](type, opts);
|
|
if (!element.dispatchEvent(event)) {
|
|
ret = false;
|
|
}
|
|
}
|
|
else if (document.createEvent) {
|
|
event = document.createEvent(ctor);
|
|
event.initEvent(type, opts.bubbles, opts.cancelable);
|
|
if (!element.dispatchEvent(event)) {
|
|
ret = false;
|
|
}
|
|
}
|
|
else if (typeof element.fireEvent === 'function') {
|
|
event = $.extend(document.createEventObject(), opts);
|
|
if (!element.fireEvent('on' + type, event)) {
|
|
ret = false;
|
|
}
|
|
}
|
|
else if (typeof element[type]) {
|
|
element[type]();
|
|
}
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Strips HTML and returns just text.
|
|
*
|
|
* @param {String|Element|jQuery} html
|
|
* A string of HTML content, an Element DOM object or a jQuery object.
|
|
*
|
|
* @return {String}
|
|
* The text without HTML tags.
|
|
*
|
|
* @todo Replace with http://locutus.io/php/strings/strip_tags/
|
|
*/
|
|
Bootstrap.stripHtml = function (html) {
|
|
if (html instanceof $) {
|
|
html = html.html();
|
|
}
|
|
else if (html instanceof Element) {
|
|
html = html.innerHTML;
|
|
}
|
|
var tmp = document.createElement('DIV');
|
|
tmp.innerHTML = html;
|
|
return (tmp.textContent || tmp.innerText || '').replace(/^[\s\n\t]*|[\s\n\t]*$/, '');
|
|
};
|
|
|
|
/**
|
|
* Provide a helper method for displaying when something is unsupported.
|
|
*
|
|
* @param {String} type
|
|
* The type of unsupported object, e.g. method or option.
|
|
* @param {String} name
|
|
* The name of the unsupported object.
|
|
* @param {*} [value]
|
|
* The value of the unsupported object.
|
|
*/
|
|
Bootstrap.unsupported = function (type, name, value) {
|
|
Bootstrap.warn('Unsupported by Drupal Bootstrap: (@type) @name -> @value', {
|
|
'@type': type,
|
|
'@name': name,
|
|
'@value': typeof value === 'object' ? JSON.stringify(value) : value
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Provide a helper method to display a warning.
|
|
*
|
|
* @param {String} message
|
|
* The message to display.
|
|
* @param {Object} [args]
|
|
* Arguments to use as replacements in Drupal.formatString.
|
|
*/
|
|
Bootstrap.warn = function (message, args) {
|
|
if (this.settings.dev && console.warn) {
|
|
console.warn(Drupal.formatString(message, args));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Wraps a plugin with common functionality.
|
|
*
|
|
* @param {Function} constructor
|
|
* A plugin constructor being wrapped.
|
|
* @param {Object|Function} plugin
|
|
* The plugin being wrapped.
|
|
* @param {Boolean} [extend = false]
|
|
* Whether to add super extensibility.
|
|
*/
|
|
Bootstrap.wrapPluginConstructor = function (constructor, plugin, extend) {
|
|
var proto = constructor.prototype;
|
|
|
|
// Add a jQuery UI like option getter/setter method.
|
|
var option = this.option;
|
|
if (proto.option === void(0)) {
|
|
proto.option = function () {
|
|
return option.apply(this, arguments);
|
|
};
|
|
}
|
|
|
|
if (extend) {
|
|
// Handle prototype properties separately.
|
|
if (plugin.prototype !== void 0) {
|
|
for (var key in plugin.prototype) {
|
|
if (!plugin.prototype.hasOwnProperty(key)) continue;
|
|
var value = plugin.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 plugin.prototype;
|
|
|
|
// Handle static properties.
|
|
for (key in plugin) {
|
|
if (!plugin.hasOwnProperty(key)) continue;
|
|
value = plugin[key];
|
|
if (typeof value === 'function') {
|
|
constructor[key] = this.superWrapper(constructor[key] || function () {}, value);
|
|
}
|
|
else {
|
|
constructor[key] = $.isPlainObject(value) ? $.extend(true, {}, constructor[key], value) : value;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Add Bootstrap to the global Drupal object.
|
|
Drupal.bootstrap = Drupal.bootstrap || Bootstrap;
|
|
|
|
})(window._, window.jQuery, window.Drupal, window.drupalSettings);
|