info343/labs/5/writeup.php

<section id="introduction">
   <h3>Introduction</h3>
   
   <figure class="example">
      <a href="output/lab5_lightbox.png">
         <img src="output/lab5_lightbox.png" alt="Screenshot of the lightbox" />
         <figcaption>The lightbox when an image has been clicked.</figcaption>
      </a>
   </figure>
   
   <p>This lab offers more practice with event handling and jQuery. We’ll build a Facebook-style “lightbox” — an image gallery which magnifies an image when clicked, and allows the user to navigate between enlarged images.</p>
</section>
      
<section>
   <?= t_head('Download Skeleton Files', 5) ?>

   <p>Download the following skeleton files:</p>

   <p class="resource"><a href="lightbox.html">lightbox.html</a></p>
   <p class="resource"><a href="lightbox.js">lightbox.js</a></p>

   <p>The HTML skeleton links to a provided CSS file (which you do not need to download or edit), the jQuery library, and to your <samp>lightbox.js</samp> file.</p>

   <p>In the body of the HTML is a div called <code>gallery</code> into which you’ll inject a series of images. When clicked, these images will launch the “lightbox” interface to view an enlarged version of the image.</p>

   <p>The lightbox itself is another div in the HTML file, called <code>lightbox</code>. It is initially hidden by our CSS code; we’ll make it be shown when an image is clicked.</p>

   <p>Also in the <code>lightbox</code> is a div called <code>container</code>. This is a 600px × 600px area in the middle of the lightbox where the enlarged image will live, and which contains two navigation links.</p>

   <p>You will be dynamically changing the <code>href</code> attributes of these navigation links and attaching <code>.click()</code> handlers to them so that they navigate between photos in the gallery.</p>

   <p>The JavaScript skeleton contains a list of photos. These are stored as an array of objects; the array is called <code>IMAGES</code>. Each object in the array has a <code>.file</code> and <code>.caption</code> property.</p>

   <p>If you want to access the <var>i</var>th filename and caption, you’d write the following:</p>

<pre><code>var i = 5;
alert("The image at index " + i + "'s filename is '" + IMAGES[i].file + ".\\n\\n" +
      "Its caption is: '" + IMAGES[i].caption + "'.");</code></pre>

   <p>You will use this list of filenames and captions to create the HTML <code>img</code> tags, and to navigate through the gallery in order.</p>

   <p>The JavaScript file contains an <code>alert</code> at the top. Ensure this gets executed, then delete it from the file.</p>
</section>

<section>
   <?= t_head('Inject Images', '10') ?>

   <figure class="example">
      <a href="output/lab5_gallery.png">
         <img src="output/lab5_gallery.png" alt="After the images are injected into the gallery" />
         <figcaption>Your page should look like this after the images are injected into the gallery.</figcaption>
      </a>
   </figure>

   <p>First, write the necessary JavaScript code to create a new <code>img</code> tag for each file listed in <code>IMAGES</code>.</p>

   <p>Recall that the jQuery <a href="../../lectures/special-effects/#slide2"><code>$.each()</code> function</a> allows you to iterate over a standard JavaScript array or object. Use this to loop through the images.</p>

   <p>Each time your callback function is executed, you should create a new <code>img</code> tag (using <a href="../../lectures/more-events-creating-elements/#slide15">jQuery’s <code>$('&lt;img&gt;')</code> syntax</a>) and inject it into the <code>#gallery</code> div using <code>.appendTo()</code>.</p>
</section>

<section>
   <?= t_head('Launch Lightbox When Clicked', '10–15') ?>

   <p>Now we want to make it so that when we click an image, the lightbox appears with that photo enlarged.</p>

   <p>Your code that created the <code>img</code> tags injected them directly into <code>#gallery</code>. Begin by modifying that to also create a hyperlink (an <code>a</code> tag), inject the <code>img</code> into that, and then inject the link into the <code>#gallery</code>.</p>

   <p>The link’s <code>href</code> attribute should link to the image’s filename, just like the <code>img</code>’s <code>src</code> attribute.</p>

   <p>Then give each link a <code>.click()</code> handler. Set this to call a function — say, <code>enlarge</code>, which will cause the lightbox to appear.</p>

   <p>In your <code>enlarge</code> function, we’ll somehow need to know which image to display. One easy approach is to inspect the <code>href</code> attribute of the link that was clicked, and display that image in the lightbox.</p>

   <ul>
      <li>
         <p>Begin by making your <code>enlarge</code> function accept <a href="../../lectures/more-events-creating-elements/#slide7">an <code>event</code> parameter</a>, and call <code>event.preventDefault()</code> to prevent the browser from visiting the link’s URL when clicked.</p>
         <p class="important note">If your browser visits the image when clicked, you haven’t done this properly. It should do nothing at all!</p>
      </li>
      <li>
         <p>Now make your <code>enlarge</code> function alert the <code>href</code> attribute of the link clicked.</p>
      
         <p class="note">Recall that inside an event handler function, the <a href="../../lectures/more-events-creating-elements/#slide2"><code>this</code> keyword</a> refers to the element the event occurred on. Or you can make your function accept <a href="../../lectures/more-events-creating-elements/#slide7">an <code>event</code> parameter</a>, and use its <code>.target</code> property.</p>
      </li>
      <li>
         <p>Then create a new <code>img</code> tag with the same <code>src</code> attribute as the <code>href</code> of the link clicked.</p>
         <p>Inject this new photo into the <code>#container</code> div.</p>
         <p>Call <code>.show()</code> on the <code>#lightbox</code> to display it.</p>
      </li>
   </ul>
