Subscribe
Recent Tweets
About Me

I've worked as a consultant and software developer in the financial industry for over 10 years. I've worked for three of the top five Canadian banks in both retail banking and capital markets. I'm currently working with a small consulting firm in downtown Toronto.

« Multiplayer tic-tac-toe in Java using the WebSocket API, Netty (NIO), and jQuery | Main | Connection pooling with Hibernate 3.3.x and C3P0 »
Tuesday
Dec282010

Fixing Fancybox with AJAX (and Wicket)

A personal project I'm working on uses the jQuery Fancybox library to display images. It's a great lightbox, except for one small problem: AJAX refreshes break it.

A page I'm working on accepts multiple image uploads. Once the images are successfully processed asynchronously on the server, an image panel on the same page is automatically refreshed. The AJAX callback works fine and updates the panel view with the newly uploaded photos, but the net result is that Fancybox stops working completely.

The solution is pretty simple: Fancybox needs to be re-initialized after the view is refreshed. This involves invoking a JavaScript function after the AJAX callback completes. Fortunately, I'm using Wicket, which makes invoking JavaScript after the callback a breeze.

Here's the (condensed version of) code for my behavior class that handles the repainting, callback, and post-callback JS invocation:

        final IBehavior repaintBehavior = new AbstractDefaultAjaxBehavior() {
@Override
protected void respond(AjaxRequestTarget target)
{
ThingModel thingModel = new ThingModel(thingId);
UnprocessedPhotosModel photosModel = new UnprocessedPhotosModel(getCurrentUser().getId(), tokenId);
List photos = photosModel.getObject();

for (Photo photo : photos)
{
photo.setThing(thingModel.getObject());
ServiceFactory.getPhotoService().savePhoto(photo);
}

target.addComponent(photosPanel);
target.appendJavascript("$.fancyboxResetBusy(); reinitFancybox();");
}

@Override
public void renderHead(IHeaderResponse response)
{
super.renderHead(response);
CharSequence callback = getCallbackScript();
response.renderJavascript("function uploadCompleted() { " + callback + "}", "customUploadCompleted");
}
};

For those who aren't familiar with Wicket, the renderHead method is rendering a custom JavaScript function in the view. The multiple upload Flash component I use invokes the rendered JS function after it finishes processing the images. When the Flash uploader invokes uploadCompleted, Wicket handles all the AJAX plumbing and executes the Java code in the respond method above.

The two most important lines of code are the calls to target. target.addComponent instructs Wicket to refresh the photo panel, and target.appendJavascript instructs Wicket to invoke the functions in the String parameter after the panel is refreshed.

Let's take a look at the reinitFancybox JS function which gets invoked after the panel is refreshed.

function reinitFancybox() {	
// CSS
$(".gallery img, portfolio.list img, .portfolio.grid-2 div img, a.fancy, ul.screens img").css("opacity", "1");

// ON MOUSE OVER
$(".gallery img, .portfolio-list img, .portfolio.grid-2 div img, a.fancy, ul.screens img").hover(function () {

// SET OPACITY TO 100%
$(this).stop().animate({
opacity: 0.5
}, "fast");
},

// ON MOUSE OUT
function () {

// SET OPACITY BACK TO 100%
$(this).stop().animate({
opacity: 1
}, "fast");
});

// INIT FANCYBOX
$("li.image a, a.fancy, .portfolio.grid li a.folio-zoom, .portfolio-list.image a").fancybox({
'titlePosition' : 'over'
});
}

Most of the code above re-initializes the CSS hover effects I use to pretty up my image panel. The last piece is responsible for re-initializing Fancybox itself. At this point, we should be done, Fancybox should work perfectly again. But there's a small catch, the Fancybox script includes a global variable named busy which is set to true at the beginning of almost every single function. If for any reason the script is interrupted before busy is set back to false, re-initialization will always abort. Here's a snippet of the Fancybox code below:

$.fancybox = function(obj) {
if (busy) {
return;
}

busy = true;

...

It's pretty obvious that if busy is true Fancybox won't initialize. We need to make sure busy is set to false before attempting to re-initialize. Unfortunately, this requires a small kludge addition to the Fancybox script itself. I've added the following public function:

$.fancyboxResetBusy = function() {
busy = false;
}

Invoking this before the initialize function ensures the initialization doesn't return prematurely.

This fix worked for me, let me know if you get any mileage out of it (or if a better solution exists).

Reader Comments (2)

I'd enjoy your help with a very similar issue... I have a row of images on page load. The user can delete an image from the row, but even though I use jquery's remove() (which is supposed to remove the image from the DOM), it still shows in the fancybox overlay. In short, I need to re-initialize the fancybox after the jquery remove() function is called. Help? Thanks!

March 13, 2011 | Unregistered CommenterMark

Hello, a basic question regarding Wicket & Fancybox

fancybox requires that the link containing the thumbnail points to the path of the big picture.

In my case path/to/bigpicture is generated by wicket since the images are loaded dynamically (from database, the thumbnails are displayed in a GridView).

How do you get the absolute path (the generated one) of the big picture when constructing the page ? Do you need to mount it as the repeater gets populated ?
Thank you for your help.

September 19, 2011 | Unregistered CommenterPierre

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>