Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

Gabriel Ward
Gabriel Ward
20,222 Points

Adding a next button to simple lightbox.

I have a simple lightbox that appears when an image in my image gallery is clicked on. The lightbox has the image appended to it as well as a button. THe lightbox also disappears when it is clicked on. I want to create the programming in jQuery necessary to make it that when the button is clicked on, the next image in the gallery is appended to the lightbox and the current one is detached. ANy help is greatly appreciated. Here's my code:

<div class="grid-container imageGallery">
        <div class="grid-3">
            <a href="img/item-01.png">
                <div class='image'>
                    <img class="feat-img" src="img/item-01.png" alt='Image by Bob'/>
                    <div class='text'></div>
                </div>
            </a>
            <!-- <p class='caption'>Caption</p> -->
        </div>
        <div class="grid-3">
            <a href="img/item-02.png"><div class='image'>
                <img class="feat-img" src="img/item-02.png" alt='Image by Jim'/>
                    <div class='text'></div>
            </div></a>
            <p class='caption'>Caption</p>
        </div>
        <div class="grid-3">
            <a href="img/item-03.png"><div class='image'>
                <img class="feat-img" src="img/item-03.png" alt='Image by Johnny'/>
                    <div class='text'></div>
            </div></a>
            <p class='caption'>Caption</p>
        </div>
        <div class="grid-3">
            <a href="img/item-04.png"><div class='image'>
                <img class="feat-img" src="img/item-04.png" alt='Image by Dave'/>
                    <div class='text'></div>
            </div></a>
            <p class='caption'>Caption</p>
        </div>
    </div>
$(document).ready(function(){
//Problem: When user clicks on an image they are taken to a dead end.
//Solution: Create an overlay with the image centered on it - Lightbox

    var $lightbox = $('<div class ="lightbox"></div>');
    var $lightboxImage = $('<img>');
    var $nextLightboxImage = $('<img>');
    var $button = $('<div id="nav-icon1"><span></span><span></span><span></span></div>');
    var $caption = $('<p></p>');


    //Add an image to overlay
    $lightbox.append($lightboxImage);
    //Add a caption to the overlay
    $lightbox.append($caption);
    //Add a button to the lightbox
    $lightbox.append($button);
    //Add overlay
    $('body').append($lightbox);
    //Capture the click event on a link to an image
    $('.imageGallery a').click(function(event){
        event.preventDefault();
        var imageLocation = $(this).attr('href');
        // Update the overlay with the image that is clicked,
        $lightboxImage.attr('src', imageLocation);
        // Show the overlay.
        $lightbox.show();



        //1.3 Get the image alt attribute and set caption.
        var captionText = $(this).find('img').attr('alt');
        $caption.text(captionText);
    });
    //Append next image in gallery and detach current one when button is clicked on
    $button.click(function(){
        var nextImageLocation = $(this).next.attr('href');
        $nextLightboxImage.attr('src', nextImageLocation);
        $lightbox.detach($lightboxImage);
        $lightbox.append($nextLightboxImage);
    });
    //When the overlay is clicked
        //Hide the overlay.
    $lightbox.click(function(){
        $lightbox.hide();
    });
});

3 Answers

Okay, Gabriel, I've been able to further examine the code and figure out what is going wrong and how to fix it. It's another list:

1) There was quite a bit wrong with your code in the button click handler. First off, if you have to look up how a jQuery function works, do it! I still do it all the time, because it is impossible to memorize every single detail of every single function. With that being said, your nextImageLocation variable cannot work in this context. You have to think about how elements are being placed on the page relative to other elements, because in that line you are trying to grab the next anchor link. However, the next() function only grabs the next sibling element (in other words, the next element that is not nested beside it). That lightbox has no sibling anchor elements, so it has nothing to grab. It's going to be undefined.

2) It is a bad idea to use a totally different object for the next lightbox image. When you detach the original lightbox image and append another one, you are going to break your structure, and it looks wonky. Plus, it's completely unnecessary. You'll see that I only changed the src attribute of the current lightbox image to the next one.

3) You also have a problem that I'm ashamed to admit took me several minutes to realize which is that every time you click the lightbox, it would disappear. I kept clicking my next button and hoping that it would go to the next image, and it kept disappearing. The problem is that your next button (and your future previous button) resides within the lightbox itself, and you have an event handler set up for the entire lightbox that hides it when clicked. This is a very bad idea. What that means is that you definitely need to reduce the size of the lightbox and then check for a click outside of the lightbox when the lightbox is visible and then hide the lightbox.

