skip to Main Content

I’m trying to show/hide more info about names on click, but don’t know how to make it work of a single click.

I’m fetching JSON that has game related info and creating a div with JS like so:

fetch("thething.json")
.then(res => res.json())
.then(data =>{
games = data.map(game => {
const newDiv = document.createElement("div");
newDiv.className = "game-info";
newDiv.innerHTML = `
<p onclick="toggler()";>${game.Name}</p>
<div class="info">
<img src="${game.Thumbnail}">
<p>${game.Description}</p>
</div>
`;
document.body.appendChild(newDiv);

JSON itself has a bunch of info on games in structure like this:

[{"Name: gamename1,
  "Thumbnail: https://imglocation,
  "Description": This game is indeed a game
 },
  {"Name: gamename2,
  "Thumbnail: https://imglocation2,
  "Description": This game2 is indeed a game2
 }
]

The toggler function is written like this:

function toggler(){
    $('div.game-info').click(function(e){
        $(this).children('.info').toggle();
    });
}

It kinda works, but it takes one or more clicks to view the additional info. I know the problem is due to multiple onclick calls, but don’t know how to make it with a single click. I tried using jQuery without the toggler function, but then it opens info on all the names and not just the one which was clicked. So if someone could either tell me how to get rid of that secondary onclick or how to properly target the info which I clicked in the innerHTML section that’d be great!

2

Answers


  1. The main issue in your code is because you’re nesting event handlers, ie. the native JS one and a jQuery one. This is causing odd behaviour where you need to click multiple times for the logic to have any effect. You can avoid the issue by just using one event handler to hide/show the element on succesive clicks:

    Also note that it’s not good practice to use onclick attriutes. Event handlers should be bound unobtrusively in JS, not in HTML.

    Here’s a working example with the above changes applied:

    const game = { Name: 'foo', Thumbnail: 'foo.jpg', Description: 'description' };
    const newDiv = document.createElement("div");
    newDiv.className = "game-info";
    newDiv.innerHTML = `
      <p>${game.Name}</p>
      <div class="info hide">
        <img src="${game.Thumbnail}">
        <p>${game.Description}</p>
      </div>`;
    document.body.appendChild(newDiv);
    
    // bind event handler
    const toggler = e => e.target.nextElementSibling.classList.toggle('hide');
    document.querySelectorAll('div > p').forEach(p => p.addEventListener('click', toggler));
    .hide {
      display: none;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    Login or Signup to reply.
  2. You’re toggle() function is what is establishing the click event. This is why you have to click once before anything happens, there is no event listener before that. Also every time you click it, it will add a new event handler, which is why you’re getting odd repeated behaviors. This is one reason why it’s consider bad practice to put javascript in the html these days, keep the javascript between the <script> tags.

    Also $('div.game-info') is selecting all divs with a class of game-info, which is probably why it’s toggling everything. You probably want something like $('div').on('click', '.game-info') The .on() method allows you to provide a second selector argument, which then in the handler function this refers to the specific element that triggered the event, rather than all of them.

    If your project is using jQuery, might as well use it! Here’s what that might look like:

    $.getJSON('thething.json')
      .done(function(data) {
        $('body').append(data.map((game) => `
          <div class="game-info">
            <p class="toggle-info">${game.Name}</p>
            <div class="info">
              <img src="${game.Thumbnail}" alt="${game.Name} thumbnail">
              <p>${game.Description}</p>
            </div>
          </div>`).join(``))
        .find('.info').hide();
      })
      .fail(function(error) {
        //handle errors here
      });
    
    $('body')
      .on('click', '.toggle-info', function() {
        $(this).siblings('.info').slideToggle();
      });
    

    $.getJSON() – assuming your data source returns valid JSON, the first argument of the .done() method will be a parsed JSON object, no extra steps. And if the source isn’t valid, you can handle that in the .fail() method.

    Next, rather than mutating the DOM for every single game, you could create one giant html string and only have to mutate the DOM once. Then, again, you can put the event handler on some parent of the dynamically added html (body in this case), and use the selector argument to select a specific element that should handle the event.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search