"Image gallery" assessment


(Mattia Lorenz) #12

Hi guys,

this is my code and it’s currently working. What do you think?

/* Looping through images */
for (i = 1; i <= 5; i++) {
var newImage = document.createElement(‘img’);
newImage.setAttribute(‘src’, ‘./images/pic’ + i + ‘.jpg’);
thumbBar.appendChild(newImage);
newImage.onclick = getTheImage;
function getTheImage(e, src) {
var src = e.target.getAttribute(‘src’);
displayedImage.setAttribute(‘src’, src);
};
};

/* Wiring up the Darken/Lighten button */
btn.onclick = changeOverlay;
function changeOverlay () {
var cl = btn.getAttribute(‘class’);
if (cl === ‘dark’) {
btn.setAttribute(‘class’, ‘light’);
btn.textContent = ‘Lighten’;
overlay.style.backgroundColor = ‘rgba(0,0,0,0.5)’;
}
else {
btn.setAttribute(‘class’, ‘dark’);
btn.textContent = ‘Darken’;
overlay.style.backgroundColor = ‘rgba(0,0,0,0)’;
}
};


(Chris Mills) #13

Hi Mattia,

Your code doesn’t look too at at all — nice job!

One bit of feedback I can give you — in the following block:

function getTheImage(e, src) {
var src = e.target.getAttribute('src');
displayedImage.setAttribute('src', src);
}
}

You don’t need to define the src parameter in the function, as you are getting src inside the function body in the second line instead. So the following would work fine:

function getTheImage(e) {
var src = e.target.getAttribute('src');
displayedImage.setAttribute('src', src);
}
}

#14
Problem

Hello, I have a problem here. I don’t understand what didn’t work in my function that tries to change the source of displayedImage. My code:

var displayedImage = document.querySelector('.displayed-img');
var thumbBar = document.querySelector('.thumb-bar');

btn = document.querySelector('button');
var overlay = document.querySelector('.overlay');

for (var i = 0; i < 5; i++) {
  var newImage = document.createElement('img');
  var path = 'images/pic' + (i + 1).toString() + '.jpg';
  newImage.setAttribute('src', path);
  thumbBar.appendChild(newImage);
}

var image = document.getElementsByTagName('img');

function sourceAlter(e) {
	var source = e.target.getAttribute('src');
	displayedImage.setAttribute('src', source);
}

image.onclick = sourceAlter;

Furthermore, when I tried to change image.onclick = sourceAlter; to image.addEventListener('click', sourceAlter), the console says that image.addEventListener is not a function. Why is that so?

EDIT:
I’ve found out the problem to my code. Apparently, getElementsByTagName returns and array and therefore, I have to access the array items one by one. This also caused problems when I tried to pass image directly to addEventListener. I’ve fixed my code to the following, please grade me :slight_smile: :

var displayedImage = document.querySelector('.displayed-img');
var thumbBar = document.querySelector('.thumb-bar');

btn = document.querySelector('button');
var overlay = document.querySelector('.overlay');

for (var i = 0; i < 5; i++) {
  var newImage = document.createElement('img');
  var path = 'images/pic' + (i + 1).toString() + '.jpg';
  newImage.setAttribute('src', path);
  thumbBar.appendChild(newImage);
}

var image = document.getElementsByTagName('img');

function sourceAlter(e) {
	var source = e.target.getAttribute('src');
	displayedImage.setAttribute('src', source);
}

for (var i = 0; i < image.length; i++) {
	image[i].addEventListener('click', sourceAlter);
}

/* Wiring up the Darken/Lighten button */
btn.onclick = function() {
	if (btn.getAttribute('class') === 'dark') {
		btn.setAttribute('class', 'light');
		btn.textContent = 'Lighten';
		overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
	}

	else {
		btn.setAttribute('class', 'dark');
		btn.textContent = 'Darken';
		overlay.style.backgroundColor = 'rgba(0, 0, 0, 0)';
	}
}

(Chris Mills) #15

Hi @AvidLearner, congratulations on sorting your problem out! Pretty much all of the ways of return multiple element references will return an array that you need to loop through (whether it is getElementsByTagName, getElementsByName, or the more modern querySelectorAll).

I don’t tend to give you an exact grade, as I just don’t have time to mark all the assessment entries in detail. What I can do is test your code (it is a little different to mine, but it works fine, well done), and give you links to the marking guide and finished code so you can check it yourself:


#16

