Helpful jQuery modals

This post is actually mostly about jQuery (with some cake sprinkles), but why not…
Let’s examine a typical situation:
We have a Product model, which belongsTo a Category model.
Now we need to add some products…
An admin interface would have a category select input (or drop-down) and a few form fields to fill-in some product details. Happily, we venture to add our first product, only to realize that we have not yet created any categories… backtracking to create a category would be a shame and with a little help from jQuery we can easily solve this problem.
One state that developers rarely consider during (and after) development, is the “blank” state of the application, which in turn can lead to some unexpected behavior…
The goal is to do the following:

  1. Get to the www.example.com/products/add page
  2. Using jQuery, “realize” that our Category drop-down is empty
  3. Prompt the user through… “lightwindow-modal-jquery-ui-dialog”… To create at least one category, so we could get moving
  4. Refresh the product/add page with the newly created category neatly in place within the select input

Since I use jQuery extensively, I am going to pick jQuery UI’s dialog widget to accomplish my needs. (It may not be the most robust or feature-rich, but it plays nice with the jQuery core… and does what I need).
(It goes without saying that you should include jQuery core and jQuery UI libraries… load them from Google, if you’d like).
Anyway…
First, let’s take a look at the products/add view:

<?php $this->Html->script('jquery.form.js', array('inline' => FALSE)); ?>
<?php $this->Html->script('parent_list.js', array('inline' => FALSE)); ?>
<?php $this->Html->css(array('smoothness/jquery'), NULL, array('inline' => FALSE)); ?>

<?php
echo $this->Form->create('Product');
echo $this->Form->input('category_id', array('class' => 'parent-list'));
echo $this->Form->input('name');
echo $this->Form->end('Add product');
?>

<?php
echo $this->Form->create('Category', array('action' => 'add'));
echo $this->Form->input('name');
echo $this->Form->end('Create category');
?>

Stepping through the code, the first thing we see is the jquery.form plugin. This will be used for submitting the “category add” form via AJAX, from the modal. (Will show that later on).
Next, we load parent_list.js, which is going to handle most of the magic.
Finally, in the view we have a div category-form (which will be turned by jQuery UI into a dialog/modal).
As you can see both forms are very simple, for purpose of this example.
Now, let’s see the jQuery file, which does all the heavy lifting:

(function ($) {
var getModal = function () {
$('#category-form').dialog({
modal: true,
autoOpen: false,
title: 'Create a category'
});

if($('.parent-list').children().size() === 0) {
$('#category-form').dialog('open');
}
};

var showResponse = function (response) {
if(response == 'saved') {
$('#category-form').dialog('close');
window.location.reload();
}
};

$(document).ready(function() {
getModal();

var submitOptions = {
success: showResponse,
dataType: 'json'
};

$('#CategoryAddForm').ajaxForm(submitOptions);
});
}(jQuery));

This is more JavaScript/jQuery than I usually show, so let’s take a look at some interesting things.
First, you’ll notice that the entire code is wrapped into a “self-calling anonymous function”. To keep things simple, the two benefits of using such pattern is to avoid conflicts and global vars (although it is not evident from this example, but a little googlin’ will explain things in more detail).
Next, notice the function declarations: var getModal = function () ... by using such assignment we avoid a couple of problems in everybody’s favorite IE (the above method will define the function at parse-time, rather than run-time). It also kind of “forces” the developer to declare and call things in proper sequence without hoping that the browser might pick up some of the slack, where developer was careless. At any rate, run your JavaScript through JSLint before going to production ;)
Now that the general stuff is out of the way, let’s see the “most important” line of code:
if($('.parent-list').children().size() === 0)
The above will check the category select input (i.e. parent of the product) to see if it has any options (options are children of the category select in this case). If not, the jQuery goodness kicks in and we get a modal/dialog which displays the category add form, as you saw from the products/add view above.
Once the name is entered and submitted via AJAX, I send back “saved” from the server as a response (assuming that all goes well). At which point we close the dialog and refresh the page. Now, we have our newly added category as one of the options… and can continue to add the product without backtracking to create a category in some other UI.
Hopefully this serves as a decent example on how to build a much more intelligent UI and improve user experience, with the help from the lovely jQuery and CakePHP.