diff --git a/javascript/UserForm.js b/javascript/UserForm.js index f4bc055..26bb719 100644 --- a/javascript/UserForm.js +++ b/javascript/UserForm.js @@ -3,14 +3,11 @@ */ jQuery(function ($) { - var $userform = $('.userform'); + // A reference to the UserForm instance. + var userform = null; // Settings that come from the CMS. - var CONSTANTS = { - ENABLE_LIVE_VALIDATION: $userform.data('livevalidation') !== void 0, - DISPLAY_ERROR_MESSAGES_AT_TOP: $userform.data('toperrors') !== void 0, - HIDE_FIELD_LABELS: $userform.data('hidefieldlabels') !== void 0 - }; + var CONSTANTS = {}; // Common functions that extend multiple classes. var commonMixin = { @@ -43,6 +40,9 @@ jQuery(function ($) { this.$el = element instanceof jQuery ? element : $(element); this.steps = []; + // Add an error container which displays a list of invalid steps on form submission. + this.errorContainer = new ErrorContainer(this.$el.children('.error-container')); + // Listen for events triggered my form steps. this.$el.on('userform.step.prev', function (e) { self.prevStep(); @@ -56,6 +56,11 @@ jQuery(function ($) { self.jumpToStep(stepNumber - 1); }); + // When a field becomes valid, remove errors from the error container. + this.$el.on('userform.form.valid', function (e, fieldId) { + self.errorContainer.removeStepLink(fieldId); + }); + this.$el.validate(this.validationOptions); return this; @@ -77,15 +82,37 @@ jQuery(function ($) { error.insertAfter(element); } }, + // Callback for handling the actual submit when the form is valid. + // Submission in the jQuery.validate sence is handled at step level. + // So when the final step is submitted we have to also check all previous steps are valid. + submitHandler: function (form, e) { + var isValid = true; + + // Validate the final step. + userform.steps[userform.steps.length - 1].valid = $(form).valid(); + + // Check for invalid previous steps. + $.each(userform.steps, function (i, step) { + if (!step.valid) { + isValid = false; + userform.errorContainer.addStepLink(step); + } + }); + + if (isValid) { + form.submit(); + } else { + userform.errorContainer.show(); + } + }, + // When a field becomes valid. success: function (error) { var errorId = $(error).attr('id'); error.remove(); - if (CONSTANTS.DISPLAY_ERROR_MESSAGES_AT_TOP) { - // Pass the field's ID with the event. - $userform.trigger('userform.form.valid', [errorId.substr(0, errorId.indexOf('-error'))]); - } + // Pass the field's ID with the event. + userform.$el.trigger('userform.form.valid', [errorId.substr(0, errorId.indexOf('-error'))]); } }; @@ -100,6 +127,8 @@ jQuery(function ($) { return; } + step.id = this.steps.length; + this.steps.push(step); }; @@ -128,17 +157,25 @@ jQuery(function ($) { * @desc Jumps to a specific form step. */ UserForm.prototype.jumpToStep = function (stepNumber) { - var targetStep = this.steps[stepNumber]; + var targetStep = this.steps[stepNumber], + isValid = false; // Make sure the target step exists. if (targetStep === void 0) { return; } - // Validate the current section. - // Users can navigate to step's they've already viewed - // even if the current step is invalid. - if (!this.$el.valid() && targetStep.viewed === false) { + // Validate the form. + // This well effectivly validate the current step and not the entire form. + // This is because hidden fields are excluded from validation, and all fields + // on all other steps, are currently hidden. + isValid = this.$el.valid(); + + // Set the 'valid' property on the current step. + this.steps[stepNumber - 1 >= 0 ? stepNumber - 1 : 0].valid = isValid; + + // Users can navigate to step's they've already viewed even if the current step is invalid. + if (isValid === false && targetStep.viewed === false) { return; } @@ -202,6 +239,49 @@ jQuery(function ($) { } }; + /** + * @func addStepLink + * @param {object} step - FormStep instance. + * @desc Adds a link to a form step as an error message. + */ + ErrorContainer.prototype.addStepLink = function (step) { + var self = this, + itemID = step.$el.attr('id') + '-error-link', + $itemElement = this.$el.find('#' + itemID), + stepID = step.$el.attr('id'), + stepTitle = step.$el.data('title'); + + // If the item already exists we don't need to do anything. + if ($itemElement.length) { + return; + } + + $itemElement = $('