jQuery Gallery Lightbox

August 23, 2011

I wanted to do a gallery where images had tags, and you could turn on or off tags to filter the images shown. The working version of this can be found at http://baydonsmeadow.org.uk/gallery/.

This gallery would be done in Django, using jQuery Lightbox plugin for zooming images.

Django Model

Here's the code for the Django model for an image. This makes use of the ImageWithThumbsField provided by django-thumbs. See http://code.google.com/p/django-thumbs/ for more details.

class GalleryImage(models.Model):
    """
    A photo in the gallery
    """
    last_modified = models.DateTimeField(default=datetime.now,editable=False)
    created = models.DateTimeField(default=datetime.now,editable=False)
    date_taken = models.DateField(default=date.today)
    DATE_RESOLUTION_CHOICES = (
        ('D', 'Exact Date'),
        ('M', 'Month and Year'),
        ('Y', 'Year'),
    )
    date_resolution = models.CharField(max_length=1, default='D', choices=DATE_RESOLUTION_CHOICES)
    photo = ImageWithThumbsField(upload_to='gallery', sizes=((150,150),(800,600)))
    name = models.CharField(max_length=50, help_text="Image name")
    tags = models.CharField(max_length=200, help_text="Space separated words")

    def __unicode__(self):
        return "%s" % (self.name)

    @property
    def display_date(self):
        lFunctions = {
         'D': format(self.date_taken, "jS M Y"),
         'M': format(self.date_taken, "M Y"),
         'Y': format(self.date_taken, "Y"),
         }
        return lFunctions[self.date_resolution]

    def save(self):
        self.last_modified = datetime.now()
        super(GalleryImage, self).save()

    class Meta:
        ordering = ['-created']

Using:

photo = ImageWithThumbsField(upload_to='gallery', sizes=((150,150),(800,600)))

means that for each photo uploaded, a version is produced at 150x150, and another at 800x600. We'll use the 150x150 one to show a thumbnail, and the larger one in a zoomed lightbox.

Django View

In our views.py, we're going to pass a list of images to the page, along with a list of all the available tags. Tags are just space separated words, and there can be multiple tags for an image. We're just going to add the tags as classes on the image, and show or hide them using JavaScript.

def home(request):
    """
    Gallery home
    """
    lImages = GalleryImage.objects.all().order_by('-date_taken')

    # work out distinct list of tags
    lListOfAllTags = {}
    for image in lImages:
        lTags = image.tags.split(' ')
        for tag in lTags:
            try:
                lCount = lListOfAllTags[tag]
            except KeyError:
                lCount = 0
            lCount += 1
            lListOfAllTags[tag] = lCount
    return render_auth(request, 'gallery_home.html', {
                                                      "Images" : lImages,
                                                      "Tags" : lListOfAllTags,
                                                      })

Django HTML Template

In the HTML we're showing all the images by default, and then letting the user hide some of them by turning off certain tags.

This HTML is used to show the list of tags available:

<h3>Select or deselect tags</h3>
<ul>
{% for tagname, tagcount in Tags.items %}
  <li>
    <span class="taglink" id="tag_{{tagname}}">
      <span class="tagname">{{tagname}}</span>
      <span class="tagcount"> ({{tagcount}} image{{ tagcount|pluralize }})</span>
    </span>
  </li>
{% endfor %}
</ul>

The following HTML is used to show all the images:

<div id="photos">
<ul>
{% for image in Images %}
  <li>
    <a href="{{image.photo.url_800x600}}" title="{{image.display_date}} - {{image.name}}" class="visible">
      <img src="{{image.photo.url_150x150}}" class="{{image.tags}}"/>
    </a>
  </li>
{% endfor %}
</ul>
</div>

Note here that we're referring to the image by the sizes we defined in the models.py file. Also note that the title attribute is on the a tag, and that tag has class visible by default.

Having the title on the a tag rather than the image means that it is shown as a title on the lightbox later, as well as on a tooltip for each thumbnail.

JavaScript

All that remains is some JavaScript functionality. Our script first of all adds a lightbox to all visible links:

$('#photos a.visible').lightBox();

Next, we add a click handler to each tag on the page:

$('.taglink').click(function(){
    var lTagName = $(this).attr('id').substring("tag_".length);
    if ($(this).hasClass('tag-disabled')){
      // show pictures
      $("." + lTagName).parent().parent().show("slow");
      $("." + lTagName).parent().addClass('visible');
      $(this).removeClass('tag-disabled');
      $(this).parent('li').removeClass('tag-disabled');
  } else {
      // hide pictures
      $("." + lTagName).parent().parent().hide("slow");
      $("." + lTagName).parent().removeClass('visible');
      $(this).addClass('tag-disabled');
      $(this).parent('li').addClass('tag-disabled');
  }
  $('#photos a.visible').lightBox();
});

This code gets hold of the id for the tag that has been clicked, and strips off the tag_ prefix. It then looks to see if the tag is enabled or disabled by looking for the tag-disabled class on the tag. If it's not there is shows the appropriate pictures, or if it is there is hides them.

When we're showing photos, we're using the following four lines:

$("." + lTagName).parent().parent().show("slow");
$("." + lTagName).parent().addClass('visible');
$(this).removeClass('tag-disabled');
$(this).parent('li').removeClass('tag-disabled');

The first line shows the images, using animation so the image appears to grow. The .show() is done on the <li> tag - we navigate to the img parent which is the a, then to that's parent which is the li.

The second line marks the a tag as visible. This is used when we recreate the lightbox to reflect just the visible images (see below).

The third line removes the tag-disabled class from the photo tag link. This is used by the if statement to decide whether a tag is enabled or not.

The fourth line removes the tag-disabled class from the photo tag's li. This is used in the CSS to show a tick or cross against the tag, showing whether it is active or not:

#leftbar ul li {
    list-style-image: url('/site_media/images/yes_24.png')
}

#leftbar ul li.tag-disabled {
    list-style-image: url('/site_media/images/no_24.png')
}

Finally we re-run the lightbox script on the newly visible images. This ensures that the lightbox next and previous buttons work through only the visible images.

References