Created
May 14, 2024 11:12
-
-
Save gregwiechec/1aac3ad34ce8bfacfb42d01bf5e1c05a to your computer and use it in GitHub Desktop.
Cascading dropdown
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
}); | |
} | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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