Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't hide modal before it is "shown?" #34213

Open
binseq opened this issue Jun 9, 2021 · 9 comments
Open

Can't hide modal before it is "shown?" #34213

binseq opened this issue Jun 9, 2021 · 9 comments
Labels

Comments

@binseq
Copy link

binseq commented Jun 9, 2021

In Bootstrap 5, calling Modal.hide() has no effect until modal is "shown" (it works as expected if the fade css class is removed).

Suppose you have list of items and a single modal. When you click on each item, details about that item are shown in the modal, but you accidentally clicked the wrong item, so you want to quickly cancel by clicking the backdrop while it's fading in. Currently you cannot do that. You have to wait until the wrong item is shown to you before you can close it.

Is there a way around this?

Maybe there should be an optional parameter, Modal.hide(force=true).

Without proposed fix: https://jsfiddle.net/o7wdt6r5/
With proposed fix: https://jsfiddle.net/fgyuerLo/

<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<title>Modal Test</title>
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">

	<style>
		.show {
			 transition: opacity 100ms;
		}
		.hide {
			 transition: opacity 10ms;
		}
	</style>

</head>
<body>

	<p>
		Click a button and quickly try to cancel the modal by clicking the backdrop.
	</p>

	<!-- Buttons -->
	<button type="button" class="btn btn-primary item-button" data-item-info="You clicked # 1">
		Item # 1
	</button>

	<button type="button" class="btn btn-primary item-button" data-item-info="You clicked # 2">
		Item # 2
	</button>

	<button type="button" class="btn btn-primary item-button" data-item-info="You clicked # 3">
		Item # 3
	</button>

	<!-- Modal -->
	<div class="modal fade" id="myModal" tabindex="-1" aria-labelledby="myModalLabel" aria-hidden="true">
		<div class="modal-dialog">
			<div class="modal-content">
				<div class="modal-body">
					...
				</div>
			</div>
		</div>
	</div>

	<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script>
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
	<script>
		var myModal = new bootstrap.Modal(document.getElementById('myModal'), {
			keyboard: false
		})

		$('.item-button').click(function(){
			$('#myModal').find('.modal-body').text($(this).data('item-info'))
			myModal.show();
		})

		$('.modal').click(function(e){
			if(!$(e.target).hasClass("modal-body")){
				console.log('Clicked modal backdrop, calling .hide()...')
				myModal.hide();
			}
		})

	</script>

</body>
</html>
@mdo
Copy link
Member

mdo commented Jun 9, 2021

Possibly a duplicate of #34055?

@GeoSot
Copy link
Member

GeoSot commented Jun 10, 2021

Try to use the following scripts on your test:

https://deploy-preview-34085--twbs-bootstrap.netlify.app/docs/5.0/dist/js/bootstrap.bundle.min.js

https://deploy-preview-34085--twbs-bootstrap.netlify.app/docs/5.0/dist/css/bootstrap.min.css

I suppose it will work fine. (and please feedback us)

@GeoSot
Copy link
Member

GeoSot commented Jun 10, 2021