@chrismills I have some questions, though, if you don’t mind me asking.

  1. Here, my function sourceAlter only has the parameter e and therefore, I did not pass any parameters when I called the function, hence image[i].addEventListener('click', sourceAlter) without any parentheses and parameters. However, what if my function requires two parameters, e.g. sourceAlter(a, b), how do I pass on the parameters to said function?

  2. I don’t quite understand how looping works in JavaScript. It somehow does not make sense to me how for (...) in my code above worked. I do know that it loops five times (as expressed in the conditional statement i < image.length) and that whenever an image with the order i is clicked, we run a function. But, when we click picture 1, 2,3 and then 1 again, it somehow worked? There is not decrement in the for loop, so how did it happen? Is the loop run over again after each action (in this case, click (but doesn’t have to be necessarily a click))?


(Chris Mills) #17

So to answer the first question, this isn’t the simplest, but it is possible. This stack overflow answer is useful, and contains multiple ways of doing it: https://stackoverflow.com/questions/256754/how-to-pass-arguments-to-addeventlistener-listener-function

For the second question, the loop only runs once. But one of the main things the loop does is to add an onclick handler to each image, using this code:

newImage.onclick = function(e) {
    var imgSrc = e.target.getAttribute('src');
    displayImage(imgSrc);
}

because that handler is added to each image, it is run every time an image is clicked.


(Impresently) #18

I’m a little confused on how event objects work.

In my for loop the following code works:

for (i = 1; i <= 5; i++) {
    var newImage = document.createElement('img');
    newImage.setAttribute('src', "images\/pic" + i + ".jpg");
    thumbBar.appendChild(newImage);
    function displayThumb(clickedImage) {
        var imageThumb = clickedImage.target.getAttribute('src');
        displayedImage.setAttribute('src', imageThumb);
    };
    newImage.addEventListener('click', displayThumb);
}

While the following does not work:

for (i = 1; i <= 5; i++) {
    var newImage = document.createElement('img');
    newImage.setAttribute('src', "images\/pic" + i + ".jpg");
    thumbBar.appendChild(newImage);
    function displayThumb(clickedImage) {
        var imageThumb = newImage.getAttribute('src');
        displayedImage.setAttribute('src', imageThumb);
    };
    newImage.addEventListener('click', displayThumb);
}

In other words, what is the event object in the displayThumb function doing in the first example that allows it to be functioning on the entire loop, whereas in the second example the loop is completed, and the click events only function on the last thumbnail.


(Mittineague) #19

I know how you feel, I’m not new to JavaScript and I don’t understand it well enough to easily explain it, but it has to do with “closures” and “lexical scope”

I think the “Creating closures in loops: A common mistake” section near the end of this page does the best job at explaining that I’ve seen.


(Chris Mills) #20

Hi there @impresently!

This is a tricky aspect of JavaScript, and I don’t blame you for having trouble with it. Let’s try to clear things up.

When a function is run as a direct result of an event firing, due to an event listener being added to the object on which the event fires, the function implicitly has an event object available inside its lexical scope (i.e. inside the function, but not outside it). The reason we write e, or event, or in this case clickedImage as a parameter is to give a definition to this event object — i.e. a name we can refer to it as.

So in this case, each time the loop is run, the displayThumb() function is defined, and we add an event listener to the newImage created on each round of the loop that makes it so that displayThumb() is run when the image in question is clicked on.

By the way, it would arguably be more efficient and better practice to define the function only once, outside the loop, rather than each time it runs (see our version at https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/gallery/main.js), but yours still works OK.

So the event object passed into the function each time an image is clicked (clickedImage in your case) varies depending on which image was clicked. It contains information relevant to the image that was clicked.

Its target attribute contains a reference to the image itself — “the target of the event”. This is why when you do clickedImage.target, you can directly access the properties available on image. We can therefore use getAttribute('src') to access the contents of the image’s src attribute (the path to the image), etc.

Now, we also need to talk about the order in which things are run around loops. When you run a loop and it has a function inside it, the different loop iterations all run first, then the functions inside the loop iterations will run afterwards.

This is why the second code example DOESN’T work properly. By the time the loop iterations have all run, newImage's src value is equal to the path to the last image. The event listeners are all set up correctly, but the function will always set the main image displayed to that last image.

If you instead get the src from e.target.getAttribute(), the value will be relative to the image clicked on, not the value of newImage after the loops have all run, which is always equal to the last image.

Does that help? Happy to discuss some more if you are still not clear.


#21

Hi there, I have a question regarding my version:

