I have a requirement to create a cascading dropdown for country and state.When a country is selected the states are populated automatically. How to accomplish the same for Sitecore Form?
1 Answer
I got the same requirement and I have done some customization for this.
Step 1: Go to core database and under this location (/sitecore/client/Applications/FormsBuilder/Components/Layouts/PropertyGridForm/PageSettings/Settings) create a new control by copying DropDownList and name it CascadeDropDownList. Under this new control, add some text box parameters under ListItems for binding the second drop down list.
In each text box parameter, you need to fill these field values as per your parameter name.
And make an entry of each parameter under ListItems like this.
Step 2: Create a new Model that will be inherited from DropDownListViewModel like this.
using System;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.ExperienceForms.Mvc.Models.Fields;
namespace Universal.Feature.Forms.CustomModels
{
[Serializable]
public class CascadeDropDownListViewModel : DropDownListViewModel
{
public virtual string SecondDropDownSelectedValue { get; set; }
public string SecondDropListLabel { get; set; }
public string SecondDropListValue { get; set; }
public string SecondDropListText { get; set; }
public string SecondDropListFieldName { get; set; }
protected override void InitItemProperties(Item item)
{
Assert.ArgumentNotNull(item, "item");
base.InitItemProperties(item);
SecondDropListLabel = item?.Fields[FormsConstants.ItemConstants.SecondDropListLabel]?.Value;
SecondDropListValue = item?.Fields[FormsConstants.ItemConstants.SecondDropListValue]?.Value;
SecondDropListText = item?.Fields[FormsConstants.ItemConstants.SecondDropListText]?.Value;
SecondDropListFieldName = item?.Fields[FormsConstants.ItemConstants.SecondDropListFieldName]?.Value;
}
protected override void UpdateItemFields(Item item)
{
Assert.ArgumentNotNull(item, "item");
base.UpdateItemFields(item);
var label = item.Fields[FormsConstants.ItemConstants.SecondDropListLabel];
if (label != null)
{
label.SetValue(SecondDropListLabel, true);
}
var value = item.Fields[FormsConstants.ItemConstants.SecondDropListValue];
if (value != null)
{
value.SetValue(SecondDropListValue, true);
}
var text = item.Fields[FormsConstants.ItemConstants.SecondDropListText];
if (text != null)
{
text.SetValue(SecondDropListText, true);
}
var fieldName = item.Fields[FormsConstants.ItemConstants.SecondDropListFieldName];
if (fieldName != null)
{
fieldName.SetValue(SecondDropListFieldName, true);
}
}
}
}
Step 3: Create a view to binding the Model value.
@using Sitecore.ExperienceForms.Mvc.Html
@model Universal.Feature.Forms.CustomModels.CascadeDropDownListViewModel
<div class="universalcascadingselectlist">
<label for="@Html.IdFor(m => Model.Value)" class="@Model.LabelCssClass">@Html.DisplayTextFor(t => Model.Title)</label>
<select id="@Html.IdFor(m => Model.Value)" name="@Html.NameFor(m => Model.Value)" class="@Model.CssClass universalcascadingselectlist-ddn" data-sc-tracking="@Model.IsTrackingEnabled" data-sc-field-name="@Model.Name" @Html.GenerateUnobtrusiveValidationAttributes(m => m.Value)>
@if (Model.ShowEmptyItem)
{
<option label=" "></option>
}@foreach (var item in Model.Items)
{
<option value="@item.Value" selected="@item.Selected">@item.Text</option>
}
</select>
@Html.ValidationMessageFor(m => Model.Value)
<div class="child-ddl">
<label for="@Html.IdFor(m => Model.SecondDropDownSelectedValue)" class="@Model.LabelCssClass">@Html.DisplayTextFor(t => Model.SecondDropListLabel)</label>
<select id="@Html.IdFor(m => Model.SecondDropDownSelectedValue)" name="@Html.NameFor(m => Model.SecondDropDownSelectedValue)" class="universalcascadingselectlist-child-ddn" data-sc-tracking="@Model.IsTrackingEnabled" data-sc-text="@Model.SecondDropListText" data-sc-value="@Model.SecondDropListValue" data-sc-field-name="@Model.SecondDropListFieldName"></select>
</div>
Step 4: Now a JS call to get the state values by selecting a country. Or you can say by selecting the first dropdown value we are getting values and filling the second dropdown value.
let UniversalCascadingSelectList = function (selectListComponent) {
this.selectListComponent = selectListComponent;
this.parentSelectList = this.selectListComponent.querySelector('.universalcascadingselectlist-ddn');
this.childSelectList = this.selectListComponent.querySelector('.universalcascadingselectlist-child-ddn');
this.textField = this.childSelectList.getAttribute("data-sc-text");
this.valueField = this.childSelectList.getAttribute("data-sc-value");
};
UniversalCascadingSelectList.prototype.Init = function () {
var _this = this;
_this.PopulateChildList();
this.parentSelectList.addEventListener('change', function () {
_this.PopulateChildList();
});
};
UniversalCascadingSelectList.prototype.PopulateChildList = function () {
var childSelectList = this.childSelectList;
//get the value from select list 1
var itemId = this.parentSelectList.value;
//pass it via the get, as querystring parameter
$.get("/universalapi/Feature/Forms/CascadeDropdownList/SelectItems", { itemId: itemId, textFieldName: this.textField, valueFieldName: this.valueField }, function (data, status) {
//clear and load SelectList2
childSelectList.innerHTML = "";
if (status === "success") {
$.each(data, function (key, entry) {
var addedOption = document.createElement("option");
addedOption.text = entry.Text;
addedOption.value = entry.Value;
childSelectList.add(addedOption, 1);
});
}
});
};
window.addEventListener('load', function () {
let selectLists = document.querySelectorAll('.universalcascadingselectlist');
if (selectLists) {
selectLists.forEach(function (selectListComponent) {
// Construct new FeaturedData
var selectList = new UniversalCascadingSelectList(selectListComponent);
// Init functionality on load
selectList.Init();
});
}
});
Step 5: And a controller to get the data.
using System;
using System.Linq;
using System.Web.Mvc;
using Sitecore.Data;
using Sitecore.Data.Items;
namespace Universal.Feature.Forms.Controllers.CascadeDropdownList
{
public class CascadeDropdownListController : Controller
{
[HttpGet]
public JsonResult SelectItems(string itemId, string textFieldName, string valueFieldName)
{
if (string.IsNullOrEmpty(itemId))
return new JsonResult();
if(Sitecore.Context.Database == null)
return new JsonResult();
var parentItem = Sitecore.Context.Database.GetItem(ID.Parse(itemId));
if (parentItem == null)
return new JsonResult();
var items = parentItem.GetChildren().Select(x => new SelectListItem { Text = GetValueForStandardFields(textFieldName, x) ?? x.Fields[textFieldName].Value, Value = GetValueForStandardFields(valueFieldName, x) ?? x.Fields[valueFieldName].Value });
return Json(items, JsonRequestBehavior.AllowGet);
}
private string GetValueForStandardFields(string fieldName, Item item)
{
if (fieldName.Equals("ID", StringComparison.OrdinalIgnoreCase))
return item.ID.ToString();
if (fieldName.Equals("Name", StringComparison.OrdinalIgnoreCase))
return item.Name;
return null;
}
}
}
Step 6: Make an entry in master database under this location (/sitecore/system/Settings/Forms/Field Types/Lists). Make a copy of existing DropDown List and name it Cascade Dropdown List and fill the required fields.
That's it and under Sitecore the control will be listed
and you can enter the values.