Monday, March 5, 2012

Using jQuery custom events to organize code better

One problem when creating complex user interfaces comes when you have lots of plugins interacting asynchronously. For example, you might have a modal popup that ajaxes an image carousel in. And maybe you use the same modal code to play videos (probably using HTML5 with Flash fallback.) Or maybe you're using collapsible side panels and cool eye-catching widgets like Isotope side-by-side.

The naive way to structure code like that would typically go along these lines:

//let's suppose we're loading a carousel
$("#carousel").load("/carousel/", function() {
  
  //but we have an Isotope panel
  //on the page and we need to hide it
  hideIsotopePanel()

  //and we also need to hide modal popups
  killModals()

  //and make sure videos/audio
  //aren't playing in the background
  killVideos()

  //get the idea?
})

The problem, of course, is that hiding the Isotope panel, killing videos, modal popups and God knows what else are not part of the carousel logic. It just so happens that when including this carousel on the page, you need to de-clutter your page so that your user isn't overwhelmed.

The headache starts when you have to worry about the permutations of what needs to hide when: now there's a carousel on the page. When is it supposed to hide? Where do I put the code for that? What else exists in the page that needs to hide when the carousel loads?

Ideally, we'd like to organize the code for doing all of this showing and hiding in their own modules: Isotope hiding code goes where the rest of the Isotope code is, modal hiding code goes where the rest of modal code is, and so on. But how can we do that?

jQuery custom events

One way is to use jQuery custom events. We could rewrite the carousel loading function above like this:

$("#carousel").load("/carousel/", function() {

  //fire a custom event we just made up
  $(document).trigger("carousel-loaded")

})

This is basically the same thing the browser does behind the scenes when you click on an element, and an event handler magically fires. The difference here is that you can define when the event gets fired, and what it's called.

Now we can go into the Isotope module and define there when it's appropriate to hide the component:

//in the file where we're doing all
//the Isotope heavy-lifting

$(document).on("carousel-loaded", function() {

  hideIsotopePanel()

})

What's the advantage of refactoring code around like that?

The main advantage is that now it's easier to manage orthogonality (i.e. hiding something on one corner case, but forgetting to on another):

$("#isotope").load("/isotope/", function() {

  $(document).trigger("isotope-loaded")

})


$(document).on("carousel-loaded modal-loaded", function() {

  hideIsotopePanel()

})

The snippet above is just an extension of the refactoring idea above. It allows me to fire Isotope-specific events, and ensures the Isotope panel hides when modal popups load (in addition to hiding when the carousel loads). By refactoring all my code to use custom event like this, I can set the groundwork to make other permutations also easy to maintain (e.g. I would be able to go into the code dealing with modals, and, without plowing through thousands of lines of code, I could easily make the modal hide on video load, on carousel load, and so on and so on.)

No comments:

Post a Comment