From 3217695713d6cba419d7f40ae125027468447102 Mon Sep 17 00:00:00 2001 From: David Craig Date: Mon, 10 Aug 2015 13:40:32 +1200 Subject: [PATCH] port features - hide field labels - date picker --- javascript/UserForm.js | 177 +++++++++++++++++++++++++++++++++-------- templates/UserForm.ss | 13 ++- 2 files changed, 156 insertions(+), 34 deletions(-) diff --git a/javascript/UserForm.js b/javascript/UserForm.js index 28d88aa..76f3532 100644 --- a/javascript/UserForm.js +++ b/javascript/UserForm.js @@ -5,18 +5,43 @@ jQuery(function ($) { // Settings that come from the CMS. var CONSTANTS = { - ERROR_CONTAINER_ID: '', - ENABLE_LIVE_VALIDATION: false, - DISPLAY_ERROR_MESSAGES_AT_TOP: false, - HIDE_FIELD_LABELS: false, - MESSAGES: {} + FORM_ID: 'UserForm_Form', // $Form.FormName.JS + ERROR_CONTAINER_ID: '', // $ErrorContainerID.JS + ENABLE_LIVE_VALIDATION: false, // $EnableLiveValidation + DISPLAY_ERROR_MESSAGES_AT_TOP: true, // $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. + * @param {object} element + * @return {object} - The UserForm instance. * @desc The form */ function UserForm(element) { @@ -43,7 +68,7 @@ jQuery(function ($) { /** * @func UserForm.addStep - * @param object step - An instance of FormStep. + * @param {object} step - An instance of FormStep. * @desc Adds a step to the UserForm. */ UserForm.prototype.addStep = function (step) { @@ -57,7 +82,7 @@ jQuery(function ($) { /** * @func UserForm.setCurrentStep - * @param object step - An instance of FormStep. + * @param {object} step - An instance of FormStep. * @desc Sets the step the user is currently on. */ UserForm.prototype.setCurrentStep = function (step) { @@ -76,7 +101,7 @@ jQuery(function ($) { /** * @func UserForm.jumpToStep - * @param number stepNumber + * @param {number} stepNumber * @desc Jumps to a specific form step. */ UserForm.prototype.jumpToStep = function (stepNumber) { @@ -116,11 +141,83 @@ jQuery(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 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 = $('
  • '); + messageElement + .attr('id', elementID) + .find('a') + .attr('href', location.pathname + location.search + anchor) + .html(message); + + this.$el.find('ul').append(messageElement); + + // link back to original input via aria + // Respect existing non-error aria-describedby + if ( !describedBy ) { + describedBy = elementID; + } else if ( !describedBy.match( new RegExp( "\\b" + elementID + "\\b" ) ) ) { + // Add to end of list if not already present + describedBy += " " + elementID; + } + + input.attr('aria-describedby', describedBy); + } + }; + /** * @func FormStep * @constructor - * @param object element - * @return object - The FormStep instance. + * @param {object} element + * @return {object} - The FormStep instance. * @desc Creates a form step. */ function FormStep(element) { @@ -128,6 +225,10 @@ jQuery(function ($) { this.$el = element instanceof jQuery ? element : $(element); + if (CONSTANTS.DISPLAY_ERROR_MESSAGES_AT_TOP) { + this.errorContainer = new ErrorContainer(this.$el.find('.error-container')); + } + // Has the step been viewed by the user? this.viewed = false; @@ -157,6 +258,7 @@ jQuery(function ($) { errorClass: 'required', errorElement: 'span', errorPlacement: function (error, element) { + debugger; error.addClass('message'); if(element.is(':radio') || element.parents('.checkboxset').length > 0) { @@ -167,7 +269,7 @@ jQuery(function ($) { if (CONSTANTS.DISPLAY_ERROR_MESSAGES_AT_TOP) { // TODO - //applyTopErrorMessage(element, error.html()); + this.errorContainer.updateErrorMessage(element, error.html()); } }, @@ -187,27 +289,11 @@ jQuery(function ($) { } }; - /** - * @func FormStep.show - * @desc Show the form step. Looks after aria attributes too. - */ - FormStep.prototype.show = function () { - this.$el.attr('aria-hidden', false).show(); - }; - - /** - * @func FormStep.hide - * @desc Hide the form step. Looks after aria attributes too. - */ - FormStep.prototype.hide = function () { - this.$el.attr('aria-hidden', true).hide(); - }; - /** * @func ProgressBar * @constructor - * @param object element - * @return object - The Progress bar instance. + * @param {object} element + * @return {object} - The Progress bar instance. * @desc Creates a progress bar. */ function ProgressBar(element) { @@ -236,7 +322,7 @@ jQuery(function ($) { /** * @func ProgressBar.update - * @param number newStep + * @param {number} newStep * @desc Update the progress element to show a new step. */ ProgressBar.prototype.update = function (newStep) { @@ -277,6 +363,10 @@ jQuery(function ($) { var userform = new UserForm($('.userform')), progressBar = new ProgressBar($('#userform-progress')); + // Extend classes with common functionality. + $.extend(FormStep.prototype, commonMixin); + $.extend(ErrorContainer.prototype, commonMixin); + // Extend the default validation options with conditional options // that are set by the user in the CMS. if (CONSTANTS.ENABLE_LIVE_VALIDATION) { @@ -299,13 +389,23 @@ jQuery(function ($) { $.each(validator.errorList, function () { // TODO - //applyTopErrorMessage($(this.element), this.message); + this.errorContainer.updateErrorMessage($(this.element), this.message); }); }, onfocusout: false }); } + // Conditionally hide field labels and use HTML5 placeholder instead. + if (CONSTANTS.HIDE_FIELD_LABELS) { + $('#' + CONSTANTS.FORM_ID + ' label.left').each(function () { + var $label = $(this); + + $('[name="' + $label.attr('for') + '"]').attr('placeholder', $label.text()); + $label.remove(); + }); + } + // Display all the things that are hidden when JavaScript is disabled. $.each(['#userform-progress', '.step-navigation'], function (i, selector) { $(selector).attr('aria-hidden', false).show(); @@ -327,6 +427,17 @@ jQuery(function ($) { userform.$el.children('.Actions').attr('aria-hidden', true).hide(); } + // Enable jQuery UI datepickers + $(document).on('click', 'input.text[data-showcalendar]', function() { + var $element = $(this); + + $element.ssDatepicker(); + + if($element.data('datepicker')) { + $element.datepicker('show'); + } + }); + // Make sure the form doesn't expire on the user. Pings every 3 mins. setInterval(function () { $.ajax({ url: 'UserDefinedForm_Controller/ping' }); diff --git a/templates/UserForm.ss b/templates/UserForm.ss index 692e385..a60e2b9 100644 --- a/templates/UserForm.ss +++ b/templates/UserForm.ss @@ -5,7 +5,7 @@ <% if $Message %>

    $Message

    <% else %> - + <% end_if %>
    @@ -13,10 +13,21 @@ <% loop $FormSteps %>
    + <% if $DisplayErrorMessagesAtTop %> + + <% end_if %> +

    $Title

    + <% loop $Fields %> $FieldHolder <% end_loop %> + <% include UserFormStepNav ContainingPage=$Top %>
    <% end_loop %>