lib/public/javascripts/edifice/ajax_form.js in edifice-0.0.5 vs lib/public/javascripts/edifice/ajax_form.js in edifice-0.5.0

- old
+ new

@@ -1,187 +1,127 @@ -/* - Turn a standard form into an 'ajax' form. +(function($) { +var defaults = { + status_element: this, // element to set to .submitting/.success/.error as we submit + // a hash of input selector -> validation functions/names + validators: {}, + // type of data to pass around (and thus what's seen by e.g. success) + dataType: 'html' +}; + +// EVENTS that fire during the course of the life of the submission: +// submit, invalid, success | error (+ user_error | server_error), complete +// +// -> success,errors + complete are all passed the data you would expect from +// jQuery.ajax + +$.edifice_widgets.ajax_form = function() { return this.each(ajax_form); } - -- Relies on the action that the form points to to return a 422 with a new, - 'errored' form if there is a (server-side) validation problem. - Otherwise calls the various callbacks.. +function ajax_form() { + var $form = $(this).edifice_form(); + $form.settings = $form.read_options(defaults); + $.extend($form, methods); + + $form.initialize(); +} +var methods = { + initialize: function() { + this.prepare_validators(); + this.prepare_submit(); + }, + + prepare_validators: function() { + var $form = this; - Extra arguments: - - status_sel: the selector of an element to set the classes 'submitting/success/error' - on depending on the state of the form. Defaults to the form itself. - - validators: a hash of input selectors to validation functions (or names) - for client-side validation. -*/ - -(function($){ - $.edifice_widgets.ajax_form = function() { - return this.each(function(){ - var settings = $(this).read_options({ - status_sel: this, // element to set to .submitting/.success/.error as we submit - //default callbacks - error: function(x, t, e, form){ - if (x.status >= 400 && x.status < 500) { // a CLIENT ERROR - //validation failed replace form with new one - var $new_form = $(x.responseText); - $(form).replaceWith($new_form); - // -- not sure this is the best way to pass extra settings through, but it seems to work. - $new_form.create_widget('ajax_form', settings); - } else if (x.status >= 500 && x.status < 600) { // a SERVER ERROR - // replace the body proxy with the result (i.e replace the entire page) - $('#body_proxy').before($(x.responseText)).remove(); - } else { - alert('Form failed. Please try again.'); - } - }, - success: function(d, s, form){}, - complete: function(x, t, form){}, - submit: function(e){}, // return false to cancel submit - validators: {} - }); + // setup validators from settings + for (var selector in $form.settings.validators) { + $form.set_validator($(selector), $form.settings.validators[selector]); + } + + // setup validators from html + $form.fields().filter('[data-widget-validator]').each(function() { + $form.set_validator($(this), $(this).attr('data-widget-validator')); + }); + + // listen to validator + this.fields().live('change.ajax_form focusout.ajax_form', function() { + $form.validate($(this)); + }); + }, + + prepare_submit: function() { + var $form = this; + this.submit(function(event) { + event.preventDefault(); - // prepare the validators that are references - for (var selector in settings.validators) { - var validator = settings.validators[selector]; - if (typeof(validator) === 'string') { - validator = settings.validators[selector] = $.edifice_widgets.ajax_form.validators[validator]; - } - $(selector).bind('change.ajax_form', {validator: validator}, function(event) { - validate($(this), event.data.validator); - }); + // do pre-submit validations + if (!$form.valid()) { + $form.trigger('invalid'); + $form.error_fields().eq(0).focus(); // focus the first error + return false; // we are done. } + $form.submits().attr('disabled', true); // disable submit buttons - function set_status_class(name) { - $.each(['form_submitting', 'form_success', 'form_error'], function(i, old_name) { - $(settings.status_sel).removeClass(old_name); - }); - $(settings.status_sel).addClass(name); - }; + // TODO - set status class - $(this).bind("submit.ajax_form", function(ev){ - var form = this; - - ev.preventDefault(); - - // do client-side validations - var valid = true, $first_error; - for (var selector in settings.validators) { - if (!validate($(selector), settings.validators[selector])) { - if (valid) { valid = false; $first_error = $(selector); } - } - } - if (!valid) { $first_error.focus(); return false; } - - // fire a custom event allowing anyone to cancel the submission - var event = $.Event('submitting.ajax_form'); - event.cancel = false; - event.submitEvent = ev; - $(form).trigger(event); - - if (event.cancel) - return false; + // send up the form and process the results + $.ajax({ + url: $form.attr('action'), type: $form.attr('method'), + dataType: $form.settings.dataType, + data: $.param($form.serializeArray()), + cache: false, + error: function (x, t, e) { $form.error(x, t, e); }, + success: function (data, status) { + $form.trigger('success', data, status); + }, + complete: function (request, text_status) { + $form.trigger('complete', request, text_status); - // also run the user defined submit function - if (settings.submit(ev) === false) return false; - - // disable the first submit button on the form - $(form).find('input[type=submit]:first').attr('disabled', true); - - // set the status - set_status_class('form_submitting'); - - // send up the form and process the results - $.ajax({ - cache: false, - data: $.param($(form).serializeArray()), - type: form.method, - url: form.action, - error: function (x, t, e) { - settings.error(x, t, e, form); - set_status_class('form_error'); - }, - success: function (d, s) { - settings.success(d, s, form); - set_status_class('form_success'); - }, - complete: function (x, t) { - // enable the first submit button on the form - $(form).find('input[type=submit]:first').attr('disabled', false); - - settings.complete(x, t, form); - } - }); - - return false; + $form.submits().removeAttr('disabled'); + } }); - }); - }; - - // call this on a div that represents a form element. - // sets the right classes and enables disables actual form elements - $.fn.form_element_state = function(command) { - switch (command) { - case 'disabled': - this.addClass('disabled') - .find('input, select').attr('disabled', true); - break; - case 'enabled': - this.removeClass('disabled') - .find('input, select').removeAttr('disabled'); - break; - } - return this; - }; - - function clearError(id) { - // run over the input and the label pointing to it - $('.fieldWithErrors').find('> #' + id + ', > label[for=' + id + ']').unwrap(); - $('label[for=' + id + ']').next('.formError').remove(); - }; - - function addError(id, error) { - // right now this is causing focus issues and we don't need it, so I won't waste time - // fixing it.... but it could be used for different form layouts... - // $('#' + id + ', label[for=' + id + ']').wrap('<div class="fieldWithErrors"></div>'); - $('label[for=' + id + ']').after('<div class="formError">' + error + '</div>'); - }; - - // calls a validator, and takes care of setting the errors string - function validate($input, validator) { - var id = $input.attr('id'); - // clear existing validations - clearError(id); - - var error = validator($input); - if (error === true) { - return true; - } else { - addError(id, error); + return false; - } - } + }); + }, - // validators expect a form element and return true or an error message - $.edifice_widgets.ajax_form.validators = { - not_empty: function($input) { - return $input.val() ? true : 'must not be empty'; - } - }; - - // standardize .focus() for input tags. IE doesn't place the focus at the - // end of the input box, this fixes it - $.fn.inputFocus = function() { - return this.each(function(){ - if ($.browser.msie) { - if (this.createTextRange) { - var FieldRange = this.createTextRange(); - FieldRange.moveStart('character', this.value.length); - FieldRange.collapse(); - FieldRange.select(); - } + error: function(request, text_status, error) { + this.trigger('error', request, status, error); + + // handle the different possible errors that we can see + if (request.status >= 400 && request.status < 500) { + // CLIENT ERROR -- server-side validation failed. + this.trigger('client_error', request, status, error); + + // if data is html, we replace this content of the form with the content + // of the form that we've just received back + if (this.settings.dataType === 'html') { + // wrap in a div incase there are a bunch of floating elements, pull the form out + var $new_form = $('<div>').append(request.responseText).find('#' + this.attr('id')); + + this.html($new_form.html()); + this.prepare_validators(); + + } else if (this.settings.dataType === 'json') { + // we will be receiving an error object back, we can pass it straight into form.js + this.set_errors($.parseJSON(request.responseText)); } else { - this.focus(); + throw "Don't know how to handle dataType " + this.settings.dataType; } - }); - }; -}(jQuery)); - + } else if (x.status >= 500 && x.status < 600) { + // a SERVER ERROR -- something unrecoverable happened on the server + this.trigger('server_error', request, status, error); + + // we aren't going to do anything here. + // FIXME: we should probably have a way to set this behaviour at the application level. + // for instance, you probably just want to redirect to somewhere, or show a dialog, + // or popup something or.....? + } else { + // some other kind of error. Revisit + alert('Form failed. Please try again.'); + } + + + }, +} + +}(jQuery)); \ No newline at end of file