question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

First of all, thanks and congrats for such a small, elegant and accessible dialog solution šŸ‘

Error

I step into an error while opening a modal from within another dialog, so multiple modals opened at the same time. (everything kept working, but the following error logs to console)

Note: If this is the expected behaviour, as in, we shouldn’t open more than one dialog at once, ignore this report and close it.

Demo

Steps:

  1. Open Modal one
  2. Open modal two from modal 1 content
  3. look at console

Error log

Maximum call stack size exceeded.
    at o ((index):72)
    at e._maintainFocus ((index):72)

Details

version: ^4.0.0

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:1
  • Comments:7 (7 by maintainers)

github_iconTop GitHub Comments

2reactions
renatodeleaocommented, Apr 4, 2018

@HugoGiraudel I’ve back to this.

It was apparently a simple fix. If the event.target has attribute data-a11y-dialog-show that is not equal to instance node reference this.node.id then we are in the presence of another dialog, so skip the setFocusTofirstItem on this instance for now, that will be triggered later again when the second modal opens and that time it will evalute to true and focus the correct item

/**
 * Private event handler used when making sure the focus stays within the
 * currently open dialog
 *
 * @access private
 * @param {Event} event
 */
A11yDialog.prototype._maintainFocus = function (event) {
  // If the dialog is shown and the focus is not within the dialog element,
  // move it back to its first focusable child, unless another dialog is going to be opened :)
  var dialogTarget = event.target.getAttribute('data-a11y-dialog-show');
  if (this.shown && !this.node.contains(event.target) && dialogTarget === this.node.id ) {
	setFocusToFirstItem(this.node);
  }
};

This appears to work fine but…

Easy tiger

This only works for click events. But we’re talking about an accessible dialog. We still need to handle other input methods, keyboard, and the problem is not at the opening but when we close.

escape click signal ever instance to fire it’s .hide() method.

A11yDialog.prototype._bindKeypress = function (event) {
  // If the dialog is shown and the ESCAPE key is being pressed, prevent any
  // further effects from the ESCAPE key and hide the dialog

  // ---> we are closing all of openInstances, not the topMostOne šŸ¤”
  if (this.shown && event.which === ESCAPE_KEY) {
  event.preventDefault();
  this.hide();
}

That is not a bad default behaviour and we could end this conversation here but…

what if want to close only the last opened?

Each instance is isolated and has no idea about the state their siblings, by design. So we can’t call it a problem. That being said a couple of workarounds came to my mind to solve this.

  1. we could save all instances to a global var, and rely on DOM order to define which is o top of which
// data-a11y-dialog is applied to every dialog element
const dialogEls = Array.from(document.querySelectorAll(['data-a11y-dialog']))
window.a11yDialogs = []
dialogEls.map((el, i) => a11yDialogs[i]Ā = new A11yDialog(el))
if (this.shown && event.which === ESCAPE_KEY) {
  event.preventDefault();
  console.log('close escape')
  // if we have the current activeElement is all fine, but the user could have clicked the dialog body, losing focus, therefore error is thown    
  let ae = document.activeElement.closest('[data-a11y-dialog]');
  if(ae){
    window.A11yDialogs.find(instance => intance.node.id === ae.id).hide()
  } else {
   // just hide the last one in array
    window.A11yDialogs[window.A11yDialogs.length - 1].hide()
  }
}
  1. … or, as I don’t like to make personal affairs public, we could instead save a reference for each instance inside each Dialog node on the constructor, similar to other plugins
 /**
   * Define the constructor to instantiate a dialog
   *
   * @constructor
   * @param {Element} node
   * @param {(NodeList | Element | string)} targets
   */
  function A11yDialog (node, targets) {
  ...
  // Keep a reference of the instance on the node
   this.node._A11yDialog = this;
}

and then we could check for open dialog siblings on ā€˜escape click’


A11yDialog.prototype.getOpenSiblings = function () {
  // is open and isn't mysefl
  this.openSiblings = $$('[data-a11y-dialog]').filter(sib => sib._A11yDialog.shown && sib.id !== this.node.id)
}

And then same on Escape click event

if (this.shown && event.which === ESCAPE_KEY) {
  event.preventDefault();
  console.log('close escape')
  // if we have the current activeElement is all fine, but the user could have clicked the dialog body, losing focus, therefore error is thown    
  let ae = document.activeElement.closest('[data-a11y-dialog]');
  if(ae){
   ae._A11yDialog.hide()
  } else {
    // just hide the last one in array
    this._getOpenSiblings()
    this.openSiblings[this.openSiblings.length -1].hide()
  }
}

Note: focusedBeforeDialog behaviour starts failing if we have >2 dialogs. I have no eloquent explanation for this yet, but saving this element on the constructor as this.focusedBeforeDialog solves it.

demo here

Final Notes

I’m still not happy with any of this solutions because they are no-solutions. We’re relying on DOM order to check for the top most Dialog and i don’t like the smell of that idea. I don’t like to dictate how other people should organize their DOM, i don’t event know if the dialog is there at DOM load.

Shameless question (request for advice/help): how would you handle it? from top of my mind:

  • Maybe we should add an order attribute data-a11y-dialog-order="" on open, by checking other opened instances
  • Maybe that order attribute could be a simple timeStamp (Date.now())

I’ve back to this because:

  1. I have a confirmation dialog situation (are you sure? Y/N) that needs to be open on submit of the first. The first part of this ā€œblog postā€ deals with it as i don’t need to keep it open after submit and can close them all.
  2. I was thinking about making an accessible flyout menu which would fork 95% of the code of this repo. and flyout menus can be dropdowns or sidedrawers, layout is not up to me to decide, but multiple instances need to be open at the same time.

Well that’s a wrap, sry for the long post! Cheers!

0reactions
renatodeleaocommented, Feb 3, 2021

@HugoGiraudel Sure, i’ll gladly do it!

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to Create nested Dialog in JavaScript (ES5) Dialog control
A Dialog can be nested within another Dialog. The below sample contains parent and child Dialog (inner Dialog). Step 1: Create two div...
Read more >
Nested dialogs
Nesting dialogs is a questionable design pattern that is not referenced anywhere in the HTML 5.2 Dialog specification.
Read more >
Nested Dialog MenuItem Dropdown - CodeSandbox
Nested Dialog MenuItem Dropdown. https://github.com/mui-org/material-ui/blob/master/docs/src/pages/components/dialogs/FormDialog.js.
Read more >
Nested dialogs - HyvƤ Docs
Nested dialogs. Modals can be displayed from within another dialog. The only requirement is that each dialog has a unique Alpine.js x-ref name....
Read more >
Nested dialogs - JSFiddle - Code Playground
<p>This is a nested dialog for displaying information. The dialog window can be moved, resized and closed with the 'x' icon.</p>.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found