Commit 48c5efa4 authored by Pierre Vanduynslager's avatar Pierre Vanduynslager Committed by Johann-S
Browse files

Fix JS components console error "Error: <Component> is transitioning"

parent ce0e2f8e
main cleanup-floating-forms cssvar-function dependabot/npm_and_yarn/stylelint-and-stylelint-config-twbs-bootstrap-15.3.0 extend-snippets feat/data-target floating-always-visible floating-labels-icons fod-main-banner form-controls-with-icons github/fork/719media/patch-13 github/fork/719media/patch-14 github/fork/719media/patch-9 github/fork/ChellyAhmed/fix-typo-reboot.md github/fork/ChellyAhmed/offcanvas-scroll-back github/fork/CtrlAltLilith/main github/fork/Elysiome/offcanvas-optional-window-resizing github/fork/JanSargsyan/main github/fork/LunicLynx/support-different-line-height-for-buttons github/fork/Psixodelik/main github/fork/Ronid1/ronid1/offcanvas_static_backdrop github/fork/RyanBerliner/tooltip-accessibility github/fork/SantiagoPVazquez/Feature-default-border-bottom-to-dropdown-item github/fork/Sir-Genius/utils github/fork/Sumit-Singh-8/main github/fork/Viktor-VERA2020/offcanvas-slide github/fork/Zivangu9/input-group-for-form-control-plaintext github/fork/alpadev/alpadev/call-dispose-on-component-reinstantiation github/fork/astagi/fix/tree-shake-modules github/fork/compnerd/dark-accordion-icon github/fork/derSascha/dropdown-dont-close-on-input-click github/fork/dev-ph1l/main github/fork/donquixote/issue-33861-utl-mixin github/fork/florianlacreuse/mixin-make-row-gutter-y github/fork/gregorw/main github/fork/iteggmbh/transitionend-dispose-race github/fork/jdelStrother/patch-1 github/fork/jonnysp/form-floating github/fork/jonnysp/independent-offcanvas github/fork/jonnysp/theme-dark-on-card-and-modal-fix github/fork/josefdlange/floating-label-placeholder-opacity github/fork/julien-deramond/enhance-change-version.js github/fork/julien-deramond/main-jd-fix-offset-content github/fork/julien-deramond/main-jd-issue-with-utitlies github/fork/julien-deramond/main-xmr-pa11y-ci-jd-add-hideElements github/fork/kyletsang/fix-tooltip-padding github/fork/lacutah/CheckboxCenteringDocumentation github/fork/lekoala/patch-3 github/fork/louismaximepiton/main-kld-lmp-collapse-proposal github/fork/louismaximepiton/main-lmp-card-inner-border-radius-fix github/fork/louismaximepiton/main-lmp-carousel-multiple-images github/fork/louismaximepiton/main-lmp-css-var-init github/fork/louismaximepiton/main-lmp-disabled-floating-label-fix github/fork/louismaximepiton/main-lmp-input-range-fix github/fork/louismaximepiton/main-lmp-shift-color github/fork/louismaximepiton/main-lmp-table-active-tr-fix github/fork/maciek-szn/switch github/fork/michael-roth/feature/19964-multiple-tab-targets github/fork/mistic100/dom-utils github/fork/nkdas91/accordion github/fork/nstungcom/fix-missing-modal-open-class github/fork/oraliahdz/animation-utilities github/fork/pine3ree/patch-7 github/fork/pouwerkerk/unindent-scss-docs-shortcode github/fork/smares/smares-no-scolling-on-modal-close github/fork/tgm-git/patch-1 gs-forms gs-toasts-with-animated-progress-bar gs/add-history-helper gs/change-version-dir-on-docs gs/data-must-set-onlu-one-instance gs/docs/fix-drop-down-error gs/event-handler-2 gs/make-docs-js-build gs/make-simple-attribute-toggler gs/popover-fix-doc gs/provide-steConfig-method gs/scrollspy-smoothscroll-option-use-browser-history gs/streamline-jqueryInterface gs/support-drop-down-in-navbar gs/test-js-generic-trigger gs/try-web-components gs/tweak-collapse-js-selector gs/use-event-handler-in-cocmponent gs/use-rollup-replace-for-version jo-docs-thanks-page jo-ssr-friendly logical-props-spacing-utils main-fod-disabled-form-check-label main-fod-nested-accordion main-fod-simpler-table-structure main-fod-table-separator main-fod-utilities-contrast main-jd-abbr-title main-jd-add-chips main-jd-add-doc-for-sass-custom-colors main-jd-add-enable-host-to-handle-web-components main-jd-browserstack-fine-tune main-jd-browserstack-updates main-jd-docs-consistent-usage-of-css-sections-step-2 main-jd-fix-docs-headers-in-white main-jd-fix-highlight-docs-border-radius main-jd-fix-placeholder-color-background-params-for-img-markup main-jd-glossary-experiment main-jd-postcss-drop-empty-css-vars main-jd-proto-doc-astro main-jd-skip-navigation-component main-jd-stackblitz-for-examples main-jd-upgrade-browserlistrc main-jd-use-host main-lmp-dark-theme-customization main-lmp-handle-scroll-target main-lmp-tab-fix main-mc-opensearch main-xmr-bundlewatch-action main-xmr-eslint-plugin-compat main-xmr-hugo-docs-vendor main-xmr-hugo-rm-ver main-xmr-linkinator-prod main-xmr-min-mangle main-xmr-pa11y-ci more-darkmode-examples nested-dropdowns patrickhlauke-issue37428 patrickhlauke-use-of-color-tweaks pr/34102 pr/37590 previous-next-docs-links sticky-thead utilities-functions-mixin v4-dev v4-dev-dropdown-hide-method v530-dev v6-postcss-custom-media v6-spinner-dots v6/gs/use-floating-ui-in-place-of-popper xmr/dev xmr/docs-png xmr/docs-svgs xmr/hugo-reorg-files xmr/js-2 xmr/markdownlint xmr/prepare-530-alpha2 xmr/xo v5.3.0-alpha1 v5.2.3 v5.2.2 v5.2.1 v5.2.0 v5.2.0-beta1 v5.1.3 v5.1.2 v5.1.1 v5.1.0 v5.0.2 v5.0.1 v5.0.0 v5.0.0-beta3 v5.0.0-beta2 v5.0.0-beta1 v5.0.0-alpha3 v5.0.0-alpha2 v5.0.0-alpha1 v4.6.2 v4.6.1 v4.6.0 v4.5.3 v4.5.2 v4.5.1 v4.5.0 v4.4.1 v4.4.0 v4.3.1 v4.3.0 v4.2.1 v4.2.0 v4.1.3 v4.1.2 v4.1.1 v4.1.0 v4.0.0 v4.0.0-beta.3 v4.0.0-beta.2 v4.0.0-beta
8 merge requests!28721Hot test,!27561Adds font-weight-medium to font weight classes,!22391V4 dev,!22598test,!25326Adjust examples,!23995Add back cursor: pointer for .btn-link,!23178Spinner,!17021v4
Showing with 101 additions and 155 deletions
+101 -155
{% callout danger %}
#### Asynchronous methods and transitions
All API methods are **asynchronous** and start a **transition**. They returns to the caller as soon as the transition is started but **before it ends**. In addition, a method call on a **transitioning component will be ignored**.
[See our Javascript documentation for more informations.]({{ site.baseurl }}/getting-started/javascript/#content)
{% endcallout %}
......@@ -229,6 +229,9 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap
### Methods
{% capture callout-include %}{% include callout-danger-async-methods.md %}{% endcapture %}
{{ callout-include | markdownify }}
#### `.carousel(options)`
Initializes the carousel with an optional options `object` and starts cycling through items.
......@@ -249,15 +252,15 @@ Stops the carousel from cycling through items.
#### `.carousel(number)`
Cycles the carousel to a particular frame (0 based, similar to an array).
Cycles the carousel to a particular frame (0 based, similar to an array). **Returns to the caller before the target item has been shown** (i.e. before the `slid.bs.carousel` event occurs).
#### `.carousel('prev')`
Cycles to the previous item.
Cycles to the previous item. **Returns to the caller before the previous item has been shown** (i.e. before the `slid.bs.carousel` event occurs).
#### `.carousel('next')`
Cycles to the next item.
Cycles to the next item. **Returns to the caller before the next item has been shown** (i.e. before the `slid.bs.carousel` event occurs).
### Events
......
......@@ -178,6 +178,9 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap
### Methods
{% capture callout-include %}{% include callout-danger-async-methods.md %}{% endcapture %}
{{ callout-include | markdownify }}
#### `.collapse(options)`
Activates your content as a collapsible element. Accepts an optional options `object`.
......@@ -190,15 +193,15 @@ $('#myCollapsible').collapse({
#### `.collapse('toggle')`
Toggles a collapsible element to shown or hidden.
Toggles a collapsible element to shown or hidden. **Returns to the caller before the collapsible element has actually been shown or hidden (i.e. before the `shown.bs.collapse` or `hidden.bs.collapse` event occurs).
#### `.collapse('show')`
Shows a collapsible element.
Shows a collapsible element. **Returns to the caller before the collapsible element has actually been shown** (i.e. before the `shown.bs.collapse` event occurs).
#### `.collapse('hide')`
Hides a collapsible element.
Hides a collapsible element. **Returns to the caller before the collapsible element has actually been hidden** (i.e. before the `hidden.bs.collapse` event occurs).
### Events
......
......@@ -547,6 +547,9 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap
### Methods
{% capture callout-include %}{% include callout-danger-async-methods.md %}{% endcapture %}
{{ callout-include | markdownify }}
#### `.modal(options)`
Activates your content as a modal. Accepts an optional options `object`.
......
......@@ -404,6 +404,9 @@ To make tabs fade in, add `.fade` to each `.tab-pane`. The first tab pane must a
### Methods
{% capture callout-include %}{% include callout-danger-async-methods.md %}{% endcapture %}
{{ callout-include | markdownify }}
#### $().tab
Activates a tab element and content container. Tab should have either a `data-target` or an `href` targeting a container node in the DOM.
......
......@@ -277,6 +277,9 @@ Options for individual popovers can alternatively be specified through the use o
### Methods
{% capture callout-include %}{% include callout-danger-async-methods.md %}{% endcapture %}
{{ callout-include | markdownify }}
#### `$().popover(options)`
Initializes popovers for an element collection.
......
......@@ -254,6 +254,9 @@ Options for individual tooltips can alternatively be specified through the use o
### Methods
{% capture callout-include %}{% include callout-danger-async-methods.md %}{% endcapture %}
{{ callout-include | markdownify }}
#### `$().tooltip(options)`
Attaches a tooltip handler to an element collection.
......
......@@ -36,6 +36,18 @@ Alternatively, to target a specific plugin, just include the plugin's name as a
$(document).off('.alert.data-api')
{% endhighlight %}
## Events
Bootstrap provides custom events for most plugins' unique actions. Generally, these come in an infinitive and past participle form - where the infinitive (ex. `show`) is triggered at the start of an event, and its past participle form (ex. `shown`) is triggered on the completion of an action.
All infinitive events provide [`preventDefault()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault) functionality. This provides the ability to stop the execution of an action before it starts.
{% highlight js %}
$('#myModal').on('show.bs.modal', function (e) {
if (!data) return e.preventDefault() // stops modal from being shown
})
{% endhighlight %}
## Programmatic API
We also believe you should be able to use all Bootstrap plugins purely through the JavaScript API. All public APIs are single, chainable methods, and return the collection acted upon.
......@@ -54,32 +66,41 @@ $('#myModal').modal('show') // initializes and invokes show immed
Each plugin also exposes its raw constructor on a `Constructor` property: `$.fn.popover.Constructor`. If you'd like to get a particular plugin instance, retrieve it directly from an element: `$('[rel="popover"]').data('popover')`.
### Default settings
You can change the default settings for a plugin by modifying the plugin's `Constructor.DEFAULTS` object:
### Asynchronous functions and transitions
All programmatic API methods are **asynchronous** and returns to the caller once the transition is started but **before it ends**.
In order to execute an action once the transition is complete, you can listen to the corresponding event.
{% highlight js %}
$.fn.modal.Constructor.DEFAULTS.keyboard = false // changes default for the modal plugin's `keyboard` option to false
$('#myCollapse').on('shown.bs.collapse', function (e) {
// Action to execute once the collapsible area is expanded
})
{% endhighlight %}
## No conflict
In addition a method call on a **transitioning component will be ignored**.
{% highlight js %}
$('#myCarousel').on('slid.bs.carousel', function (e) {
$('#myCarousel').carousel('2') // Will slide to the slide 2 as soon as the transition to slide 1 is finished
})
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.
$('#myCarousel').carousel('1') // Will start sliding to the slide 1 and returns to the caller
$('#myCarousel').carousel('2') // !! Will be ignored, as the transition to the slide 1 is not finished !!
{% endhighlight %}
### Default settings
You can change the default settings for a plugin by modifying the plugin's `Constructor.Default` object:
{% highlight js %}
var bootstrapButton = $.fn.button.noConflict() // return $.fn.button to previously assigned value
$.fn.bootstrapBtn = bootstrapButton // give $().bootstrapBtn the Bootstrap functionality
$.fn.modal.Constructor.Default.keyboard = false // changes default for the modal plugin's `keyboard` option to false
{% endhighlight %}
## Events
Bootstrap provides custom events for most plugins' unique actions. Generally, these come in an infinitive and past participle form - where the infinitive (ex. `show`) is triggered at the start of an event, and its past participle form (ex. `shown`) is triggered on the completion of an action.
## No conflict
All infinitive events provide [`preventDefault()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault) functionality. This provides the ability to stop the execution of an action before it starts.
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.
{% highlight js %}
$('#myModal').on('show.bs.modal', function (e) {
if (!data) return e.preventDefault() // stops modal from being shown
})
var bootstrapButton = $.fn.button.noConflict() // return $.fn.button to previously assigned value
$.fn.bootstrapBtn = bootstrapButton // give $().bootstrapBtn the Bootstrap functionality
{% endhighlight %}
## Version numbers
......@@ -100,8 +121,8 @@ Bootstrap's plugins don't fall back particularly gracefully when JavaScript is d
**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.
{% endcallout %}
## Transitions
## Util
For simple transition effects, include `transition.js` once alongside the other JS files. If you're using the compiled (or minified) `bootstrap.js`, there is no need to include this—it's already there.
All Bootstrap Javascript depend on `util.js` and it has to be included alongside the other JS files. If you're using the compiled (or minified) `bootstrap.js`, there is no need to include this—it's already there.
Transition.js is a basic helper for `transitionEnd` events as well as a CSS transition emulator. It's used by the other plugins to check for CSS transition support and to catch hanging transitions.
`util.js` includes utility functions and a basic helper for `transitionEnd` events as well as a CSS transition emulator. It's used by the other plugins to check for CSS transition support and to catch hanging transitions.
......@@ -120,10 +120,9 @@ const Carousel = (($) => {
// public
next() {
if (this._isSliding) {
throw new Error('Carousel is sliding')
if (!this._isSliding) {
this._slide(Direction.NEXT)
}
this._slide(Direction.NEXT)
}
nextWhenVisible() {
......@@ -134,10 +133,9 @@ const Carousel = (($) => {
}
prev() {
if (this._isSliding) {
throw new Error('Carousel is sliding')
if (!this._isSliding) {
this._slide(Direction.PREV)
}
this._slide(Direction.PREV)
}
pause(event) {
......
......@@ -120,11 +120,8 @@ const Collapse = (($) => {
}
show() {
if (this._isTransitioning) {
throw new Error('Collapse is transitioning')
}
if ($(this._element).hasClass(ClassName.SHOW)) {
if (this._isTransitioning ||
$(this._element).hasClass(ClassName.SHOW)) {
return
}
......@@ -204,11 +201,8 @@ const Collapse = (($) => {
}
hide() {
if (this._isTransitioning) {
throw new Error('Collapse is transitioning')
}
if (!$(this._element).hasClass(ClassName.SHOW)) {
if (this._isTransitioning ||
!$(this._element).hasClass(ClassName.SHOW)) {
return
}
......
......@@ -87,7 +87,6 @@ const Modal = (($) => {
this._isShown = false
this._isBodyOverflowing = false
this._ignoreBackdropClick = false
this._isTransitioning = false
this._originalBodyPadding = 0
this._scrollbarWidth = 0
}
......@@ -112,13 +111,13 @@ const Modal = (($) => {
show(relatedTarget) {
if (this._isTransitioning) {
throw new Error('Modal is transitioning')
return
}
if (Util.supportsTransitionEnd() &&
$(this._element).hasClass(ClassName.FADE)) {
if (Util.supportsTransitionEnd() && $(this._element).hasClass(ClassName.FADE)) {
this._isTransitioning = true
}
const showEvent = $.Event(Event.SHOW, {
relatedTarget
})
......@@ -161,17 +160,18 @@ const Modal = (($) => {
event.preventDefault()
}
if (this._isTransitioning) {
throw new Error('Modal is transitioning')
if (this._isTransitioning || !this._isShown) {
return
}
const transition = Util.supportsTransitionEnd() &&
$(this._element).hasClass(ClassName.FADE)
const transition = Util.supportsTransitionEnd() && $(this._element).hasClass(ClassName.FADE)
if (transition) {
this._isTransitioning = true
}
const hideEvent = $.Event(Event.HIDE)
$(this._element).trigger(hideEvent)
if (!this._isShown || hideEvent.isDefaultPrevented()) {
......@@ -191,6 +191,7 @@ const Modal = (($) => {
$(this._dialog).off(Event.MOUSEDOWN_DISMISS)
if (transition) {
$(this._element)
.one(Util.TRANSITION_END, (event) => this._hideModal(event))
.emulateTransitionEnd(TRANSITION_DURATION)
......@@ -307,7 +308,7 @@ const Modal = (($) => {
_hideModal() {
this._element.style.display = 'none'
this._element.setAttribute('aria-hidden', 'true')
this._element.setAttribute('aria-hidden', true)
this._isTransitioning = false
this._showBackdrop(() => {
$(document.body).removeClass(ClassName.OPEN)
......
......@@ -124,12 +124,11 @@ const Tooltip = (($) => {
constructor(element, config) {
// private
this._isEnabled = true
this._timeout = 0
this._hoverState = ''
this._activeTrigger = {}
this._isTransitioning = false
this._tether = null
this._isEnabled = true
this._timeout = 0
this._hoverState = ''
this._activeTrigger = {}
this._tether = null
// protected
this.element = element
......@@ -250,9 +249,6 @@ const Tooltip = (($) => {
const showEvent = $.Event(this.constructor.Event.SHOW)
if (this.isWithContent() && this._isEnabled) {
if (this._isTransitioning) {
throw new Error('Tooltip is transitioning')
}
$(this.element).trigger(showEvent)
const isInTheDom = $.contains(
......@@ -284,9 +280,11 @@ const Tooltip = (($) => {
const container = this.config.container === false ? document.body : $(this.config.container)
$(tip)
.data(this.constructor.DATA_KEY, this)
.appendTo(container)
$(tip).data(this.constructor.DATA_KEY, this)
if (!$.contains(this.element.ownerDocument.documentElement, this.tip)) {
$(tip).appendTo(container)
}
$(this.element).trigger(this.constructor.Event.INSERTED)
......@@ -308,8 +306,7 @@ const Tooltip = (($) => {
const complete = () => {
const prevHoverState = this._hoverState
this._hoverState = null
this._isTransitioning = false
this._hoverState = null
$(this.element).trigger(this.constructor.Event.SHOWN)
......@@ -319,7 +316,6 @@ const Tooltip = (($) => {
}
if (Util.supportsTransitionEnd() && $(this.tip).hasClass(ClassName.FADE)) {
this._isTransitioning = true
$(this.tip)
.one(Util.TRANSITION_END, complete)
.emulateTransitionEnd(Tooltip._TRANSITION_DURATION)
......@@ -333,9 +329,6 @@ const Tooltip = (($) => {
hide(callback) {
const tip = this.getTipElement()
const hideEvent = $.Event(this.constructor.Event.HIDE)
if (this._isTransitioning) {
throw new Error('Tooltip is transitioning')
}
const complete = () => {
if (this._hoverState !== HoverState.SHOW && tip.parentNode) {
tip.parentNode.removeChild(tip)
......@@ -344,7 +337,6 @@ const Tooltip = (($) => {
this._cleanTipClass()
this.element.removeAttribute('aria-describedby')
$(this.element).trigger(this.constructor.Event.HIDDEN)
this._isTransitioning = false
this.cleanupTether()
if (callback) {
......@@ -366,7 +358,7 @@ const Tooltip = (($) => {
if (Util.supportsTransitionEnd() &&
$(this.tip).hasClass(ClassName.FADE)) {
this._isTransitioning = true
$(tip)
.one(Util.TRANSITION_END, complete)
.emulateTransitionEnd(TRANSITION_DURATION)
......
......@@ -45,31 +45,11 @@
<script src="../../dist/carousel.js"></script>
<script>
// Should throw an error because carousel is in transition
function testCarouselTransitionError() {
var err = false
var $carousel = $('#carousel-example-generic')
$carousel.on('slid.bs.carousel', function () {
$carousel.off('slid.bs.carousel')
if (!err) {
alert('No error thrown for : testCarouselTransitionError')
}
})
try {
$carousel.carousel('next').carousel('prev')
}
catch (e) {
err = true
console.error(e.message)
}
}
$(function () {
$(function() {
// Test to show that the carousel doesn't slide when the current tab isn't visible
$('#carousel-example-generic').on('slid.bs.carousel', function (event) {
$('#carousel-example-generic').on('slid.bs.carousel', function(event) {
console.log('slid at ', event.timeStamp)
})
testCarouselTransitionError()
})
</script>
</body>
......
......@@ -60,30 +60,5 @@
<script src="../../../docs/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/collapse.js"></script>
<script>
// JavaScript Test
$(function () {
testCollapseTransitionError()
});
// Should throw an error because carousel is in transition
function testCollapseTransitionError() {
var err = false
$('#collapseOne').on('hidden.bs.collapse', function (e) {
$(this).off('hidden.bs.collapse')
if (!err) {
alert('No error thrown for : testCollapseTransitionError')
}
})
try {
$('#collapseOne').collapse('hide').collapse('show')
}
catch (e) {
err = true
console.error(e.message)
}
}
</script>
</body>
</html>
......@@ -187,26 +187,6 @@
}
}
// Should throw an error because modal is in transition
function testModalTransitionError() {
var err = false
// Close #myModal
$('#myModal').on('shown.bs.modal', function () {
$('#myModal').modal('hide').off('shown.bs.modal')
if (!err) {
alert('No error thrown for : testModalTransitionError')
}
})
try {
$('#myModal').modal('show').modal('hide')
}
catch (e) {
err = true
console.error(e.message)
}
}
$(function () {
$('[data-toggle="popover"]').popover()
$('[data-toggle="tooltip"]').tooltip()
......@@ -219,7 +199,6 @@
$('#firefoxModal').on('focus', reportFirefoxTestResult.bind(false))
$('#ff-bug-input').on('focus', reportFirefoxTestResult.bind(true))
})
testModalTransitionError()
})
</script>
</body>
......
......@@ -41,26 +41,7 @@
<script>
$(function () {
$('[data-toggle="tooltip"]').tooltip()
testTooltipTransitionError()
})
// Should throw an error because tooltip is in transition
function testTooltipTransitionError() {
var err = false
$('#btnOne').on('shown.bs.tooltip', function () {
$('#btnOne').tooltip('hide').off('shown.bs.tooltip')
if (!err) {
alert('No error thrown for : testTooltipTransitionError')
}
})
try {
$('#btnOne').tooltip('show').tooltip('hide')
}
catch (e) {
err = true
console.error(e.message)
}
}
</script>
</body>
</html>
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment