Recently I've been working on a couple of sites that contain forms, and I mean a lot forms. One thing I found that was common among all these sites was an address field that had multiple input fields (street name, post codes country, etc.) but yet stored all this information as one field. Umbraco Forms doesn't come with a field like this out the box so here's my guide on how to create a custom address field that will look a little something like this:
First thing to do is set up our new Address field a constructor. In our address field we're running a bit of custom validation to make sure all of our four address fields are filled in. Once we know they've all got a value we'll then process our inputs to combine them all into one convenient string. This value will then be applied to a hidden field. This hidden field is what's actually submitted and stored.
Our address field will consist on four text inputs: House Number, Street Name, Town and Postcode.
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Umbraco.Forms.Core; namespace SmartContactUmbraco.Core.App_Plugins.UmbracoForms.PreValueSources { public class AddressField : Umbraco.Forms.Core.FieldType { public AddressField() { this.Id = new Guid("122a9fdb-bf4d-4487-aad9-5480d385bbf1"); this.Name = "Address"; this.Description = "This renders fields required for a manual address entry"; this.Icon = "icon-search"; this.DataType = FieldDataType.String; this.SortOrder = 10; this.SupportsRegex = true; this.FieldTypeViewName = "FieldType.AddressField.cshtml"; } public override IEnumerable<object> ProcessSubmittedValue(Field field, IEnumerable<object> postedValues, HttpContextBase context) { List<object> vals = new List<object>(); string fieldId = field.Id.ToString(); string houseNo = context.Request[fieldId + "_houseNameNumber"]; string streetname = context.Request[fieldId + "_streetName"]; string town = context.Request[fieldId + "_town"]; string postcode = context.Request[fieldId + "_postcode"]; string address = string.Join(",", houseNo, streetname, town, postcode); vals.Add(address); return vals; } public override IEnumerable<string> ValidateField(Form form, Field field, IEnumerable<object> postedValues, HttpContextBase context) { var returnStrings = new List<string>(); string fieldId = field.Id.ToString(); string houseNo = context.Request[fieldId + "_houseNameNumber"]; string streetname = context.Request[fieldId + "_streetName"]; string town = context.Request[fieldId + "_town"]; string postcode = context.Request[fieldId + "_postcode"]; if (string.IsNullOrEmpty(houseNo) || string.IsNullOrEmpty(streetname) || string.IsNullOrEmpty(town) || string.IsNullOrEmpty(postcode)) { returnStrings.Add("Please ensure all address fields are filled out"); } // Also validate it against the original default method. returnStrings.AddRange(base.ValidateField(form, field, postedValues, context)); return returnStrings; } } }
Now our constructor is good to go we've got a couple of views to set up. The first one which will generate the UI control and set it a value. This is located in: Views\Partials\Forms\Fieldtypes\FieldType.AddressField.cshtml
@model Umbraco.Forms.Mvc.Models.FieldViewModel <input id="@Model.Id" class="text" maxlength="500" name="@Model.Name" type="text" value="@Model.Value" /> placeholder="@Model.PlaceholderText" }} @{if (Model.Mandatory || Model.Validate) { data-val="true" }} @{if (Model.Mandatory) { data-val-required="@Model.RequiredErrorMessage" }} @{if (Model.Validate) { data-val-regex="@Model.InvalidErrorMessage" data-regex="@Html.Raw(Model.Regex)" }} />
The next view is what Umbraco forms will render as an example of how the frontend will look. This is located in: App_Plugins\UmbracoForms\Backoffice\Common\FieldTypes\adddressfield.cshtml
<input type="text" tabindex="-1" class="input-block-level" style="max-width: 100px" />
All that's now left to create the frontend for the input. I've done this in default forms theme:
Views\Partials\Forms\default\FieldTypes\FieldType.AddressField.cshtml.
There's not much exciting here just some bootstrap classes to style out the form fields however note the hidden field which is what stores the combined address fields.
@{ string houseNumber = string.Empty; string streetName = string.Empty; string town = string.Empty; string postcode = string.Empty; if (!string.IsNullOrEmpty(Model.Value)) { List<string> valueArr = Model.Value.Split(',').ToList(); if (!string.IsNullOrEmpty(valueArr[0]) && valueArr[0] != " ") { houseNumber = valueArr[0]; } if (!string.IsNullOrEmpty(valueArr[1]) && valueArr[1] != " ") { streetName = valueArr[1]; } if (!string.IsNullOrEmpty(valueArr[2]) && valueArr[2] != " ") { town = valueArr[2]; } if (!string.IsNullOrEmpty(valueArr[3]) && valueArr[3] != " ") { postcode = valueArr[3]; } } } <div class="form-address"> <div class="row"> <div class="col-12"> <div class="form-address"> <div class="row"> <div class="col-12"> <div class="row form-group mb-3"> <div class="col-12 col-md-5"> @Html.Label("House Name or Number", new { @class = "mb-1 d-block" }) @Html.TextBox( Model.Id + "_houseNameNumber", houseNumber, new { @class = "form-control form-text" }) </div> <div class="col-12 col-md-5"> @Html.Label("Street", new { @class = "mb-1 d-block" }) @Html.TextBox(Model.Id + "_streetName", streetName,new { @class = "form-control form-text" }) </div> </div> <div class="row"> <div class="col-12 col-md-5"> @Html.Label("Town", new { @class = "mb-1 d-block" }) @Html.TextBox(Model.Id + "_town", town, new { @class = "form-control form-text" }) </div> <div class="col-12 col-md-5"> @Html.Label("Postcode", new { @class = "mb-1 d-block" }) @Html.TextBox(Model.Id + "_postcode", postcode, new { @class = "form-control form-text" }) </div> </div> </div> </div> </div> </div> </div> <input type="hidden" name="@Model.Name" id="@Model.Id" value="@Model.Value" maxlength="500" @{if (string.IsNullOrEmpty(Model.PlaceholderText) == false) { <text> placeholder="@Model.PlaceholderText" </text> }} @{if (Model.Mandatory || Model.Validate) { <text> data-val="true" </text> }} @{if (Model.Mandatory) { <text> data-val-required="@Model.RequiredErrorMessage" </text> }} @{if (Model.Validate) { <text> data-val-regex="@Model.InvalidErrorMessage" data-regex="@Html.Raw(Model.Regex)" </text> }} /> </div>
This is obviously just a basic implementation, there's plenty of ways you could expand on this address field. You could pre-populate the fields using an address lookup, validate the address or even show the supplied address on a map. But that's a blog post for another day.