UPDATE: On the 31st July, Episerver released Form field dependencies, which is available on Episerver.Forms version 4.15.0. So if you got here because you’d like to have a dropdown that has dependencies on other elements, just update your Episerver.Forms package. Otherwise, if you’re here to see how you can extend Episerver Form fields, the below should be a good example.
How do I link two dropdown fields in Episerver such that the values of a dropdown are based on the selected value of another?
Solution:
- Use the existing Episerver form field “Selection” for the first dropdown
- Expose a new dropdown field (i.e. “Secondary selection”) that would derive from the field above and introduce new properties
The code
- Extend the OptionItem.cs Episerver class
public class DependentOptionItem : OptionItem { [Display( Name = "Value relevant to", Order = 4000)] public virtual string ValueRelevantTo { get; set; } }
- Tell Episerver how to treat the new custom option item as as a property
[PropertyDefinitionTypePlugIn] public class DependentOptionItemProperty : PropertyList<DependentOptionItem> { protected override DependentOptionItem ParseItem(string value) { return JsonConvert.DeserializeObject<DependentOptionItem>(value); } }
- Now, we can expose a new dropdown field, calling it “Secondary selection”. Things to note:
- Derive from SelectionElementBlockBase (existing dropdown field)
- GroupName & ordering has been set so it’s positioned next to the existing dropdown in the CMS
- The ImageUrl has been set to make the authors happy
- The “Items” property has been overriden to use our new DependentOptionItem above
[ContentType( DisplayName = "Secondary Selection", GUID = "d9f2477f-3d53-441a-b5eb-75eebb7106b7", GroupName = "BasicElements", Description = "Used to display values that are dependent on another selection element's value", Order = 2301)] [ImageUrl("/static/images/contenttypes/selectionelementblock.png")] public class SecondarySelectionElementBlock : SelectionElementBlockBase<DependentOptionItem> { [EditorDescriptor(EditorDescriptorType = typeof(CollectionEditorDescriptor))] public override IEnumerable<DependentOptionItem> Items { get; set; } [Display( Name = "Dependent on field", Order = -3001)] [Required] [AllowedTypes(typeof(InputElementBlockBase))] public virtual ContentReference DependentOnField { get; set; } }
- It’s time to register a UIDescriptor for our new dropdown field
[UIDescriptorRegistration] public class SecondarySelectionElementBlockDescriptor : FormElementBlockDescriptor<SecondarySelectionElementBlock> { }
- Add some CSS to make sure an icon gets rendered against our new dropdown field (feel free to use your own! I just copied the icon from the existing dropdown)
.epi-forms-icon.epi-forms-secondaryselectionelementblock__icon { background: url(/static/gfx/formIcons16x16.png) 0px -64px no-repeat; }
- HTML time – this is pretty much a clone of the SelectionElementBlock.cshtml but utilising the two new properties:
@model YourLegalFriend.Content.Forms.SecondarySelectionElementBlock @{ var formElement = Model.FormElement; var labelText = Model.Label; var placeholderText = Model.PlaceHolder; var defaultOptionItemText = !string.IsNullOrWhiteSpace(placeholderText) ? placeholderText : Html.Translate(string.Format("/episerver/forms/viewmode/selection/{0}", Model.AllowMultiSelect ? "selectoptions" : "selectanoption")); var defaultOptionSelected = Model.Items.Count(x => x.Checked.HasValue && x.Checked.Value) <= 0 ? "selected=\"selected\"" : ""; var items = Model.GetItems(); var defaultValue = Model.GetDefaultValue(); } @using (Html.BeginElement(Model, new { @class = "FormSecondarySelection FormSelection" + Model.GetValidationCssClasses(), data_f_type = "selection" })) { <label for="@formElement.Guid" class="Form__Element__Caption">@labelText</label> <select name="@formElement.ElementName" id="@formElement.Guid" @(Model.AllowMultiSelect ? "multiple" : "") @Model.AttributesString @RenderDependentField() disabled="disabled"> <option disabled="disabled" @defaultOptionSelected value="">@defaultOptionItemText</option> @foreach (var item in items) { var defaultSelectedString = Model.GetDefaultSelectedString(item, defaultValue); var selectedString = string.IsNullOrEmpty(defaultSelectedString) ? string.Empty : "selected"; <option value="@item.Value" @selectedString @defaultSelectedString data-f-datainput data-relevant-to="@item.ValueRelevantTo">@item.Caption</option> } </select> } @helper RenderDependentField() { if (Model.DependentOnField != null) { @Html.Raw("data-dependent-on=\"" + Model.DependentOnField.GetContentGuid() + "\"") } }
- Javascript time – this handles disabling the options that are not relevant to the selected value of the primary dropdown
$('.EPiServerForms .FormSecondarySelection').each(function() { var $select = $(this).find('select'); var $primarySelect = $('#' + $select.data("dependent-on")); // connect the primary select item to the secondary $primarySelect.change(function() { if ($select.is(':disabled')) { $select.prop('disabled', false); } // only display secondary options that are relevant to the primary value var primaryValue = $(this).val(); $select.find('option').hide(); $select.find('option[data-relevant-to="' + primaryValue + '"]').show(); }); });
Build and run the application…
You should now see two dropdowns:
And also on the Basic Form elements panel:
And author away!