From eda99074f887aaf3a43afd4cdb1fa28308962bdf Mon Sep 17 00:00:00 2001 From: Mark Otto Date: Fri, 21 Feb 2025 21:06:51 -0800 Subject: [PATCH] Remove jQuery support in plugins --- js/src/alert.js | 24 ----------- js/src/button.js | 18 -------- js/src/carousel.js | 26 ------------ js/src/collapse.js | 27 ------------ js/src/dom/event-handler.js | 36 +--------------- js/src/dropdown.js | 24 ----------- js/src/modal.js | 25 +---------- js/src/offcanvas.js | 23 ----------- js/src/popover.js | 24 ----------- js/src/scrollspy.js | 24 +---------- js/src/tab.js | 24 +---------- js/src/toast.js | 23 +---------- js/src/tooltip.js | 26 +----------- js/src/util/index.js | 27 ------------ site/src/content/docs/components/popovers.mdx | 2 +- .../docs/getting-started/javascript.mdx | 41 ------------------- 16 files changed, 8 insertions(+), 386 deletions(-) diff --git a/js/src/alert.js b/js/src/alert.js index 88232bceba..a0e6cab970 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -8,7 +8,6 @@ import BaseComponent from './base-component.js' import EventHandler from './dom/event-handler.js' import { enableDismissTrigger } from './util/component-functions.js' -import { defineJQueryPlugin } from './util/index.js' /** * Constants @@ -53,23 +52,6 @@ class Alert extends BaseComponent { EventHandler.trigger(this._element, EVENT_CLOSED) this.dispose() } - - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Alert.getOrCreateInstance(this) - - if (typeof config !== 'string') { - return - } - - if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { - throw new TypeError(`No method named "${config}"`) - } - - data[config](this) - }) - } } /** @@ -78,10 +60,4 @@ class Alert extends BaseComponent { enableDismissTrigger(Alert, 'close') -/** - * jQuery - */ - -defineJQueryPlugin(Alert) - export default Alert diff --git a/js/src/button.js b/js/src/button.js index a797f5050d..7697edecf2 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -7,7 +7,6 @@ import BaseComponent from './base-component.js' import EventHandler from './dom/event-handler.js' -import { defineJQueryPlugin } from './util/index.js' /** * Constants @@ -37,17 +36,6 @@ class Button extends BaseComponent { // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE)) } - - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Button.getOrCreateInstance(this) - - if (config === 'toggle') { - data[config]() - } - }) - } } /** @@ -63,10 +51,4 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => { data.toggle() }) -/** - * jQuery - */ - -defineJQueryPlugin(Button) - export default Button diff --git a/js/src/carousel.js b/js/src/carousel.js index 68d11a32f2..a8df5f7b53 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -10,7 +10,6 @@ import EventHandler from './dom/event-handler.js' import Manipulator from './dom/manipulator.js' import SelectorEngine from './dom/selector-engine.js' import { - defineJQueryPlugin, getNextActiveElement, isRTL, isVisible, @@ -404,25 +403,6 @@ class Carousel extends BaseComponent { return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT } - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Carousel.getOrCreateInstance(this, config) - - if (typeof config === 'number') { - data.to(config) - return - } - - if (typeof config === 'string') { - if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { - throw new TypeError(`No method named "${config}"`) - } - - data[config]() - } - }) - } } /** @@ -465,10 +445,4 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => { } }) -/** - * jQuery - */ - -defineJQueryPlugin(Carousel) - export default Carousel diff --git a/js/src/collapse.js b/js/src/collapse.js index 9f0c60cc53..2010d16f38 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -9,7 +9,6 @@ import BaseComponent from './base-component.js' import EventHandler from './dom/event-handler.js' import SelectorEngine from './dom/selector-engine.js' import { - defineJQueryPlugin, getElement, reflow } from './util/index.js' @@ -251,26 +250,6 @@ class Collapse extends BaseComponent { element.setAttribute('aria-expanded', isOpen) } } - - // Static - static jQueryInterface(config) { - const _config = {} - if (typeof config === 'string' && /show|hide/.test(config)) { - _config.toggle = false - } - - return this.each(function () { - const data = Collapse.getOrCreateInstance(this, _config) - - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } - - data[config]() - } - }) - } } /** @@ -288,10 +267,4 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( } }) -/** - * jQuery - */ - -defineJQueryPlugin(Collapse) - export default Collapse diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 561d8751d7..5b2d4c1dd8 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -5,8 +5,6 @@ * -------------------------------------------------------------------------- */ -import { getjQuery } from '../util/index.js' - /** * Constants */ @@ -261,38 +259,8 @@ const EventHandler = { return null } - const $ = getjQuery() - const typeEvent = getTypeEvent(event) - const inNamespace = event !== typeEvent - - let jQueryEvent = null - let bubbles = true - let nativeDispatch = true - let defaultPrevented = false - - if (inNamespace && $) { - jQueryEvent = $.Event(event, args) - - $(element).trigger(jQueryEvent) - bubbles = !jQueryEvent.isPropagationStopped() - nativeDispatch = !jQueryEvent.isImmediatePropagationStopped() - defaultPrevented = jQueryEvent.isDefaultPrevented() - } - - const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args) - - if (defaultPrevented) { - evt.preventDefault() - } - - if (nativeDispatch) { - element.dispatchEvent(evt) - } - - if (evt.defaultPrevented && jQueryEvent) { - jQueryEvent.preventDefault() - } - + const evt = hydrateObj(new Event(event, { bubbles: true, cancelable: true }), args) + element.dispatchEvent(evt) return evt } } diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 96094a3e65..a31f801d5b 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -11,7 +11,6 @@ import EventHandler from './dom/event-handler.js' import Manipulator from './dom/manipulator.js' import SelectorEngine from './dom/selector-engine.js' import { - defineJQueryPlugin, execute, getElement, getNextActiveElement, @@ -336,23 +335,6 @@ class Dropdown extends BaseComponent { getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus() } - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Dropdown.getOrCreateInstance(this, config) - - if (typeof config !== 'string') { - return - } - - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } - - data[config]() - }) - } - static clearMenus(event) { if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) { return @@ -446,10 +428,4 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( Dropdown.getOrCreateInstance(this).toggle() }) -/** - * jQuery - */ - -defineJQueryPlugin(Dropdown) - export default Dropdown diff --git a/js/src/modal.js b/js/src/modal.js index dd61649ecc..538d218653 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -12,7 +12,7 @@ import Backdrop from './util/backdrop.js' import { enableDismissTrigger } from './util/component-functions.js' import FocusTrap from './util/focustrap.js' import { - defineJQueryPlugin, isRTL, isVisible, reflow + isRTL, isVisible, reflow } from './util/index.js' import ScrollBarHelper from './util/scrollbar.js' @@ -313,23 +313,6 @@ class Modal extends BaseComponent { this._element.style.paddingLeft = '' this._element.style.paddingRight = '' } - - // Static - static jQueryInterface(config, relatedTarget) { - return this.each(function () { - const data = Modal.getOrCreateInstance(this, config) - - if (typeof config !== 'string') { - return - } - - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } - - data[config](relatedTarget) - }) - } } /** @@ -369,10 +352,4 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( enableDismissTrigger(Modal) -/** - * jQuery - */ - -defineJQueryPlugin(Modal) - export default Modal diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 8d1feb13bb..decc156439 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -12,7 +12,6 @@ import Backdrop from './util/backdrop.js' import { enableDismissTrigger } from './util/component-functions.js' import FocusTrap from './util/focustrap.js' import { - defineJQueryPlugin, isDisabled, isVisible } from './util/index.js' @@ -207,22 +206,6 @@ class Offcanvas extends BaseComponent { }) } - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Offcanvas.getOrCreateInstance(this, config) - - if (typeof config !== 'string') { - return - } - - if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { - throw new TypeError(`No method named "${config}"`) - } - - data[config](this) - }) - } } /** @@ -273,10 +256,4 @@ EventHandler.on(window, EVENT_RESIZE, () => { enableDismissTrigger(Offcanvas) -/** - * jQuery - */ - -defineJQueryPlugin(Offcanvas) - export default Offcanvas diff --git a/js/src/popover.js b/js/src/popover.js index 612c5218fc..b8383dc0d5 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -6,7 +6,6 @@ */ import Tooltip from './tooltip.js' -import { defineJQueryPlugin } from './util/index.js' /** * Constants @@ -69,29 +68,6 @@ class Popover extends Tooltip { _getContent() { return this._resolvePossibleFunction(this._config.content) } - - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Popover.getOrCreateInstance(this, config) - - if (typeof config !== 'string') { - return - } - - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } - - data[config]() - }) - } } -/** - * jQuery - */ - -defineJQueryPlugin(Popover) - export default Popover diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 368092de45..f3473ab2bd 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -9,7 +9,7 @@ import BaseComponent from './base-component.js' import EventHandler from './dom/event-handler.js' import SelectorEngine from './dom/selector-engine.js' import { - defineJQueryPlugin, getElement, isDisabled, isVisible + getElement, isDisabled, isVisible } from './util/index.js' /** @@ -259,22 +259,6 @@ class ScrollSpy extends BaseComponent { } } - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = ScrollSpy.getOrCreateInstance(this, config) - - if (typeof config !== 'string') { - return - } - - if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { - throw new TypeError(`No method named "${config}"`) - } - - data[config]() - }) - } } /** @@ -287,10 +271,4 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => { } }) -/** - * jQuery - */ - -defineJQueryPlugin(ScrollSpy) - export default ScrollSpy diff --git a/js/src/tab.js b/js/src/tab.js index dfaef0ffa2..989a37a657 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -8,7 +8,7 @@ import BaseComponent from './base-component.js' import EventHandler from './dom/event-handler.js' import SelectorEngine from './dom/selector-engine.js' -import { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index.js' +import { getNextActiveElement, isDisabled } from './util/index.js' /** * Constants @@ -263,23 +263,6 @@ class Tab extends BaseComponent { _getOuterElement(elem) { return elem.closest(SELECTOR_OUTER) || elem } - - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Tab.getOrCreateInstance(this) - - if (typeof config !== 'string') { - return - } - - if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { - throw new TypeError(`No method named "${config}"`) - } - - data[config]() - }) - } } /** @@ -306,10 +289,5 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => { Tab.getOrCreateInstance(element) } }) -/** - * jQuery - */ - -defineJQueryPlugin(Tab) export default Tab diff --git a/js/src/toast.js b/js/src/toast.js index d5d9c0ee0c..d5db423fe9 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -8,7 +8,7 @@ import BaseComponent from './base-component.js' import EventHandler from './dom/event-handler.js' import { enableDismissTrigger } from './util/component-functions.js' -import { defineJQueryPlugin, reflow } from './util/index.js' +import { reflow } from './util/index.js' /** * Constants @@ -193,21 +193,6 @@ class Toast extends BaseComponent { clearTimeout(this._timeout) this._timeout = null } - - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Toast.getOrCreateInstance(this, config) - - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } - - data[config](this) - } - }) - } } /** @@ -216,10 +201,4 @@ class Toast extends BaseComponent { enableDismissTrigger(Toast) -/** - * jQuery - */ - -defineJQueryPlugin(Toast) - export default Toast diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 097477f7a1..9b7584e639 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -10,7 +10,7 @@ import BaseComponent from './base-component.js' import EventHandler from './dom/event-handler.js' import Manipulator from './dom/manipulator.js' import { - defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop + execute, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index.js' import { DefaultAllowlist } from './util/sanitizer.js' import TemplateFactory from './util/template-factory.js' @@ -604,29 +604,5 @@ class Tooltip extends BaseComponent { this.tip = null } } - - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Tooltip.getOrCreateInstance(this, config) - - if (typeof config !== 'string') { - return - } - - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } - - data[config]() - }) - } } - -/** - * jQuery - */ - -defineJQueryPlugin(Tooltip) - export default Tooltip diff --git a/js/src/util/index.js b/js/src/util/index.js index c271cc5368..4f35f31a5d 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -176,14 +176,6 @@ const reflow = element => { element.offsetHeight // eslint-disable-line no-unused-expressions } -const getjQuery = () => { - if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) { - return window.jQuery - } - - return null -} - const DOMContentLoadedCallbacks = [] const onDOMContentLoaded = callback => { @@ -205,23 +197,6 @@ const onDOMContentLoaded = callback => { const isRTL = () => document.documentElement.dir === 'rtl' -const defineJQueryPlugin = plugin => { - onDOMContentLoaded(() => { - const $ = getjQuery() - /* istanbul ignore if */ - if ($) { - const name = plugin.NAME - const JQUERY_NO_CONFLICT = $.fn[name] - $.fn[name] = plugin.jQueryInterface - $.fn[name].Constructor = plugin - $.fn[name].noConflict = () => { - $.fn[name] = JQUERY_NO_CONFLICT - return plugin.jQueryInterface - } - } - }) -} - const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => { return typeof possibleCallback === 'function' ? possibleCallback.call(...args) : defaultValue } @@ -284,12 +259,10 @@ const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed } export { - defineJQueryPlugin, execute, executeAfterTransition, findShadowRoot, getElement, - getjQuery, getNextActiveElement, getTransitionDurationFromElement, getUID, diff --git a/site/src/content/docs/components/popovers.mdx b/site/src/content/docs/components/popovers.mdx index 5ce8fd4277..5cf6befea8 100644 --- a/site/src/content/docs/components/popovers.mdx +++ b/site/src/content/docs/components/popovers.mdx @@ -177,7 +177,7 @@ Note that for security reasons the `sanitize`, `sanitizeFn`, and `allowList` opt | `popperConfig` | null, object, function | `null` | To change Bootstrap's default Popper config, see [Popper's configuration](https://popper.js.org/docs/v2/constructors/#options). When a function is used to create the Popper configuration, it's called with an object that contains the Bootstrap's default Popper configuration. It helps you use and merge the default with your own configuration. The function must return a configuration object for Popper. | | `sanitize` | boolean | `true` | Enable or disable the sanitization. If activated `'template'`, `'content'` and `'title'` options will be sanitized. | | `sanitizeFn` | null, function | `null` | Here you can supply your own sanitize function. This can be useful if you prefer to use a dedicated library to perform sanitization. | -| `selector` | string, false | `false` | If a selector is provided, popover objects will be delegated to the specified targets. In practice, this is used to also apply popovers to dynamically added DOM elements (`jQuery.on` support). See [this issue]([[config:repo]]/issues/4215) and [an informative example](https://codepen.io/Johann-S/pen/djJYPb). **Note**: `title` attribute must not be used as a selector. | +| `selector` | string, false | `false` | If a selector is provided, popover objects will be delegated to the specified targets. In practice, this is used to also apply popovers to dynamically added DOM elements. See [this issue]([[config:repo]]/issues/4215) and [an informative example](https://codepen.io/Johann-S/pen/djJYPb). **Note**: `title` attribute must not be used as a selector. | | `template` | string | `'
'` | Base HTML to use when creating the popover. The popover's `title` will be injected into the `.popover-inner`. `.popover-arrow` will become the popover's arrow. The outermost wrapper element should have the `.popover` class and `role="popover"`. | | `title` | string, element, function | `''` | Default title value if `title` attribute isn't present. If a function is given, it will be called with its `this` reference set to the element that the popover is attached to. | | `trigger` | string | `'hover focus'` | How popover is triggered: click, hover, focus, manual. You may pass multiple triggers; separate them with a space. `'manual'` indicates that the popover will be triggered programmatically via the `.popover('show')`, `.popover('hide')` and `.popover('toggle')` methods; this value cannot be combined with any other trigger. `'hover'` on its own will result in popovers that cannot be triggered via the keyboard, and should only be used if alternative methods for conveying the same information for keyboard users is present. | diff --git a/site/src/content/docs/getting-started/javascript.mdx b/site/src/content/docs/getting-started/javascript.mdx index 0ad72e9a4d..5a617d5a1a 100644 --- a/site/src/content/docs/getting-started/javascript.mdx +++ b/site/src/content/docs/getting-started/javascript.mdx @@ -292,47 +292,6 @@ const tooltip = new bootstrap.Tooltip(yourTooltipEl, { }) ``` -## Optionally using jQuery - -**You don't need jQuery in Bootstrap 5**, but it's still possible to use our components with jQuery. If Bootstrap detects `jQuery` in the `window` object, it'll add all of our components in jQuery's plugin system. This allows you to do the following: - -```js -// to enable tooltips with the default configuration -$('[data-bs-toggle="tooltip"]').tooltip() - -// to initialize tooltips with given configuration -$('[data-bs-toggle="tooltip"]').tooltip({ - boundary: 'clippingParents', - customClass: 'myClass' -}) - -// to trigger the `show` method -$('#myTooltip').tooltip('show') -``` - -The same goes for our other components. - -### No conflict - -Sometimes it is necessary to use Bootstrap plugins with other UI frameworks. In these circumstances, namespace collisions can occasionally occur. If this happens, you may call `.noConflict` on the plugin you wish to revert the value of. - -```js -const bootstrapButton = $.fn.button.noConflict() // return $.fn.button to previously assigned value -$.fn.bootstrapBtn = bootstrapButton // give $().bootstrapBtn the Bootstrap functionality -``` - -Bootstrap does not officially support third-party JavaScript libraries like Prototype or jQuery UI. Despite `.noConflict` and namespaced events, there may be compatibility problems that you need to fix on your own. - -### jQuery events - -Bootstrap will detect jQuery if `jQuery` is present in the `window` object and there is no `data-bs-no-jquery` attribute set on ``. If jQuery is found, Bootstrap will emit events thanks to jQuery's event system. So if you want to listen to Bootstrap's events, you'll have to use the jQuery methods (`.on`, `.one`) instead of `addEventListener`. - -```js -$('#myTab a').on('shown.bs.tab', () => { - // do something... -}) -``` - ## Disabled JavaScript Bootstrap's plugins have no special fallback when JavaScript is disabled. If you care about the user experience in this case, use [`