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

Is there a way to have a span update its color upon clicking a link?

I have 4 Spans and I'm looking to create an interactive feature where clicking a link in a span changes the color of that span. Additionally, when another link in a different span is clicked, the color of that span changes while reverting the previous ...

Issue with data entry: unable to enter the letter 'S' in search field

This bug has been a real challenge for me. It's strange, but I'm unable to type the letter "S" into the search input field. Oddly enough, the keyboard seems to be functioning properly. Please find the sandbox below for reference: https://codes ...

Is it possible to use an ngClick function in one directive to toggle data in another?

Currently, I am in the process of developing a weather application using Angular 1.5.8 where users should have the option to switch between imperial and metric units for temperature and wind speed. The toggle feature along with all the weather data fetche ...

Encountering issues when trying to incorporate SASS and CSS libraries in NextJS

I have been attempting to integrate the wix-style-react plugin into my NextJS project, but I am encountering difficulties when trying to build. According to their documentation, they utilize Stylable, SASS, and CSS Modules. After installing the plugin and ...

When attempting to add a variable using the next() function, I encountered an error with the BehaviorSubject. The error message displayed was "this.count.next is not a function"

In my Angular service, there is a variable called count that I need to monitor for updates. Whenever this count variable is updated, I want to assign its new value to another variable in a separate component. import {BehaviorSubject} from "rxjs/BehaviorSu ...

Wrap the text in Alphine using PHP

Currently, I am dealing with a situation where I have a string that needs to be wrapped using the Yii tag like Yii::t('app' , 'what_string'). The reason for this is because I require it to be translated per string on another page. Howev ...

Transform JSX into JSON or a string, then reverse the process

I am looking to store the state of a React Component in a database. Json.stringify(myComponent); However, when I attempt to reuse the component using JSON.parse, I encounter Error: Objects are not valid as a React child (found: object with keys {type, k ...

Achieving full white space utilization with inline block elements

When attempting to stack elements on top of each other, I encounter unwanted white space due to varying heights of the elements. I am trying to figure out how to move boxes 6 and 7 up while keeping all corresponding elements beneath them aligned properly. ...

I am encountering a horizontal scroll bar despite setting the width to 100%

I am just starting out as a beginner in web development. I decided to create a webpage to practice my HTML and CSS skills. However, when I set the width of all elements to 100%, I noticed that I am getting a horizontal scroll bar. I have tried troubleshoot ...

What are the steps to ensure that data retrieved from an API is accurately displayed in a table?

My current goal is to collect data from the crypto compare API and display it in a table format. Although I am able to generate the necessary elements and append them to the table body, I am facing an unusual issue. Each time I use a for loop to iterate th ...

What is the best way to center a Checkbox in HTML5 and CSS?

I've been struggling to center my Checkbox with label on my current website project. Despite searching online for solutions, I'm still unable to find the right method! If you're curious, you can check out the source code of my webpage at ( ...

Encountering issues with JSON.Parse in JavaScript leads to errors

I'm encountering issues with JSON parsing in my code and I can't figure out the cause of it. I have a function that calls two ajax functions, one at the start and another in the success function. Everything seems to be working fine until I try to ...

Utilizing browser's local storage to dynamically update text in buttons and panels

In my file, I have the following code snippet (I've removed other irrelevant code) <div data-role="panel" id="loc_panel"> <h2>Panel Header</h2> <p>info about this stop</p> </div> <!-- /panel --> < ...

Waiting for response in AngularJS Controller and setting callback

I have developed an AngularJS application with controllers.js and factories.js that I am excited about. However, I am facing a challenge when trying to manipulate the values within the controller (retrieved from the factories). The issue is that these val ...

Using jQuery to refresh a div element

I am struggling with updating the "right" div in my jsp page using the script below. Despite being contained in a single file, the update does not seem to work. Any assistance is appreciated. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//E ...

Embed a React component seamlessly within HTML code

Hey there! I have a situation where I'm pulling valid HTML content from the database and I need to replace merge tags with working React components. The HTML is generated using a WYSIWYG editor. Let me give you an example: const link = <a onClick= ...

Fast method or tool for generating a detailed CSS element list from deeply nested elements

I have been using Chrome dev tools to work on a code base that was not created by me. There are numerous deeply nested elements scattered throughout the code. I find myself having to manually search through the HTML structure in order to select them with ...

Is it possible to dynamically pass a component to a generic component in React?

Currently using Angular2+ and in need of passing a content component to a generic modal component. Which Component Should Pass the Content Component? openModal() { // open the modal component const modalRef = this.modalService.open(NgbdModalCompo ...

"Uncovering the ways iOS disrupts background-size functionality

I am currently working on creating several marked images that will adjust in size to fit the length of their content. This functionality works well on most browsers, except for those on iOS devices like iPhone and iPad. Take a look at the image-link below ...

Executing secure journey within TypeScript

Just came across an enlightening article on Medium by Gidi Meir Morris titled Utilizing ES6's Proxy for secure Object property access. The concept is intriguing and I decided to implement it in my Typescript project for handling optional nested object ...