Skip to content

Instantly share code, notes, and snippets.

@gregwiechec
Created May 14, 2024 11:12
Show Gist options
  • Save gregwiechec/1aac3ad34ce8bfacfb42d01bf5e1c05a to your computer and use it in GitHub Desktop.
Save gregwiechec/1aac3ad34ce8bfacfb42d01bf5e1c05a to your computer and use it in GitHub Desktop.
Cascading dropdown
define([
"dojo/_base/declare",
"dijit/_Widget",
"dijit/_TemplatedMixin",
"dijit/form/Select",
"epi/dependency",
"epi/routes",
"epi/shell/store/JsonRest",
], function(
declare,
_Widget,
_TemplatedMixin,
Select,
dependency,
routes,
JsonRest
) {
function ensureStore() {
var registry = dependency.resolve("epi.storeregistry");
if (registry._stores["optialloy.getcascadingvalues"]) {
return;
}
registry.add("optialloy.getcascadingvalues",
new JsonRest({
target: routes.getRestPath({ moduleArea: "app", storeName: "cascadingvalues" })
})
);
}
function getEmptyItem() {
return { label: "", value: "" };
}
return declare([_Widget, _TemplatedMixin], {
templateString: `<div class="dijitInline" tabindex="-1" role="presentation"></div>`,
cascadingSelectControls: [],
numberOfDropdowns: 2,
_onDropdownChangeHandle: function (sender, index) {
return function() {
if (!this._onChangeEnabled) {
console.log("changes off: " + this._getCurrentValue());
return;
}
var currentValue = sender._getCurrentValue();
console.log("changing: " + this._getCurrentValue());
sender._onChangeEnabled = false;
for (var selectIndex = index + 1; selectIndex < this.numberOfDropdowns; selectIndex++) {
this.cascadingSelectControls[selectIndex].set("value", "");
}
if (index !== (this.numberOfDropdowns - 1)) {
this._populateDropdown(currentValue, index + 1).then(function() {
});
}
sender.cascadingSelectControls[index].focus();
setTimeout(function() {
sender._set("value", currentValue);
sender.onChange(currentValue);
sender._onChangeEnabled = true;
}, 0)
}.bind(sender);
},
buildRendering: function () {
this.inherited(arguments);
this.cascadingSelectControls = [];
for (var currentSelectIndex = 0; currentSelectIndex < this.numberOfDropdowns; currentSelectIndex++) {
var selectContainer = document.createElement("div");
selectContainer.classList.add("dijitInline");
selectContainer.classList.add("epi-row-number");
this.domNode.appendChild(selectContainer);
var select = new Select({
name: "select" + currentSelectIndex
});
var onChange = select.on("change", this._onDropdownChangeHandle(this, currentSelectIndex));
this.own(onChange);
select.placeAt(selectContainer);
this.cascadingSelectControls.push(select);
this.own(select);
}
ensureStore();
},
_setValueAttr: function(value) {
this.store = this.store || dependency.resolve("epi.storeregistry").get("optialloy.getcascadingvalues");
var values = (value || "").split("-");
if (values.length < this.numberOfDropdowns) {
var emptyArray = Array(this.numberOfDropdowns - values.length).fill("");
Array.prototype.push.apply(values, Array.apply(null, emptyArray));
}
this._onChangeEnabled = false;
var resolvedCount = 0;
values.forEach(function (dropdownValue, index) {
this._populateDropdown(value, index).then(function () {
if (dropdownValue) {
this.cascadingSelectControls[index].set("value", dropdownValue);
}
resolvedCount++;
if (index === (values.length - 1)) {
setTimeout(function () {
this._onChangeEnabled = resolvedCount === this.numberOfDropdowns;
}.bind(this), 100);
}
}.bind(this));
}, this);
},
_getCurrentValue: function() {
return this.cascadingSelectControls.map(x => x.get("value")).join("-");
},
_populateDropdown: function (currentValue, index) {
return new Promise(resolve => {
if (!currentValue && index !== 0) {
this.cascadingSelectControls[index].set("options", [getEmptyItem()]);
//this.cascadingSelectControls[index].set("value", "");
resolve();
return;
}
this.store.query({currentValue: currentValue, level: index, propertyName: this.name }).then(function (result) {
result = result.map(function(x) {
return {
label: x.text,
value: x.value
};
});
result.unshift(getEmptyItem());
this.cascadingSelectControls[index].set("options", result);
//this.cascadingSelectControls[index].set("value", "");
resolve();
}.bind(this));
});
}
});
});
using EPiServer.Shell.ObjectEditing;
using EPiServer.Shell.ObjectEditing.EditorDescriptors;
using EPiServer.Shell.Services.Rest;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace OptiAlloy;
public static class CascadingDropdownEditor
{
public const string UIHint = "cascadingdropdown";
}
[EditorDescriptorRegistration(TargetType = typeof(string), UIHint = CascadingDropdownEditor.UIHint)]
public class CascadingDropdownEditorDescriptor: EditorDescriptor
{
public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable<Attribute> attributes)
{
base.ModifyMetadata(metadata, attributes);
this.ClientEditingClass = "optialloy/cascading-dropdown";
var cascadingDropdownAttribute = attributes.OfType<CascadingDropdownAttribute>().FirstOrDefault();
metadata.EditorConfiguration["numberOfDropdowns"] = cascadingDropdownAttribute?.NumberOfCascadingLevels ?? 2;
}
}
[RestStore("cascadingvalues")]
public class CascadingValuesStore(ICascadingValuesResolver cascadingValuesResolver) : RestControllerBase
{
[HttpGet]
public ActionResult Get(string currentValue, int level, string propertyName)
{
if (string.IsNullOrWhiteSpace(currentValue))
{
currentValue = "";
}
var values = (currentValue ?? "").Split('-');
return Rest(cascadingValuesResolver.GetValues(values, level));
}
}
[AttributeUsage(AttributeTargets.Property)]
public class CascadingDropdownAttribute : Attribute
{
public int NumberOfCascadingLevels { get; set; } = 2;
public CascadingDropdownAttribute(int numberOfCascadingLevels)
{
NumberOfCascadingLevels = numberOfCascadingLevels;
}
}
public interface ICascadingValuesResolver
{
IEnumerable<SelectListItem> GetValues(IEnumerable<string> values, int level);
}
using EPiServer.ServiceLocation;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace OptiAlloy;
[ServiceConfiguration(typeof(ICascadingValuesResolver))]
public class ShoppingCenterResolver: ICascadingValuesResolver
{
private List<Country> countries =
[
new Country
{
Id = "1",
Name = "USA",
States = new[]
{
new State
{
Id = "1_1",
Name = "Ohio",
Cities = new[]
{
new City
{
Name = "Columbus",
ShoppingCenters = new[] { "Columbus Red", "Columbus Blue", "Columbus Green" }
},
new City
{
Name = "Cleveland",
ShoppingCenters = new[] { "Cleveland Aqua", "Cleveland Blue" }
},
new City
{
Name = "Springfield",
ShoppingCenters = new[] { "Springfield Orange", "Springfield Red" }
},
}
},
new State
{
Id = "1_2",
Name = "Utah",
Cities = new[]
{
new City
{
Name = "Salt Lake City",
ShoppingCenters = new[] { "Salt Lake City Orange", "Salt Lake City Red" }
},
new City
{
Name = "West Valley City",
ShoppingCenters = new[] { "West Valley City Orange", "West Valley City Red" }
},
new City
{
Name = "West Jordan",
ShoppingCenters = new[] { "West Jordan Orange", "West Jordan Red" }
},
}
},
new State
{
Id = "1_3",
Name = "Vermont",
Cities = new City[]
{
new()
{
Name = "Burlington",
ShoppingCenters = new[] { "Burlington Orange", "Burlington Red" }
},
new()
{
Name = "South Burlington",
ShoppingCenters = new[] { "South Burlington Orange", "South Burlington Red" }
},
}
}
}
},
new Country
{
Id = "2",
Name = "Canada",
States = new List<State>()
{
new()
{
Id = "2_1",
Name = "Ontario",
Cities = new List<City>()
{
new()
{
Name = "Toronto",
ShoppingCenters = new[] { "Toronto Orange", "Toronto Red" }
},
new()
{
Name = "Ottawa",
ShoppingCenters = new[] { "Ottawa Orange", "Ottawa Red" }
},
}
},
new()
{
Id = "2_2",
Name = "Quebec",
Cities = new List<City>()
{
new()
{
Name = "Montreal",
ShoppingCenters = new[] { "Montreal Orange", "Montreal Red" }
}
}
},
}
}
];
public IEnumerable<SelectListItem> GetValues(IEnumerable<string> values, int level)
{
if (level == 0)
{
return countries.Select(x => new SelectListItem
{
Text = x.Name,
Value = x.Id
});
}
var valuesArray = values.ToArray();
if (valuesArray.Length < 1)
{
return Enumerable.Empty<SelectListItem>();
}
// country
var country = countries.FirstOrDefault(x => x.Id == valuesArray[0]);
if (country == null)
{
return Enumerable.Empty<SelectListItem>();
}
if (level == 1)
{
return country.States.Select(x => new SelectListItem
{
Text = x.Name,
Value = x.Id
});
}
// state
if (valuesArray.Length < 2)
{
return Enumerable.Empty<SelectListItem>();
}
var state = country.States.FirstOrDefault(x => x.Id == valuesArray[1]);
if (state == null)
{
return Enumerable.Empty<SelectListItem>();
}
if (level == 2)
{
return state.Cities.Select(x => new SelectListItem
{
Text = x.Name,
Value = x.Name.Replace(" ", "")
});
}
// city
if (valuesArray.Length < 3)
{
return Enumerable.Empty<SelectListItem>();
}
var city = state.Cities.FirstOrDefault(x => x.Name.Replace(" ", "") == valuesArray[2]);
if (city == null)
{
return Enumerable.Empty<SelectListItem>();
}
if (level == 3)
{
return city.ShoppingCenters.Select(x => new SelectListItem
{
Text = x,
Value = x.Replace(" ", "")
});
}
return Enumerable.Empty<SelectListItem>();
}
}
public class Country
{
public string Id { get; set; }
public string Name { get; set; }
public IEnumerable<State> States { get; set; }
}
public class State
{
public string Id { get; set; }
public string Name { get; set; }
public IEnumerable<City> Cities { get; set; }
}
public class City
{
public string Name { get; set; }
public IEnumerable<string> ShoppingCenters { get; set; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment