2

I created function which create elements

function createElements(value) {
  //Create div .toggler
  const toggler = document.createElement('div');
  toggler.classList.add('toggler');

  //Create div .content
  const content = document.createElement('div');
  content.classList.add('content');

  //Append div .toggler in div .container
  container.append(toggler);

  //Append div .content in div .container
  container.append(content);

  toggler.innerText = 'Click me';
  content.innerText = value;
}

And function which should show and hide content if I'll click toggler 'Click me'

function toggleContent() {
  //Get created elements
  const togglers = document.querySelectorAll('.toggler'),
    contents = document.querySelectorAll('.content');

  //Toggle both div .active
  togglers.forEach((toggler, index) => {
    const content = contents[index];

    //Get div .content height
    const contentHeight = content.clientHeight + 'px';

    //Set div .content height
    content.style.height = '0px';

    //Toggle event
    toggler.addEventListener('click', () => {
      if (!toggler.classList.contains('active')) {
        toggler.classList.add('active');
      } else {
        toggler.classList.remove('active');
      }

      if (!content.classList.contains('active')) {
        content.classList.add('active');
        content.style.height = contentHeight;
      } else {
        content.classList.remove('active');
        content.style.height = '0px';
      }
    });
  });
}

Put these both functions in event of button

//Create html elements and set value inner them event
btn.addEventListener('click', () => {
  createElements(input.value);
  toggleContent();

  //Clear input value
  input.value = '';
  //   console.log(input.value);
});

All works fine, but if I add second element, first element toggler does not work

//Get html elements
const container = document.querySelector('.container'),
  input = document.querySelector('.input'),
  btn = document.querySelector('.btn');

function createElements(value) {
  //Create div .toggler
  const toggler = document.createElement('div');
  toggler.classList.add('toggler');

  //Create div .content
  const content = document.createElement('div');
  content.classList.add('content');

  //Append div .toggler in div .container
  container.append(toggler);

  //Append div .content in div .container
  container.append(content);

  toggler.innerText = 'Click me';
  content.innerText = value;
}

function toggleContent() {
  //Get created elements
  const togglers = document.querySelectorAll('.toggler'),
    contents = document.querySelectorAll('.content');

  //Toggle both div .active
  togglers.forEach((toggler, index) => {
    const content = contents[index];

    //Get div .content height
    const contentHeight = content.clientHeight + 'px';

    //Set div .content height
    content.style.height = '0px';

    //Toggle event
    toggler.addEventListener('click', () => {
      if (!toggler.classList.contains('active')) {
        toggler.classList.add('active');
      } else {
        toggler.classList.remove('active');
      }

      if (!content.classList.contains('active')) {
        content.classList.add('active');
        content.style.height = contentHeight;
      } else {
        content.classList.remove('active');
        content.style.height = '0px';
      }
    });
  });
}

//Create html elements and set value inner them event
btn.addEventListener('click', () => {
  createElements(input.value);
  toggleContent();

  //Clear input value
  input.value = '';
  //   console.log(input.value);
});
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.content {
  overflow: hidden;
}
    <input type="text" class="input" />
    <button class="btn">add</button>
    
    <div class="container">
      <!-- <div class="toggler"></div>
      <div class="content"></div> -->
    </div>
levonogiy
  • 23
  • 5

3 Answers3

2

The first toggler (and subsequent ones as you add more elements) stop working because of 2 reasons:

  1. every time a new element is added you add another click listener to the existing elements (so when 2nd element is added, 1st element gets another click listener, this causes some weirdness later on)
  2. every time a new element is added for every existing item you remember their clientHeight so if they are actually in a collapsed state, you will store 0px to contentHeight so the new click handler will then be always using 0px also for expanded state height. It would be better to toggle display: none and display: block (or inline or inline-block whichever you need)

I moved the click handler registration to the first function, in this way every created element gets only one click handler.

I removed the clientHeight/contentHeight logic and just added a CSS rule so that .content items are visible when .active and not visible otherwise. So the change was this part in CSS

.content {
  display: none;
}

.content.active {
   display: block;
}

I kept the logic that when you add a new item it will collapse all existing items. I think that is what you had in you logic too, but let me know if you don't want that.

The change was in JS code

//Toggle both div .active
togglers.forEach((toggler, index) => {
    const content = contents[index];

    // I added this to keep the same logic you had before
    // if you would like to not collapse them, just remove the 2 lines
    toggler.classList.remove('active');
    content.classList.remove('active');
});

//Get html elements
const container = document.querySelector('.container'),
  input = document.querySelector('.input'),
  btn = document.querySelector('.btn');

function createElements(value) {
  //Create div .toggler
  const toggler = document.createElement('div');
  toggler.classList.add('toggler');

  //Create div .content
  const content = document.createElement('div');
  content.classList.add('content');

  //Append div .toggler in div .container
  container.append(toggler);

  //Append div .content in div .container
  container.append(content);

  toggler.innerText = 'Click me';
  content.innerText = value;

  toggler.addEventListener('click', () => {
      if (!toggler.classList.contains('active')) {
        toggler.classList.add('active');
      } else {
        toggler.classList.remove('active');
      }

      if (!content.classList.contains('active')) {
        content.classList.add('active');
      } else {
        content.classList.remove('active');
      }
    });
}