4) I added an extra class called "main" to each top level div that houses the images in the image gallery. You only gave me a snippet of code, but I assume since each one of those top level divs has a numbered grid (grid-3) that there are other numbered grids, and I use a piece of code that targets those top level divs.

Read over the comments in the JavaScript.

Here is the updated HTML:

<div class="grid-container imageGallery">
        <div class="grid-3 main">
            <a href="img/item-01.png">
                <div class='image'>
                    <img class="feat-img" src="img/item-01.png" alt='Image by Bob'/>
                    <div class='text'></div>
                </div>
            </a>
            <p class='caption'>Caption</p>
        </div>
        <div class="grid-3 main">
            <a href="img/item-02.png">
            <div class='image'>
                <img class="feat-img" src="img/item-02.png" alt='Image by Jim'/>
                    <div class='text'></div>
            </div></a>
            <p class='caption'>Caption</p>
        </div>
        <div class="grid-3 main">
            <a href="img/item-03.png"><div class='image'>
                <img class="feat-img" src="img/item-03.png" alt='Image by Johnny'/>
                    <div class='text'></div>
            </div></a>
            <p class='caption'>Caption</p>
        </div>
        <div class="grid-3 main">
            <a href="img/item-04.png"><div class='image'>
                <img class="feat-img" src="img/item-04.png" alt='Image by Dave'/>
                    <div class='text'></div>
            </div></a>
            <p class='caption'>Caption</p>
        </div>
    </div>

And the updated JavaScript:

    //Problem: When user clicks on an image they are taken to a dead end.
    //Solution: Create an overlay with the image centered on it - Lightbox

        var $lightbox = $('<div class ="lightbox"></div>');
        var $lightboxImage = $('<img>');
        var $button = $('<button>Next</button>');
        var $caption = $('<p></p>');
        //This hold the href for images  
        var curImgSrc;
        //This array will hold all of the references for img sources
        var imgRef = [];
        //This will iterate over every div with a class of main
        $('div.main').each(function(i, el) {
        //Store the value of each anchor's href into the img ref array
        imgRef[i] = $(el).children('a').attr('href');
        });


        //Add an image to overlay
        $lightbox.append($lightboxImage);
        //Add a caption to the overlay
        $lightbox.append($caption);
        //Add a button to the lightbox
        $lightbox.append($button);
        //Add overlay
        $('body').append($lightbox);
        //Capture the click event on a link to an image
        $('.imageGallery a').click(function(event){
            event.preventDefault();
            //DO NOT PUT VAR KEYWORD IN FRONT OF THIS curImgSrc VARIABLE.
            //This variable is in the global context so that it is cached and can be accessed
            //by other functions
            //This contains the source for the current image
            curImgSrc = $(this).attr('href');
            // Update the overlay with the image that is clicked,
            $lightboxImage.attr('src', curImgSrc);
            // Show the overlay.
            $lightbox.show();
            //1.3 Get the image alt attribute and set caption.
            var captionText = $(this).find('img').attr('alt');
            $caption.text(captionText);
        });
        //Append next image in gallery and detach current one when button is clicked on
        $button.click(function(){            
            //Find out where href exists in array
            var loc = imgRef.indexOf(curImgSrc);
            //Increase location counter
            loc += 1;
            //If location is now past the indices of the array
            if (loc === imgRef.length) {
                //Restart from first image
                loc = 0;
            }
            //Change the current image source variable
            curImgSrc = imgRef[loc];
            //And change the src for the current image to this variable
            $lightboxImage.attr('src', curImgSrc);
        });
        //When the overlay is clicked
            //Hide the overlay.
    //    $lightbox.click(function(){
    //        $lightbox.hide();
    //    });

This still doesn't change the captions, but I will help you with that later. For now, I've got to enjoy my Easter! Happy Easter!

Gabriel Ward
Gabriel Ward
20,222 Points

Wow thank you so much Marcus. That worked well, and your explanation is great.

Can I ask you how long you've been learning for? And what things have you done to get to where you're at? Any tips for how I can improve would be greatly appreciated.

Thanks for your time,

Gabe

I've been learning for several years now. It was more off and on learning until about 2 years ago when I really started diving into web development.

The very best way for me to learn personally was through a lot of trial and error and knowing how to utilize certain tools. The Mozilla Developer Network is an awesome tool for reference to plain JavaScript, as it provides excellent, up to date documentation, as well as examples. jQuery has their own API site that has references to all available jQuery commands. If you ever have any shred of doubt about whether you are using any function right, look it up. Think of that function in reference to other functions when you're chaining them like I do in the code below.