The solution is to remove the if block: if (!this._isShown || this._isTransitioning) {

if (!this._isShown || this._isTransitioning) {

or by genlty initializing this.isShown = this._element.classList.contains(CLASS_NAME_SHOW) .

binseq added a commit to binseq/bootstrap that referenced this issue Jun 11, 2021
This is a solution to hide() method not working when fade css class is used. It is unnecessary to check weather the modal is transitioning before hiding the modal.
Issue: twbs#34213
@alpadev
Copy link
Contributor

alpadev commented Jun 11, 2021

@GeoSot I'm afraid #34085 wouldn't fix that. The problem here is that while the modal is transitioning it just returns from calls to .show() and .hide() and does nothing.

Simply removing _isTransitioning like in #34232 can cause the events to fire in wrong order e.g. shown is dispatched after the hide event. If some 3rd party makes use of them, it may break their implementation. So IMO this is not the proper way to fix that.

Not sure if we really should do something here at all. It's not like the transitions take forever and how often does it happen that someone does a misclick and is able to react fast enough to really make use of that. Even if so, you can simply click a few times and it would eventually close. Also with the check for _isTransitioning removed, if I click too fast on the backdrop it wouldn't close on the first click.

Here is the OPs code with his fix from #34232 applied https://jsfiddle.net/z6p0oujt/. IMO the improvement from the that fix is negligible as it requires a certain timing to make use of the change at all. (You have to wait for the backdrop before you click and it saves you some ms that is needed to fully show the modal.)

If it happens on a frequent basis, one can simply remove the fade class so it wouldn't be a problem at all.

@GeoSot
Copy link
Member

GeoSot commented Jun 12, 2021

But it is related to #33727 . Right?

@binseq
Copy link
Author

binseq commented Jun 12, 2021

Here is the OPs code with his fix from #34232 applied https://jsfiddle.net/z6p0oujt/. IMO the improvement from the that fix is negligible as it requires a certain timing to make use of the change at all. (You have to wait for the backdrop before you click and it saves you some ms that is needed to fully show the modal.)

If it happens on a frequent basis, one can simply remove the fade class so it wouldn't be a problem at all.

It took me a while to figure out why the demo I posted wasn't working with the updated fix, then I realized a small change to the CSS was required for it to work.
Try it now: https://jsfiddle.net/fgyuerLo/

Also, this was just a simple demo of one use case, least complex example that could think of. The reason I need hide to respond instantly is I want to hide the modal when user navigates back in the browser. I do that by modifying the URL (with pushState) with a special marker to indicate that .show() has been called. For example, pushState CURRENT_URL + #modal. Then I listen for popstate event and examine the url. If the #modal hash is missing, then I know the user navigated back so I call .hide(). Now the problem is if user navigates back before hide() is ready to respond, the browser is already on CURRENT_URL but modal is still showing. Now if user navigates back again, the browser is back two pages.

In the case that user closes modal in the normal way (other than back navigation) I call history.go(-1) and hide() so the the user is back at CURRENT_URL and ideally now modal is also hidden.

@ddanielou
Copy link

@binseq I faced a similar problem with offcanvas recently. I ended up listening once for the "shown" event before calling .hide(), e.g.

function forceHide(el){
    el.addEventListener('shown.bs.modal', () => bootstrap.Modal.getInstance(el).hide(), {once: true});
}

Though that wouldn't help get rid of the small delay, at least .hide() would work. It's also somewhat logical, as you (arguably) wouldn't hide something that isn't shown yet.

@alpadev
Copy link
Contributor

alpadev commented Jun 15, 2021

But it is related to #33727 . Right?

Yes, somewhat. The problem is a bit different but it got something to do with transitions being asynchronous and methods are called in a synchronous way. IMO, we would need to implement some way of cancelling transitions or to automatically queue code that is called during the component being busy.

@pauljura
Copy link

pauljura commented Mar 4, 2024

Hi team, I want to add my support for a resolution to this issue.

My use case is that I display a table of items, and clicking on at item will display a large modal with an edit screen. The content of the modal gets loaded asynchronously on 'show.bs.modal'. However, if loading the modal content fails (e.g. session has timed out, or permission error) then I want to display a different, smaller modal with an error message. Since I can't display two modals at once, I have to close the first one. But if the error happens quickly, before the main modal has finished transitioning, then I can't close it.

I would very much like a single method to both hide an already open modal and also hide it immediately if it's in the middle of being shown.

You might wonder why I don't just display the error message in the main modal... It's because that one has a custom toolbar and buttons intended for editing, so if I was going to show an error message then I would want to hide these elements, and then reveal them again if the modal gets re-opened. The other workaround is to just manually hide the entire modal. Either way feels hacky.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants