Unable to close expanded grid item using close button

Currently, I am working on a simple 3 column grid where each grid item consists of an image and a close button. The functionality I want to achieve is that when a grid item is clicked, the image should expand and the close button should become visible.

The behavior I'm aiming for is that if another grid item is clicked while one is already expanded, the previously expanded one should go back to normal and the newly clicked one should expand. Additionally, clicking on the close button should return the currently expanded grid item to its original state. I have managed to implement most of this functionality through JS by adding and removing classes using the classList function.

However, I seem to be facing an issue with the close button functionality. Despite not seeing the added class in the console log output (indicating it has been removed), the grid item does not revert to its normal state when the close button is clicked. This has left me puzzled and unsure about how to proceed.

Below is the snippet of my code:

document.addEventListener("DOMContentLoaded", function(e) {
  const containers = document.querySelectorAll('.container');
  let previouslyClickedContainer = '';

  containers.forEach(container => {
    const closeBtn = container.querySelector('.close-btn');

    container.addEventListener('click', () => {

      if (previouslyClickedContainer !== '') {
        previouslyClickedContainer.classList.remove('enlarge');
      }

      container.classList.add('enlarge');
      previouslyClickedContainer = container;

      if (closeBtn.getAttribute('aria-pressed') === 'true') {
        closeBtn.setAttribute('aria-pressed', 'false');
      }
    });

    closeBtn.addEventListener('click', () => {
      container.classList.remove('enlarge');
      previouslyClickedContainer = '';
      closeBtn.setAttribute('aria-pressed', 'true');
    });
  });
});
.main-grid {
  gap: 5rem;
  grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
  grid-auto-rows: minmax(350px, 1fr);
}

.container {
  position: relative;
  z-index: 99;
  width: 100%;
  height: 100%;
  cursor: pointer;
  transition: all 600ms ease-in-out;
}

.container .close-btn {
  position: absolute;
  z-index: 101;
  inset-inline-end: 0;
  margin-block-start: 1.25rem;
  margin-inline-end: .5rem;
  opacity: 0;
  transition: opacity 600ms ease-in-out;
}

.container img {
  height: 100%;
}


/* class that is added and removed */

.enlarge {
  z-index: 100;
  width: 150%;
  height: 150%;
  box-shadow: 0px 0px 12px 0px hsl( var(--clr-dark) / .6);
}

.enlarge .close-btn {
  opacity: 1;
}

.close-btn {
  position: relative;
  width: 32px;
  transform: rotate(45deg);
}

.close-btn,
.close-btn::after {
  height: 2px;
  background-color: white;
}

.close-btn::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  top: -.5rem;
  transform: rotate(-90deg) translateX(-.5rem);
}
<div class="main-grid">
  <div class="container">
    <div role="button" class="close-btn" aria-pressed="false" data-close-category-btn></div>
    <img src="./assets/coffee-table.webp" alt="coffee table">
  </div>
  <div class="container">
    <div role="button" class="close-btn" aria-pressed="false" data-close-category-btn></div>
    <img src="./assets/coffee-table.webp" alt="coffee table">
  </div>
  <div class="container">
    <div role="button" class="close-btn" aria-pressed="false" data-close-category-btn></div>
    <img src="./assets/coffee-table.webp" alt="coffee table">
  </div>
</div>

Answer №1

When looking at your code, it seems like there was quite a mess within a short amount of lines. My suggestion would be to try out simpler scenarios to really grasp some key concepts.

The messy parts I mentioned include using variables defined outside their scopes and gaining a deeper understanding of how event bubbling functions.

After fixing most of the smaller issues, the main challenge left was preventing the click event from propagating once the close button was clicked. The issue arose because both the close button and the container div handled the click event, causing the event fired by the close button to propagate to the parent. To quickly address this, I checked in the parent's click handler if the triggering element was as expected, and in the close button's click handler, I forcefully stopped the propagation.

The demo may seem simplistic, but it effectively showcases dealing with event bubbling (clicking on a container will enlarge it in red, and clicking the corresponding close button will return it to its original state):

const containers = document.querySelectorAll('.container');
let previouslyClickedContainer = '';

