Add front-end form navigation

This commit is contained in:
David Craig 2015-08-06 11:01:21 +12:00 committed by Damian Mooyman
parent d83a450307
commit f1c1ec67ec
5 changed files with 261 additions and 13 deletions

View File

@ -35,6 +35,29 @@ class UserForm extends Form {
$this->extend('updateForm');
}
/**
* Used for partial caching in the template.
*
* @return string
*/
public function getLastEdited() {
return $this->controller->LastEdited;
}
/**
* @return array
*/
public function getNumberOfSteps() {
$steps = new ArrayList();
$numberOfSteps = $this->controller->Fields()->filter('ClassName', 'EditableFormStep')->Count();
for($i = 0; $i < $numberOfSteps; $i++) {
$steps->push($i);
}
return $steps;
}
/**
* Get the form steps.
*

View File

@ -1,9 +1,196 @@
jQuery(function($) {
/**
* @file Manages the multi-step navigation.
*/
jQuery(function ($) {
/**
* Make sure the form does not expire on the user.
* @func UserForm
* @constructor
* @param object element
* @return object - The UserForm instance.
* @desc The form
*/
setInterval(function() {
// Ping every 3 mins.
$.ajax({url: "UserDefinedForm_Controller/ping"});
}, 180*1000);
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.
this.$el.on('userform.progress.jump', function (e, stepNumber) {
self.jumpToStep(stepNumber - 1);
});
return this;
}
/**
* @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();
};
/**
* @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;
}
this.currentStep.hide();
this.setCurrentStep(targetStep);
};
/**
* @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 FormStep
* @constructor
* @param object element
* @return object - The FormStep instance.
* @desc Creates a form step.
*/
function FormStep(element) {
this.$el = element instanceof jQuery ? element : $(element);
// Bind the step navigation event listeners.
this.$el.find('.step-button-prev').on('click', function (e) {
e.preventDefault();
$(this).closest('.userform').trigger('userform.step.prev');
});
this.$el.find('.step-button-next').on('click', function (e) {
e.preventDefault();
$(this).closest('.userform').trigger('userform.step.next');
});
this.hide();
return this;
}
/**
* @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.
* @desc Creates a progress bar.
*/
function ProgressBar(element) {
this.$el = element instanceof jQuery ? element : $(element);
this.buttons = [];
// Trigger events when the user clicks step buttons.
this.$el.find('.step-button-jump').each(function (i, stepButton) {
$(stepButton).on('click', function (e) {
e.preventDefault();
$('.userform').trigger('userform.progress.jump', [$(this).text()]);
});
});
return this;
}
/**
* @func main
* @desc Bootstraps the front-end.
*/
function main() {
var userform = new UserForm($('.userform')),
progressBar = new ProgressBar($('#userform-progress'));
// 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();
});
// Initialise the form steps.
userform.$el.find('.form-step').each(function (i, element) {
var step = new FormStep(element);
userform.addStep(step);
});
userform.setCurrentStep(userform.steps[0]);
// Hide the form-wide actions on multi-step forms.
// Because JavaScript is enabled we'll use the actions contained
// in the final step's navigation.
if (userform.steps.length > 1) {
userform.$el.children('.Actions').attr('aria-hidden', true).hide();
}
// Make sure the form doesn't expire on the user. Pings every 3 mins.
setInterval(function () {
$.ajax({ url: 'UserDefinedForm_Controller/ping' });
}, 180 * 1000);
}
main();
});

View File

@ -0,0 +1,16 @@
<% cached "UserForms_Navigation", $LastEdited %>
<% if $NumberOfSteps.Count > "1" %>
<nav id="userform-progress" aria-hidden="true" style="display:none;">
<div class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="1" aria-valuemin="1" aria-valuemax="$NumberOfSteps.Count">
<span class="sr-only">Step 1 of $NumberOfSteps.Count</span>
</div>
</div>
<ul>
<% loop $NumberOfSteps %>
<li><button class="step-button-jump">$Pos</button></li>
<% end_loop %>
<ul>
</nav>
<% end_if %>
<% end_cached %>

View File

@ -0,0 +1,23 @@
<% if $FirstLast == "first last" %>
<% else %>
<nav class="step-navigation" aria-hidden="true" style="display:none;">
<ul>
<% if $FirstLast == "first" %>
<% else %>
<li><button class="step-button-prev">Prev</button><li>
<% end_if %>
<% if $FirstLast == "last" %>
<% if $ContainingPage.Actions %>
<div class="Actions">
<% loop $ContainingPage.Actions %>
$Field
<% end_loop %>
</div>
<% end_if %>
<% else %>
<li><button class="step-button-next">Next</button></li>
<% end_if %>
</ul>
</nav>
<% end_if %>

View File

@ -1,6 +1,6 @@
<% if $IncludeFormTag %>
<form $AttributesHTML>
<% end_if %>
<% include UserFormProgress %>
<form class="userform" $AttributesHTML>
<% if $Message %>
<p id="{$FormName}_error" class="message $MessageType">$Message</p>
@ -12,11 +12,12 @@
<% if $Legend %><legend>$Legend</legend><% end_if %>
<% loop $FormSteps %>
<fieldset>
<fieldset class="form-step">
<h2>$Title</h2>
<% loop $Fields %>
$FieldHolder
<% end_loop %>
<% include UserFormStepNav ContainingPage=$Top %>
</fieldset>
<% end_loop %>
@ -26,11 +27,9 @@
<% if $Actions %>
<div class="Actions">
<% loop $Actions %>
$Field
$Field
<% end_loop %>
</div>
<% end_if %>
<% if $IncludeFormTag %>
</form>
<% end_if %>