google / material-design-lite

Material Design Components in HTML/CSS/JS
https://getmdl.io
Apache License 2.0
32.27k stars 5.04k forks source link

click outside mdl-dialog to dismiss #5030

Open jab opened 7 years ago

jab commented 7 years ago

The "Dismissing dialogs" section of the material design dialog spec says:

Dialogs can be dismissed by touching/clicking outside of a dialog

It might be worth adding support for this to mdl-dialog, such as by adding a special CSS class. The mdl javascript could automatically add a click handler that dismisses any currently-visible mdl-dialog with this class when it detects that the user has clicked outside the dialog.

What MDL Version are you using? (please be specific, e.g. major.minor.patch)

1.3.0

Thanks for your consideration and for the great work on mdl!

Garbee commented 7 years ago

We aren't able to apply events to the native ::backdrop pseudo element in browsers. Therefore this part of the MD spec isn't possible given the native web technology we are using for the dialog implementation. This is one of the deferred pieces of the specification decided on when using the dialog element in order to get the required functionality without building our own polyfill or complete modal engine outright.

If anyone knows of a way to add an event listener onto the ::backdrop pseudo element, please comment with a functioning demo and we can re-open this to get it addressed.

Thank you.

ZanderBrown commented 7 years ago

A google for html5 dialog dismiss by backdrop presents http://stackoverflow.com/a/26984690/3975351:

Backdrop clicks can be detected using the dialog bounding rect.

var dialog = document.getElementByTagName('dialog');
dialog.showModal();
dialog.addEventListener('click', function (event) {
    var rect = dialog.getBoundingClientRect();
    var isInDialog=(rect.top <= event.clientY && event.clientY <= rect.top + rect.height && rect.left <= event.clientX && event.clientX <= rect.left + rect.width);
    if (!isInDialog) {
        dialog.close();
    }
});

~Haven't been able to test but it looks promising~

ZanderBrown commented 7 years ago

And here's a working demo: http://codepen.io/13b/pen/ygxePd

Garbee commented 7 years ago

That method has a glaring failure, which is if any inner content (say <select> nodes) expand beyond the dialog, then click selections for those will cause a close. Further in my testing I'm seeing the check fire after clicking the regular close button. Which isn't acceptable either.

Overall, using that check to test for whether the click is "in" the dialog or not can lead to wonky areas for developers. Which we'd rather avoid. The goal is to know specifically if an event occurs against the backdrop node itself, and nothing else. Simply detecting whether it is out of the bounds of the dialog node isn't sufficient.

Garbee commented 7 years ago

So adding in the tagName check from the comment on SO fixed the select problem (and other associated issues.) However, there is still the problem that when clicking outside the event fires multiple times. This then causes the first close to succeed but the rest to cause errors in the console since it is already closed. We don't want to spam the console with errors.

Opening this back up for more investigation to get things just right, but there is a workable pattern to make this achievable.

zhongdongy commented 7 years ago

Well, in my opinion, I don't support to add this feature to the dialog elements in both desktop and mobile browsers, even when there is a way to trigger click event to the outside area of a dialog.

I think a dialog is quite different from a Message box, which means a dialog can do more than just showing information or warnings. A dialog contains interactions itself, and that could be dismissing the dialog or doing something such as choosing items from a list of things, having a online talk as in a chat room.

So, I think, a click at the outside area to dismiss the dialog will blur the boundary between dialog and message box.

(By a non-native English speaker. Some grammar errors exist in my message and hope you guys don't mind it)

Get Outlook for Androidhttps://aka.ms/ghei36


From: Zander notifications@github.com Sent: Wednesday, February 8, 2017 4:10:31 AM To: google/material-design-lite Cc: Subscribed Subject: Re: [google/material-design-lite] click outside mdl-dialog to dismiss (#5030)

And here's a working demo: http://codepen.io/13b/pen/ygxePd

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHubhttps://github.com/google/material-design-lite/issues/5030#issuecomment-278124992, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AXZFZ2us9FU6Mo1RL-WEtWAQJTBsriuAks5raM-3gaJpZM4L57RX.

Garbee commented 7 years ago

Dialogs can be dismissed by touching/clicking outside of a dialog or by using the system back button (Android). Alternatively, dialog behavior can be overridden so that users must explicitly choose one of the actions.

Dialogs should support this by the spec. Whether you use it as message box is an implementation detail on the developers end. If however you'd like to not have the click handler, you just don't include our js execution for the component which will add it on. It's as simple as that to get the native dialog handling.

I think the only final major issues to adding the extra JS into core are:

jab commented 7 years ago

Just noting that the other approach mentioned in that SO post works too, i.e. adding a child div to the dialog element and then checking if the div contains evt.target in the click handler. According to the author that reduces layout cycles compared to the bounds check. Is that preferable?

Garbee commented 7 years ago

No. We don't want to require any extra Dom nodes.

islamhaqq commented 7 years ago

any update on this?

According to some of those hot fixes, does the error on 2nd close of the dialog when clicking outside have to do with event bubbling?

https://codepen.io/Anaphase/pen/ZJapme?editors=0010

Mimyka commented 6 years ago

You've just to precise the callback of addEventListener with a named function, and remove the event listener on dialog close. Just don't forget to recreate event inside click on dialog caller.

document.querySelector('.dialog-show').addEventListener('click', function() {
      document.querySelector('dialog').showModal();
      document.querySelector('dialog').addEventListener('click', outsideDialog);
});

function outsideDialog(event) {
  var dialog = this;
  var rect = dialog.getBoundingClientRect();
  var isInDialog=(rect.top <= event.clientY && event.clientY <= rect.top + rect.height
      && rect.left <= event.clientX && event.clientX <= rect.left + rect.width);
  if (!isInDialog) {
    dialog.close();
    dialog.removeEventListener('click', outsideDialog);
  }
}

I've no console errors with this.

simonferndriger commented 6 years ago

This solution looks great and should be part of the framework.

However, it only works on Chrome, but it didn't work with FireFox, there is a JavaScript error "ReferenceError: event is not defined "backdropClick_ self-hosted:952:17"

RayyanNafees commented 1 year ago

Someone just add a PR to their existing modal code with this solution 🙏🙏