containers.forEach(container => {

    container.addEventListener('click', () => {
        if(
          window.event.currentTarget.classList.contains("close-btn")
        )return false;
    
        const closeBtn = container.querySelector('.close-btn');

        if(previouslyClickedContainer !== '') {
            previouslyClickedContainer.classList.remove('enlarge');
        }

        container.classList.add('enlarge');
        previouslyClickedContainer = container;

        if(closeBtn.getAttribute('aria-pressed') === 'true') {
            closeBtn.setAttribute('aria-pressed', 'false');
        }
    });
    
    const closeBtn = container.querySelector('.close-btn');
    closeBtn.addEventListener('click', () => {
        const closeBtn = window.event.target;
        const container = closeBtn.parentElement;
        container.classList.remove('enlarge');

        previouslyClickedContainer = '';
        closeBtn.setAttribute('aria-pressed', 'true');
        
        window.event.stopPropagation();
    });
});
.main-grid { 
    gap: 5rem; 
    grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
    grid-auto-rows: minmax(350px, 1fr);
}

.container {
    position: relative;
    z-index: 99;
    width: 100%;
    height: 100%;
    cursor: pointer;
    transition: all 600ms ease-in-out;
}

/*
.container .close-btn {
    position: absolute;
    z-index: 101;
    inset-inline-end: 0;
    margin-block-start: 1.25rem;
    margin-inline-end: .5rem;
    opacity: 0;
    transition: opacity 600ms ease-in-out;
    border: solid 2px purple;
}
*/

.close-btn{
}

.close-btn::after{
  color:black;
  content: 'close';
}

.container img {
    height: 100%;
}

/* class that is added and removed */
.enlarge {
    z-index: 100;
    width: 150%;
    height: 150%;
    box-shadow: 0px 0px 12px 0px hsl( var(--clr-dark) / .6 );
    border: solid 2px red;
}

.enlarge .close-btn {
    opacity: 1;
}

.close-btn {
    position: relative;
    width: 32px;
    transform: rotate(45deg);
}

.close-btn,
.close-btn::after {
    height: 2px;
    background-color: white;
}

/*
.close-btn::after {
    content: '';
    position: absolute;
    inset-inline: 0;
    top: -.5rem;
    transform: rotate(-90deg) translateX(-.5rem);
}
*/
<div class="main-grid">
  <div class="container">
    <div
      role="button"
      class="close-btn"
      aria-pressed="false"
      data-close-category-btn></div>
    <img src="./assets/coffee-table.webp" alt="coffee table">
    </div>
    <div class="container">
      <div
        role="button"
        class="close-btn"
        aria-pressed="false"
        data-close-category-btn=""></div>
      <img src="./assets/coffee-table.webp" alt="coffee table" >
    </div>
    <div class="container">
      <div
        role="button"
        class="close-btn"
        aria-pressed="false"
        data-close-category-btn=""></div>
      <img src="./assets/coffee-table.webp" alt="coffee table">
    </div>
</div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Creating chained fetch API calls with dependencies in Next.js

I am a novice who is delving into the world of API calls. My goal is to utilize a bible api to retrieve all books, followed by making another call to the same api with a specific book number in order to fetch all chapters for that particular book. After ...

Unable to establish a connection with the docker container

I have developed a server-api application. When running the app on my localhost, only two simple commands are needed to get it up and running: npm install npm start With just these commands, the app runs perfectly on port 3000. Now, I am trying to dock ...

Change the locale on the current path with a query in Next.js by utilizing the router

