Bootstap 5 modal is not working as expected? - javascript

I've run into a strange to me issue that I'm not sure how to solve correctly. I'm using Bootstap 5. Let me provide a small example:
const reportBtns = Array.from(document.querySelectorAll('.report'))
reportBtns.forEach((btn) => {
btn.addEventListener('click', () => {
const myModal = new bootstrap.Modal(document.getElementById('testModal'))
myModal.show()
const modalBtn = document.querySelector('.btn-submit')
modalBtn.addEventListener('click', (e) => {
console.log('clicked')
})
})
})
.report:hover {
cursor: pointer;
}
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.0.1/dist/js/bootstrap.bundle.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.0.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<ul>
<li class="report">
<span>Report</span>
</li>
<li class="report">
<span>Report</span>
</li>
<li class="report">
<span>Report</span>
</li>
</ul>
<div class="modal" tabindex="-1" id="testModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Modal body text goes here.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-submit">Submit</button>
</div>
</div>
</div>
</div>
So, when I click Submit for the first time in my console I see clicked logged one time, however, when I close the modal, open it again and click Submit for the second time, I see clicked logged three times instead of two. Next time it will be 6 clicked and so on. I kind of understand that the reference to old modals is kept for some reason, and when I click Submit on a new modal previous modals are submitting too.
It doesn't make any sense to me why it is happening. I tried using modal.dispose() when modal was closed , but it didn't work, and I'm not even sure if I was using it correctly or it's been used for this purpose.
reportBtns.forEach((btn) => {
btn.addEventListener('click', async () => {
const myModal = new bootstrap.Modal(document.getElementById('testModal'))
myModal.show()
const modalRef = document.querySelector('#testModal')
modalRef.addEventListener('hidden.bs.modal', function () {
const modal = bootstrap.Modal.getInstance(modalRef)
if (modal) {
modal.dispose()
}
})
const modalBtn = document.querySelector('.btn-submit')
modalBtn.addEventListener('click', (e) => {
console.log('clicked')
})
})
})
I'd appreciate your help.

You add an event listener everytime the 'Report' button is clicked, yet you forgot to remove the event listener after the modal closes. The number of event listeners "stack up," and eventually multiple event listeners are fired when the submit button is clicked, causing it to log it multiple times.
I recommend adding an event listener to all buttons using querySelectorAll and a for loop once.
const reportBtns = Array.from(document.querySelectorAll('.report'))
reportBtns.forEach((btn) => {
btn.addEventListener('click', () => {
const myModal = new bootstrap.Modal(document.getElementById('testModal'))
myModal.show()
})
})
const modalBtns = document.querySelectorAll('.btn-submit')
for (let i = 0; i < modalBtns.length; i++) {
modalBtns[i].addEventListener('click', (e) => {
console.log('clicked')
})
}
.report:hover {
cursor: pointer;
}
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.0.1/dist/js/bootstrap.bundle.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" />
<ul>
<li class="report">
<span>Report</span>
</li>
<li class="report">
<span>Report</span>
</li>
<li class="report">
<span>Report</span>
</li>
</ul>
<div class="modal" tabindex="-1" id="testModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Modal body text goes here.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-submit">Submit</button>
</div>
</div>
</div>
</div>

Related

why is my console returning id is not defined

