I am having a problem understanding the cleanest way to filter a dropdown dependent on the selection of another dropdown. The trick here is taking into account things such as: What if the user initially selects something in the States dropdown and then resets the States dropdown by selecting Choose..., I would want the Companies dropdown to reset back to Choose... as well. I am having issues with this because Blazor doesn't appear to allow me to two-way bind to a property such as SelectedStateId and additionally call a function change. I would have zero issues if it did because this is how I do it in Angular. It looks like I am stuck with either using two way binding or calling a function onchange, but can't do both. Am I missing something here?
A few things/questions:
- Pretend I am loading the initial lists in from a service, but for now I am hardcoding. (I believe I accomplished this)
- If user resets the State dropdown at any point I want to reset the Companies dropdown as well.
- Do I really need to create two separate lists for Companies to accomplish this. One to keep the state of the initial list of companies and one to manage the filtered list that the dropdown will be set to.
- If I remove the onchange event and replace it with bind-value I will lose the ability to call a function to filter. Wont I?
Here is my code:
@page "/"
<PageTitle>Index</PageTitle>
<div >
<label for="state" >State</label>
<select id="state" @onchange="stateChanged">
<option value="0" selected>Choose...</option>
@foreach (var state in StatesDb)
{
<option value="@state.Id">@state.Name</option>
}
</select>
</div>
<div >
<label for="company" >Company</label>
<select id="company" @onchange="companyChanged">
<option value="0" selected>Choose...</option>
@foreach (var company in CompaniesDb)
{
<option value="@company.Id">@company.Name</option>
}
</select>
</div>
@code {
List<State> StatesDb { get; set; }
List<Company> CompaniesDb { get; set; }
List<Utility> CompaniesFiltered { get; set; }
int? SelectedStateId { get; set; }
int? SelectedCompanyId { get; set; }
protected override void OnInitialized()
{
StatesDb = new List<State>() {
new State(){ Id = 1, Name = "Delaware", Abbreviation = "DE"},
new State(){ Id = 2, Name = "Pennslyvania", Abbreviation = "PA"},
new State(){ Id = 3, Name = "New Jersey", Abbreviation = "NJ"},
};
CompaniesDb = new List<Utility>() {
new Company(){ Id = 1, Name = "Company 1", StateIds = { 1 } },
new Company(){ Id = 2, Name = "Company 2", StateIds = { 2 } },
new Company(){ Id = 3, Name = "Company 3", StateIds = { 1,3 }
}
};
CompaniesFiltered = CompaniesDb;
}
void stateChanged(ChangeEventArgs e) {
int value = int.Parse(e.Value.ToString());
if (value != 0) {
SelectedStateId = value;
}
filterCompanies();
}
void filterCompanies() {
CompaniesFiltered = CompaniesDb.Where(x => (SelectedStateId == null || x.StateIds.Contains((int)SelectedStateId))).ToList();
}
}
CodePudding user response:
You should act as follows to be able to have both data binding and UI change:
<CustomInputSelect ValueExpression="@(()=>state.Id)"
Value="@state.Id"
ValueChanged="@((int value) => stateChanged(value ))" id="state">
<ValidationMessage For="()=>state.Id"></ValidationMessage>
@foreach (var item in StatesDb)
{
<option value="@state.Id">@state.Name</option>
}
</CustomInputSelect>
You can see the CustomInputSelect
implementation in this post.
Finally, the stateChanged
method can implement as follows:
void stateChanged(int value) {
if (value != 0) {
SelectedStateId = value;
}
filterCompanies();
}
CodePudding user response:
Here's a one page demo that shows how you can achieve what I think you are trying to do. And hopefully answers your questions.
- I wasn't sure of the difference between a
State
and aUtility
, so I've treated them as one. - I've created
Company
andState
based on your code. - All the lists/arrays are abstracted as
IEnumerable<>
. - I've refactored some of the code to keep the demo as clean as possible.
@page "/"
<PageTitle>Index</PageTitle>
<div >
<label for="state" >State</label>
<select id="state" @bind=SelectedStateId>
@this.ShowChoose(SelectedStateId)
@foreach (var state in StatesDb)
{
<option value="@state.Id">@state.Name</option>
}
</select>
</div>
<div >
<label for="company" >Company</label>
<select id="company" disabled="@this.IsCompanyDisabled" @bind=SelectedCompanyId>
@this.ShowChoose(SelectedCompanyId)
@foreach (var company in CompaniesFiltered)
{
<option value="@company.Id">@company.Name</option>
}
</select>
</div>
@code {
private IEnumerable<State> StatesDb { get; set; } = new List<State>() {
new State(){ Id = 1, Name = "Delaware", Abbreviation = "DE"},
new State(){ Id = 2, Name = "Pennslyvania", Abbreviation = "PA"},
new State(){ Id = 3, Name = "New Jersey", Abbreviation = "NJ"},
};
private IEnumerable<Company> CompaniesDb { get; set; } = new List<Company>() {
new Company(){ Id = 1, Name = "Company 1", StateIds = new List<int> { 1 } },
new Company(){ Id = 2, Name = "Company 2", StateIds = new List<int> { 2 } },
new Company(){ Id = 3, Name = "Company 3", StateIds = new List<int> { 1,3 }
}
};
private IEnumerable<Company> CompaniesFiltered
=> CompaniesDb.Where(x => (SelectedStateId == null || x.StateIds.Contains((int)SelectedStateId))).ToList();
private bool IsCompanyDisabled => SelectedStateId is null || SelectedStateId == 0;
int? _selectedStateId = 0;
int? SelectedStateId
{
get => _selectedStateId;
set
{
if (value != _selectedStateId)
{
_selectedStateId = value;
SelectedCompanyId = 0;
}
}
}
int? SelectedCompanyId { get; set; }
private RenderFragment ShowChoose(int? value) => (__builder) =>
{
if (value is null || value == 0)
{
<option value="0" disabled selected>Choose...</option>
}
};
public class State
{
public int Id { get; set; }
public string Name { get; set; } = "No Name Provided";
public string Abbreviation { get; set; } = "No Abbreviation Provided";
}
public class Company
{
public int Id { get; set; }
public string Name { get; set; } = "No Name Provided";
public IEnumerable<int> StateIds { get; set; } = Enumerable.Empty<int>();
}
}