HTML Academy
Let’s prepare for school
Getting to know events22/25
Back to the list of tasks
  • 1. Introduction to events
  • 2. How to add a handler
  • 3. How event handlers work
  • 4. Default actions
  • 5. Please pass the function
  • 6. Hiding the popup
  • 7. Pressing a key
  • 8. Choosing a key
  • 9. With one key press
  • 10. Summary of “Events in JavaScript”, part 1
  • 11. First program: “Don’t be shy”
  • 12. Welcome to our photo gallery
  • 13. Click ’em all!
  • 14. Adding an image
  • 15. A bug has crept into the system
  • 16. Scope
  • 17. Global scope
  • 18. Changing variables from outside
  • 19. Becoming Independent
  • 20. Closures
  • 21. Let’s prepare for school
  • 22. Fixing the gallery
  • 23. Getting to the heart of the matter
  • 24. Summary of “Events in JavaScript”, part 2
  • 25. The Second Program: “Señor Tomato”
Getting to the heart of the matter
  • Sign up
  • Log in

Loading…
Everything will be ready in few seconds

  • Theory
  • Theory
  • Comments

Fixing the gallery

Now that we have learned about scopes and closures, let’s return to our assignment. What has happened to our code? Why doesn’t the program work the way that it is supposed to?

for (var i = 0; i < thumbnails.length; i++) {
  thumbnails[i].addEventListener('click', function () {
    fullPhoto.src = photos[i];
  });
}

The handlers that we created use the same counter variable i. The difficulty is that the handlers are not triggered immediately. Their code is only executed at the time the event occurs. By the time the first click handler runs, the loop has fully completed, the counter reaches its maximum value of  5 , and the handler function takes this value. In this example, the counter is declared with var at the top level. The loop does not create a separate scope for it on each iteration. Therefore, the value of i is found in the global scope, and so each handler accesses this value of 5. And we do not have a thumbnail with such an index or an element in the photos array, so no image is substituted.

How can we fix this problem? With closures over separate local data, that’s how!

We will create a function that will take a thumbnail and its corresponding element from the photos array as parameters. And we will be able to add handlers inside this function. We will call this function once for each thumbnail. Each call creates a separate local scope, and its handler keeps access to that call’s parameters through its closure.

Each handler will access the parameters from a different function call, so it uses the matching thumbnail and photo path instead of the shared loop counter.

Let’s gradually improve our program so that it works correctly.

Comments

  • index.html
  • style.css
  • script.js
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Muffin Gallery</title> <link href="gallery/setting.css" rel="stylesheet"> <link href="style.css" rel="stylesheet"> </head> <body class="pattern"> <section class="gallery"> <h1>Muffin Gallery</h1> <p class="gallery__photo-full"> <img class="full-photo" src="gallery/laptop-large.jpg" width="550" height="367" alt="Large photo"> </p> <p class="gallery__photo-previews"> <button class="gallery__photo-preview" type="button"> <img src="gallery/laptop.jpg" alt="Preview with laptop"> </button> <button class="gallery__photo-preview" type="button"> <img src="gallery/microphone.jpg" alt="Preview with microphone"> </button> <button class="gallery__photo-preview" type="button"> <img src="gallery/keyboard.jpg" alt="Preview with keyboard"> </button> <button class="gallery__photo-preview" type="button"> <img src="gallery/signboard.jpg" alt="Muffin with a sign"> </button> <button class="gallery__photo-preview" type="button"> <img src="gallery/tree.jpg" alt="Preview with tree"> </button> </p> </section> <script src="script.js"></script> </body> </html>
CSS
.gallery { display: flex; flex-direction: column; align-items: center; } .gallery__photo-full { margin-bottom: 12px; } .gallery__photo-full img { display: block; } .gallery__photo-previews { display: flex; list-style-type: none; margin: 0; width: 550px; padding: 0; justify-content: space-between; } .gallery__photo-preview { padding: 0; border: 0; border-radius: 0; background: none; } .gallery__photo-preview:hover, .gallery__photo-preview:focus { opacity: 0.7; } .gallery__photo-preview img { display: block; object-fit: cover; width: 100px; height: 100px; } .gallery__photo-preview--active img { outline: 5px solid red; outline-offset: -5px; } .pattern { background: url("gallery/leaves-pattern.png") 0 0 repeat; }
JavaScript
var photos = [ 'gallery/laptop-large.jpg', 'gallery/microphone-large.jpg', 'gallery/keyboard-large.jpg', 'gallery/signboard-large.jpg', 'gallery/tree-large.jpg' ]; var thumbnails = document.querySelectorAll('.gallery__photo-preview'); var fullPhoto = document.querySelector('.full-photo'); for (var i = 0; i < thumbnails.length; i++) { thumbnails[i].addEventListener('click', function () { fullPhoto.src = photos[i]; }); }

What didn’t you like in this task?

Thanks! We’ll fix everything at once!

Click inside the mini browser to put the focus in this window.

100%
Console
Goalscompleted
0
    1. Before the loop declare a function addThumbnailClickHandler with the following two parameters: thumbnail and photo.
    2. Add a click handler to thumbnail in the function body.
    3. Output thumbnail to the console inside this handler, and then on the next line output photo to the console.
    4. Replace all of the code in the loop body with the addThumbnailClickHandler function call with the arguments thumbnails[i] and photos[i].
    5. Click on any two thumbnails.

    Cookies ∙ Privacy ∙ License Agreement ∙ About ∙ Contacts ∙ © HTML Academy OÜ, 2019−2026

    VISAMastercard

    Log in

    or

    Forgot your password?

    Sign up

    Sign up

    or
    Log in

    Restore access

    Have you forgotten your password or lost access to your profile? Enter your email connected to your profile and we will send you a link to restore access.

    Forgot to connect your email to the profile? Email us and we’ll help.