</section>

<section>
   <?= t_head('Set Image Caption', '10') ?>

   <p>So far we’ve been able to create and inject an image into the lightbox, but it’s missing something: it doesn’t have an <code>alt</code> attribute. How do we get its alt text?</p>

   <p>We could loop through the list of <code>IMAGES</code> checking each one’s <code>.file</code> against the <code>href</code> of the link. When we find the one that matches, we take its <code>.caption</code> and add that to the image. But that looping is inefficient.</p>

   <p>Instead, we’re going to modify our <code>.click()</code> event handler so that it <strong>passes the index of that image</strong> to our <code>enlarge</code> function. Then our enlarge function will look up that image’s <code>.file</code> and <code>.caption</code> from <code>IMAGES</code> directly.</p>

   <p>In our <code>$.each()</code> callback function we’re passed an index at each iteration:</p>

<pre><code>$.each(IMAGES, function(<strong>index</strong>, image) {
   ...
});</code></pre>

   <p>That index is useful to our <code>.click()</code> handler function, and it turns out there’s a way we can pass it on:</p>

<pre><code>$.each(IMAGES, function(<strong>index</strong>, image) {
   ...
   var \$newlink = $('&lt;a&gt;').attr('href', image.file);
   \$newlink.click(function(<strong>event</strong>) {
      enlarge(<strong>event</strong>, <strong>index</strong>);
   });
});</code></pre>

   <p>Here we’ve created a go-between which passes the index along to <code>enlarge</code>, along with the <code>event</code> object it needs to stop the browser from visiting the link.</p>

   <p>Make these changes to your code, and modify your <code>enlarge</code> function to use the <code>index</code> parameter it’s given to get the <code>.caption</code> of the appropriate image from <code>IMAGES</code>. Use that as the <code>img</code>’s <code>alt</code> attribute.</p>

   <p>Since we’re now getting the caption from the global <code>IMAGES</code> array, you might as well get the filename from there as well. Change your code to get the <code>.file</code> from there, instead of using the clicked link’s <code>href</code> attribute.</p>
</section>

<section>
   <?= t_head('Set Navigation Links', '10–15') ?>

   <figure class="example" style="margin-top: 4em">
      <img src="output/lab5_navigation_link.png" alt="The rightward navigation link" />
      <figcaption>The rightward navigation link.</figcaption>
   </figure>

   <p>Currently the left and right navigation links don’t do anything. (When clicked, they’ll basically refresh the page.)</p>

   <p>Now that we know the index of the current image in the <code>IMAGES</code> array, we can easily access the next and previous images in the list just by looking at the next or previous index. This will enable us to populate the previous/next links such that they navigate through the gallery.</p>

   <p><ins datetime="Thu, Oct 25, 11:30 PM">Every time we display an image in the lightbox — both when we <code>enlarge</code> the initial image, and when we navigate to another image — we’ll need to set the next and previous navigation links.</p>

   <ol>
      <li><ins>Let’s split the link-changing functionality out to a separate function — called, for example, <code>setNavigation</code> — and call it from our <code>enlarge</code> function. Make your enlarge function pass the current index to <code>setNavigation</code>, and alert it there.</ins></li>
      <li>
         <p><ins>Then, in your <code>setNavigation</code> function, attach <code>.click()</code> handlers to the <code>#prev</code> and <code>#next</code> links. Since we want both links to cause a new image to be loaded, let’s make these click handlers both call a function <code>loadImage</code>.</ins></p>
         <p><ins>Test that your <code>loadImage</code> function is being called correctly by alerting something in it. Then be sure to capture the <code>event</code> object and call <code>event.preventDefault()</code> so that the browser doesn’t visit the link.</ins></p>
      </li>
      <li>Now, add the same <strong>anonymous intermediary function</strong> to your link <code>.click()</code> handler. Make it accept the <code>event</code> object, and pass that and the index to your <code>loadImage</code> function. Make <code>loadImage</code> now alert the index it’s given.</li>
      <li>Now make the click on <code>#prev</code> pass the index of <em>the previous image</em>, and the click on <code>#next</code> pass the index of <em>the next image</em>. Make sure that your index “wraps around” from the first image to the end of the list, and vice versa.</li>
      <li>Now, set the <code>href</code> attributes of the next/previous links to be the image file at that index in <code>IMAGES</code>, so that if we were to disable <code>event.preventDefault()</code>, the browser would navigate to that image.</li>
      <li><ins datetime="Wed, Oct 25, 8:30 PM">Finally, there’s one detail we need to clean up: if we call just call <code>.click()</code> on <code>#prev</code>/<code>#next</code> every time it’s clicked, we’re <em>adding</em> an event handler every time, not overwriting the previous one. Add a call to <code>.unbind()</code> on the prev/next links to remove the previous click handler before attching the new one.</ins></li>
   </ol>