Managing locale changes centrally can be tricky. When a user selects a new locale from a list, I use useRouter to redirect them to the current page in the selected locale like this: var router = useRouter(); const changeLocale = (selectedLocale) => { ...

Creating an Interactive Table Using HTML, JavaScript, and PHP?

After reviewing this project, I am looking to customize the js function in relation to my own table. The key focus is on the following code: $("#employee_table tr:last").after("<tr id='row"+$rowno+"'><td><input type='text&apo ...

Issue with Axios Get method: Data not displaying in table

Can someone assist me with displaying JSON data on my website? I am using an API with a security token, and everything seems to be working fine until I try to display the JSON data on my page. I can see the data in my console, but not on the actual page. ...

Issues arising from script tags causing disturbances in CSS

As a newcomer to the world of web design, I recently embarked on a journey with Bootstrap and also started exploring the platform Codepen. However, after working on a project in Codepen and attempting to transfer my code to Sublime, some unexpected changes ...

Adjust the hyperlink color of <a> to change when hovering over <tr>

When I hover over a <tr> tag, I want the color of the associated <a> element to change to white. I attempted to achieve this using jQuery in the following way: <script> $('tr').hover(function(){ $('a').cs ...

How come certain invisible screen elements suddenly become visible when the document is printed?

When creating a play-off tournament bracket, I face the challenge of adjusting for varying numbers of participants in each round. Typically, there are 2^n participants in each tour: 16, 8, 4, 2 which can be easily accommodated in a flex column layout. Howe ...

Upon opening index.html in the browser, the jQuery code fails to execute, as no errors are displayed in the console

I am struggling to make a simple toggleClass() function work in jQuery and I can't seem to figure out why. This is just a beginner exercise for me with jQuery. The code works perfectly when I test it as a snippet or manually in the console, but when I ...

Press the jQuery button to reset all input fields

I have created a table with input fields where class teachers can store their students' data for the principal to review. Each row in the table contains an update and reset button, allowing teachers to save or clear the information entered in the fiel ...

What is the method to retrieve the unique individual IDs and count the number of passes and fails from an array

Given a data array with IDs and Pass/Fail statuses, I am looking to create a new array in Angular 6 that displays the unique IDs along with the count of individual Pass/Fail occurrences. [ {"id":"2","status":"Passed"}, {"id":"5","status":"Passed"}, {"id": ...

Unpacking and reassigning variables in Vue.js 3 using TypeScript

I am working with a component that has input parameters, and I am experimenting with using destructuring assignment on the properties object to reassign variables with different names: <script setup lang="ts"> const { modelValue: isSelected ...

Numerous perspectives of the TradingView Widget

I am facing an issue with the real-time chart component from tradingview.com. The chart is rendering twice at the start and every time I make a change in my code, it renders again. This results in multiple renders as shown in the image below. If anyone k ...

Is there a way for me to determine the total duration of a testcase's execution?

What is the most useful command/target to use from beginning to end of a testcase in Selenium IDE - HTML? Could you provide an example? ...

Creating a Paytm payment link using API in a React Native app without the need for a server

Suppose a user enters all their details and the total cost of their order amounts to 15000 rupees. In that case, the app should generate a Paytm payment link for this amount and automatically open it in a web view for easy payment processing. Any suggesti ...

What steps can I take to prevent the odd gaps from appearing in my horizontal flexbox gallery?

Take a look at this code: .gallery { display: flex; overflow-x: auto; resize: both; background-color: rgb(200, 200, 200); padding: 10px; height: 200px; } .item { display: flex; flex: 0 0 auto; max-width: 100%; } .item img { max-w ...

What is the best way to extract the frameset from a frame window?

Here is a code snippet to consider: function conceal(frameElem) { var frameSet = frameElem.frameSet; //<-- this doesn't seem to be working $(frameSet).attr('cols', '0,*'); } //conceal the parent frame conceal(window.pa ...

Discover the steps to display a user's username following a successful login utilizing Passport.js

I want to display the message "Welcome back "username here" " to a user after they have logged in successfully on the website. The issue is that logging in with passport is not a typical request and response process, as the authentication occurs within th ...

Creating a toggle label using just CSS: a step-by-step guide

Recently, I was trying to create a toggle label using HTML, CSS, and JavaScript. Here is the code snippet that I experimented with: function genre_itemclick(item){ if(item.classList.contains('light_blue_border_button')) { ...

What is the best way to implement dynamic comment display similar to Stack Overflow using Asp.net and Jquery?

I have created a small web application using Asp.net and C#. Currently, I am able to retrieve comments by refreshing the entire page. However, I would like to achieve this functionality without having to do a full refresh. For instance Let's say the ...