If you use Chrome or Firefox, you have access to a very powerful web console. I used to (and still do, in fact) use the console.log() function to log out absolutely random things to the console so that I can ensure they're doing what I want. For example, in this code, I put a console.log statement right after doing quite a few things like this:

//This will iterate over every div with a class of main
$('div.main').each(function(i, el) {
        //Store the value of each anchor's href into the img ref array
        imgRef[i] = $(el).children('a').attr('href');
});
console.log(imgRef);

That console.log will output the imgRef array so that I could verify that each anchor element's href value went into that array. Just about every time I set a variable for the first time, I do a console.log of its contents on the next line, just to be sure I'm doing what I need to be doing. It's how I learned what variables are going to become objects, arrays, etc. When you console.log a lot of things, you start to see patterns in the way that certain things are initialized, and you can predict what a variable might be or what a chain of functions will do.

I also have to recommend CodeAcademy's free JavaScript course for a great intro to JavaScript. It will really help.

Gabriel Ward
Gabriel Ward
20,222 Points

Thanks for that detailed explanation. I took the CodeAcademy course about 4 months ago, and yes it was great. I understand many of the basics when I follow along with tutorials, it's building things for myself where I start to get lost, because there's so much going on and it's tough to know where to begin.

There is one thing I'm still trying to get my head around and that's the way parameters are defined and used. For example in your code above, where does the e1 come from, and what exactly does it mean in this context?

$('div.main').each(function(i, el) {
        //Store the value of each anchor's href into the img ref array
        imgRef[i] = $(el).children('a').attr('href');
        });

Do you try and code everyday, and are you a professional in anyway at this stuff now?

Cheers.

I'm still trying to get myself into the field, and I'm hoping to land a job sometime this year in the industry. I've built some pretty big projects so far which have helped my understanding greatly: SketchMeh - Paint-like App, Custom Calculator, Weather App, To-Do List, and Flickr Search.

Those two variables in the function call can be named anything you'd like, it is only their order which is important. In the jQuery each function, it defines that you can optionally set "(index,element)" in the function call. Here is the article on each on that jQuery API site: https://api.jquery.com/each/. That is where I learned that I could utilize those variables to my advantage.

Gabriel Ward
Gabriel Ward
20,222 Points

Cool I hope you manage to get into the field this year.

I'm familiar with the documentation but sometimes don't understand it. Here I'm struggling to understand which element e1 refers to or means. What element is it referring to?

Ah, "el" is a variable that contains the current element in the iteration of the each loop. The "each" function is a loop that goes through a given set of elements. In this case, it is looping through each "div.main" (each div with a class of "main"). So, each time it finds a div with a class of "main", the element is being referenced in the "el" variable. And then I had to convert it into a jQuery object by wrapping it with $(). Does that make more sense?

Gabriel Ward
Gabriel Ward
20,222 Points

Ok so it could be called anything? For example:

$('div.main').each(function(i, bobJim) {
        //Store the value of each anchor's href into the img ref array
        imgRef[i] = $(bobJim).children('a').attr('href');
        });

It can be called almost anything although bobJim is perfectly acceptable! :D I have to say almost anything because it has to be a valid variable name that isn't a protected keyword such as top, id, etc. but bobJim is plenty unique and will definitely work haha

Love that, btw! That's pretty awesome haha

Gabriel Ward
Gabriel Ward
20,222 Points

Ok cool that will help my understanding here I think. I've been learning for about 5 months. I'm at that level where I've got the basics down of JavaScript/jQuery, but going to that next level is difficult. I think it comes down to defining the problem and what steps can be taken. Interpreting the documentation is difficult at times, and examples like the one we just talked through can be confusing because it seems so arbitrary.

I've actually added a previous button. It cycles backwards fine, but it stops when it gets to the first image. It doesn't go cycle to the last image again and work backwards from there. Thoughts?

