Dynamically displaying Form Responses with Field Titles
Hi there,
I don't have a question -- I just wanted to post my solution for a problem that I'm sure others struggled with and I could not find help anywhere in the documentation or forums for (I only found a few blog posts and forum posts that got you half way).
I wanted to build a widget that lets our client display their form responses formatted on a page for review and printing, which means I needed to get both form responses and the field titles, and I didn't want to manually set the form field names, I wanted one dynamic control for displaying the results of any form.
Below is how I solved it (if you see anywhere where it can be improved please tell me, I'm new to the sitefinity API):
You'll need the following references:
using
Telerik.Sitefinity;
using
Telerik.Sitefinity.Modules.Forms;
using
Telerik.Sitefinity.Forms.Model;
using
Telerik.Sitefinity.Model;
using
System.ComponentModel;
// get form
FormsManager formsManager = FormsManager.GetManager();
var form = formsManager.GetFormByName(
"sf_testform"
);
// if you want all the entries
var entries = formsManager.GetFormEntries(form.EntriesTypeName);
// get one entry (i.e. form response) by guid ID
var entry = formsManager.GetFormEntries(form.EntriesTypeName).Single(fe => fe.Id == yourFormResponseGuidId);
private
class
ControlForDisplay
public
string
fieldName
get
;
set
;
public
string
fieldTitle
get
;
set
;
public
Guid siblingId
get
;
set
;
public
Guid Id
get
;
set
;
public
string
controlType
get
;
set
;
// build a fieldname/title dictionary
var fields = (from ctrl
in
form.Controls.Where(c => c.Properties.Count(c2 => c2.Name.Equals(
"MetaField"
)) > 0).Cast<FormControl>()
select
new
ControlForDisplay
fieldName = ctrl.Properties.FirstOrDefault(p => p.Name.Equals(
"MetaField"
)).ChildProperties.FirstOrDefault(cp => cp.Name ==
"FieldName"
).Value,
fieldTitle = ctrl.Properties.FirstOrDefault(p => p.Name.Equals(
"Title"
)).Value,
siblingId = ctrl.SiblingId,
Id = ctrl.Id,
controlType = "formField"
).ToList<ControlForDisplay>();
// get section headers
var sectionHeaders = (from ctrl
in
form.Controls.Where(c => c.Properties.Count(c2 => c2.Name.Equals(
"Title"
)) > 0 && c.Properties.Count(c2 => c2.Name.Equals(
"MetaField"
)) == 0).Cast<FormControl>()
select
new
ControlForDisplay
fieldName =
string
.Empty,
fieldTitle = ctrl.Properties.FirstOrDefault(p => p.Name.Equals(
"Title"
)).Value,
siblingId = ctrl.SiblingId,
Id = ctrl.Id,
controlType =
"sectionheader"
).ToList<ControlForDisplay>();
// get instructional fields
var instructionalText = (from ctrl
in
form.Controls.Where(c => c.Properties.Count(c2 => c2.Name.Equals(
"Html"
)) > 0).Cast<FormControl>()
select
new
ControlForDisplay
fieldName =
string
.Empty,
fieldTitle = ctrl.Properties.FirstOrDefault(p => p.Name.Equals(
"Html"
)).Value,
siblingId = ctrl.SiblingId,
Id = ctrl.Id,
controlType =
"instructionaltext"
).ToList<ControlForDisplay>();
fields.AddRange(sectionHeaders);
fields.AddRange(instructionalText);
// sort fields (first place the first item)
List<ControlForDisplay> sortedFields =
new
List<ControlForDisplay>();
sortedFields.Add(fields.Single(f => f.siblingId == Guid.Empty));
fields.Remove(sortedFields[0]);
Guid currentSiblingId = sortedFields[0].Id;
foreach
(ControlForDisplay field
in
fields)
ControlForDisplay nextField = fields.Single(f => f.siblingId == currentSiblingId);
sortedFields.Add(nextField);
currentSiblingId = nextField.Id;
// sort fields (first place the first item)
List<ControlForDisplay> sortedFields =
new
List<ControlForDisplay>();
sortedFields.Add(fields.Single(f => f.siblingId == Guid.Empty));
fields.Remove(sortedFields[0]);
Guid currentSiblingId = sortedFields[0].Id;
foreach
(ControlForDisplay field
in
fields)
ControlForDisplay nextField = fields.Single(f => f.siblingId == currentSiblingId);
sortedFields.Add(nextField);
currentSiblingId = nextField.Id;
// iterate across field names in dictionary getting responses
var formResponse = from fld
in
sortedFields
select
new
Title = fld.fieldTitle,
Response = fld.fieldName ==
string
.Empty ?
""
: entry.GetValue(fld.fieldName),
ControlType = fld.controlType
;
// display the responses
litFormTitle.Text = form.Title;
rptFormResponses.DataSource = formResponse;
rptFormResponses.DataBind();
Nice job Tony, much appreciated.
Thank you for that! I guess I have a general understanding of what you did, but I don't understand this line:
Response = fld.fieldName == string.Empty ? "" : entry.GetValue(fld.fieldName),
Hi,
Sorry about that. I just realized I didn't drop a line of code. But where I was declaring 'var entries' to grab all form responses, I had a single var to grab a single response:
// get one entry (i.e. form response) by guid ID
var entry = formsManager.GetFormEntries(form.EntriesTypeName).Single(fe => fe.Id == yourFormResponseGuidId);
Response = fld.fieldName == string.Empty ? "" : entry.GetValue(fld.fieldName),
Thanks! That helped :) but now I have another question. I inherited the Forms control and made a custom one because I want the form to redirect to a custom coded page and I want to pass the submitted response to it.
void CustomFormsControl_BeforeFormAction(object sender, System.ComponentModel.CancelEventArgs e)
//set the form action to redirect
this.FormData.SubmitAction = Telerik.Sitefinity.Forms.Model.SubmitAction.PageRedirect;
//set redirect url here you can pass the this.FormId (id of the form) as query string parameter
this.FormData.RedirectPageUrl = "~/Custom/Pages/RedirHere.aspx?id=" + this.XXXXXXXX.Id.ToString() + "&FormName=" + this.FormData.Name.ToString();
Ok, thanks to Telerik support, I got my answer and turned it into this :)
Hi Eric,
To get all the entries you need to remove the call to .FirstOrDefault() as it will return a single FormEntry result. You can also replace it with ToList(). Then you can cycle through the entries with
foreach (var e in entry)
where e will be of type FormEntry (and not an anonymous type). You can use the different FormEntry properties when binding to either a Repeater or Grid.
Great job. Thanks a lot!
Hi,
Do you have any idea what to do with the code if below my submit button, I have a container in which I place a few text boxes that I hide with css. I use them to code values into the the form that I can read or set, something like "NiceFormName" or an email address to send the results to. My question is that when I use your code to spin through the elements, the textboxes inside the container all have a giud of 000000000000, and there's no way to sort them. Yet they are still in order on the form itself. What am I missing to get the sort order out of such a form? Thanks!
Hi Eric,
I'm not sure what do you mean with GUID of 0000000 exactly. Are these text fields normal form field controls only with applied CSS class that hides them? How do you hide them - is it with display:none? I think when you hide form elements with such style they aren't actually posted to the server after a form submission.
Kind regards,I'm sorry, I meant the SiblingID Guid, they are all 0's. I hope the screenshot helps.
I followed this post:
http://blog.falafel.com/blogs/noelrice/11-06-06/Dynamically_Retrieving_Sitefinity_Form_Entries.aspx
One possibility: In getting this to work I found that 'siblingId' only applies to form elements within the same container.
The very first form element of a container gets a siblingId of an all-zero GUID (essentially an empty GUID). So your data suggests that you have form elements in different containers. So you'd need to place all your fields in the same container, and use a different technique to CSS hide them (say giving them all a certain class, or something like that).
Yes, I have them in a different container, because even though I hide them, they still take up space at the bottom of the page. Because of this, I put them in as small text boxes, into a container with 5 columns, so they are as compact as possible. If I use display: none, they won't be rendered at all, will they? I'll have to think about it some more, thank you!
I'm brand new to Sitefinity and have been tasked to find a solution to have a user print form results. This sounds almost exactly like what we need, however I'm not sure how to go about implementing it. Can someone please point me in the right direction so we can try to use Tony's solution?
Thanks!
-Nic
Tony's solution is good and appreciated, but it is incomplete. It only works if grid layouts are NOT used in the form.
What is the correct solution to find the first (sorted) control in the form (instead of the Guid.Empy only approach)?