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