$.fn.fastSerialize = function() {
    // http://dev.jquery.com/wiki/Plugins/FastSerialize
    var a = [];
    $('input,textarea,select,button', this).each(function() {
        var n = this.name;
        var t = this.type;
        if ( !n || this.disabled || t == 'reset' ||
            (t == 'checkbox' || t == 'radio') && !this.checked ||
            (t == 'submit' || t == 'image' || t == 'button') && this.form.clicked != this ||
            this.tagName.toLowerCase() == 'select' && this.selectedIndex == -1)
            return;
        if (t == 'image' && this.form.clicked_x)
            return a.push({name: n+'_x', value: this.form.clicked_x},
                          {name: n+'_y', value: this.form.clicked_y}
        );
        if (t == 'select-multiple') {
            $('option:selected', this).each( function() {
                a.push({name: n, value: this.value});
            });
            return;
        }
        a.push({name: n, value: this.value});
    });
    return a;
};

function inputSetEmptyText(jq_input, default_text, default_text_style) {
    // focus event empties field
    jq_input.focus(function() {
        if (default_text == jq_input.val()) {
            jq_input.val('');
            if (default_text_style) jq_input.removeAttr('style');
        }
    });
    // blur event sets the default properties
    jq_input.blur(function() {
        if ((!jq_input.val()) || default_text == jq_input.val()) {
            jq_input.val(default_text);
            if (default_text_style) jq_input.attr('style', default_text_style);
        }
    });
    // form.submit event empties the field
    $(jq_input.get(0).form).submit(function() {
        jq_input.blur();
        if (default_text == jq_input.val()) jq_input.val('');
    });
    // set the defaults
    jq_input.blur();
}


var initAjaxFormValidator = function(form, url_validate, showError, hideError) {
    // Reusable validator to notify user of errors in a form using
    // ajax to fetch the errors.
    // form: Reference to form object
    // url_validate: the URL to post the form data to, and retrieve the errors.
    //               The errors should come JSON encoded in the following
    //               format: {'fieldname': 'error string', 'otherfield': 'etc'}
    // showError: a callback to show an error on the form. function(field, error)
    // hideError: a callback to reset a field.

    var req = null;
    var pending = null;
    var fields = jQuery.map(form.elements, function(f) { return f; });
    
    var showValidationErrors = function(errors) {
        jQuery.map(fields, function(f) {
            if (!f.has_focused) return;
            var error = errors[f.name];
            if (error) showError(f, error);
            else hideError(f);
        });
    }
    
    var ajaxValidateForm = function(e) {
        e.target.has_focused = true;
        if (req) {
            pending = function() {
                ajaxValidateForm(e);
            }
            return;
        }
        var postdata = {};
        jQuery.map(fields, function(f) {
            postdata[f.name] = f.value;
        });
        req = jQuery.ajax({
            url: url_validate,
            type: 'POST',
            success: function(errors) {
                showValidationErrors(errors);
                window.setTimeout(function() {
                    req = null;
                    if (pending) {
                        pending();
                        pending = false;
                    }
                }, 2000);
            },
            data: postdata,
            dataType: 'json'
        });
    }
    
    jQuery.map(fields, function(f) {
        f.has_focused = false;
        $(f).blur(ajaxValidateForm);
    });
    
    $(form).submit(function() {
        if (req) {
            req.abort();
        }
        ajaxValidateForm = function(e) { false; }
    });
}