/** * @file Manages the multi-step navigation. */ jQuery(function ($) { // Settings that come from the CMS. var CONSTANTS = { FORM_ID: 'UserForm_Form', // $Form.FormName.JS ERROR_CONTAINER_ID: '', // $ErrorContainerID.JS ENABLE_LIVE_VALIDATION: false, // $EnableLiveValidation DISPLAY_ERROR_MESSAGES_AT_TOP: false, // $DisplayErrorMessagesAtTop HIDE_FIELD_LABELS: false, // $HideFieldLabels MESSAGES: {} // var meaasges }; // TODO // var messages = {<% loop $Fields %><% if $ErrorMessage && not $SetsOwnError %><% if $ClassName == 'EditableCheckboxGroupField' %> // '{$Name.JS}[]': '{$ErrorMessage.JS}'<% if not Last %>,<% end_if %><% else %> // '{$Name.JS}': '{$ErrorMessage.JS}'<% if not Last %>,<% end_if %><% end_if %><% end_if %><% end_loop %> // }; // Common functions that extend multiple classes. var commonMixin = { /** * @func show * @desc Show the form step. Looks after aria attributes too. */ show: function () { this.$el.attr('aria-hidden', false).show(); }, /** * @func hide * @desc Hide the form step. Looks after aria attributes too. */ hide: function () { this.$el.attr('aria-hidden', true).hide(); } }; /** * @func UserForm * @constructor * @param {object} element * @return {object} - The UserForm instance. * @desc The form */ function UserForm(element) { var self = this; this.$el = element instanceof jQuery ? element : $(element); this.steps = []; // Listen for events triggered my form steps. this.$el.on('userform.step.prev', function (e) { self.prevStep(); }); this.$el.on('userform.step.next', function (e) { self.nextStep(); }); // Listen for events triggered by the progress bar. $('#userform-progress').on('userform.progress.changestep', function (e, stepNumber) { self.jumpToStep(stepNumber - 1); }); this.$el.validate(this.validationOptions); return this; } /* * Default options for step validation. These get extended in main(). */ UserForm.prototype.validationOptions = { ignore: ':hidden', errorClass: 'required', errorElement: 'span', errorPlacement: function (error, element) { error.addClass('message'); if(element.is(':radio') || element.parents('.checkboxset').length > 0) { error.insertAfter(element.closest('ul')); } else { error.insertAfter(element); } }, 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'))]); } }, messages: CONSTANTS.MESSAGES, rules: { // TODO // <% loop $Fields %> // <% if $Validation %><% if ClassName == EditableCheckboxGroupField %> // '{$Name.JS}[]': {$ValidationJSON.RAW}, // <% else %> // '{$Name.JS}': {$ValidationJSON.RAW}, // <% end_if %><% end_if %> // <% end_loop %> } }; /** * @func UserForm.addStep * @param {object} step - An instance of FormStep. * @desc Adds a step to the UserForm. */ UserForm.prototype.addStep = function (step) { // Make sure we're dealing with a form step. if (!step instanceof FormStep) { return; } this.steps.push(step); }; /** * @func UserForm.setCurrentStep * @param {object} step - An instance of FormStep. * @desc Sets the step the user is currently on. */ UserForm.prototype.setCurrentStep = function (step) { // Make sure we're dealing with a form step. if (!step instanceof FormStep) { return; } this.currentStep = step; this.currentStep.show(); // Record the user has viewed the step. step.viewed = true; step.$el.addClass('viewed'); }; /** * @func UserForm.jumpToStep * @param {number} stepNumber * @desc Jumps to a specific form step. */ UserForm.prototype.jumpToStep = function (stepNumber) { var targetStep = this.steps[stepNumber]; // 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) { return; } this.currentStep.hide(); this.setCurrentStep(targetStep); this.$el.trigger('userform.form.changestep', [stepNumber]); }; /** * @func UserForm.nextStep * @desc Advances the form to the next step. */ UserForm.prototype.nextStep = function () { this.jumpToStep(this.steps.indexOf(this.currentStep) + 1); }; /** * @func UserForm.prevStep * @desc Goes back one step (not bound to browser history). */ UserForm.prototype.prevStep = function () { this.jumpToStep(this.steps.indexOf(this.currentStep) - 1); }; /** * @func ErrorContainer * @constructor * @param {object} element - The error container element. * @return {object} - The ErrorContainer instance. * @desc Creates an error container. Used to display step error messages at the top. */ function ErrorContainer(element) { this.$el = element instanceof jQuery ? element : $(element); // Set the error container's heading. this.$el.find('h4').text(ss.i18n._t('UserForms.ERROR_CONTAINER_HEADER', 'Please correct the following errors and try again:')); return this; } /** * @func hasErrors * @return boolean * @desc Checks if the error container has any error messages. */ ErrorContainer.prototype.hasErrors = function () { return this.$el.find('.error-list').children().length > 0; }; /** * @func removeErrorMessage * @desc Removes an error message from the error container. */ ErrorContainer.prototype.removeErrorMessage = function (fieldId) { this.$el.find('#' + fieldId + '-top-error').remove(); // If there are no more error then hide the container. if (!this.hasErrors()) { this.hide(); } }; /** * @func ErrorContainer.updateErrorMessage * @param {object} $input - The jQuery input object which contains the field to validate. * @param {object} message - The error message to display (html escaped). * @desc Update an error message (displayed at the top of the form). */ ErrorContainer.prototype.updateErrorMessage = function ($input, message) { var inputID = $input.attr('id'), anchor = '#' + inputID, elementID = inputID + '-top-error', messageElement = $('#' + elementID), describedBy = $input.attr('aria-describedby'); // The 'message' param will be an empty string if the field is valid. if (!message) { // Style issues as fixed if they already exist messageElement.addClass('fixed'); return; } messageElement.removeClass('fixed'); this.show(); if (messageElement.length === 1) { // Update the existing error message. messageElement.show().find('a').html(message); } else { // Generate better link to field $input.closest('.field[id]').each(function(){ anchor = '#' + $(this).attr('id'); }); // Add a new error message messageElement = $('