Dynamically displaying Form Responses with Field Titles

Posted by Community Admin on 03-Aug-2018 00:44

Dynamically displaying Form Responses with Field Titles

All Replies

Posted by Community Admin on 06-Dec-2011 00:00

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;


The first thing was to get the form and its entries. I'm just hard-coding the form name here.

// 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);


The next thing to do is determine the field names and titles of the fields on the form. For that I used a LINQ query, and data-shaped it into a small class. Here's the class:
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;



and here's the LINQ queries. It grabs the field name, title, id, and sibling id (which you need for sorting), and makes sure to only grab 'metafield' controls (i.e. the controls you actually care about).

// 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>();

Two more queries add instructional text and section headers into the collection.

// 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);


Now you sort the fields in the order they appear on your form. I couldn't find an extension method to do this, so I did it manually. I'm sure this can be improved, I just wanted to it work first. Sitefinity marks the control with a 'sibling id', i.e. the id of the control above it. An odd way to sort, but it makes sense for their drag and drop interface. (note: the first item in the list has an empty Guid of zeroes for sibling 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;


Finally I iterate across the entries, grab the response for each form field and display the results. I'm just dropping a bit of HTML into a Literal on the ASCX page right now, but you could use the final results however you like.

// 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();



And that's it! Took awhile, but it works. Not sure if there are any potential problems here, but I thought I'd post it.

-Tony

Posted by Community Admin on 06-Dec-2011 00:00

Nice job Tony, much appreciated.

Posted by Community Admin on 21-Dec-2011 00:00

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),

Can you please explain it to me? Also, how/where do you define "entry"? It doesn't exist in my context. Thanks!

Posted by Community Admin on 21-Dec-2011 00:00

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);

I've updated the post to add this line of code. It's just a reference to one particular form response that you want to print. So if you use 'entries' to list your form responses, you can select one via  and get it via the formsManager by GuidID.

This line:
Response = fld.fieldName == string.Empty ? "" : entry.GetValue(fld.fieldName),
is a ternary operator (see here) which is just if-statement shorthand.

It sets the Response only if the fieldname is not empty. So if I have a fieldname, then get the value the user entered. If I don't have a fieldname (section headers, for example don't have fieldnames), then just set it to empty.

This is because sitefinity forms contain not only form fields, but also section headers and separators. You only want to GetValue() for a form field.

Thanks!
Tony

Posted by Community Admin on 21-Dec-2011 00:00

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();
                
        

Have you any idea what I would pass here in place of the XXXXXXX to get the id (GUID) to a page with your code on it?

Posted by Community Admin on 22-Dec-2011 00:00

Ok, thanks to Telerik support, I got my answer and turned it into this :)

 void CustomFormsControl_BeforeFormAction(object sender, System.ComponentModel.CancelEventArgs e)
       

            FormsManager formsManager = FormsManager.GetManager();
            var form = formsManager.GetFormByName(this.FormData.Name.ToString());
                       
            //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
            var manager = FormsManager.GetManager();
            var entry = manager.GetFormEntries(this.FormData).Where(fE => fE.ReferralCode == this.FormData.FormEntriesSeed.ToString()).FirstOrDefault();
            Guid entryId;
            string strGuid = null;
            if (entry != null)
           
                entryId = entry.Id;
                strGuid = entryId.ToString();
           
            
            //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?Guid=" + strGuid + "&FormName=" + this.FormData.Name.ToString();
   

       

But I have a couple more questions...

I'm not familiar with Repeaters (thats what you used to show the data, right?) so I used a Datagrid, but it doesn't show the response column. I also don't know Linq but from what I understood, the formResponse is an anonymous type of (String, Object, String) with Object being the Response. Do you know if that might be what's causing the response not to show up?

One more question, in the line I was asking about with "entry", how would you use "entries" to get all of the responses to the form? Ok, thanks for your time!

Eric

Posted by Community Admin on 26-Dec-2011 00:00

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.

Greetings,
Lubomir Velkov
the Telerik team
Do you want to have your say in the Sitefinity development roadmap? Do you want to know when a feature you requested is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items

Posted by Community Admin on 26-Jan-2012 00:00

Great job. Thanks a lot!

Posted by Community Admin on 03-Feb-2012 00:00

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!

Eric

Posted by Community Admin on 06-Feb-2012 00:00

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,
Lubomir Velkov
the Telerik team
Do you want to have your say in the Sitefinity development roadmap? Do you want to know when a feature you requested is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items

Posted by Community Admin on 06-Feb-2012 00:00

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 

and I have my fields hidden with: visibility:hidden !important;
They pass the data I need well.

I can still get their data programatically, but because I know what they are called. I would like to spin through the fields in order somehow, not knowing what they are.

Posted by Community Admin on 06-Feb-2012 00:00

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).

Posted by Community Admin on 06-Feb-2012 00:00

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!

Eric

Posted by Community Admin on 20-Apr-2012 00:00

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

Posted by Community Admin on 06-Oct-2016 00:00

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)?

This thread is closed