2

I am trying to add the same drop down functionality to an amount n of "cards". How could I do this in javascript without having to create new code for each card? This is my current code that I have tried, but even if I got this working it would only work for the first card: (working example below)

my html:

<div class="ideanode">
 <div class="ideanodeheader">Need</div>
 <div class="content">
 <div class="title">
 <h3 contenteditable="True" onclick='this.focus();'>Title</h3>
 </div>
 <i class="fas fa-sort-down title-arrow"></i>
 <div class="maintext">
 <textarea placeholder="Text" class="maintextinput"></textarea>
 </div>
 <i class="fas fa-sort-down maintxt-arrow"></i>
 <div class="comments">
 <textarea placeholder="Comments" class="commentsinput"></textarea>
 </div>
 </div>
 </div>

js:

var maintxt = document.querySelector(".maintext");
var title = document.querySelector(".title");
var titleArrow = document.querySelector(".title-arrow");
var mainArrow = document.querySelector(".maintxt-arrow");
var comments = document.querySelector(".comments");
var mainArrows = document.querySelectorAll(".maintxt-arrow")
titleArrow.addEventListener('click', function() {
 maintxt.classList.toggle("hidden");
 mainArrow.classList.toggle("hidden");
 if (comments.classList.contains("hidden")){
 ;
 } else {
 comments.classList.toggle("hidden"); 
 };
});
mainArrow.addEventListener("click", function() {
 comments.classList.toggle("hidden");
});

Example: https://codepen.io/ricodon1000/pen/Baoxwed I would like for the dropdown arrows to trigger the boxes below them, the upper arrow should trigger only the box below it on, but if both boxes are open both should be closed. The arrow that is below should only trigger the box directly below it on and off (display: none on and off).

T.J. Crowder
1.1m201 gold badges2k silver badges2k bronze badges
asked May 10, 2020 at 8:35
4
  • 1
    Add each item in array and iterate over it Commented May 10, 2020 at 8:38
  • Related: stackoverflow.com/questions/14442896/… Commented May 10, 2020 at 8:39
  • yes but how could I iterate over multiple items at once, because if I add the titleArrows in an array each one only has to work on its respective maintxt and comments boxes. The first title arrow on the first maintxt item the second one on the second and so on Commented May 10, 2020 at 8:40
  • 1
    I added the html! Commented May 10, 2020 at 8:48

3 Answers 3

1

Do the same for each node, and limit your querySelector to the group

const ideanodes = [...document.querySelectorAll('.ideanode')];
ideanodes.forEach(ideanode => {
 const maintxt = ideanode.querySelector(".maintext");
 const title = ideanode.querySelector(".title");
 const titleArrow = ideanode.querySelector(".title-arrow");
 const mainArrow = ideanode.querySelector(".maintxt-arrow");
 const comments = ideanode.querySelector(".comments");
 titleArrow.addEventListener('click', function() {
 maintxt.classList.toggle("hidden");
 mainArrow.classList.toggle("hidden");
 if (comments.classList.contains("hidden")) {;
 } else {
 comments.classList.toggle("hidden");
 };
 });
 mainArrow.addEventListener("click", function() {
 comments.classList.toggle("hidden");
 });
});
T.J. Crowder
1.1m201 gold badges2k silver badges2k bronze badges
answered May 10, 2020 at 9:03
Sign up to request clarification or add additional context in comments.

3 Comments

FWIW, the NodeList from querySelectorAll has its own forEach now, which is at least as well supported as it being iterable (which .. relies on -- if you wanted to avoid requiring iterability, use Array.from instead), possibly slightly more so. So probably best just to use it directly. (And if you need to polyfill it, I explain how to do that here.)
I find it interesting nobody comments on the empty if branch till now...
@Icepickle i assume it is irrelevant to the current problem and that is why it is not included
1

You have a couple of options here, but the thing they have in common is that when you get a click on a .title-arrow element, you know which .title-arrow was clicked because it's the value of this (and event.currentTarget) in the event callback. From there, you can find the other elements it relates to using various DOM properties and methods, such as closest or the element version of querySelector.

So you could loop through all the .title-arrow elements and hook click on them, and then work it out from there. But I think you probably want to use event delegation instead: Hook click once, on the container for the various .ideanode elements, and then use event.target to figure out what to do.

I assume all those .ideanode elements are in a container, e.g. something like:

<div class="container">
 <div class="ideanode">
 <div class="ideanodeheader">Need</div>
 <div class="content">
 <div class="title">
 <h3 contenteditable="True" onclick='this.focus();'>Title</h3>
 </div>
 <i class="fas fa-sort-down title-arrow">v</i>
 <div class="maintext">
 <textarea placeholder="Text" class="maintextinput"></textarea>
 </div>
 <i class="fas fa-sort-down maintxt-arrow">v</i>
 <div class="comments">
 <textarea placeholder="Comments" class="commentsinput"></textarea>
 </div>
 </div>
 </div>
 <div class="ideanode">
 <div class="ideanodeheader">Need</div>
 <div class="content">
 <div class="title">
 <h3 contenteditable="True" onclick='this.focus();'>Title</h3>
 </div>
 <i class="fas fa-sort-down title-arrow">v</i>
 <div class="maintext">
 <textarea placeholder="Text" class="maintextinput"></textarea>
 </div>
 <i class="fas fa-sort-down maintxt-arrow">v</i>
 <div class="comments">
 <textarea placeholder="Comments" class="commentsinput"></textarea>
 </div>
 </div>
 </div>
 <!-- ...and so on... -->
