3 * Drupal Bootstrap object.
7 * All Drupal Bootstrap JavaScript APIs are contained in this namespace.
11 (function (_, $, Drupal, drupalSettings) {
16 settings: drupalSettings.bootstrap || {}
20 * Wraps Drupal.checkPlain() to ensure value passed isn't empty.
22 * Encodes special characters in a plain-text string for display as HTML.
25 * The string to be encoded.
30 * @ingroup sanitization
32 Bootstrap.checkPlain = function (str) {
33 return str && Drupal.checkPlain(str) || '';
37 * Creates a jQuery plugin.
40 * A jQuery plugin identifier located in $.fn.
41 * @param {Function} plugin
42 * A constructor function used to initialize the for the jQuery plugin.
43 * @param {Boolean} [noConflict]
44 * Flag indicating whether or not to create a ".noConflict()" helper method
47 Bootstrap.createPlugin = function (id, plugin, noConflict) {
48 // Immediately return if plugin doesn't exist.
49 if ($.fn[id] !== void 0) {
50 return this.fatal('Specified jQuery plugin identifier already exists: @id. Use Drupal.bootstrap.replacePlugin() instead.', {'@id': id});
53 // Immediately return if plugin isn't a function.
54 if (typeof plugin !== 'function') {
55 return this.fatal('You must provide a constructor function to create a jQuery plugin "@id": @plugin', {'@id': id, '@plugin': plugin});
58 // Add a ".noConflict()" helper method.
59 this.pluginNoConflict(id, plugin, noConflict);
65 * Diff object properties.
67 * @param {...Object} objects
68 * Two or more objects. The first object will be used to return properties
72 * Returns the properties of the first passed object that are not present
73 * in all other passed objects.
75 Bootstrap.diffObjects = function (objects) {
76 var args = Array.prototype.slice.call(arguments);
77 return _.pick(args[0], _.difference.apply(_, _.map(args, function (obj) {
78 return Object.keys(obj);
83 * Map of supported events by regular expression.
85 * @type {Object<Event|MouseEvent|KeyboardEvent|TouchEvent,RegExp>}
87 Bootstrap.eventMap = {
88 Event: /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,
89 MouseEvent: /^(?:click|dblclick|mouse(?:down|enter|leave|up|over|move|out))$/,
90 KeyboardEvent: /^(?:key(?:down|press|up))$/,
91 TouchEvent: /^(?:touch(?:start|end|move|cancel))$/
95 * Extends a jQuery Plugin.
98 * A jQuery plugin identifier located in $.fn.
99 * @param {Function} callback
100 * A constructor function used to initialize the for the jQuery plugin.
102 * @return {Function|Boolean}
103 * The jQuery plugin constructor or FALSE if the plugin does not exist.
105 Bootstrap.extendPlugin = function (id, callback) {
106 // Immediately return if plugin doesn't exist.
107 if (typeof $.fn[id] !== 'function') {
108 return this.fatal('Specified jQuery plugin identifier does not exist: @id', {'@id': id});
111 // Immediately return if callback isn't a function.
112 if (typeof callback !== 'function') {
113 return this.fatal('You must provide a callback function to extend the jQuery plugin "@id": @callback', {'@id': id, '@callback': callback});
116 // Determine existing plugin constructor.
117 var constructor = $.fn[id] && $.fn[id].Constructor || $.fn[id];
118 var proto = constructor.prototype;
120 var obj = callback.apply(constructor, [this.settings]);
121 if (!$.isPlainObject(obj)) {
122 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});
125 // Add a jQuery UI like option getter/setter method.
126 var option = this.option;
127 if (proto.option === void(0)) {
128 proto.option = function () {
129 return option.apply(this, arguments);
133 // Handle prototype properties separately.
134 if (obj.prototype !== void 0) {
135 for (var key in obj.prototype) {
136 if (!obj.prototype.hasOwnProperty(key)) continue;
137 var value = obj.prototype[key];
138 if (typeof value === 'function') {
139 proto[key] = this.superWrapper(proto[key] || function () {}, value);
142 proto[key] = $.isPlainObject(value) ? $.extend(true, {}, proto[key], value) : value;
146 delete obj.prototype;
148 // Handle static properties.
150 if (!obj.hasOwnProperty(key)) continue;
152 if (typeof value === 'function') {
153 constructor[key] = this.superWrapper(constructor[key] || function () {}, value);
156 constructor[key] = $.isPlainObject(value) ? $.extend(true, {}, constructor[key], value) : value;
163 Bootstrap.superWrapper = function (parent, fn) {
165 var previousSuper = this.super;
167 var ret = fn.apply(this, arguments);
169 this.super = previousSuper;
179 * Provide a helper method for displaying when something is went wrong.
181 * @param {String} message
182 * The message to display.
183 * @param {Object} [args]
184 * An arguments to use in message.
187 * Always returns FALSE.
189 Bootstrap.fatal = function (message, args) {
190 if (this.settings.dev && console.warn) {
191 for (var name in args) {
192 if (args.hasOwnProperty(name) && typeof args[name] === 'object') {
193 args[name] = JSON.stringify(args[name]);
196 Drupal.throwError(new Error(Drupal.formatString(message, args)));
202 * Intersects object properties.
204 * @param {...Object} objects
205 * Two or more objects. The first object will be used to return properties
209 * Returns the properties of first passed object that intersects with all
210 * other passed objects.
212 Bootstrap.intersectObjects = function (objects) {
213 var args = Array.prototype.slice.call(arguments);
214 return _.pick(args[0], _.intersection.apply(_, _.map(args, function (obj) {
215 return Object.keys(obj);
220 * An object based once plugin (similar to jquery.once, but without the DOM).
223 * A unique identifier.
224 * @param {Function} callback
225 * The callback to invoke if the identifier has not yet been seen.
227 * @return {Bootstrap}
229 Bootstrap.once = function (id, callback) {
230 // Immediately return if identifier has already been processed.
231 if (this.processedOnce[id]) {
234 callback.call(this, this.settings);
235 this.processedOnce[id] = true;
240 * Provide jQuery UI like ability to get/set options for Bootstrap plugins.
242 * @param {string|object} key
243 * A string value of the option to set, can be dot like to a nested key.
244 * An object of key/value pairs.
246 * (optional) A value to set for key.
249 * - Returns nothing if key is an object or both key and value parameters
250 * were provided to set an option.
251 * - Returns the a value for a specific setting if key was provided.
252 * - Returns an object of key/value pairs of all the options if no key or
253 * value parameter was provided.
255 * @see https://github.com/jquery/jquery-ui/blob/master/ui/widget.js
257 Bootstrap.option = function (key, value) {
258 var options = $.isPlainObject(key) ? $.extend({}, key) : {};
260 // Get all options (clone so it doesn't reference the internal object).
261 if (arguments.length === 0) {
262 return $.extend({}, this.options);
265 // Get/set single option.
266 if (typeof key === "string") {
267 // Handle nested keys in dot notation.
268 // e.g., "foo.bar" => { foo: { bar: true } }
269 var parts = key.split('.');
273 for (var i = 0; i < parts.length - 1; i++) {
274 obj[parts[i]] = obj[parts[i]] || {};
281 if (arguments.length === 1) {
282 return obj[key] === void 0 ? null : obj[key];
289 // Set multiple options.
290 $.extend(true, this.options, options);
294 * Adds a ".noConflict()" helper method if needed.
297 * A jQuery plugin identifier located in $.fn.
298 * @param {Function} plugin
299 * @param {Function} plugin
300 * A constructor function used to initialize the for the jQuery plugin.
301 * @param {Boolean} [noConflict]
302 * Flag indicating whether or not to create a ".noConflict()" helper method
305 Bootstrap.pluginNoConflict = function (id, plugin, noConflict) {
306 if (plugin.noConflict === void 0 && (noConflict === void 0 || noConflict)) {
308 plugin.noConflict = function () {
316 * Replaces a Bootstrap jQuery plugin definition.
319 * A jQuery plugin identifier located in $.fn.
320 * @param {Function} callback
321 * A callback function that is immediately invoked and must return a
322 * function that will be used as the plugin constructor.
323 * @param {Boolean} [noConflict]
324 * Flag indicating whether or not to create a ".noConflict()" helper method
327 Bootstrap.replacePlugin = function (id, callback, noConflict) {
328 // Immediately return if plugin doesn't exist.
329 if (typeof $.fn[id] !== 'function') {
330 return this.fatal('Specified jQuery plugin identifier does not exist: @id', {'@id': id});
333 // Immediately return if callback isn't a function.
334 if (typeof callback !== 'function') {
335 return this.fatal('You must provide a valid callback function to replace a jQuery plugin: @callback', {'@callback': callback});
338 // Determine existing plugin constructor.
339 var constructor = $.fn[id] && $.fn[id].Constructor || $.fn[id];
340 var plugin = callback.apply(constructor, [this.settings]);
342 // Immediately return if plugin isn't a function.
343 if (typeof plugin !== 'function') {
344 return this.fatal('Returned value from callback is not a usable function to replace a jQuery plugin "@id": @plugin', {'@id': id, '@plugin': plugin});
347 // Add a ".noConflict()" helper method.
348 this.pluginNoConflict(id, plugin, noConflict);
354 * Simulates a native event on an element in the browser.
356 * Note: This is a fairly complete modern implementation. If things aren't
357 * working quite the way you intend (in older browsers), you may wish to use
358 * the jQuery.simulate plugin. If it's available, this method will defer to
361 * @see https://github.com/jquery/jquery-simulate
363 * @param {HTMLElement|jQuery} element
364 * A DOM element to dispatch event on. Note: this may be a jQuery object,
365 * however be aware that this will trigger the same event for each element
366 * inside the jQuery collection; use with caution.
367 * @param {String} type
368 * The type of event to simulate.
369 * @param {Object} [options]
370 * An object of options to pass to the event constructor. Typically, if
371 * an event is being proxied, you should just pass the original event
372 * object here. This allows, if the browser supports it, to be a truly
376 * The return value is false if event is cancelable and at least one of the
377 * event handlers which handled this event called Event.preventDefault().
378 * Otherwise it returns true.
380 Bootstrap.simulate = function (element, type, options) {
381 // Handle jQuery object wrappers so it triggers on each element.
382 if (element instanceof $) {
384 element.each(function () {
385 if (!Bootstrap.simulate(this, type, options)) {
392 if (!(element instanceof HTMLElement)) {
393 this.fatal('Passed element must be an instance of HTMLElement, got "@type" instead.', {
394 '@type': typeof element,
398 // Defer to the jQuery.simulate plugin, if it's available.
399 if (typeof $.simulate === 'function') {
400 new $.simulate(element, type, options);
406 for (var name in this.eventMap) {
407 if (this.eventMap[name].test(type)) {
413 throw new SyntaxError('Only rudimentary HTMLEvents, KeyboardEvents and MouseEvents are supported: ' + type);
415 var opts = {bubbles: true, cancelable: true};
416 if (ctor === 'KeyboardEvent' || ctor === 'MouseEvent') {
417 $.extend(opts, {ctrlKey: !1, altKey: !1, shiftKey: !1, metaKey: !1});
419 if (ctor === 'MouseEvent') {
420 $.extend(opts, {button: 0, pointerX: 0, pointerY: 0, view: window});
423 $.extend(opts, options);
425 if (typeof window[ctor] === 'function') {
426 event = new window[ctor](type, opts);
427 return element.dispatchEvent(event);
429 else if (document.createEvent) {
430 event = document.createEvent(ctor);
431 event.initEvent(type, opts.bubbles, opts.cancelable);
432 return element.dispatchEvent(event);
434 else if (typeof element.fireEvent === 'function') {
435 event = $.extend(document.createEventObject(), opts);
436 return element.fireEvent('on' + type, event);
438 else if (typeof element[type]) {
445 * Strips HTML and returns just text.
447 * @param {String|Element|jQuery} html
448 * A string of HTML content, an Element DOM object or a jQuery object.
451 * The text without HTML tags.
453 * @todo Replace with http://locutus.io/php/strings/strip_tags/
455 Bootstrap.stripHtml = function (html) {
456 if (html instanceof $) {
459 else if (html instanceof Element) {
460 html = html.innerHTML;
462 var tmp = document.createElement('DIV');
463 tmp.innerHTML = html;
464 return (tmp.textContent || tmp.innerText || '').replace(/^[\s\n\t]*|[\s\n\t]*$/g, '');
468 * Provide a helper method for displaying when something is unsupported.
470 * @param {String} type
471 * The type of unsupported object, e.g. method or option.
472 * @param {String} name
473 * The name of the unsupported object.
475 * The value of the unsupported object.
477 Bootstrap.unsupported = function (type, name, value) {
478 Bootstrap.warn('Unsupported by Drupal Bootstrap: (@type) @name -> @value', {
481 '@value': typeof value === 'object' ? JSON.stringify(value) : value
486 * Provide a helper method to display a warning.
488 * @param {String} message
489 * The message to display.
490 * @param {Object} [args]
491 * Arguments to use as replacements in Drupal.formatString.
493 Bootstrap.warn = function (message, args) {
494 if (this.settings.dev && console.warn) {
495 console.warn(Drupal.formatString(message, args));
500 * Add Bootstrap to the global Drupal object.
504 Drupal.bootstrap = Drupal.bootstrap || Bootstrap;
506 })(window._, window.jQuery, window.Drupal, window.drupalSettings);