javascript .closest to utilise either one of two selectors - javascript

I'm currently utilizing .closest in vanilla js to find a div(modal container) with a certain class, then on click of the button closes that div(modal container). all works fine, but the issue i'm running into now is I need to find either one of two divs(two different types of modals). So depending which one of these "div (modal containers)" are closest to the button - close that modal.
<div class="modal-a">
<div class="modal__header">
<button class="btn" data-close-btn>close</button>
</div>
</div>
<div class="modal-b">
<button class="btn" data-close-btn>close</button>
</div>
//-------------- Javacript
const closeBtn = querySelectorAll(['data-close-btn]);
closeBtn.forEach(button => {
const modal = button.closest('.modal-a'); // works as expected
button.addEventListener('click', (e)=> closeModal(modal));
});
function closeModal(modal) {
modal.classList.remove('.active');
}
what im trying to achieve
const modal = button.closest('.modal-a') || button.closest('.modal-b');
const modal = button.closest('.modal-a, .modal-b');
both these obviously fails the first time but works thereafter although in the console there is always a failure on click, so how do i write this to only use the relevant selector?
Hope this makes sense what im trying to explain.

You can use a single eventlistener and event delegation for that:
document.addEventListener("click", e => {
target = e.target.closest(".modal-a, .modal-b")
if(!target || !e.target.matches("[data-close-btn]")) return;
alert("You clicked a modal-Button")
})
<div class="modal-a">
<button class="btn" data-close-btn>close</button>
</div>
<div class="modal-b">
<button class="btn" data-close-btn>close</button>
</div>
<h2>Button outside of any Modal</h2>
<button class="btn" data-close-btn>close</button>

Related

How to addeventlistener to modal buttons in handlebars template

I am creating a web app that connects to a news API and shows articles.
For each article, I have created a card and a modal in handlebars.
Upon clicking the button on each respective card, I would like a modal to open with its unique information.
I am trying to add an event listener for the button on the card to open the modal.
<div class="">
{{!-- #each article --}}
<div class="row">
{{#each articles}}
<div class="col-12-sm col-6-md col-3-lg">
<div class="card m-2">
<div class="card-body">
<h5 class="card-title">{{title}}</h5>
<p class="card-text">{{description}}</p>
</div>
<img class="card-image" src="{{urlToImage}}" alt="Card image cap">
<button id="mybtn" class="{{#index}}">Open Modal</button>
{{#index}}
</div>
</div>
{{/each}}
</div>
</div>
{{#each articles}}
<!-- The Modal -->
<div id="modid" class="modal">
<!-- Modal content -->
<div class="modal-content">
<span class="close">×</span>
<p>Some text in the Modal..</p>
</div>
</div>
{{/each}}
<script>
let modal = document.getElementById("modid");
let btn = document.getElementById("mybtn");
let span = document.getElementsByClassName("close")[0];
btn.onclick = function() {
modal.style.display = "block";
}
span.onclick = function() {
modal.style.display = "none";
}
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
}
const btn = document.querySelector('.{{#index}}');
btn.addEventListener('click', function(event){
console.log('Button Clicked');
}
</script>
Clicking on the "Open Modal" button does nothing, nor does it give an error.
I am unsure if the button class {{#index}} is being read properly by the script.
Any assistance or tips would be greatly appreciated!
First, I think a better approach than using class="{{#index}} on each button would be to use a data-attribute, like data-open-modal="{{#index}}". This will allow us to use a query selector to get all of the open modal buttons as well as allow us to get the specific index in our click handler.
Next, we cannot use a single id for each of our modal elements as you are doing with id="modid". This leads to multiple elements with the same id, "modid", which is invalid HTML. Also, it prevents us from specifying which modal we want to open. Let's just remove that id.
In our JavaScript, we can select all of the open modal buttons and modals and store them in variables:
const openModalButtons = document.querySelectorAll('[data-open-modal]');
const modals = document.querySelectorAll('.modal');
Next, we can loop through all of openModalButtons and assign a click handler to each.
openModalButtons.forEach(openModalButton => {
openModalButton.addEventListener('click', (event) => {
const openIndex = Number(event.target.dataset.openModal);
modals.forEach((modal, index) => {
modal.classList.toggle('open', index === openIndex);
modal.classList.toggle('closed', index !== openIndex)
});
});
});
Notice that our click handler gets the data-open-modal index value from the button that was clicked. This is the index of the modal to show.
Next, it loops over each modal element and sets the "open" class if the index is equal to the index we want to show; otherwise, it sets the "closed" class.
I have created a fiddle for reference.
Update: To handle "close" button clicks:
The close button is very similar to the open button. You would need to attach an event listener and you would need a way to know which modal element to target. We could use a data-attribute again, like <button class="close" data-close-modal="{{#index}}">×</button>. Note: To be semantically correct, a close button should be a <button element, not a <span>.
Our click event listener for the close buttons would be very similar to that of the open buttons. This time, however, we won't loop through all of the modals; we will just update the modal whose index in the array of modal elements is equal to the data-close-modal attribute value of the clicked close button.
const closeModalButtons = document.querySelectorAll('[data-close-modal]');
closeModalButtons.forEach(closeModalButton => {
closeModalButton.addEventListener('click', (event) => {
const closeIndex = Number(event.target.dataset.closeModal);
modals[closeIndex].classList.remove('open');
modals[closeIndex].classList.add('closed');
});
});
Note: You may very well not require an .open and a .closed class for you modals - one may be sufficient. This is just an example.
An updated fiddle has been creeated.

Is it possible to set html element in an Angular component as child of dynamically created component so that it opens mat-menu?

I am trying to achieve situation described in How to open mat-menu from button click of a dynamically created open layers custom control?
So I want to add mat-menu opener for open-layers custom control that needs to be created dynamically. In original question there's good answer which I can't use because I don't have conrol for the map creation code.
So I have an angular PopUpMenuComponent (app-popup-menu) with HTML:
<div id="mat-menu-opener" [matMenuTriggerFor]="menu"></div>
<mat-menu #menu="matMenu">
<button mat-menu-item>Item 1</button>
<button mat-menu-item>Item 2</button>
</mat-menu>
In app.component.html I have only the 3rd party map component that I have no control and the pop-up menu component :
<app-map-openlayers></app-map-openlayers>
<app-popup-menu></app-popup-menu>
In app.component.ts the custom control that I want to open the mat-menu i try following:
private createMenuButtonElement(): HTMLElement {
const element = document.createElement('div');
element.className = `ol-control button menu-opener`;
const popUpOpener = document.getElementById('mat-menu-opener');
if (popUpOpener !== null) {
element.appendChild(popUpOpener);
}
return element;
}
So I'm moving the div-element to custom control's child. The move works but the menu is not opened when I click the control. When I check in developer tools Element-tab the div-element looks ok, but the mat-menu does not.
<div class="ol-control button popup-menu-button" data-automation-id="mapBtn" title="">
<div _ngcontent-ng-cli-universal-c213="" aria-haspopup="true" id="mat-menu-opener" class="mat-menu-trigger" ng-reflect-menu="[object Object]"></div>
</div>
mat-menu element is found in page but doesn't have the buttons in it:
<app-popup-menu _ngcontent-ng-cli-universal-c360=""></app-popup-menu>
The question is: Can this be done this way?
Capture the MatMenuTrigger as a view child and in the createMenuButtonElement function use it to toggle, open or close the menu.
<div id="mat-menu-opener" #trig="matMenuTrigger" [matMenuTriggerFor]="menu"></div>
<mat-menu #menu="matMenu">
<button mat-menu-item>Item 1</button>
<button mat-menu-item>Item 2</button>
</mat-menu>
<app-map-openlayers></app-map-openlayers>
#ViewChild('trig') menuTrigger: MatMenuTrigger;
private createMenuButtonElement(): HTMLElement {
const element = document.createElement('div');
element.className = `ol-control button menu-opener`;
element.addEventListener('click', () => {
this.menuTrigger.toggleMenu();
});
return element;
}

How to use the same function for different buttons to display their own dropdown lists

I am going to have multiple dropdown buttons to show their own dropdown lists. In HTML, the dropdown lists are the sibling elements of buttons. When I use the querySelector and click whichever button, the first buttons dropdown list shows up naturally. Which selector should I use instead or any other solutions? How can I use buttonDropdown() function for each button?
Thanks in advance.
<div class="container">
<button class="dropdown-button">
<!-- content -->
</button>
<div class="dropdown-button-list">
<!-- content -->
</div>
</div>
const dropdownButton = document.querySelector('.dropdown-button')
dropdownButton.addEventListener('click', showDropdown)
function showDropdown() {
document.querySelector.('dropdown-button-list').classList.add('show')
}
If you associate them together inside of an element (like I used class='dropdown-group') then you can easily find the associated list to the button by utilizing an event listener and
evt.target.closest('.dropdown-group').querySelector('.dropdown-button-list')
window.addEventListener('load', () => {
document.querySelectorAll('.dropdown-button').forEach(el => el.addEventListener('click', e => buttonDropdown))
})
function buttonDropdown(evt) {
// button is evt.target
// associated dropdown is:
// evt.target.closest('.dropdown-group').querySelector('.dropdown-button-list')
}
<div class="container">
<div class='dropdown-group'>
<button class="dropdown-button">
<!-- content -->
</button>
<div class="dropdown-button-list">
<!-- content -->
</div>
</div>
</div>

Change the outcome of a variable based on a button push to match a different button

I have a button with an id that sets a global variable like this:
<div class="mybuttons"
<button id="mapOne" data-toggle="modal" data-target="#map-1-scene-1">Scene</button>
<button class="no-click-span" id="mapOneCurrent" data-toggle="modal" data-target="#map-1-scene-1"><i class="fas fa-charging-station fa2x"></i> Current</button>
</div>
Then in JS:
var mapNumber;
const mybuttons = document.querySelectorAll('.mybuttons button');
mybuttons.forEach(mybutton => {
mybutton.addEventListener('click', processClick);
});
function processClick() {
window.mapNumber = this.id; // the id of the clicked button
}
The second button in the div with the id #mapOneCurrent just reopens the modal without refreshing the data.
What I would like to happen, is if the second button is pushed (eg #mapOneCurrent) that the variable mapNumber just remains as mapOne (without the word "Current" at the end of it). So it would almost be as if the other button had been pushed.
Is this possible to do in this type of scenario?
This should do what you want:
var mapNumber;
const mybuttons = [...document.querySelectorAll('.mybuttons button')];
mybuttons.forEach(mybutton=>{
mybutton.addEventListener('click',function() {
window.mapNumber = this.id.replace("Current",""); // the id of the clicked button
console.log(mapNumber);
});
})
<div class="mybuttons">
<button id="mapOne" data-toggle="modal" data-target="#map-1-scene-1">Scene</button>
<button class="no-click-span" id="mapOneCurrent" data-toggle="modal" data-target="#map-1-scene-1"><i class="fas fa-charging-station fa2x"></i>Current</button>
</div>
However, you could simplify it by using "delegated event listening" to:
var mapNumber;
document.querySelector('.mybuttons').addEventListener('click',function(ev){
if (ev.target.tagName==="BUTTON") {
window.mapNumber = ev.target.id.replace("Current","");
console.log(mapNumber);
}
})
<div class="mybuttons">
<button id="mapOne" data-toggle="modal" data-target="#map-1-scene-1">Scene</button>
<button class="no-click-span" id="mapOneCurrent" data-toggle="modal" data-target="#map-1-scene-1"><i class="fas fa-charging-station fa2x"></i>Current</button>
</div>
In this snippet the event is listening to clicks on the wrapper container .mybuttobs but will trigger actions only if an inside BUTTON was clicked.

Button Onclick only working on first element

When clicking button on elements, overlay box only works with first one, not the rest
I tried already to add 2 classes but not working as I read that that might be the issue, but I am not able to make it work properly.
<div class="container">
<input type="button" value="Contactar ahora" id="Overly" class="overly"
/>
</div>
<div id="ogrooModel" class="modalbox ogroobox" >
<div class="dialog">
<button title="Close" onClick="overlay()" class="closebutton" id="close">close</button>
<div style="min-height: 150px;">
</div>
</div>
</div>
<script>
//only javascript
document.getElementById("Overly").addEventListener("click", function(){
var e =document.getElementsByClassName("modalbox");
e[0].style.display = 'block';
}) ;
document.getElementById("close").addEventListener("click", function(){
var e =document.getElementsByClassName("modalbox");
e[0].style.display= 'none';
});
</script>
What exactly to change in that code so the rest of elements display the box after clicking on button?
You don't need onClick="overlay()" for your close button, as you are already binding it with a click event listener in your script.

Categories