</div>

So you can do this (see comments):

// A single handler on the container
document.querySelector(".container").addEventListener("click", function(event) {
 // Find the arrow that was clicked and the .ideanode it's in, if any
 const arrow = event.target.closest(".title-arrow, .maintxt-arrow");
 const ideanode = arrow && arrow.closest(".ideanode");
 if (!ideanode || !this.contains(ideanode)) {
 // Click wasn't on a `.title-arrow` or a `.maintxt-arrow` within an `.ideanode`
 return;
 }
 if (arrow.matches(".title-arrow")) {
 // It was a .title-arrow
 titleArrowClick.call(arrow, ideanode, event);
 } else {
 // It was a .maintxt-arrow
 mainArrowClick.call(arrow, ideanode, event);
 }
});
function titleArrowClick(ideanode, event) {
 // Use `querySelector` to look for elements within `.ideanode`
 ideanode.querySelector(".maintext").classList.toggle("hidden");
 ideanode.querySelector(".maintxt-arrow").classList.toggle("hidden");
 const comments = ideanode.querySelector(".comments");
 // (Couldn't the following be replaced with `comments.classList.add("hidden");` ?)
 if (comments.classList.contains("hidden")){
 ;
 } else {
 comments.classList.toggle("hidden"); 
 };
}
function mainArrowClick(ideanode, event) {
 ideanode.querySelector(".comments").classList.toggle("hidden");
}

Live Example:

// A single handler on the container
document.querySelector(".container").addEventListener("click", function(event) {
 // Find the arrow that was clicked and the .ideanode it's in, if any
 const arrow = event.target.closest(".title-arrow, .maintxt-arrow");
 const ideanode = arrow && arrow.closest(".ideanode");
 if (!ideanode || !this.contains(ideanode)) {
 // Click wasn't on a `.title-arrow` or a `.maintxt-arrow` within an `.ideanode`
 return;
 }
 if (arrow.matches(".title-arrow")) {
 // It was a .title-arrow
 titleArrowClick.call(arrow, ideanode, event);
 } else {
 // It was a .maintxt-arrow
 mainArrowClick.call(arrow, ideanode, event);
 }
});
function titleArrowClick(ideanode, event) {
 // Use `querySelector` to look for elements within `.ideanode`
 ideanode.querySelector(".maintext").classList.toggle("hidden");
 ideanode.querySelector(".maintxt-arrow").classList.toggle("hidden");
 const comments = ideanode.querySelector(".comments");
 // (Couldn't the following be replaced with `comments.classList.add("hidden");` ?)
 if (comments.classList.contains("hidden")){
 ;
 } else {
 comments.classList.toggle("hidden"); 
 };
}
function mainArrowClick(ideanode, event) {
 ideanode.querySelector(".comments").classList.toggle("hidden");
}
.hidden {
 display: none;
}
<div class="container">
 <div class="ideanode">
 <div class="ideanodeheader">Need</div>
 <div class="content">
 <div class="title">
 <h3 contenteditable="True" onclick='this.focus();'>Title</h3>
 </div>
 <i class="fas fa-sort-down title-arrow">v</i>
 <div class="maintext">
 <textarea placeholder="Text" class="maintextinput"></textarea>
 </div>
 <i class="fas fa-sort-down maintxt-arrow">v</i>
 <div class="comments">
 <textarea placeholder="Comments" class="commentsinput"></textarea>
 </div>
 </div>
 </div>
 <div class="ideanode">
 <div class="ideanodeheader">Need</div>
 <div class="content">
 <div class="title">
 <h3 contenteditable="True" onclick='this.focus();'>Title</h3>
 </div>
 <i class="fas fa-sort-down title-arrow">v</i>
 <div class="maintext">
 <textarea placeholder="Text" class="maintextinput"></textarea>
 </div>
 <i class="fas fa-sort-down maintxt-arrow">v</i>
 <div class="comments">
 <textarea placeholder="Comments" class="commentsinput"></textarea>
 </div>
 </div>
 </div>
 <!-- ...and so on... -->
</div>

answered May 10, 2020 at 9:04

1 Comment

@Rico - No worries! I'd missed out a couple of .classLists in the code, fixed that doing the live example. One advantage to event delegation like this is that when you add further .ideanode elements, you don't have to worry about hooking them up, they just work.
-1

Here's a suggestion, out of the many possibilities to tackle such a thing:

You start with the most basic array of classnames references and build you way into an Object with keys & values which values represent DOM nodes.

For the events part, you should very much use event-delegation so you won't need to bind an event for each and every card. Just make sure they have the same parent node, and bind your events there.

// preparation
var elms = ['maintext', 'title-arrow', 'maintxt-arrow'].reduce((acc, name) => {
 acc[name] = document.querySelector(`.${name}`)
 return acc
 }, {}
)
// iterating each "variable"
for( let elm in elms )
 console.log(elm)
<div class='maintext'></div>
<div class='title-arrow'></div>
<div class='maintxt-arrow'></div>

answered May 10, 2020 at 9:04

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.