Help with custom modal

Hello,
i m using this simple custom modal that works perfect:

but now i need to use it on html table that are generated inside while loop!

For now it works only for first line. I know why (because of ID) but i do not know how to fix it so on every line there would be an unique modal popup!
Please help me understood this!

Here is what i could do: https://jsfiddle.net/0wstxp81/1/
it passes value and open modeal just for first row :frowning:

Hi, can you share the code that isnā€™t working to give us a better idea of what you are trying to accomplish.

@James_Hibbard here it is: https://jsfiddle.net/0wstxp81/1/

With the buttons, we can have them reference the modal that they are used for. Itā€™s best to not use unique identifiers when it comes to scripting. Using them though to provide a unique reference though, such as from a button to a modal, works well.

<button data-modal="modal1" class="modal-button">First modal</button>
<button data-modal="modal2" class="modal-button">Second modal</button>

<div id="modal1" class="modal">
...
const modalButtons = document.querySelectorAll('.modal-button');
Array.from(modalButtons).forEach(function (button) {
  ...
});

Getting the modal needs to in relation to the data-modal attribute, and getting the close element needs to be in relation to the modal dialog to support multiple modals. The getModalFromButton() function is used because weā€™ll find it useful from an openModal() function later on too.

function getModalFromButton(button) {
  const modalId = button.getAttribute("data-modal");
  return modal = document.getElementById(modalId);
}
...
Array.from(modalButtons).forEach(function (button) {
  const span = getModalFromButton(button).querySelector(".close");
  ...
});

For the button to be more consistent with good behaviour, and to prevent the event from being lost if other events are added to it, weā€™ll use addEventListener to add the events.

.modal.show {
  display: block;
}
function openModal(modal) {
  modal.classList.add("show");
}