here's my code:

 //Problem: When user clicks on an image they are taken to a dead end.
    //Solution: Create an overlay with the image centered on it - Lightbox

        var $lightbox = $('<div class ="lightbox"></div>');
        var $lightboxImage = $('<img>');
        var $prevButton = $('<button>Previous</button>');
        var $button = $('<button>Next</button>');
        var $caption = $('<p></p>');
        //This hold the href for images  
        var curImgSrc;
        //This array will hold all of the references for img sources
        var imgRef = [];
        //This will iterate over every div with a class of main
        $('div.main').each(function(i, el) {
        //Store the value of each anchor's href into the img ref array
        imgRef[i] = $(el).children('a').attr('href');
        });


        //Add an image to overlay
        $lightbox.append($lightboxImage);
        //Add a caption to the overlay
        $lightbox.append($caption);
        //Add a button to the lightbox
        $lightbox.append($prevButton);
        $lightbox.append($button);
        //Add overlay
        $('body').append($lightbox);
        //Capture the click event on a link to an image
        $('.imageGallery a').click(function(event){
            event.preventDefault();
            //DO NOT PUT VAR KEYWORD IN FRONT OF THIS curImgSrc VARIABLE.
            //This variable is in the global context so that it is cached and can be accessed
            //by other functions
            //This contains the source for the current image
            curImgSrc = $(this).attr('href');
            // Update the overlay with the image that is clicked,
            $lightboxImage.attr('src', curImgSrc);
            // Show the overlay.
            $lightbox.show();
            //1.3 Get the image alt attribute and set caption.
            var captionText = $(this).find('img').attr('alt');
            $caption.text(captionText);
        });
        //Append next image in gallery and detach current one when button is clicked on
        $button.click(function(){            
            //Find out where href exists in array
            var loc = imgRef.indexOf(curImgSrc);
            //Increase location counter
            loc += 1;
            //If location is now past the indices of the array
            if (loc === imgRef.length) {
                //Restart from first image
                loc = 0;
            }
            //Change the current image source variable
            curImgSrc = imgRef[loc];
            //And change the src for the current image to this variable
            $lightboxImage.attr('src', curImgSrc);
        });
        $prevButton.click(function(){            
            //Find out where href exists in array
            var loc = imgRef.indexOf(curImgSrc);
            //Increase location counter
            loc -= 1;
            //If location is now past the indices of the array
            if (loc === imgRef.length) {
                //Restart from first image
                loc = 0;
            }
            //Change the current image source variable
            curImgSrc = imgRef[loc];
            //And change the src for the current image to this variable
            $lightboxImage.attr('src', curImgSrc);
        });

Well, you have to look at the logic presented here and how these variables work. You are correct in the first two lines of the event handler. You should be getting the location the exact same way, and you want to subtract 1 from the current location. The problem is within your if statement. That statement is for the next button because of the way the location works in the next button. The location is being increased by 1 each time. When you click that button and you are on the very last image, the location will increase to be 1 past the last index in the array.

To break this down: imgRef when the init is completed (and the code you provided) will have 4 strings in it, containing references to images. That means that the indices in imgRef are: 0, 1, 2, 3. So, when you're on the last image and you click next, location gets +1, becoming 4. 4 is also the length of the array. This math is done by the program, so that it doesn't matter how many items are in the array, it will always execute the same, making it modular and dynamic.

So, with that in mind, I want you to come up with the answer. What should this if-statement be: "if (loc === ???)" If you get too frustrated, I'll help out again.

Oh, and I forgot to mention, that loc should be changed within the if-statement to something dynamic as well. Again, if you get frustrated and give up, come back here, and I'll help you out, Gabe.

Gabriel Ward
Gabriel Ward
20,222 Points

Yip I was onto that enough to recognise that loc would need to be changed.

My first thought is that it should be

$prevButton.click(function(){            
            //Find out where href exists in array
            var loc = imgRef.indexOf(curImgSrc);
            //Increase location counter
            loc -= 1;
            //If location is now past the indices of the array
            if (loc === imgRef[0]) {
                //Restart from first image
                loc = imgRef.length;
            }
            //Change the current image source variable
            curImgSrc = imgRef[loc];
            //And change the src for the current image to this variable
            $lightboxImage.attr('src', curImgSrc);
        });

That's not working though.

Gabriel Ward
Gabriel Ward
20,222 Points

Yip I was onto that enough to recognise that loc would need to be changed.

My first thought is that it should be

$prevButton.click(function(){            
            //Find out where href exists in array
            var loc = imgRef.indexOf(curImgSrc);
            //Increase location counter
            loc -= 1;
            //If location is now past the indices of the array
            if (loc === imgRef[0]) {
                //Restart from first image
                loc = imgRef.length;
            }
            //Change the current image source variable
            curImgSrc = imgRef[loc];
            //And change the src for the current image to this variable
            $lightboxImage.attr('src', curImgSrc);
        });

That's not working though.

Hi Marcus, thanks for your reply - it was extremely helpful for creating the nav buttons!

However, I haven't quite figured out how to reduce the size of the lightbox so that it'll close only when I click outside of the image. Currently, everything is appended to an "overlay" div with the following properties:

