Dynamically displaying Form Responses with Field Titles - General Discussions - General Discussions - Progress Community
 General Discussions

Dynamically displaying Form Responses with Field Titles

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


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

    Can you please explain it to me? Also, how/where do you define "entry"? It doesn't exist in my context. Thanks!
  • 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
  • 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?
  • 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

  • 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
  • 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!

    Eric
  • 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
  • 333593_SiblingIDs.png
    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.
  • 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!

    Eric
  • 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