function toggleContent() {
  //Get created elements
  const togglers = document.querySelectorAll('.toggler'),
    contents = document.querySelectorAll('.content');

  //Toggle both div .active
  togglers.forEach((toggler, index) => {
    const content = contents[index];

    toggler.classList.remove('active');
    content.classList.remove('active');
  });
}

//Create html elements and set value inner them event
btn.addEventListener('click', () => {
  createElements(input.value);
  toggleContent();

  //Clear input value
  input.value = '';
  //   console.log(input.value);
});
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.content {
  display: none;
}

.content.active {
   display: block;
}
    <input type="text" class="input" />
    <button class="btn">add</button>
    
    <div class="container">
      <!-- <div class="toggler"></div>
      <div class="content"></div> -->
    </div>
Ma3x
  • 5,761
  • 2
  • 17
  • 22
  • I'm a newbie to JS, could you please guide me on the first problem, which you described! – levonogiy Jan 09 '22 at 22:45
  • @levonogiy The first problem is fixed in the code above as well. The problem in your original code was that you called `toggler.addEventListener('click' ...` inside a `forEach` loop (this one `togglers.forEach((toggler, index) => ...`) so you added a duplicate listener to all existing elements every time the Add button was clicked. Would you like to know anything else about this? – Ma3x Jan 09 '22 at 22:50
  • Understand, ty very much! – levonogiy Jan 09 '22 at 22:55
  • @levonogiy Also see my last edit, I explained where the code actually changed. – Ma3x Jan 09 '22 at 22:55
1

In your function toggleContent() you are adding a new event listener to all existing togglers every time you add a new element.

This means that when you add two elements, the first toggler will have two events, and both events would execute and cause unexpected behaviour.

One solution may be to add a new class to the togglers when adding the event listener to them, so you are sure that you are not appending more than one listener to the same element. Then, in your document.querySelectorAll() function you can use the not operator to exclude those elements that already have an event listener

//Get html elements
const container = document.querySelector('.container'),
  input = document.querySelector('.input'),
  btn = document.querySelector('.btn');

function createElements(value) {
  //Create div .toggler
  const toggler = document.createElement('div');
  toggler.classList.add('toggler');

  //Create div .content
  const content = document.createElement('div');
  content.classList.add('content');

  //Append div .toggler in div .container
  container.append(toggler);

  //Append div .content in div .container
  container.append(content);

  toggler.innerText = 'Click me';
  content.innerText = value;
}

function toggleContent() {
  //Get created elements
  const togglers = document.querySelectorAll(
    '.toggler:not(.event-added)');   // using not operator
  const contents = document.querySelectorAll(
    '.content:not(.event-added)');   // using not operator

  //Toggle both div .active
  togglers.forEach((toggler, index) => {
    const content = contents[index];

    //Get div .content height
    const contentHeight = content.clientHeight + 'px';

    //Set div .content height
    content.style.height = '0px';

    //Toggle event
    toggler.addEventListener('click', () => {

      toggler.classList.add("event-added");    // Add new class
      content.classList.add("event-added");

      if (!toggler.classList.contains('active')) {
        toggler.classList.add('active');
      } else {
        toggler.classList.remove('active');
      }

      if (!content.classList.contains('active')) {
        content.classList.add('active');
        content.style.height = contentHeight;
      } else {
        content.classList.remove('active');
        content.style.height = '0px';
      }
    });
  });
}

//Create html elements and set value inner them event
btn.addEventListener('click', () => {
  createElements(input.value);
  toggleContent();

  //Clear input value
  input.value = '';
  //   console.log(input.value);
});


        
 * {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.content {
  overflow: hidden;
}
<input type="text" class="input" />
<button class="btn">add</button>

<div class="container">
  <!-- <div class="toggler"></div>
  <div class="content"></div> -->
</div>
AlexSp3
  • 2,201
  • 2
  • 7
  • 24
1

Not sure to understand your question...

const
  container = document.querySelector('.container')
, input     = document.querySelector('.input')
, btn       = document.querySelector('.btn')
, contents  = []
  ;
btn.onclick = e =>
  {
  let inValue = input.value.trim()

  if (!inValue) return

  const
    toggler = document.createElement('div')  
  , content = document.createElement('div') 
    ;
  toggler.className = 'toggler'
  toggler.innerText = 'Click me'

  content.className   = 'content'
  content.textContent = inValue;

  container.append(toggler);
  container.append(content);

  contents.push(content)

  input.value = '' // clear input
  toggler.click()
  input.focus()
  }
container.onclick = e =>
  {
  if (!e.target.matches('div.toggler')) return

  let nxtContent = e.target.nextElementSibling

  if (nxtContent.classList.toggle('active'))
    contents.forEach(c=>c.classList.toggle('active', c===nxtContent))
  }
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  }
.content {
  display: none;
  }
.content.active {
  display: block;
  }
.toggler {
  cursor: pointer;
  }
<input type="text" class="input" />
<button class="btn">add</button>

<div class="container">
  <!-- <div class="toggler"></div>
  <div class="content"></div> -->
</div>

You want to do something like that?

Mister Jojo
  • 20,093
  • 6
  • 21
  • 40