0

I am using the following code form w3schools that uses a button to toggle text:

function myFunction() {
    var x = document.getElementById("myDIV");
    if (x.style.display === "none") {
        x.style.display = "block";
    } else {
        x.style.display = "none";
    }
}
<button onclick="myFunction()">Try it</button>

<div id="myDIV">
This is my DIV element.
</div>

Now, I want to have a page with many buttons that toggle different texts (every button corresponds to a unique text). Of course, I can make it work by copying the function each time I create a new text; myFunction1 <-> myDIV1, myFunction2 <-> myDIV2 etc.

But is there a smarter way to do it? I am thinking it can be done with a single function that select id's in a specific form but I don't know how.

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
cgss
  • 233
  • 2
  • 10
  • 1
    Does this answer your question? [onClick to get the ID of the clicked button](https://stackoverflow.com/questions/4825295/onclick-to-get-the-id-of-the-clicked-button) – 0stone0 Jul 05 '23 at 15:49
  • Or use `event.target.id` – 0stone0 Jul 05 '23 at 15:50
  • 1
    you can create a toggle function and pass the id as an argument, so every time you onClick on any button it will call your function with the id you pass it - ```
    This is Text 1.
    ```
    – Akii J Jul 05 '23 at 15:57
  • https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_delegation Go to the part that says Event delegation, for some reason MDN links to hash bangs isn't working.. – Keith Jul 05 '23 at 15:58
  • Is each button grouped together with the text it's meant to toggle? Or are they on separate parts of the page? You may not need ids if they're grouped. – Andy Jul 05 '23 at 16:00
  • "Is there a smarter way to do it" -- you could pass the id to the function, or use a `data-` attribute on the button that points to the id. But "smarter" is subjective, and we prefer objective questions here on Stack Overflow. – Heretic Monkey Jul 05 '23 at 16:01
  • @0stone0 They don't want to operate on the same element they clicked on. They click on a button, it changes the style of a div. – Barmar Jul 05 '23 at 16:01

3 Answers3

4

Since you are learning JavaScript, I suggest learning to not use some traditionally bad habits.

One thing that is generally more hassle than it's worth are inline click handlers. Meaning onclick='myfunction()' on your elements. Instead using addEventListener offers more capabilities.

Also, another cool thing to learn is toggling a CSS class on your elements instead of changing the style directly in JavaScript.

So for my answer I actually add a click handler to the body. Then when you click I check to see if the element you clicked has a specific class. I gave your buttons a class of btnToggle.

Next on each button I use data attributes to dictate what that button should target. This is done by using data-target="#divID".

Now in the click handler, I get that target by getting the clicked element and data.target.

Now I use a CSS class called active that sets display to display.

Then I simply use the clicked element's class list to toggle that class.

document.body.addEventListener("click",(e) => {
   const el = e.target;
   if(el.classList.contains("btnToggle")){
      const targetDiv = document.querySelector(el.dataset.target)
      targetDiv.classList.toggle("active")
   }
});
.contentDiv{display:none}
.active{
  display:block
}
<button data-target="#myDIV1" class="btnToggle">Try it 1</button>

<div id="myDIV1" class="contentDiv">
This is my DIV element. 1
</div>

<button data-target="#myDIV2" class="btnToggle">Try it 2</button>

<div id="myDIV2" class="contentDiv">
This is my DIV element. 2
</div>
Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
imvain2
  • 15,480
  • 1
  • 16
  • 21
  • 1
    You could simplify this a tad. Instead of using ID's & data attributes, you could just do -> `const targetDiv = el.nextElementSibling;` – Keith Jul 05 '23 at 16:09
  • 1
    @Keith Yeah there are ways to simplify. I intentionally didn't use nextElementSibling as it assumes the div is directly after the button. So it forces OP to follow a specific layout. – imvain2 Jul 05 '23 at 16:12
  • 2
    Yeah, fair point. But on the positive side by using `nextElementSibling` the OP can just copy & paste and not have to worry about ID's. – Keith Jul 05 '23 at 16:20
  • While this code works the use of data-* attributes to store an ID to use as a target reference is not scalable. ID's should be completely avoided when possible (which is most of the time). Also, using event delegation is a good approach, but setting up the handler on `body` is not a great best practice either as (depending on the depth of the DOM), the event would have to bubble higher than may be desired due to conflicts with other `click` events. – Scott Marcus Jul 05 '23 at 17:05
  • Regarding your comment about having the OP's code having to follow a specific layout, that's not necessarily a bad thing as it adds consistency to the code and (as pointed out) allows for the removal of ID's altogether. This is very common practice. – Scott Marcus Jul 05 '23 at 17:06
1

First, let me say that W3Schools is well-known to have incomplete, out of date, or just plain wrong information. You should stay as far away from it as a learning resource as possible. Instead, the Mozilla Developer Network (who are the stewards of the JavaScript language) has what is widely considered to be the authoritative source for learning and documentation on many web technologies and languages.

As to your question (and because you got the code from W3Schools) we have to back up a bit.

  • The way they show you how to set up event handling functions is a 25+ year old approach that we used before we had any standardized ways of doing things. This technique still works for legacy reasons, but should not be used today.
  • Setting styles directly on elements via the style property is discouraged. This causes what is called an inline style to be added to the element and inline styles lead to duplication of code and difficulty in overriding those styles when that is invariably needed.
  • The use of id's on elements is a very easy way to access portions of your page and work with them, but it is also discouraged because id based scripting leads to brittle code that doesn't scale well. The use of id's should also be avoided in favor of other ways (i.e., location of an element in relation to other elements).

With all that said, the better approach here would be to have your several buttons to press, but have them each just populate one common output element when they are clicked. This way, you don't need to know about any id.

See comments inline below:

// Get your element references you'll need just once,
// rather than each time the function is called.
// And note here that we can get the reference by
// searching for the first instance of an element 
// that matches the specified class.
const output = document.querySelector(".output");

// Set up events for your elements in JavaScript, not HTML
// Here, we'll set up a click event handler for the parent
// of the 3 sample buttons. When any button gets clicked,
// the click event will "bubble" up to the parent and be
// handeled there. This is called "event delegation".
document.querySelector(".wrapper").addEventListener("click", myFunction);

function myFunction(event) {
  // Check to see if the event was triggered 
  // by an element we care to handle
  if(event.target.classList.contains("topic")){
    // Update the status of the element
    event.target.closest("label").classList.toggle("active");
    
    // Check to see which button was clicked so we know which text to display
    if(event.target.classList.contains("a")){
      // This is called a "ternary operator" and works like this:
      // some value = condition ? value if condition is true : value if false
      output.textContent = output.textContent === "" ? "TOPIC A" : "";
    } else if (event.target.classList.contains("b")) {
      output.textContent = output.textContent === "" ? "TOPIC B" : "";   
    } else {
      output.textContent = output.textContent === "" ? "TOPIC C" : "";   
    }
  }
}
.hidden { display:none; }
.active { color:red; }
<div class="wrapper">
  <!-- Don't set up events with inline HTML attributes. -->
  <label><input type="checkbox" class="hidden topic a">Try it 1</label>
  <label><input type="checkbox" class="hidden topic b">Try it 2</label>
  <label><input type="checkbox" class="hidden topic c">Try it 3</label>
</div>


<!-- No matter which button gets clicked, the appropriate
     content will go here. -->
<div class="output"></div>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • Your answer seems informative but I didn't accept it since it doesn't do what I want. Try it 1 should toggle topic A on and off, try it 2 so do the same with the different text topic B and so on. – cgss Jul 05 '23 at 16:35
  • @cgss If you want more of a toggle effect, then we just need to be using checkboxes instead of buttons. I will adjust the code to reflect this. – Scott Marcus Jul 05 '23 at 16:38
  • @cgss Please see updated answer. Also, you should up vote all helpful answers. – Scott Marcus Jul 05 '23 at 16:51
  • Sorry but still not perfect. What if I wanted 2 to texts to be shown simultaneously? (I did upvoted though) – cgss Jul 05 '23 at 16:58
  • @cgss You didn't ask about that use case. Please understand that SO isn't a code writing service. We're not here to write "perfect" code for you. We're here to explain why certain approaches are better or worse and show examples of what we mean. It is still your responsibility to take that information and continue to learn from it. The code that I've provided shows the best-practice approach to solving the question you've asked. Tweaking it from here should be something that you should now work on with the head start my (or other) answers have provided. – Scott Marcus Jul 05 '23 at 17:02
1

To try and tie together mine and Keith's comments - re grouping and event delegation - depending on how your page is arranged you can get away with a more simplified HTML markup, and JS code.

For example, let's say you have a series of sections, and each section has a paragraph and corresponding button immediately below it. Using event delegation you can add one listener to the containing element (in this case .container), and have that catch all of the events from its child elements as they "bubble up" the DOM.

The listener's handler would first check to see if the element that fired the event matches a button element, and it would then grab that button's previousElementSibling (the paragraph element). Using CSS and classList's toggle method, you can then toggle that text on/off, or to a different colour, etc. This example fades it in/out.

// Get the container element
const container = document.querySelector('.container');

// Assign one listener to the container
container.addEventListener('click', handleClick);

// Using the event from the listener
// 1) Check the target of the event is a button
// 2) Grab the button's sibling paragraph element
// 3) Toggle the show class on the paragraph
function handleClick(e) {
  if (e.target.matches('button')) {
    const para = e.target.previousElementSibling;
    para.classList.toggle('show');
  }
}
.container  { display: grid; grid-template-columns: repeat(2, 100px); gap: 0.25rem;}
.container section { background-color: #efefef; border: 1px solid #cdcdcd; padding: 0.25rem; text-align: center;}
p { opacity:0; transition: opacity 0.2s ease-in-out;}
.show { opacity: 1; }
<section class="container">
  <section>
    <p>Text one</p>
    <button type="button">Toggle text</button>
  </section>
  <section>
    <p>Text two</p>
    <button type="button">Toggle text</button>
  </section>
  <section>
    <p>Text three</p>
    <button type="button">Toggle text</button>
  </section>
  <section>
    <p>Text four</p>
    <button type="button">Toggle text</button>
  </section>
</section>
Andy
  • 61,948
  • 13
  • 68
  • 95