var displayedImage = document.querySelector('.displayed-img');
var thumbBar = document.querySelector('.thumb-bar');

btn = document.querySelector('button');
var overlay = document.querySelector('.overlay');

for(var i = 1; i < 6; i++){
  var newImage = document.createElement('img');
  newImage.setAttribute('src', "images/pic" + i + ".jpg");
  thumbBar.appendChild(newImage);
};

thumbBar.addEventListener('click', function(e){
	var imgSrc = e.target.getAttribute('src');
	displayedImage.setAttribute('src', imgSrc);
});

So I added the eventListener to “thumbBar”, rather than to its child “newImage” as in finished example, and in this way avoided mentioning it in the loop altogether. It works as intended and I spotted no errors, but I would like to know if this version is consistent with the standards and can be used alongside with yours. Thanks! :slight_smile:


(Chris Mills) #22

Hey there!

Yes, this works well, due to event bubbling. I think I didn’t implement like that originally because I wanted to keep things more obvious, but I don’t think it actually makes a lot of difference.

Nice work.


(Askaroff5131) #23

Hello everyone,

I feel so bad about it but I gave up on this assignment and looked at the example code.

The reason I gave up is because I got so confused that I could not continue any more yet I knew I was going in the right direction. I knew that I had to and how to create the loop and use the even object but I just got stuck on one part.