</section>

<section>
   <?= t_head('Load Previous/Next Image', '5–10') ?>

   <p>Now modify your <code>loadImage</code> function to alter the <code>src</code> and <code>alt</code> attributes of the <code>img</code> inside <code>#container</code> to be the correct values. As before, get them from the <code>IMAGES</code> array using the index passed.</p>
</section>

<section>
   <?= t_head('Click to Dismiss', '10') ?>

   <p>Currently there’s no way to get out of the lightbox. We want to make the lightbox disappear when ever the user clicks outside the image. (Basically, anywhere on the fuchsia, except for the small portion of links which overlap it.)</p>

   <p>Begin by attaching a <code>.click()</code> handler to the lightbox that will <code>.hide()</code> it, and see what happens. What if you click on the image, or on a link?</p>

   <p>The problem with this handler is that it will cause the lightbox to disappear if you click on the lightbox <em>or any of its children</em> — so clicking on the image itself, or even on the links, cuases the lightbox to disappear.</p>

   <p>This is a quirk of event handling: our event handler function is getting called even when the div itself is not the direct target. This is why the browser gives us <code>event.target</code>: so we can see what element was actually the target.</p>

   <p>To ensure we make the lightbox disappear only when the div itself is clicked (and not its children), we want to be sure <code>event.target</code> is the <code>lightbox</code> div itself. If it isn’t, we don’t want to dismiss the lightbox.</p>

   <p>Remember that <code>event.target</code> is a <strong>standard DOM object</strong>. You can use <code>==</code> to compare it to another DOM object — <strong>but NOT to a jQuery object</strong>!</p>

   <p>Probably the easiest way to get the <code>lightbox</code> div is to use our old standby: <code>document.getElementById()</code>. (Another way is to get the 0th DOM object out of a jQuery object: <code>$('#lightbox')[0]</code>.)</p>
</section>

<section>
   <?= t_head('Extra Features (optional)', '15–20') ?>

   <p>Here are a couple of suggestions for extra ways to improve our lightbox:</p>

   <ul>
      <li>
         <p>Attach a <code>.keyup()</code> event handler to the body tag <code>$(document.body)</code> to listen for keypresses. Allow the user to use the left/right arrow keys for navigation, and dismiss the lightbox with the Escape key.</p>
         <p>You’ll need to inspect the <code>event.which</code> property to figure out which key was pressed. We suggest beginning by outputting this value to <code>console.log</code> and then pressing keys to see which values to check for.</p>
      </li>
      <li>
         <p>Add fancy special effects. Make the lightbox <code>.fadeIn()</code> and <code>.fadeOut()</code>, and make images fade to transition between them.</p>
         <p>To make images fade-transition, you’ll actually need to have two images in the <code>#container</code> at once — one in front of the other — so you’ll have to modify your code to inject a second <code>img</code> and remove the first after the effect is finished, rather than simply modifying the existing image.</p>
         <p>You can also explore moving the image slide left/right in addition to fading in/out, for a more animated effect.</p>
      </li>
      <li>Explore using the <code>$(window).scroll()</code> handler to prevent the user from scrolling the page when the lightbox is visible.</li>
   </ul>
</section>

<footer>
   <p>If you’ve finished everything, good job! Make some more improvements to the lightbox, such as adding a caption beneath it and allowing keyboard navigation outside the lightbox.</p>
</footer>