I am trying to make simple tabs program in js, i am new to js and dont have much knowlage about it,so be understandable. I set my code up using this tutorial https://wesbos.com/javascript/06-serious-practice-exercises/tabs. And this is my code
HTML
<body>
<div class="wrapper">
<div class="tabs">
<div role="tablist" aria-label="Programming Languages">
<button role="tab" aria-selected="false" id="js">
JavaScript
</button>
<button role="tab" aria-selected="false" id="ruby">Ruby
</button>
<button role="tab" aria-selected="false" id="php">
PHP
</button>
</div>
<div role="tabpanel" aria-labelledby="js" hidden>
<p>JavaScript is great!</p>
</div>
<div role="tabpanel" aria-labelledby="ruby" hidden>
<p>Ruby is great</p>
</div>
<div role="tabpanel" aria-labelledby="php" hidden>
<p>PHP is great!</p>
</div>
</div>
</div>
<script src="./tabs.js"></script>
</body>
CSS
button[aria-selected="true"] {
background: orange;
box-shadow: none;
}
JS
const tabs = document.querySelector('.tabs');
const tabButtons = tabs.querySelectorAll('[role="tab"]');
const tabPanels = tabs.querySelectorAll('[role="tabpanel"]');
function handleTabClick(event) {
console.log(event);
}
tabButtons.forEach(button => button.addEventListener('click', handleTabClick));
function handleTabClick(event) {
// hide all tab panels
tabPanels.forEach(panel => {
panel.hidden = true;
});
// mark all tabs as unselected
tabButtons.forEach(tab => {
// tab.ariaSelected = false;
tab.setAttribute('aria-selected', false);
});
// mark the clicked tab as selected
event.currentTarget.setAttribute('aria-selected', true);
// find the associated tabPanel and show it!
const { id } = event.currentTarget;
}
const tabPanel = tabs.querySelector(`[aria-labelledby="${id}"]`);
console.log(tabPanel);
tabPanel.hidden = false;

javascript tabs without passing any parameters to function