I am going to explain my confusion with the “looping through the images and event handler” part only as I have not yet done the darkening effect part.

  1. I found the “Steps to complete” the most confusing. For example, in the first section of the “Adding an onclick handler to each thumbnail image”, in this part - “This can be done by running the getAttribute() function on the <img> in each case …” - I could not understand why the function would run on the <img> which is as I understand a different element in the html located in the full-img div when the newImage is the one we need with a location in the thumb-bar div. My question is why is the <img> mentioned here at all when all this section wants us to do is create the following code where <img> or full-img div is not even mentioned; newImage.onclick = function(e) {

var imgSrc = e.target.getAttribute(‘src’);

Please correct me if I am wrong about this.

  1. This part - “Run a function, passing it the returned src value as a parameter. You can call this function whatever you like.” - I would have probably figured out somehow if I had been a bit more patient and not gotten stuck with the section above.
  2. The same with the third section.

Since I was doing this all on my on my Iphone, I had to store all the images on an external server online which gave me links to them. So I created an array with the links and accessed it with the loop thus assigning them as attributes to newImage. However, it does not seem to work. Can you please explain why;

Here’s my code;

for ( i = 0; i = 4; i++ ) {

var newImage = document.createElement(‘img’);

var images = [“https://s33.postimg.cc/71rt7bggv/pic1.jpg”, “https://s33.postimg.cc/4kg202bzz/pic2.jpg”,

https://s33.postimg.cc/tqh06w5kf/pic3.jpg”,

https://s33.postimg.cc/xa2xwpnpr/pic4.jpg”,

https://s33.postimg.cc/5mq8ima8v/pic5.jpg”];

newImage.setAttribute(‘src’, images[i]);

thumbBar.appendChild(newImage);

newImage.onclick = function(e) {

var imgSrc = e.target.getAttribute(‘src’);

displayImage(imgSrc);

}

}

function displayImage(imgSrc) {

displayedImage.setAttribute(‘src’, imgSrc);

Another question that still boggles me is that it seems to be okay to call a function and define it later. Not only that but also call a function inside a scope closed by braces which is the loop and define it later outside of that scope. To my knowledge, this contradicts some of the lessons on JS here. I know Chris sent a link for an explanation earlier but since the content there is a lil advanced, I could not understand it. I would appreciate a simple explanation.

By the way, could anyone let me know how long it took them to do this whole assignment? A few hours or days? I was so impatient that I looked at the example after having re-read the steps to complete 3 or 4 times and still remaining confused about it. Is it best to just leave it for a while and get back to it with a fresh mind?

Special thanks to Chrismills and everyone trying to help others who have a hard time grasping the assignment.

Thank you!!!


(Chris Mills) #24

Hi there @askaroff5131! Sorry for not replying so long, but I have been on holiday.

So I will try to answer your questions one by one, and see if I can clear up this confusion.

  1. In each loop iteration, newImage is an <img> element we have created and then appended inside the thumb-bar div. Earlier on in the loop we create this image and set its src attribute to the value of one of the images we want to show as a thumb. In the event handler we attach to each image, we retrieve that very same src value using getAttribute(), and then pass it to the displayImage() function. This function sets the src of the displayedImage (the main big image) to equal this passed src value. So when you click a thumb, the main display image shows the same image.

  2. and 3. OK, fair enough. I think the above explanation should help.

iPhone — wow, you are doing this all on your iPhone? This is impressive!

Sample code — your code wasn’t working because first of all, you were creating an infinite loop by setting your exit condition to i = 4. If you change this to i < 5, you should get a better result. Also, your displayImage() function needs a closing bracket at the end of it — }. You could put your array outside the loop too — you only need to create this once. Apart from that it looks OK.

Why is it OK to call a function and then define it later? Because of execution order — when the browser interprets JS code, it will look at the functions first, make sure they are OK and able to run successfully, before then running other code that relies on those functions.

How long to complete an assignment? I’d say a few hours at most. I think that if you are struggling after a couple of hours, it is OK to go and look up the sample code, see how it works, then try to recreate it from memory. There is no point sitting there being frustrated for ages.


(Askaroff5131) #25

Hello Chris, thank you for your assistance! Much appreciated.

My code has finally worked. To be honest, this was not an easy task.

Especially, the creation of the function (DisplayImage) part was quite difficult to comprehend for me.

If you happen to have time to answer the following quick question, I would be grateful;

Why does the exit condition, i = 4, create an infinite loop?

Thank you in advance.

Have a great week!


(Renanmartineli) #26
/* Looping through images */

for (var i = 1; i <= 5; i++) {
  var newImage = document.createElement('img');
  newImage.setAttribute('src', "images/pic" + [i] + ".jpg");
  thumbBar.appendChild(newImage);

  
  function displayImage(e) {
    var getImage = e.target.getAttribute("src");
    displayedImage.setAttribute("src", getImage);
  }

  newImage.addEventListener('click', displayImage);
}

/* Wiring up the Darken/Lighten button */

function overlayBg() {
  getBtnAtt = btn.getAttribute("class");

  if (getBtnAtt === "dark") {
    btn.setAttribute("class", "light");
    btn.textContent = "Lighten";
    overlay.style.backgroundColor = "rgb(0, 0, 0, 0.5)";
  } else {
    btn.setAttribute("class", "dark");
    btn.textContent = "Darken";
    overlay.style.backgroundColor = "rgb(0, 0, 0, 0)";
  }
}

btn.addEventListener('click', overlayBg);

(Chris Mills) #27

The idea of an exit condition is that it defines a condition that is:

  1. true when the loop begins running
  2. false at some point later, at which point the loop stops running

So if it is set to i < 5 and i starts at 0, the loop will begin running because i is currently less than 5. When i gets to 5, the loop will stop running.

If it is set to i = 4 and i starts at 0, the loop will begin running but the condition will return false at the very beginning. This causes it to run infinitely, for reasons I’m not really very clear on. :wink:

It is a bit of a weird infinite loop situation. Normally they will keep running because the exit condition will always return true, as in these examples: http://www.scriptingmaster.com/javascript/infinite-loops.asp


(Saytec) #28

Hi,

Thanks for your great support. I have a question regarding the “displayedImage” element. surprisingly I’m not able to add an event listener or onclick event to that specific object. Could you please check and let me know what is wrong with my code?

for (var i = 0; i < 5; i++) {
  imgUrl = 'images/pic'+(i+1)+'.jpg';
  var newImage = document.createElement('img');
  newImage.setAttribute('src', imgUrl);
  thumbBar.appendChild(newImage);

  newImage.addEventListener ('click', function(e){
    console.log('image url is: '+e.target.getAttribute('src'));
    displayedImage.setAttribute('src', e.target.getAttribute('src'));
  } );

}

displayedImage.addEventListener ('click', console.log('hi'));

Thanks in advance.


(Saytec) #29

OK I got it. It is because the “overlay” div covers “newImage” (the big image’s div). Now how can I access a “div” beneath another one?


(Chris Mills) #30

Cool, glad you figured it out! Sorry for the slowness in replying, but I was at a workweek last week.

So if I understand your question correctly, you are asking how to interact with an element that is stacked below another one in the stacking order (i.e. as controlled by z-index?)

Well, there is not really a way to click on it directly. its “hit area” is effectively hidden. You could create a button on the page somewhere else that when pressed controls the element in question. Or you could program a keyboard shortcut that does something with it, or add it to the tab index of the page using the tabindex attribute.

Both of these techniques are explained in https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Building_in_keyboard_accessibility


(Saytec) #31

Thank you so much for your help. and no worries for the timing :wink: I really appreciate all the helps.