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 plugin
= callback
.apply(constructor, [this.settings
]);
119 if (!$.isPlainObject(plugin
)) {
120 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
});
123 this.wrapPluginConstructor(constructor, plugin
, true);
128 Bootstrap
.superWrapper = function (parent
, fn
) {
130 var previousSuper
= this.super;
132 var ret
= fn
.apply(this, arguments
);
134 this.super = previousSuper
;
144 * Provide a helper method for displaying when something is went wrong.
146 * @param {String} message
147 * The message to display.
148 * @param {Object} [args]
149 * An arguments to use in message.
152 * Always returns FALSE.
154 Bootstrap
.fatal = function (message
, args
) {
155 if (this.settings
.dev
&& console
.warn
) {
156 for (var name
in args
) {
157 if (args
.hasOwnProperty(name
) && typeof args
[name
] === 'object') {
158 args
[name
] = JSON
.stringify(args
[name
]);
161 Drupal
.throwError(new Error(Drupal
.formatString(message
, args
)));
167 * Intersects object properties.
169 * @param {...Object} objects
170 * Two or more objects. The first object will be used to return properties
174 * Returns the properties of first passed object that intersects with all
175 * other passed objects.
177 Bootstrap
.intersectObjects = function (objects
) {
178 var args
= Array
.prototype.slice
.call(arguments
);
179 return _
.pick(args
[0], _
.intersection
.apply(_
, _
.map(args
, function (obj
) {
180 return Object
.keys(obj
);
185 * Normalizes an object's values.
187 * @param {Object} obj
188 * The object to normalize.
191 * The normalized object.
193 Bootstrap
.normalizeObject = function (obj
) {
194 if (!$.isPlainObject(obj
)) {
199 if (typeof obj
[k
] === 'string') {
200 if (obj
[k
] === 'true') {
203 else if (obj
[k
] === 'false') {
206 else if (obj
[k
].match(/^[\d-.]$/)) {
207 obj
[k
] = parseFloat(obj
[k
]);
210 else if ($.isPlainObject(obj
[k
])) {
211 obj
[k
] = Bootstrap
.normalizeObject(obj
[k
]);
219 * An object based once plugin (similar to jquery.once, but without the DOM).
222 * A unique identifier.
223 * @param {Function} callback
224 * The callback to invoke if the identifier has not yet been seen.
226 * @return {Bootstrap}
228 Bootstrap
.once = function (id
, callback
) {
229 // Immediately return if identifier has already been processed.
230 if (this.processedOnce
[id
]) {
233 callback
.call(this, this.settings
);
234 this.processedOnce
[id
] = true;
239 * Provide jQuery UI like ability to get/set options for Bootstrap plugins.
241 * @param {string|object} key
242 * A string value of the option to set, can be dot like to a nested key.
243 * An object of key/value pairs.
245 * (optional) A value to set for key.
248 * - Returns nothing if key is an object or both key and value parameters
249 * were provided to set an option.
250 * - Returns the a value for a specific setting if key was provided.
251 * - Returns an object of key/value pairs of all the options if no key or
252 * value parameter was provided.
254 * @see https://github.com/jquery/jquery-ui/blob/master/ui/widget.js
256 Bootstrap
.option = function (key
, value
) {
257 var options
= $.isPlainObject(key
) ? $.extend({}, key
) : {};
259 // Get all options (clone so it doesn't reference the internal object).
260 if (arguments
.length
=== 0) {
261 return $.extend({}, this.options
);
264 // Get/set single option.
265 if (typeof key
=== "string") {
266 // Handle nested keys in dot notation.
267 // e.g., "foo.bar" => { foo: { bar: true } }
268 var parts
= key
.split('.');
272 for (var i
= 0; i
< parts
.length
- 1; i
++) {
273 obj
[parts
[i
]] = obj
[parts
[i
]] || {};
280 if (arguments
.length
=== 1) {
281 return obj
[key
] === void 0 ? null : obj
[key
];
288 // Set multiple options.
289 $.extend(true, this.options
, options
);
293 * Adds a ".noConflict()" helper method if needed.
296 * A jQuery plugin identifier located in $.fn.
297 * @param {Function} plugin
298 * @param {Function} plugin
299 * A constructor function used to initialize the for the jQuery plugin.
300 * @param {Boolean} [noConflict]
301 * Flag indicating whether or not to create a ".noConflict()" helper method
304 Bootstrap
.pluginNoConflict = function (id
, plugin
, noConflict
) {
305 if (plugin
.noConflict
=== void 0 && (noConflict
=== void 0 || noConflict
)) {
307 plugin
.noConflict = function () {
315 * Replaces a Bootstrap jQuery plugin definition.
318 * A jQuery plugin identifier located in $.fn.
319 * @param {Function} callback
320 * A callback function that is immediately invoked and must return a
321 * function that will be used as the plugin constructor.
322 * @param {Boolean} [noConflict]
323 * Flag indicating whether or not to create a ".noConflict()" helper method
326 Bootstrap
.replacePlugin = function (id
, callback
, noConflict
) {
327 // Immediately return if plugin doesn't exist.
328 if (typeof $.fn
[id
] !== 'function') {
329 return this.fatal('Specified jQuery plugin identifier does not exist: @id', {'@id': id
});
332 // Immediately return if callback isn't a function.
333 if (typeof callback
!== 'function') {
334 return this.fatal('You must provide a valid callback function to replace a jQuery plugin: @callback', {'@callback': callback
});
337 // Determine existing plugin constructor.
338 var constructor = $.fn
[id
] && $.fn
[id
].Constructor
|| $.fn
[id
];
339 var plugin
= callback
.apply(constructor, [this.settings
]);
341 // Immediately return if plugin isn't a function.
342 if (typeof plugin
!== 'function') {
343 return this.fatal('Returned value from callback is not a usable function to replace a jQuery plugin "@id": @plugin', {'@id': id
, '@plugin': plugin
});
346 this.wrapPluginConstructor(constructor, plugin
);
348 // Add a ".noConflict()" helper method.
349 this.pluginNoConflict(id
, plugin
, noConflict
);
355 * Simulates a native event on an element in the browser.
357 * Note: This is a fairly complete modern implementation. If things aren't
358 * working quite the way you intend (in older browsers), you may wish to use
359 * the jQuery.simulate plugin. If it's available, this method will defer to
362 * @see https://github.com/jquery/jquery-simulate
364 * @param {HTMLElement|jQuery} element
365 * A DOM element to dispatch event on. Note: this may be a jQuery object,
366 * however be aware that this will trigger the same event for each element
367 * inside the jQuery collection; use with caution.
368 * @param {String|String[]} type
369 * The type(s) of event to simulate.
370 * @param {Object} [options]
371 * An object of options to pass to the event constructor. Typically, if
372 * an event is being proxied, you should just pass the original event
373 * object here. This allows, if the browser supports it, to be a truly
377 * The return value is false if event is cancelable and at least one of the
378 * event handlers which handled this event called Event.preventDefault().
379 * Otherwise it returns true.
381 Bootstrap
.simulate = function (element
, type
, options
) {
382 // Handle jQuery object wrappers so it triggers on each element.
384 if (element
instanceof $) {
385 element
.each(function () {
386 if (!Bootstrap
.simulate(this, type
, options
)) {
393 if (!(element
instanceof HTMLElement
)) {
394 this.fatal('Passed element must be an instance of HTMLElement, got "@type" instead.', {
395 '@type': typeof element
,
399 // Defer to the jQuery.simulate plugin, if it's available.
400 if (typeof $.simulate
=== 'function') {
401 new $.simulate(element
, type
, options
);
407 var types
= [].concat(type
);
408 for (var i
= 0, l
= types
.length
; i
< l
; i
++) {
410 for (var name
in this.eventMap
) {
411 if (this.eventMap
[name
].test(type
)) {
417 throw new SyntaxError('Only rudimentary HTMLEvents, KeyboardEvents and MouseEvents are supported: ' + type
);
419 var opts
= {bubbles
: true, cancelable
: true};
420 if (ctor
=== 'KeyboardEvent' || ctor
=== 'MouseEvent') {
421 $.extend(opts
, {ctrlKey
: !1, altKey
: !1, shiftKey
: !1, metaKey
: !1});
423 if (ctor
=== 'MouseEvent') {
424 $.extend(opts
, {button
: 0, pointerX
: 0, pointerY
: 0, view
: window
});
427 $.extend(opts
, options
);
429 if (typeof window
[ctor
] === 'function') {
430 event
= new window
[ctor
](type
, opts
);
431 if (!element
.dispatchEvent(event
)) {
435 else if (document
.createEvent
) {
436 event
= document
.createEvent(ctor
);
437 event
.initEvent(type
, opts
.bubbles
, opts
.cancelable
);
438 if (!element
.dispatchEvent(event
)) {
442 else if (typeof element
.fireEvent
=== 'function') {
443 event
= $.extend(document
.createEventObject(), opts
);
444 if (!element
.fireEvent('on' + type
, event
)) {
448 else if (typeof element
[type
]) {
456 * Strips HTML and returns just text.
458 * @param {String|Element|jQuery} html
459 * A string of HTML content, an Element DOM object or a jQuery object.
462 * The text without HTML tags.
464 * @todo Replace with http://locutus.io/php/strings/strip_tags/
466 Bootstrap
.stripHtml = function (html
) {
467 if (html
instanceof $) {
470 else if (html
instanceof Element
) {
471 html
= html
.innerHTML
;
473 var tmp
= document
.createElement('DIV');
474 tmp
.innerHTML
= html
;
475 return (tmp
.textContent
|| tmp
.innerText
|| '').replace(/^[\s\n\t]*|[\s\n\t]*$/, '');
479 * Provide a helper method for displaying when something is unsupported.
481 * @param {String} type
482 * The type of unsupported object, e.g. method or option.
483 * @param {String} name
484 * The name of the unsupported object.
486 * The value of the unsupported object.
488 Bootstrap
.unsupported = function (type
, name
, value
) {
489 Bootstrap
.warn('Unsupported by Drupal Bootstrap: (@type) @name -> @value', {
492 '@value': typeof value
=== 'object' ? JSON
.stringify(value
) : value
497 * Provide a helper method to display a warning.
499 * @param {String} message
500 * The message to display.
501 * @param {Object} [args]
502 * Arguments to use as replacements in Drupal.formatString.
504 Bootstrap
.warn = function (message
, args
) {
505 if (this.settings
.dev
&& console
.warn
) {
506 console
.warn(Drupal
.formatString(message
, args
));
511 * Wraps a plugin with common functionality.
513 * @param {Function} constructor
514 * A plugin constructor being wrapped.
515 * @param {Object|Function} plugin
516 * The plugin being wrapped.
517 * @param {Boolean} [extend = false]
518 * Whether to add super extensibility.
520 Bootstrap
.wrapPluginConstructor = function (constructor, plugin
, extend
) {
521 var proto
= constructor.prototype;
523 // Add a jQuery UI like option getter/setter method.
524 var option
= this.option
;
525 if (proto
.option
=== void(0)) {
526 proto
.option = function () {
527 return option
.apply(this, arguments
);
532 // Handle prototype properties separately.
533 if (plugin
.prototype !== void 0) {
534 for (var key
in plugin
.prototype) {
535 if (!plugin
.prototype.hasOwnProperty(key
)) continue;
536 var value
= plugin
.prototype[key
];
537 if (typeof value
=== 'function') {
538 proto
[key
] = this.superWrapper(proto
[key
] || function () {}, value
);
541 proto
[key
] = $.isPlainObject(value
) ? $.extend(true, {}, proto
[key
], value
) : value
;
545 delete plugin
.prototype;
547 // Handle static properties.
548 for (key
in plugin
) {
549 if (!plugin
.hasOwnProperty(key
)) continue;
551 if (typeof value
=== 'function') {
552 constructor[key
] = this.superWrapper(constructor[key
] || function () {}, value
);
555 constructor[key
] = $.isPlainObject(value
) ? $.extend(true, {}, constructor[key
], value
) : value
;
562 * Add Bootstrap to the global Drupal object.
566 Drupal
.bootstrap
= Drupal
.bootstrap
|| Bootstrap
;
568 })(window
._
, window
.jQuery
, window
.Drupal
, window
.drupalSettings
);