(function($) {

    function standardAsync(action, params, successCallback, errorCallback) {
        $.ajax({
            url: action, type: 'POST', dataType: 'text',
            data: params,
            success: function(data) {
                var message = null;
                try { message = $.parseJSON(data); } catch(e) { } // NOTE: ignoring parsing errors
                successCallback(message);
            },
            error: function(xhr) {
                var message = null;
                try { message = $.parseJSON(xhr.responseText); } catch(e) { } // NOTE: ignoring parsing errors

                if(errorCallback)
                    errorCallback(message);
                else
                    alert('Application error.');
            }
        });
    }

    // convert forms to use AJAX submit and error highlighting
    $.fn.enableForms = function(startCallback, endCallback, asyncHook) {
        var async = (asyncHook == null ? standardAsync : asyncHook);

        // make a unique name for the loading state property
        var loadingStateProperty = 'asyncFormLoading' + new Date().getTime() + '' + Math.random();

        this.delegate('form', 'submit', function() {
            var form = $(this);

            // TODO: this better
            if(form.attr('action').indexOf('javascript') == 0)
                return;

            if(!form.data(loadingStateProperty)) {

                var action = form.attr('action');

                // NOTE: this is the only reliable way to prevent default browser submit on double-click
                form.data(loadingStateProperty, true).attr('action', 'javascript:void(0)');

                // notify display code (e.g. throbber)
                startCallback.call(form.get(0));

                async(action, form.serialize(), function(success) {
                    form.data(loadingStateProperty, false).attr('action', action);
                    endCallback.call(form.get(0), success);
                }, function(error) {
                    if(error == null) {
                        alert('Application error.');
                        return;
                    }

                    form.data(loadingStateProperty, false).attr('action', action);
                    endCallback.call(form.get(0), null, error);
                });

            }

            // prevent default submit behaviour
            return false;
        });

        return this;
    }

})(jQuery)
