(function($) {

   /* usage:

     jQuery.chain('#forms');

   */

   $.chain = function(containerId, settings) {
     console.log('jQuery.chain: setup');
     settings = $.extend({
       common: '.common',
       spinner: 'http://kaytwo.googlecode.com/svn/trunk/images/spinner.gif',
       spinnerClass: 'spinner',
       requiredClass: 'required',
       errorClass: 'error'
     }, settings);
     var container = jQuery(containerId);
     var forms = container.find('form');
     var first = forms.eq(0);
     var commonFields = first.find(settings.common).find(':input');
     var commonData = null;
     var spinner = jQuery('<img class="' + settings.spinnerClass + '" src="' + settings.spinner + '" />');
     forms.hide();
     first.show();
     $.each(forms, function(i) {
       var form = $(this);
       var submit = form.find('input[type=submit]').eq(0);
       form.bind('submit', function(e) {
         errorFields = [];
         form.find(':input.' + settings.requiredClass).each(function(i) {
           var field = $(this);
           console.debug(field.attr('name') + ': ' + field.val());
           field.parent().removeClass(settings.errorClass);
           if (field.attr('type') == 'checkbox' && !field.is(':checked')) {
             field.parent().addClass(settings.errorClass);
             errorFields.push(' ' + field.attr('name') + ' checkbox');
           } else if (field.val() == null || field.val().match(/^\s*$/)) {
             var label = field.prevAll('label').eq(0);
             field.parent().addClass(settings.errorClass);
             errorFields.push(' ' + label.text().replace(/[:\*]/g, '').replace(/\s*$/, ''));
           }
         });
         if (errorFields.length > 0) {
           e.preventDefault();
           alert('The following field(s) are required:' + errorFields);
           return false;
         }
         var data = '';
         if (i == 0) {
           console.log('first form');
           if (commonFields.length > 0) {
             commonData = commonFields.fieldSerialize();
             console.log('got common data: ' + commonData);
           }
         } else {
           var common = form.find(settings.common);
           common.remove();
           if (commonData) {
             data += commonData + '&';            
           }
         }
         console.log('submitting: ' + form.attr('action'));
         submit.attr('disabled', 'true');
         spinner.insertAfter(submit);
         data += form.formSerialize();
         console.log('with data: ' + data);
         if (i < (forms.length - 1)) {
           /* ajax submit */
           $.ajax({
             url: form.attr('action'),
             type: form.attr('method'),
             data: data,
             dataType: null,
             error: function(msg) {
               console.log('error: ' + msg);
             },
             complete: function (XMLHttpRequest, textStatus) {
               /* post complete */
               console.debug('chain ajax complete')
               form.slideUp();
               var nextForm = forms.eq(i + 1);
               nextForm.slideDown(function() {
                 console.log('end slidedown: focusing first input');
                 nextForm.find(':input').eq(0).focus();
               });
             }
           });
         } else {
           /* last form */
           commonFields.css('visibility', 'hidden').clone().appendTo(form);
           return true;
         }
         e.preventDefault();
         return false;
       });
     });
   };

 })(jQuery);