Array.from(modals).forEach(function (modal) {
  ...
  button.addEventListener("click", function (evt) {
    openModal(evt.target);
  }, false);
...

When closing, the span needs to know where the modal is so that it can close it.

function upToModal(el) {
  while (!(el.classList.contains("modal") || el.nodeName === "HTML")) {
    el = el.parentNode;
  }
  return el;
}
function closeModal(modal) {
  modal.classList.remove("show");
}

Array.from(modals).forEach(function (modal) {
  ...
  span.addEventListener("click", function (evt) {
    closeModal(upToModal(evt.target));
  }, false);
});

Lastly when clicking the the modal background causes it to close, itā€™s useful to have a helper function to make the if condition easier to understand.

function isModalBackground(el) {
  return el.classList.contains("modal");
}

window.onclick = function (evt) {
  var el = evt.target;
  if (isModalBackground(el)) {
    closeModal(el);
  }
}

A working example of this is shown at https://jsfiddle.net/0wstxp81/2/

1 Like

@Paul_Wilkins hey, thanks for such a grate explanation :slight_smile:

everything you showed works good!But i need them to be opened in html table that are made from while loop!
In this case looks like if i would have 10 records i would have 10 unique modals?
i made this : https://jsfiddle.net/0wstxp81/1/ hope it helps understood what i want

Well letā€™s make my modal code more generic, so that you can give it an element, and a connection between that element and the related modal will be set up.

Ultimately we want to be able to issue the following type of command:

addModal(button);

We can put all of the code in the previous jsfiddle inside of a function:

function addModal(el) {
    // all existing scripting code in here
}

The getModalFromButton() function can be make more generic, calling it getModalFromData() instead.

  function getModalFromData(el) {
    const modalId = el.getAttribute("data-modal");
    return modal = document.getElementById(modalId);
  }
  function openModal(el) {
    getModalFromData(el).classList.add("show");
  }

and we can then extract whatā€™s not needed inside of there.

The modalButtons and forEach code can be removed, leaving the rest of the code inside of the function.

function addModal(el) {
    ...
}

const modalButtons = document.querySelectorAll('.modal-button');
Array.from(modalButtons).forEach(function(el) {
  addModal(el);
});

The code that was inside of the forEach function, can have button renamed to el instead:

function addModal(el) {
  ...
  function openModalHandler(evt) {
    openModal(evt.target);
  }
  ...
  function closeModalHandler(evt) {
    closeModal(upToModal(evt.target));
  }
  ...
  const span = getModalFromData(el).querySelector(".close");

  el.addEventListener("click", openModalHandler, false);
  span.addEventListener("click", closeModalHandler, false);
  ...
}

And, the window.onclick really should be instead an event on the modal object itself. We can rename isModalBackground to a more appropriate isModalContainer() instead too.

  function isModalContainer(el) {
    ...
  }
  ...
  function modalBackgroundHandler(evt) {
    const el = evt.target;
    if (isModalContainer(el)) {
      closeModal(upToModal(el));
    }
  }
  ...
  modal.addEventListener("click", modalBackgroundHandler, false);

We can now remove our reliance on the data-modal attribute. Itā€™s useful when setting up a modal, but we donā€™t want to rely on it whenever we open a modal dialog. We can easily do that by adding a modal reference to the element that opens the modal.

  function openModal(el) {
    // modal = getModalFromData(el);
    el.modalReference.classList.add("show");
  }
  ...
  el.modalReference = modal;
  const span = modal.querySelector(".close");

Thereā€™s one more thing to make our addModal() function even more generic, and that is to be able to pass in a modal element separately. That way if the second argument isnā€™t provided, the data-model attribute will be used instead.

function addModal(el, modal) {
  // The two uses of addModal are:
  // - addModal(el) with a data-modal reference to the modal
  // - addModal(el, modal) to override the data-modal reference
  ...
  if (!modal || !isModalContainer(modal)) {
    modal = getModalFromData(el);
  }
  el.modal = modal;
  ...
}

And we now have a more generic addModal() method that we can usefully put in to use. Hereā€™s the full script, which can be seen in action at https://jsfiddle.net/0wstxp81/3/

function addModal(el, modal) {
  // The two uses of addModal are:
  // - addModal(el) with a data-modal reference to the modal
  // - addModal(el, modal) to override the data-modal reference
  
  function getModalFromData(el) {
    const modalId = el.getAttribute("data-modal");
    return document.getElementById(modalId);
  }
  function openModal(el) {
    el.modal.classList.add("show");
  }
  function closeModal(modal) {
    modal.classList.remove("show");
  }
  function upToModal(el) {
    while (!(el.classList.contains("modal") || el.nodeName === "HTML")) {
      el = el.parentNode;
    }
    return el;
  }
  function isModalContainer(el) {
    return el.classList.contains("modal");
  }
  function openModalHandler(evt) {
    openModal(evt.target);
  }
  function closeModalHandler(evt) {
    closeModal(upToModal(evt.target));
  }
  function modalBackgroundHandler(evt) {
    const el = evt.target;
    if (isModalContainer(el)) {
      closeModal(upToModal(el));
    }
  }

  if (!modal || !isModalContainer(modal)) {
    modal = getModalFromData(el);
  }
  el.modal = modal;

  const span = modal.querySelector(".close");

  el.addEventListener("click", openModalHandler, false);
  span.addEventListener("click", closeModalHandler, false);
  modal.addEventListener("click", modalBackgroundHandler, false);
}

const modalButtons = document.querySelectorAll('.modal-button');
Array.from(modalButtons).forEach(function(el) {
  addModal(el);
});
2 Likes

Using the above code, itā€™s now easy to modify your existing HTML to work with modals.

  <td data-modal="name" class="myBtn">1</td>
  ...
  <td data-modal="address" class="myBtn">1</td>
  ...
  <td data-modal="phone" class="myBtn">1</td>
<div id="name" class="modal">
  ...
</div>
<div id="address" class="modal">
  ...
</div>
<div id="phone" class="modal">
  ...
</div>

And the following scripting code below the addModal() function:

const modalButtons = document.querySelectorAll('.myBtn');
Array.from(modalButtons).forEach(function(el) {
  addModal(el);
});

You can see it working with your HTML code at https://jsfiddle.net/0wstxp81/4/

3 Likes

@Paul_Wilkins once again a grate code!Thank you so much!

But i really do not get it. ā€¦ I needed to fill inputs with line values
as in my example https://jsfiddle.net/0wstxp81/1/ i could only get first input to fill wit its value (value from an id from database)

if your code does it what em i doing wrong?:tired_face:

I see that your example doesnā€™t use any of the code that I provided.

One of the things that youā€™re doing wrong is using the same name for the unique identifiers.
Unique identifiers must be unique. Thatā€™s a good strong clue that you need to use class names for them instead.

@Paul_Wilkins Hey!I just got it working with your last code!

A big thanks for your time :slight_smile:

Thanks again!

Good one. I just noticed that a comment in my code needs to be fixed, where ā€œwith a data-model reference to the modalā€ needs to have ā€œmodelā€ spelled as ā€œmodalā€. Iā€™ve asked if someone can update my post to fix that minor thing.

1 Like

The updated addModal() function which is found at https://jsfiddle.net/0wstxp81/5/ doesnā€™t behave any different, but just has a spelling fix to the comment.

function addModal(el, modal) {
  // The two uses of addModal are:
  // - addModal(el) with a data-modal reference to the modal
  // - addModal(el, modal) to override the data-modal reference
  
  function getModalFromData(el) {
    const modalId = el.getAttribute("data-modal");
    return document.getElementById(modalId);
  }
  function openModal(el) {
    el.modal.classList.add("show");
  }
  function closeModal(modal) {
    modal.classList.remove("show");
  }
  function upToModal(el) {
    while (!(el.classList.contains("modal") || el.nodeName === "HTML")) {
      el = el.parentNode;
    }
    return el;
  }
  function isModalContainer(el) {
    return el.classList.contains("modal");
  }
  function openModalHandler(evt) {
    openModal(evt.target);
  }
  function closeModalHandler(evt) {
    closeModal(upToModal(evt.target));
  }
  function modalBackgroundHandler(evt) {
    const el = evt.target;
    if (isModalContainer(el)) {
      closeModal(upToModal(el));
    }
  }

  if (!modal || !isModalContainer(modal)) {
    modal = getModalFromData(el);
  }
  el.modal = modal;

  const span = modal.querySelector(".close");

  el.addEventListener("click", openModalHandler, false);
  span.addEventListener("click", closeModalHandler, false);
  modal.addEventListener("click", modalBackgroundHandler, false);
}

const modalButtons = document.querySelectorAll('.modal-button');
Array.from(modalButtons).forEach(function(el) {
  addModal(el);
});
1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.