#overlay {
    background: rgba(0,0,0,0.7);
    width: 100%;
    height: 100%;
    position: fixed;
    overflow: hidden;
    top: 0;
    left: 0;
    display: none;
    text-align: center;
}

Clicking anywhere would immediately close the lightbox. I want the lightbox to close only when I click outside the image, on the dark background. Any thoughts?

Hey Gabriel,

I'll go ahead and dive into this on this thread so that we aren't bogged down with extra posts not related to the material at hand.

1) I would immediately change the creation to actual button elements instead of whatever that is supposed to be up there. If you want it to be a button, use button elements!

2) You have to put some content in between the tags! Even if you decided that somehow a div would make more sense as a button than a button element, you need to have actual content in your tags as I mentioned before. Since the text only needs to be something static like "Previous" and "Next", you need to just initialize the text within the creation in jQuery like so:

var $prevButton = $("<button id='prevButton'>Previous</button>");
var $nextButton = $("<button id='nextButton'>Next</button>");

3) Once you've initialized those, append each button in order. If you want the previous button on the right, then the caption, and then the next button, you only need to append them in that order. Another advantage to using an actual button element over a div element is that buttons are inline elements so you won't have to go into the CSS to try to line them up with the caption.

4) Your click event handlers should be specific to each button:

$prevButton.click(function ()  {
//your code here
});
$nextButton.click(function () {
//your code here
});

This will get you a major start in the right direction.

Gabriel Ward
Gabriel Ward
20,222 Points

Ok thank Marcus. I got my pseudo element button to appear. At the moment it's a Hamburger icon. I'll have a go at trying that first and if it doesn't work I'll use your button method. Any suggestions as to where I'm going wrong with my $button.click code?

I'm going to take a further look at this tomorrow, but I have some ideas as to why it's not working that I'll share tomorrow along with any successful code.

Gabriel Ward
Gabriel Ward
20,222 Points

Ok cheers Marcus, look forward to hearing from you. Thanks for your help.

I'm going to post a new answer because that answer is becoming convoluted with messages lol

Keep in mind, that in the next button's if statement, it's actually comparing two integers, and it should be the same in the previous button's if statement. "loc.length" is an integer value but a dynamic one that can fluidly change with your array. imgRef[0] however is the first href string so they can never be equal, although I do think I see where you were trying to go with that, albeit in a misguided sense.

Again, look at the logic of what's happening. The current indices for the array are 0, 1, 2, 3. When loc is 0 (the beginning image), it gets a 1 subtracted from it. It then becomes -1 but -1 isn't a part of the array index.

So, we need to check when it's -1. If it is -1, we then need to reset it. Since we are going to be on the first image, it needs to be reset to the last image in the array. But, we don't want a static number such as "3" because that limits how many items we can place in the array.

You need a math statement that will become 3 dynamically based upon the number of indices in the array. The easiest way to do this is to set the location equal to the length of the array minus 1.

//If location is now past the indices of the array
if (loc === -1) {
  //Restart from last image
  loc = imgRef.length-1;
}
Gabriel Ward
Gabriel Ward
20,222 Points

Ok I follow that logic. But then I'm wondering how come in the next button we set loc to be equal to the array length, not one past it?

You learnt all this by teaching yourself? I'm beginning to question if I'm good/smart enough to learn JavaScript. Perhaps I need to find something else.

EDIT: I think I clicked on that actually. array.length is in fact one greater than loc, because the length is 4, but array indicie is 0,1,2,3

Oh no, it's not being set to be equal to the length of the array, it's being compared to see if it has gotten to that point. Keep in mind that a single = is setting something to be equal to something else. A double or triple equals sign is a comparison operator, that just returns whether the thing on the left is equal to the thing on the right.

Don't doubt yourself over some problems. I've been doing this for a while, and I sat here for about an hour this morning mulling over your next button and my embarrassment over not realizing that I was clicking the lightbox the entire time and hiding the thing! LOL!

I mean, here's the thing: it's much, much harder to try to understand someone else's code. Keep at it and play with the code! I'm telling you that if you spend enough time playing around with code, it'll snap in your mind, and you'll say, "Holy s**t! He was right! I can totally do this!" :D

Gabriel Ward
Gabriel Ward
20,222 Points

Thanks Marcus. Self-doubt is a biggie on occasion. Learning by doing is the way to go. And no need to be embarrassed.

I'm glad I could make you laugh with bobJim haha.

That is absolutely right, ol' bobJim! :D I did get a good laugh out of that haha Well, if you run into any more problems, make yourself another post and if you can't get any good, solid advice, tag me on here with the @ symbol because I'm frequently bored at work! lol!