Hello so I have tabs using javascript:
function openScreen(screen, button) {
let i;
const tabContent = document.getElementsByClassName("tab-content");
const tabButton = document.getElementsByClassName("tab-button");
for (i = 0; i < tabContent.length; i++) {
tabContent[i].style.display = "none";
}
document.getElementById(screen).style.display = "block";
for (i = 0; i < tabButton.length; i++) {
tabButton[i].classList.remove('active');
}
document.getElementById(button).classList.add('active');
}
<div class="tab-links">
<button id="login-button" class="active tab-button" onclick="openScreen('Login', 'login-button')">Login</button>
<button id="register-button" class="tab-button" onclick="openScreen('Register', 'register-button')">Register</button>
</div>
<div>
<div id="Login" class="tab-content">
<p>Login</p>
</div>
<div id="Register" class="tab-content" style="display:none">
<p>Register</p>
</div>
</div>
And this is working, but I want to do this without passing any parameters to function and do it without using onclick function. But I dont image how I should achieve this.
This is a perfect task for event delegation. You register one even listener for the click event on the element that wraps the buttons and the content. In that event listener, you can use e.target is the element from which the event originated. e.currentTarget is the one on which the listener was attached.
In the listener, you need to figure out if it was really the button. Which could be done by checking for a certain property (in this case for the data-target attribute)
function openScreen(tabContainer, button) {
// remove active class from tab-content and tab-button in the current tab container
tabContainer.querySelectorAll(".tab-content.active, .tab-button.active").forEach(elm => {
elm.classList.remove('active');
})
// add the active class to the button and the container set by the target
let screen = button.dataset.target;
document.getElementById(screen).classList.add('active');
button.classList.add('active');
}
// Add the delegate event listener to all tab-conainers
document.querySelectorAll(".tab-container").forEach(container => {
container.addEventListener('click', (e) => {
// check if the event happened on an element with the `data-target` attribute
if (e.target.dataset.target) {
const tabContainer = e.currentTarget
const button = e.target
openScreen(tabContainer, button)
}
})
})
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
<div class="tab-container">
<div class="tab-links">
<button id="login-button" data-target="Login" class="tab-button active">Login</button>
<button id="register-button" data-target="Register" class="tab-button">Register</button>
</div>
<div>
<div id="Login" class="tab-content active">
<p>Login</p>
</div>
<div id="Register" class="tab-content">
<p>Register</p>
</div>
</div>
</div>
<hr>
<div class="tab-container">
<div class="tab-links">
<button id="login-button2" data-target="Login2" class="tab-button active">Login2</button>
<button id="register-button2" data-target="Register2" class="tab-button">Register2</button>
</div>
<div>
<div id="Login2" class="tab-content active">
<p>Login2</p>
</div>
<div id="Register2" class="tab-content">
<p>Register2</p>
</div>
</div>
</div>
You can do it with data attributes https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
short example
<div class="tab" data-type="login"></div>
<div class="tab" data-type="register"></div>
Then
const tabs = document.querySelectorAll('.tab');
for (let tab of tabs) {
tab.onclick = function () {
const type = this.dataset.type;
/*now you can do all needed actions*/
}
}
Do you mean something like this?
function openScreen(e) {
const button = e.target;
const { screen } = button.dataset;
const tabs = document.querySelectorAll('.tab-content');
tabs.forEach(tab => tab.classList.toggle('active', screen === tab.id));
}
const buttons = document.querySelectorAll('button');
buttons.forEach(button => button.addEventListener('click', openScreen));
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
<div class="tab-links">
<button
id="login-button"
class="active tab-button"
data-screen="Login"
>Login</button>
<button
id="register-button"
class="tab-button"
data-screen="Register"
>Register</button>
</div>
<div>
<div id="Login" class="tab-content active">
<p>Login</p>
</div>
<div id="Register" class="tab-content">
<p>Register</p>
</div>
</div>
as your request. not passing any parameter. let me know at comment section if something doesn't work correctly.
$('#login-button').click(function(){
switchTab( $(this), $('#Login') );
});
$('#register-button').click(function(){
switchTab( $(this), $('#Register') );
});
function switchTab(this_, target_){
$('.tab-content').hide();
target_.show();
$('.tab-button').removeClass('active');
this_.addClass('active');
}
button.active{
border:1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="tab-links">
<button id="login-button" class="active tab-button">Login</button>
<button id="register-button" class="tab-button">Register</button>
</div>
<div>
<div id="Login" class="tab-content">
<p>Login</p>
</div>
<div id="Register" class="tab-content" style="display:none">
<p>Register</p>
</div>
</div>

Close child modal while keeping parent open only on close button click

So I'm running into some issues and I can't figure out how to close a child modal and keep the parent modal open, so I wanted to ask the community to see if I can get some assistance.
So here is what I'm attempting to do:
I open click on the 'Launch parent modal', I want the parent modal to show (This works great).
When I launch the child modal from inside the parent, I see the child modal (This works great).
I want to be able to click on the close button inside the child modal and go back to the parent modal since it's still is-active, otherwise if they click anywhere in the shaded are it will close all the modals as it currently is doing.
So to sum it up, I just want the child modal to close so that I can see the parent modal only if the click on the close button.
document.addEventListener('DOMContentLoaded', function() {
const rootEl = document.documentElement;
const $modals = document.querySelectorAll('.modal');
const $modalButtons = document.querySelectorAll('.modal-button');
const $modalCloses = document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button');
if ($modalButtons.length > 0) {
$modalButtons.forEach(function ($el) {
$el.addEventListener('click', function () {
var target = $el.dataset.target;
openModal(target);
});
});
}
if ($modalCloses.length > 0) {
$modalCloses.forEach(function ($el) {
$el.addEventListener('click', function () {
closeModals();
});
});
}
function openModal(target) {
var $target = document.getElementById(target);
rootEl.classList.add('is-clipped');
$target.classList.add('is-active');
}
function closeModals() {
rootEl.classList.remove('is-clipped');
$modals.forEach(function ($el) {
$el.classList.remove('is-active');
});
}
document.addEventListener('keydown', function (event) {
var e = event || window.event;
if (e.keyCode === 27) {
closeModals();
closeDropdowns();
}
})
});
<link href="https://cdn.jsdelivr.net/npm/bulma#0.9.1/css/bulma.min.css" rel="stylesheet"/>
<button class="button is-primary is-large modal-button" data-target="modal-ter" aria-haspopup="true">Launch parent modal</button>
<div id="modal-ter" class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Modal title</p>
<button class="delete" aria-label="close"></button>
</header>
<section class="modal-card-body">
<p>
This is the parent modal (Scroll down and click the child modal button).
</p>
<button class="button is-primary is-large modal-button" data-target="modal-ter2" aria-haspopup="true">Launch child modal</button>
</section>
<footer class="modal-card-foot">
<button class="button is-success">Save changes</button>
<button class="button">Cancel</button>
</footer>
</div>
</div>
<div id="modal-ter2" class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Modal title</p>
<button class="delete" aria-label="close"></button>
</header>
<section class="modal-card-body">
<p>
This is the child modal
</p>
</section>
<footer class="modal-card-foot">
<button class="button is-success">Save changes</button>
<button class="button">Cancel</button>
</footer>
</div>
</div>
I think you should bind different events for success and cancel butttons, cause they have different logics.
document.addEventListener('DOMContentLoaded', function() {
const rootEl = document.documentElement;
const $modals = document.querySelectorAll('.modal');
const $modalButtons = document.querySelectorAll('.modal-button');
const $modalCloses = document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete');
if ($modalButtons.length > 0) {
$modalButtons.forEach(function ($el) {
$el.addEventListener('click', function () {
var target = $el.dataset.target;
openModal(target);
});
});
}
if ($modalCloses.length > 0) {
$modalCloses.forEach(function ($el) {
$el.addEventListener('click', function () {
closeModals();
});
});
}
function openModal(target) {
var $target = document.getElementById(target);
rootEl.classList.add('is-clipped');
$target.classList.add('is-active');
}
function closeModals() {
rootEl.classList.remove('is-clipped');
$modals.forEach(function ($el) {
$el.classList.remove('is-active');
});
}
$modals.forEach(function ($el) {
$el.addEventListener('click', function(event) {
if (event.target.classList.contains('cancel')) {
$el.classList.remove('is-active');
}
})
});
document.addEventListener('keydown', function (event) {
var e = event || window.event;
if (e.keyCode === 27) {
closeModals();
closeDropdowns();
}
})
});
<link href="https://cdn.jsdelivr.net/npm/bulma#0.9.1/css/bulma.min.css" rel="stylesheet"/>
<button class="button is-primary is-large modal-button" data-target="modal-ter" aria-haspopup="true">Launch parent modal</button>
<div id="modal-ter" class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Modal title</p>
<button class="delete" aria-label="close"></button>
</header>
<section class="modal-card-body">
<p>
This is the parent modal (Scroll down and click the child modal button).
</p>
<button class="button is-primary is-large modal-button" data-target="modal-ter2" aria-haspopup="true">Launch child modal</button>
</section>
<footer class="modal-card-foot">
<button class="button is-success">Save changes</button>
<button class="button cancel">Cancel</button>
</footer>
</div>
</div>
<div id="modal-ter2" class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Modal title</p>
<button class="delete" aria-label="close"></button>
</header>
<section class="modal-card-body">
<p>
This is the child modal
</p>
</section>
<footer class="modal-card-foot">
<button class="button is-success">Save changes</button>
<button class="button cancel">Cancel</button>
</footer>
</div>
</div>

Modal only displays first image

I have got an issue where my modal only displays the first image. What I am trying to do is that i want to display a dynamic gallery. Every "post" can have 0-* images and based on how many images there are in the database for this post thats how many will appear. I came across this issue and have found others having similar issues but not exactly the same.
This code is basically a partial view where the post is generated. The Modal is displayd correct amount of times but only displays the first picture.
Thanks so much in advance
<ul class="imageRow">
#foreach (var item in Model)
{
<div class="col-lg-3 col-md-4 col-xs-6">
<a class="thumbnail" href="#" data-image-id="" data-toggle="modal" data-target="#image-gallery">
<img class=" img-responsive" id=" imageresource" src="data:image/jpeg;base64,#item">
</a>
</div>
<div class="modal fade" id="image-gallery" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<h4 class="modal-title" id="image-gallery-title"></h4>
</div>
<div class="modal-body">
<img class=" img-responsive" id=" imageresource" src="data:image/jpeg;base64,#item">
</div>
<div class="modal-footer">
<div class="col-md-2">
<button type="button" class="btn btn-primary" id="show-previous-image">Previous</button>
</div>
<div class="col-md-2">
<button type="button" id="show-next-image" class="btn btn-default">Next</button>
</div>
</div>
</div>
</div>
</div>
<script src="//code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/bootstrap.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js" type="text/javascript"></script>
<script>
$(document.body).on('hidden.bs.modal', function () {
$('#image-gallery').removeData('bs.modal');
});
$(document).ready(function () {
loadGallery(true, 'a.thumbnail');
//This function disables buttons when needed
function disableButtons(counter_max, counter_current) {
$('#show-previous-image, #show-next-image').show();
if (counter_max == counter_current) {
$('#show-next-image').hide();
} else if (counter_current == 1) {
$('#show-previous-image').hide();
}
}
function loadGallery(setIDs, setClickAttr) {
var current_image,
selector,
counter = 0;
$('#show-next-image, #show-previous-image').click(function () {
if ($(this).attr('id') == 'show-previous-image') {
current_image--;
} else {
current_image++;
}
selector = $('[data-image-id="' + current_image + '"]');
updateGallery(selector);
});
function updateGallery(selector) {
var $sel = selector;
current_image = $sel.data('image-id');
$('#image-gallery-caption').text($sel.data('caption'));
$('#image-gallery-title').text($sel.data('title'));
$('#image-gallery-image').attr('src', $sel.data('image'));
disableButtons(counter, $sel.data('image-id'));
}
if (setIDs == true) {
$('[data-image-id]').each(function () {
counter++;
$(this).attr('data-image-id', counter);
});
}
$(setClickAttr).on('click', function () {
updateGallery($(this));
});
}
});
</script>
}
You are using the same ID (id="image-gallery") for all of your modals. HTML element IDs are supposed to be unique! You need to generate different IDs for each of your modals.

Reset bootstrap modal

So, I am using bootstrap's modal.
I want to make a wizard style modal and came up with the following solution:
div snippets:
<div class="modal-instance hide fade" id="step1">
<div id="stepa">
...
</div>
</div>
<div id="stepb" style="display: none;">
...
</div>
Upon pressing a button in step-a step-b is loaded.
javascript snippet:
$("#stepa").replaceWith($('#stepb'));
document.getElementById('stepb').style.display = 'block';
This works ok.
But when I dismiss the modal. The div stepa has still been replaced by stepb. My solution was to build a replacement back to stepa when the modal is hidden:
$("#myModal").on("hidden", function() {
//replace the child
});
I tried:
$('#step1').children().remove();
$('#step1').append($('#stepa'));
and
$("#step1").children("div:first").replaceWith($('#stepa'));
But I am having a hardtime selecting step-a as a replacement div, probably due to it not being a separate div. My question is, is this the right approach for a wizard styled modal or should I take another approach?
It's much simpler just to hide the previous and next steps, instead of copying them.
<button type="button" data-toggle="modal" data-target="#myModal">Launch modal</button>
<div id="myModal" class="modal hide fade" data-step="1">
<div class="step step1">
<h1>Step 1</h1>
<button class="btn next">Next</button>
</div>
<div class="step step2">
<h1>Step 2</h1>
<button class="btn previous">Previous</button>
<button class="btn next">Next</button>
</div>
<div class="step step3">
<h1>Step 3</h1>
<button class="btn previous">Previous</button>
<button class="btn done" data-dismiss="modal">Done</button>
</div>
</div>
<style type="text/css">
#myModal > .step { display: none; }​
</style>
<script type="text/javascript">
$(function() {
showStep(parseInt($('#myModal').data('step')) || 1);
$('#myModal .next').on('click', function () {
showStep(parseInt($('#myModal').data('step')) + 1);
});
$('#myModal .previous').on('click', function () {
showStep(parseInt($('#myModal').data('step')) - 1);
});
$('#myModal').on('hidden', function() {
showStep(1);
});
function showStep(step) {
$('#myModal').data('step', step);
$('#myModal > .step').hide();
$('#myModal > .step' + step).show();
}
});​
</script>
http://jsfiddle.net/7kg7z/5/

Categories

Resources