There are cases when large surveys or questionnaires are not usable to set on one page. This can be solved with multipage forms using the solution provided below.
Note: the following sample works with Sitefinity 4.3 and above.
I’m going to show how to create multipage forms with the help of the current Sitefinity API and the layouts that are available in the Forms module. The idea is to create a “dynamic” layout which can show or hide its placeholders (“pages”). Each layout widget is actually a simple .ascx file with a specific html structure for recognition of the placeholders. It looks like:
<
div
runat
=
"server"
class
=
"sf_cols"
>
<
div
runat
=
"server"
class
=
"sf_colsOut sf_1col_1_100"
>
<
div
runat
=
"server"
class
=
"sf_colsIn sf_1col_1in_100 myCustomCSSClass"
>
</
div
>
</
div
>
</
div
>
What we’re going to do is to create a custom user control (“MultipageFormsLayout.cs”) that generates the placeholders (the two DIV elements with classes “sf_colsOut” and “sf_colsIn”). The idea behind this is that we need to know which form widget is placed in which placeholder (“page”). The rest is easy - when the user clicks submit we’re going to validate only the widgets in this “page”. If there are no validation errors we hide the current placeholder and show the next one. There are no postbacks, everything is done on the client with JavaScript.
The layout file will look like this:
<%@ Control Language="C#" %>
<%@ Register Assembly="SitefinityWebApp" Namespace="Telerik.Sitefinity" TagPrefix="cl" %>
<
cl:MultipageFormsLayout
runat
=
"server"
NumberOfPages
=
"3"
/>
You can see the property “NumberOfPages” – this sets the number of placeholders (“pages”) to be rendered.
The MultipageFormsLayout control
This is more or less a composite control, just which it inherits from the “HtmlGenericControl” and implements the IScriptControl interface. In order to have the placeholders always generated the Controls property is overridden:
public
override
ControlCollection Controls
{
get
{
this
.EnsureChildControls();
return
base
.Controls;
}
}
Then in the CreateChildControls we add the placeholders:
protected
override
void
CreateChildControls()
{
this
.Controls.Clear();
this
.placeholders.Clear();
this
.CreateControlHierarchy();
}
for
(var i = 0; i <
this
.NumberOfPages; i++)
{
var placeHolder =
this
.CreatePlaceholder(
this
.InnerCssClass,
this
.OuterCssClass);
if
(i > 0 && !
this
.IsBackend())
{
//if rendered in the frontend we show only the first placeholder
placeHolder.Style[HtmlTextWriterStyle.Display] =
"none"
;
}
this
.Controls.Add(placeHolder);
this
.placeholders.Add(placeHolder);
}
And the next important part is that we add the client IDs to the ScriptDescriptor of the control, so they are available in the JavaScript component responsible for the client side operation – show/hide/validation/etc.
public
IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
var descriptor =
new
ScriptControlDescriptor(
this
.GetType().FullName,
this
.ClientID);
descriptor.AddProperty(
"numberOfPages"
,
this
.NumberOfPages);
descriptor.AddProperty(
"placeholders"
,
this
.placeholders.Select(c => c.ClientID));
descriptor.AddElementProperty(
"formsControlElement"
,
this
.GetParentFormsControl().ClientID);
return
new
[] { descriptor };
}
In MultipageFormsLayout.js
_buildPageControlsMap:
function
() {
var
placeholders =
this
.get_placeholders();
var
fieldControlsMappings = $FormManager._controlIdMappings;
for
(
var
i = 0, len = placeholders.length; i < len; i++) {
var
controlsIds = [];
this
._pageControlsMap[i] = controlsIds;
var
placeHolderElement = $get(placeholders[i]);
for
(
var
key
in
fieldControlsMappings) {
var
fieldControlId = fieldControlsMappings[key];
//check if the control is a child of the placeholder
var
el = jQuery(placeHolderElement).find(
"#"
+ fieldControlId);
if
(el.length == 1) {
controlsIds.push(fieldControlId);
}
}
}
},
In the aforementioned code we actually check child elements of each placeholder and then associate them with the field controls (widgets) found there. So we create a mapping between each placeholder (“page”) and its controls.
The next step is to override the default behavior of the submit button of the form:
_overrideSubmitButtons:
function
() {
var
submitButtons = jQuery(
this
.get_formsControlElement()).find(
":submit"
);
if
(submitButtons.length > 0) {
// from the first we take the original click handler
var
submitButton = jQuery(submitButtons[0]);
var
functionBody = submitButton.attr(
"onclick"
);
this
._originalSubmitHandler =
new
Function(functionBody);
// then we override all the buttons
for
(
var
i = 0, len = submitButtons.length; i < len; i++) {
var
submitButton = jQuery(submitButtons[i]);
submitButton.attr(
"onclick"
,
null
);
submitButton.click(
this
._showNextPageDelegate);
}
}
else
{
throw
new
Error(
"Unable to override the submit buttons!"
);
}
},
showNextPage:
function
() {
var
validationResult =
this
.validatePage(
this
._currentPageNumber);
if
(validationResult) {
if
(
this
._currentPageNumber >=
this
._numberOfPages - 1) {
return
this
._originalSubmitHandler();
}
else
{
this
.hidePage(
this
._currentPageNumber);
this
._currentPageNumber++;
this
.showPage(
this
._currentPageNumber);
}
}
return
false
;
},
Of course there is a downside of this approach – layouts for different number of pages should be registered.
Having all this implemented we have to register the layout widget in Sitefinity and make it available to the Forms module.
1. Extract the zip file into the root of your website, it will create a folder named “MultipageLayout” with the required files.
2. Build your project
3. Go to Administration -> Settings
4. Open the Advanced Settings
5. Navigate to the Toolboxes section and expand it – Tooboxes -> Page Layouts ->Sections-> Two columns->Tools
6. Create new with the following settings:
Control CLR Type or Virtual Path: Telerik.Sitefinity.Web.UI.LayoutControl, Telerik.Sitefinity
Name: 3pages
Title: 3 pages
Layout Template: ~/MultipageLayout/MultipageLayout.ascx
7. Save changes
Now you can create your form. Drop the 3 pages layout and then drop the form widgets in the different placeholders (“pages”). Make sure the Submit button is not dropped in the multipage layout because it will be hidden as part of a page.
Put a form widget on some page and publish it. When you open the page you’ll see the multipage page form there.
The zip file with the source can be downloaded from here
Screenshots:
Advanced settings - layout toolbox
Advanced settings - layout registration
Layout with 3 pages
Form with 3 pages layout selected.
Form with widgets