mirror of
https://github.com/silverstripe/silverstripe-userforms.git
synced 2024-10-22 17:05:42 +02:00
Merge pull request #12 from flashbackzoo/feature/multi-page-forms-v2
Feature/multi page forms v2
This commit is contained in:
commit
0ab7e27970
@ -44,6 +44,13 @@ class UserForm extends Form {
|
|||||||
return $this->controller->LastEdited;
|
return $this->controller->LastEdited;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function getDisplayErrorMessagesAtTop() {
|
||||||
|
return $this->controller->DisplayErrorMessagesAtTop;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
@ -66,11 +73,18 @@ class UserForm extends Form {
|
|||||||
public function getFormSteps() {
|
public function getFormSteps() {
|
||||||
$steps = new ArrayList();
|
$steps = new ArrayList();
|
||||||
|
|
||||||
foreach ($this->controller->Fields()->filter('ClassName', 'EditableFormStep') as $step) {
|
foreach ($this->controller->Fields() as $field) {
|
||||||
$steps->push(array(
|
if ($field instanceof EditableFormStep) {
|
||||||
'Title' => $step->Title,
|
$steps->push($field->getFormField());
|
||||||
'Fields' => $this->getFormFields($step)
|
continue;
|
||||||
));
|
}
|
||||||
|
|
||||||
|
if(empty($steps->last())) {
|
||||||
|
trigger_error('Missing first step in form', E_USER_WARNING);
|
||||||
|
$steps->push(CompositeField::create());
|
||||||
|
}
|
||||||
|
|
||||||
|
$steps->last()->push($field->getFormField());
|
||||||
}
|
}
|
||||||
|
|
||||||
return $steps;
|
return $steps;
|
||||||
@ -81,18 +95,12 @@ class UserForm extends Form {
|
|||||||
* by using {@link updateFormFields()} on an {@link Extension} subclass which
|
* by using {@link updateFormFields()} on an {@link Extension} subclass which
|
||||||
* is applied to this controller.
|
* is applied to this controller.
|
||||||
*
|
*
|
||||||
* @param EditableFormStep $parent
|
|
||||||
*
|
|
||||||
* @return FieldList
|
* @return FieldList
|
||||||
*/
|
*/
|
||||||
public function getFormFields($parent = null) {
|
public function getFormFields() {
|
||||||
if(!$parent) {
|
|
||||||
$parent = $this->controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fields = new FieldList();
|
$fields = new FieldList();
|
||||||
|
|
||||||
foreach($parent->Fields() as $editableField) {
|
foreach($this->controller->Fields() as $editableField) {
|
||||||
// get the raw form field from the editable version
|
// get the raw form field from the editable version
|
||||||
$field = $editableField->getFormField();
|
$field = $editableField->getFormField();
|
||||||
|
|
||||||
|
@ -367,26 +367,10 @@ class UserDefinedForm_Controller extends Page_Controller {
|
|||||||
$form = UserForm::create($this);
|
$form = UserForm::create($this);
|
||||||
|
|
||||||
$this->generateConditionalJavascript();
|
$this->generateConditionalJavascript();
|
||||||
$this->generateValidationJavascript($form);
|
|
||||||
|
|
||||||
return $form;
|
return $form;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Build jQuery validation script and require as a custom script
|
|
||||||
*
|
|
||||||
* @param UserForm $form
|
|
||||||
*/
|
|
||||||
public function generateValidationJavascript(UserForm $form) {
|
|
||||||
// set the custom script for this form
|
|
||||||
Requirements::customScript(
|
|
||||||
$this
|
|
||||||
->customise(array('Form' => $form))
|
|
||||||
->renderWith('ValidationScript'),
|
|
||||||
'UserFormsValidation'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the javascript for the conditional field show / hiding logic.
|
* Generate the javascript for the conditional field show / hiding logic.
|
||||||
*
|
*
|
||||||
|
@ -18,14 +18,6 @@ class EditableFormStep extends EditableFormField {
|
|||||||
*/
|
*/
|
||||||
private static $plural_name = 'Steps';
|
private static $plural_name = 'Steps';
|
||||||
|
|
||||||
/**
|
|
||||||
* @config
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $has_many = array(
|
|
||||||
'Fields' => 'EditableFormField'
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return FieldList
|
* @return FieldList
|
||||||
*/
|
*/
|
||||||
@ -45,7 +37,7 @@ class EditableFormStep extends EditableFormField {
|
|||||||
* @return FormField
|
* @return FormField
|
||||||
*/
|
*/
|
||||||
public function getFormField() {
|
public function getFormField() {
|
||||||
return false;
|
return CompositeField::create()->setTitle($this->Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5,18 +5,43 @@ jQuery(function ($) {
|
|||||||
|
|
||||||
// Settings that come from the CMS.
|
// Settings that come from the CMS.
|
||||||
var CONSTANTS = {
|
var CONSTANTS = {
|
||||||
ERROR_CONTAINER_ID: '',
|
FORM_ID: 'UserForm_Form', // $Form.FormName.JS
|
||||||
ENABLE_LIVE_VALIDATION: false,
|
ERROR_CONTAINER_ID: '', // $ErrorContainerID.JS
|
||||||
DISPLAY_ERROR_MESSAGES_AT_TOP: false,
|
ENABLE_LIVE_VALIDATION: false, // $EnableLiveValidation
|
||||||
HIDE_FIELD_LABELS: false,
|
DISPLAY_ERROR_MESSAGES_AT_TOP: false, // $DisplayErrorMessagesAtTop
|
||||||
MESSAGES: {}
|
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
|
* @func UserForm
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param object element
|
* @param {object} element
|
||||||
* @return object - The UserForm instance.
|
* @return {object} - The UserForm instance.
|
||||||
* @desc The form
|
* @desc The form
|
||||||
*/
|
*/
|
||||||
function UserForm(element) {
|
function UserForm(element) {
|
||||||
@ -38,12 +63,53 @@ jQuery(function ($) {
|
|||||||
self.jumpToStep(stepNumber - 1);
|
self.jumpToStep(stepNumber - 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.$el.validate(this.validationOptions);
|
||||||
|
|
||||||
return this;
|
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
|
* @func UserForm.addStep
|
||||||
* @param object step - An instance of FormStep.
|
* @param {object} step - An instance of FormStep.
|
||||||
* @desc Adds a step to the UserForm.
|
* @desc Adds a step to the UserForm.
|
||||||
*/
|
*/
|
||||||
UserForm.prototype.addStep = function (step) {
|
UserForm.prototype.addStep = function (step) {
|
||||||
@ -57,7 +123,7 @@ jQuery(function ($) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @func UserForm.setCurrentStep
|
* @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.
|
* @desc Sets the step the user is currently on.
|
||||||
*/
|
*/
|
||||||
UserForm.prototype.setCurrentStep = function (step) {
|
UserForm.prototype.setCurrentStep = function (step) {
|
||||||
@ -76,7 +142,7 @@ jQuery(function ($) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @func UserForm.jumpToStep
|
* @func UserForm.jumpToStep
|
||||||
* @param number stepNumber
|
* @param {number} stepNumber
|
||||||
* @desc Jumps to a specific form step.
|
* @desc Jumps to a specific form step.
|
||||||
*/
|
*/
|
||||||
UserForm.prototype.jumpToStep = function (stepNumber) {
|
UserForm.prototype.jumpToStep = function (stepNumber) {
|
||||||
@ -116,11 +182,105 @@ jQuery(function ($) {
|
|||||||
this.jumpToStep(this.steps.indexOf(this.currentStep) - 1);
|
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 = $('<li><a></a></li>');
|
||||||
|
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
|
* @func FormStep
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param object element
|
* @param {object} element
|
||||||
* @return object - The FormStep instance.
|
* @return {object} - The FormStep instance.
|
||||||
* @desc Creates a form step.
|
* @desc Creates a form step.
|
||||||
*/
|
*/
|
||||||
function FormStep(element) {
|
function FormStep(element) {
|
||||||
@ -143,71 +303,36 @@ jQuery(function ($) {
|
|||||||
self.$el.trigger('userform.step.next');
|
self.$el.trigger('userform.step.next');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set up validation for the step.
|
if (CONSTANTS.DISPLAY_ERROR_MESSAGES_AT_TOP) {
|
||||||
this.$el.validate(this.validationOptions);
|
this.errorContainer = new ErrorContainer(this.$el.find('.error-container'));
|
||||||
|
|
||||||
|
// Listen for errors on the UserForm.
|
||||||
|
this.$el.closest('.userform').on('userform.form.error', function (e, validator) {
|
||||||
|
// The step only cares about errors if it's currently visible.
|
||||||
|
if (!self.$el.is(':visible')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add or update each error in the list.
|
||||||
|
$.each(validator.errorList, function (i, error) {
|
||||||
|
self.errorContainer.updateErrorMessage($(error.element), error.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for fields becoming valid
|
||||||
|
this.$el.closest('.userform').on('userform.form.valid', function (e, fieldId) {
|
||||||
|
self.errorContainer.removeErrorMessage(fieldId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Default options for step validation. These get extended in main().
|
|
||||||
*/
|
|
||||||
FormStep.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CONSTANTS.DISPLAY_ERROR_MESSAGES_AT_TOP) {
|
|
||||||
// TODO
|
|
||||||
//applyTopErrorMessage(element, error.html());
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
success: function (error) {
|
|
||||||
error.remove();
|
|
||||||
},
|
|
||||||
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 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
|
* @func ProgressBar
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param object element
|
* @param {object} element
|
||||||
* @return object - The Progress bar instance.
|
* @return {object} - The Progress bar instance.
|
||||||
* @desc Creates a progress bar.
|
* @desc Creates a progress bar.
|
||||||
*/
|
*/
|
||||||
function ProgressBar(element) {
|
function ProgressBar(element) {
|
||||||
@ -236,7 +361,7 @@ jQuery(function ($) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @func ProgressBar.update
|
* @func ProgressBar.update
|
||||||
* @param number newStep
|
* @param {number} newStep
|
||||||
* @desc Update the progress element to show a new step.
|
* @desc Update the progress element to show a new step.
|
||||||
*/
|
*/
|
||||||
ProgressBar.prototype.update = function (newStep) {
|
ProgressBar.prototype.update = function (newStep) {
|
||||||
@ -274,13 +399,17 @@ jQuery(function ($) {
|
|||||||
* @desc Bootstraps the front-end.
|
* @desc Bootstraps the front-end.
|
||||||
*/
|
*/
|
||||||
function main() {
|
function main() {
|
||||||
var userform = new UserForm($('.userform')),
|
var userform = null,
|
||||||
progressBar = new ProgressBar($('#userform-progress'));
|
progressBar = null;
|
||||||
|
|
||||||
|
// Extend classes with common functionality.
|
||||||
|
$.extend(FormStep.prototype, commonMixin);
|
||||||
|
$.extend(ErrorContainer.prototype, commonMixin);
|
||||||
|
|
||||||
// Extend the default validation options with conditional options
|
// Extend the default validation options with conditional options
|
||||||
// that are set by the user in the CMS.
|
// that are set by the user in the CMS.
|
||||||
if (CONSTANTS.ENABLE_LIVE_VALIDATION) {
|
if (CONSTANTS.ENABLE_LIVE_VALIDATION) {
|
||||||
$.extend(FormStep.prototype.validationOptions, {
|
$.extend(UserForm.prototype.validationOptions, {
|
||||||
onfocusout: function (element) {
|
onfocusout: function (element) {
|
||||||
this.element(element);
|
this.element(element);
|
||||||
}
|
}
|
||||||
@ -288,24 +417,28 @@ jQuery(function ($) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (CONSTANTS.DISPLAY_ERROR_MESSAGES_AT_TOP) {
|
if (CONSTANTS.DISPLAY_ERROR_MESSAGES_AT_TOP) {
|
||||||
$.extend(FormStep.prototype.validationOptions, {
|
$.extend(UserForm.prototype.validationOptions, {
|
||||||
|
// Callback for custom code when an invalid form / step is submitted.
|
||||||
invalidHandler: function (event, validator) {
|
invalidHandler: function (event, validator) {
|
||||||
var errorList = $('#' + CONSTANTS.ERROR_CONTAINER_ID + ' ul');
|
$('.userform').trigger('userform.form.error', [validator]);
|
||||||
|
|
||||||
// Update the error list with errors from the validator.
|
|
||||||
// We do this because top messages are not part of the regular
|
|
||||||
// error message life cycle, which jquery.validate handles for us.
|
|
||||||
errorList.empty();
|
|
||||||
|
|
||||||
$.each(validator.errorList, function () {
|
|
||||||
// TODO
|
|
||||||
//applyTopErrorMessage($(this.element), this.message);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
onfocusout: false
|
onfocusout: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userform = new UserForm($('.userform'));
|
||||||
|
progressBar = new ProgressBar($('#userform-progress'));
|
||||||
|
|
||||||
|
// 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.
|
// Display all the things that are hidden when JavaScript is disabled.
|
||||||
$.each(['#userform-progress', '.step-navigation'], function (i, selector) {
|
$.each(['#userform-progress', '.step-navigation'], function (i, selector) {
|
||||||
$(selector).attr('aria-hidden', false).show();
|
$(selector).attr('aria-hidden', false).show();
|
||||||
@ -327,6 +460,17 @@ jQuery(function ($) {
|
|||||||
userform.$el.children('.Actions').attr('aria-hidden', true).hide();
|
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.
|
// Make sure the form doesn't expire on the user. Pings every 3 mins.
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
$.ajax({ url: 'UserDefinedForm_Controller/ping' });
|
$.ajax({ url: 'UserDefinedForm_Controller/ping' });
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<% if $Message %>
|
<% if $Message %>
|
||||||
<p id="{$FormName}_error" class="message $MessageType">$Message</p>
|
<p id="{$FormName}_error" class="message $MessageType">$Message</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p id="{$FormName}_error" class="message $MessageType" style="display: none"></p>
|
<p id="{$FormName}_error" class="message $MessageType" aria-hidden="true" style="display: none;"></p>
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@ -13,10 +13,21 @@
|
|||||||
|
|
||||||
<% loop $FormSteps %>
|
<% loop $FormSteps %>
|
||||||
<fieldset class="form-step">
|
<fieldset class="form-step">
|
||||||
|
<% if $Top.DisplayErrorMessagesAtTop %>
|
||||||
|
<fieldset class="error-container" aria-hidden="true" style="display: none;">
|
||||||
|
<div>
|
||||||
|
<h4></h4>
|
||||||
|
<ul class="error-list"></ul>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<% end_if %>
|
||||||
|
|
||||||
<h2>$Title</h2>
|
<h2>$Title</h2>
|
||||||
<% loop $Fields %>
|
|
||||||
|
<% loop $Children %>
|
||||||
$FieldHolder
|
$FieldHolder
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
|
|
||||||
<% include UserFormStepNav ContainingPage=$Top %>
|
<% include UserFormStepNav ContainingPage=$Top %>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
|
@ -1,157 +0,0 @@
|
|||||||
(function($) {
|
|
||||||
$(document).ready(function() {
|
|
||||||
var formId = "{$Form.FormName.JS}",
|
|
||||||
errorContainerId = "{$ErrorContainerID.JS}",
|
|
||||||
errorContainer = $('<fieldset><div><h4></h4><ul></ul></div></fieldset>');
|
|
||||||
|
|
||||||
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 %>
|
|
||||||
};
|
|
||||||
|
|
||||||
$(document).on("click", "input.text[data-showcalendar]", function() {
|
|
||||||
$(this).ssDatepicker();
|
|
||||||
|
|
||||||
if($(this).data('datepicker')) {
|
|
||||||
$(this).datepicker('show');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#" + formId).validate({
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
<% if $DisplayErrorMessagesAtTop %>
|
|
||||||
applyTopErrorMessage(element, error.html());
|
|
||||||
<% end_if %>
|
|
||||||
},
|
|
||||||
success: function (error) {
|
|
||||||
error.remove();
|
|
||||||
},
|
|
||||||
messages: messages,
|
|
||||||
rules: {
|
|
||||||
<% loop $Fields %>
|
|
||||||
<% if $Validation %><% if ClassName == EditableCheckboxGroupField %>
|
|
||||||
'{$Name.JS}[]': {$ValidationJSON.RAW},
|
|
||||||
<% else %>
|
|
||||||
'{$Name.JS}': {$ValidationJSON.RAW},
|
|
||||||
<% end_if %><% end_if %>
|
|
||||||
<% end_loop %>
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Conditional options.
|
|
||||||
* Using leading commas so we don't get a trailing comma on
|
|
||||||
* the last option. Trailing commas can break IE.
|
|
||||||
*/
|
|
||||||
<% if $EnableLiveValidation %>
|
|
||||||
// Enable live validation
|
|
||||||
,onfocusout: function (element) { this.element(element); }
|
|
||||||
<% end_if %>
|
|
||||||
|
|
||||||
<% if $DisplayErrorMessagesAtTop %>
|
|
||||||
,invalidHandler: function (event, validator) {
|
|
||||||
var errorList = $('#' + errorContainerId + ' ul');
|
|
||||||
|
|
||||||
// Update the error list with errors from the validator.
|
|
||||||
// We do this because top messages are not part of the regular
|
|
||||||
// error message life cycle, which jquery.validate handles for us.
|
|
||||||
errorList.empty();
|
|
||||||
|
|
||||||
$.each(validator.errorList, function () {
|
|
||||||
applyTopErrorMessage($(this.element), this.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
,onfocusout: false
|
|
||||||
<% end_if %>
|
|
||||||
});
|
|
||||||
|
|
||||||
<% if $HideFieldLabels %>
|
|
||||||
// Hide field labels (use HTML5 placeholder instead)
|
|
||||||
$("#" + formId + "label.left").each(function() {
|
|
||||||
$("#"+$(this).attr("for"))
|
|
||||||
.attr("placeholder", $(this).text());
|
|
||||||
$(this).remove();
|
|
||||||
});
|
|
||||||
Placeholders.init();
|
|
||||||
<% end_if %>
|
|
||||||
|
|
||||||
<% if $DisplayErrorMessagesAtTop %>
|
|
||||||
/**
|
|
||||||
* @applyTopErrorMessage
|
|
||||||
* @param {jQuery} input - The jQuery input object which contains the field to validate
|
|
||||||
* @param {string} message - The error message to display (html escaped)
|
|
||||||
* @desc Update an error message (displayed at the top of the form).
|
|
||||||
*/
|
|
||||||
function applyTopErrorMessage(input, message) {
|
|
||||||
var inputID = input.attr('id'),
|
|
||||||
anchor = '#' + inputID,
|
|
||||||
elementID = inputID + '-top-error',
|
|
||||||
errorContainer = $('#' + errorContainerId),
|
|
||||||
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');
|
|
||||||
errorContainer.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 = $('<li><a></a></li>');
|
|
||||||
messageElement
|
|
||||||
.attr('id', elementID)
|
|
||||||
.find('a')
|
|
||||||
.attr('href', location.pathname + location.search + anchor)
|
|
||||||
.html(message);
|
|
||||||
errorContainer
|
|
||||||
.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 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build container
|
|
||||||
errorContainer
|
|
||||||
.hide()
|
|
||||||
.attr('id', errorContainerId)
|
|
||||||
.find('h4')
|
|
||||||
.text(ss.i18n._t(
|
|
||||||
"UserForms.ERROR_CONTAINER_HEADER",
|
|
||||||
"Please correct the following errors and try again:"
|
|
||||||
));
|
|
||||||
$('#' + formId).prepend(errorContainer);
|
|
||||||
<% end_if %>
|
|
||||||
});
|
|
||||||
})(jQuery);
|
|
Loading…
Reference in New Issue
Block a user