Polls in Umbraco

Heads Up!

This article is several years old now, and much has happened since then, so please keep that in mind while reading it.

Have you ever had clients asking to integrate polls in their content? Preferably polls managed directly in Umbraco and not in some other third party service?

It's pretty straight forward to do if you use Form Editor to do the heavy lifting. And even better, you won't need Visual Studio to implement it!

Let's have a look at how we can create an article with a built-in poll.

Install Form Editor and set up Umbraco

First and foremost we have to install Form Editor. You can do this either by downloading the Umbraco package from the latest release (it's the zip file attached to the release), or by installing it from NuGet.

Once installed, create a Form Editor data type. For now you don't need to do a lot of configuration on the data type, but at the very least you must tick the "Use form statistics" checkbox, since we'll need the form submission statistics to generate the poll results.

Finally we'll need a document type for the article. Create a document type called "Article with poll" and compose it as follows:

  • A "Content" tab with a "Title" property of type "Textstring" and a "Body text" property of type "Richtext editor".
  • A "Poll" tab with a "Form" property of type "Form Editor".

Create the first article

Create a page based on the "Article with poll" document type, and add some content on the "Content" tab. Now switch to the "Poll" tab and set up the poll form as follows:

  • On the "Form layout" tab, add a one column row to the form and then add these fields:
    • A radio button group for the poll options. Make sure it's required and enter a suitable error message.
    • A submit button for submitting the poll form.
  • On the "Receipt" tab, add a thank-you message. This message will be shown when the end users answer the poll.
  • On the "Limitations" tab, tick the "Only one submission per user" checkbox and enter a message below. This message will be shown along with the poll results to end users that have answered the poll.

Render the article

When rendering the article, we need to either show the poll results or render the poll form, depending on whether the end user has already answered the poll or not. The Form Editor property can tell us which is the case - the method "MaxSubmissionsExceededForCurrentUser()" returns true if the end user has answered the poll.

As mentioned above we'll use the form submission statistics to generate the poll results. You can read more about this feature here.

For rendering the poll form, Form Editor ships with some rendering options out of the box. We'll use the simplest one here, the "NoScript" option.

And without further ado, here's the full template required to bring it all to life:

@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@{
  Layout = null;
}
<!DOCTYPE html>
<html>
<head>
  <title>@Model.Content.Name</title>
  <style>
    /* styles for the poll results */
    ul.formStatistics {
      list-style: none;
      margin: 0;
      padding: 0;
    }
    ul.formStatistics li div.formStatisticsItem {
      display: inline-block;
      height: 15px;
      background-color: #C21F1F;
    }
  </style>
</head>
<body>
  @* render the article content *@
  <h1>@Model.Content.GetPropertyValue("title")</h1>
  @Model.Content.GetPropertyValue("bodyText")

  @{
    var form = Model.Content.GetPropertyValue<FormEditor.FormModel>("form");
    // did the current user already answer the poll?
    if(form.MaxSubmissionsExceededForCurrentUser() == false)
    {
      // nope, render the poll (the "form" property)
      @Html.Partial("FormEditor/NoScript", Umbraco.AssignedContentItem)
    }
    else
    {
      // yes, show the poll results
      // get the radio button group field from the form
      var field = form.AllValueFields().OfType<FormEditor.Fields.RadioButtonGroupField>().FirstOrDefault();
      if(field != null)
      {
        // get the submission statistics for the field
        var statistics = form.GetFieldValueFrequencyStatistics(new[] { field.FormSafeName });
        var fieldValueFrequencies = statistics.FieldValueFrequencies.FirstOrDefault();

        // first render a message based on the configured "max submissions exceeded for current user" message - revert to default texts
        <h2>@Umbraco.Coalesce(form.MaxSubmissionsForCurrentUserExceededHeader, "Thank you for voting!")</h2>
        <p>
          @Html.Raw(Umbraco.ReplaceLineBreaksForHtml(Umbraco.Coalesce(form.MaxSubmissionsForCurrentUserExceededText, "Here are the current results.")))
        </p>

        // then render the poll results based on the submission statistics (if any were found)
        if(fieldValueFrequencies != null)
        {
          <ul class="formStatistics">
            @* list all field values and their respective statistics *@
            @foreach(var fieldValue in field.FieldValues)
            {
              // get the field value frequency (if any submissions have been made for this field value)
              var fieldValueFrequency = fieldValueFrequencies.Frequencies.FirstOrDefault(f => f.Value == fieldValue.Value);
              // calculate the frequency in percent
              var frequencyInPercent = fieldValueFrequency != null ? (100.0 * fieldValueFrequency.Frequency) / statistics.TotalRows : 0.0;
              <li>
                <div>@fieldValue.Value - @Math.Round(frequencyInPercent, 1)%</div>
                <div class="formStatisticsItem" style="width: @frequencyInPercent%;"></div>
              </li>
            }
          </ul>
        }
      }
    }
  }
</body>
</html>

Full template for the "Article with poll" document type

And that's really all it takes to get the poll up and running!

Now... let's see what we can do to improve it (besides styling it)!

?Help the editors

We don't really want our editors adding all sorts of form fields to the poll form. But rather than explaining to them that they really (pretty please) shouldn't add anything but a radio button group and a submit button, we can simply limit their choices to only those two field types.

On the Form Editor data type we can configure "Field type groups" to group the available field types into whatever groups that make sense to our editors - and maybe more importantly, we can prevent field types from being available altogether. In our case we should create a single field type group called "Poll", containing only the radio button group and the submit button.

Asynchronous poll submission

If you're not a big fan of performing a full page reload when submitting the poll form, you're in luck; Form Editor supports asynchronous form submission.

For this to work we could either add AngularJS to our site and use the "Async" rendering that's bundled with Form Editor... or we could roll our own solution with VanillaJS. Since we don't have to worry about cross field validation, the latter option is super easy to do, so let's go ahead and implement it.

To perform the asynchronous form submission, all we need to do is POST the form data to the endpoint "/umbraco/FormEditorApi/Public/SubmitEntry/". The endpoint expects to find the ID of the page in the form variable "_id", but if you inspect the form you'll find it's already there - the "NoScript" rendering adds it by default.

As we no longer have a page reload to show the thank-you message when the poll is answered, we need to render it up-front and show it once the form has been submitted (and hide the form at the same time).

Here's the template from before with these two things added:

@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@{
  Layout = null;
}
<!DOCTYPE html>
<html>
<head>
  <title>@Model.Content.Name</title>
  <style>
    /* styles for the poll results */
    ul.formStatistics {
      list-style: none;
      margin: 0;
      padding: 0;
    }
    ul.formStatistics li div.formStatisticsItem {
      display: inline-block;
      height: 15px;
      background-color: #C21F1F;
    }

    /* styles to hide and show elements */
    div.hide {
      display: none;
    }
    div.show {
      display: block;
    }
  </style>
</head>
<body>
  @* render the article content *@
  <h1>@Model.Content.GetPropertyValue("title")</h1>
  @Model.Content.GetPropertyValue("bodyText")

  @{
    var form = Model.Content.GetPropertyValue<FormEditor.FormModel>("form");
    // did the current user already answer the poll?
    if(form.MaxSubmissionsExceededForCurrentUser() == false)
    {
      // nope, render the poll (the "form" property)
      <div id="pollFormContainer">
        @Html.Partial("FormEditor/NoScript", Umbraco.AssignedContentItem)        
      </div>
    
      // render a hidden receipt message based on the configured receipt message - revert to default texts
      <div id="pollReceiptContainer" class="hide">
        <h2>@Umbraco.Coalesce(form.ReceiptHeader, "Thank you for voting!")</h2>
        <p>
          @Html.Raw(Umbraco.ReplaceLineBreaksForHtml(Umbraco.Coalesce(form.ReceiptBody, "Your vote is in - reload the page to see the results.")))
        </p>
      </div>
    }
    else
    {
      // yes, show the poll results
      // get the radio button group field from the form
      var field = form.AllValueFields().OfType<FormEditor.Fields.RadioButtonGroupField>().FirstOrDefault();
      if(field != null)
      {
        // get the submission statistics for the field
        var statistics = form.GetFieldValueFrequencyStatistics(new[] { field.FormSafeName });
        var fieldValueFrequencies = statistics.FieldValueFrequencies.FirstOrDefault();

        // first render a message based on the configured "max submissions exceeded for current user" message - revert to default texts
        <h2>@Umbraco.Coalesce(form.MaxSubmissionsForCurrentUserExceededHeader, "Thank you for voting!")</h2>
        <p>
          @Html.Raw(Umbraco.ReplaceLineBreaksForHtml(Umbraco.Coalesce(form.MaxSubmissionsForCurrentUserExceededText, "Here are the current results.")))
        </p>

        // then render the poll results based on the submission statistics (if any were found)
        if(fieldValueFrequencies != null)
        {
          <ul class="formStatistics">
            @* list all field values and their respective statistics *@
            @foreach(var fieldValue in field.FieldValues)
            {
              // get the field value frequency (if any submissions have been made for this field value)
              var fieldValueFrequency = fieldValueFrequencies.Frequencies.FirstOrDefault(f => f.Value == fieldValue.Value);
              // calculate the frequency in percent
              var frequencyInPercent = fieldValueFrequency != null ? (100.0 * fieldValueFrequency.Frequency) / statistics.TotalRows : 0.0;
              <li>
                <div>@fieldValue.Value - @Math.Round(frequencyInPercent, 1)%</div>
                <div class="formStatisticsItem" style="width: @frequencyInPercent%;"></div>
              </li>
            }
          </ul>
        }
      }
    }
  }
  <script>
    document.addEventListener("DOMContentLoaded",
      function () {
        // add a submit event listener to the poll form
        var forms = document.getElementById("pollFormContainer").getElementsByTagName("form");
        if (!forms.length) {
          // yikes! this shouldn't happen...
          return;
        }
        forms[0].addEventListener("submit",
          function(e) {
            e.preventDefault();

            var data = new FormData(e.target);
            var request = new XMLHttpRequest();

            request.onreadystatechange = function() {
              if (request.readyState === 4 && request.status === 200) {
                var response = JSON.parse(request.responseText);
                // is the form set up with a redirect to a thank-you page?
                if (response.redirectUrlWithDomain) {
                  // yep, better redirect to that page
                  window.location = response.redirectUrlWithDomain;
                }
                else {
                  // nope, let's hide the poll form and show the receipt message
                  document.getElementById("pollFormContainer").classList.add("hide");
                  document.getElementById("pollReceiptContainer").classList.add("show");
                }
              }
            }

            request.open("POST", "/umbraco/FormEditorApi/Public/SubmitEntry/", true);
            request.send(data);
          });
      });
  </script>
</body>
</html>

Full template for the "Article with poll" document type - with asynchronous form submission

Help the editors even more

There are a few other things that makes sense to change in the Form Editor data type configuration, to make life easier for our editors:

We don't need the two column row layout that's created by default, so that be removed. This way we're left with only one row layout, and our editors won't be asked to choose a layout when they're adding a row.

Also, some of the Form Editor features are overkill for a poll, specifically notification/receipt emails and cross field validation. Fortunately we can remove these features from the data type by configuration.

That's all, folks!

And there you have it; a fully functional poll system, built entirely upon existing Umbraco packages. No custom code, no integration with third party services - and fully manageable from the Umbraco backoffice.

If this article has peaked your interest in Form Editor, there are more tutorials in the GitHub repository.

Have a very merry Umbraco Christmas!

Kenn Jacobsen

Kenn is on Twitter as