4

Here I am using blazor server app and trying to populate city dropdownlist according to change in country dropdownlist using @onchange="countyClicked" event and bind the dropdown with the model.

Now the problem is that the onchange event doesnot work and the city dropdownlist does not get populated on onchange of country dropdownlist.

Below is my component html for dropdownlist

<InputSelect @bind-Value="PersonModel.CountryId" class="form-control" @onchange="countyClicked">
   <option value="">Select country</option>
   @foreach (var item in Countries)
   {
      <option value="@item.CountryId">@item.CountryName</option>
   }
</InputSelect>

<InputSelect class="form-control mb-2 mr-sm-2" @bind-Value="PersonModel.CityId">
   @foreach (var city in Cities)
   {
      <option value="@city.CityId">@city.CityName</option>
   }
</InputSelect>
<br><br>



public void countyClicked(ChangeEventArgs args)
{
   var getCountryId = args.Value.ToString();
   int.TryParse(getCountryId, out int countryId);
   Cities = mainService.GetAllCityByCountryId(countryId);
}
Matthias Müller
  • 444
  • 6
  • 15
sudip chand
  • 225
  • 6
  • 14
  • Maybe do you need an `StateHasChanged()` afger mainService call? Just to refresh de second input select. – dani herrera Feb 04 '21 at 15:21
  • `` is not a Html Element rather it's a component thus `@onchange` will not work here. Either you need to make use of C# to perform a cascading change while using two way binding with `@bind-Value` or you need to use `ValueChanged` which works similar to `onchange` – Suprabhat Biswal Feb 04 '21 at 20:28

2 Answers2

7

Change your editform to take an editcontext with your model instead.

<EditForm  EditContext="EditContext">
@*You don't have to write a onchange becuase your EditContext_OnFieldChanged will catch your changes*@
<InputSelect @bind-Value="PersonModel.CountryId" class="form-control">
            <option value="">Select country</option>
            @foreach (var item in Countries)
            {
                <option value="@item.CountryId">@item.CountryName</option>
            }
        </InputSelect>

        <InputSelect class="form-control mb-2 mr-sm-2" @bind-Value="PersonModel.CityId">
            @foreach (var city in Cities)
            {
                <option value="@city.CityId">@city.CityName</option>
            }
        </InputSelect>
</<EditForm  >

With this you can subscribe to any model changes made in the form with the OnFieldChanged event.

private EditContext EditContext;

private YourModel PersonModel= new YourModel();

protected override void OnInitialized()
{
    //Don't forget to assign a value to your PersonModel!
    EditContext = new EditContext(PersonModel);

    EditContext.OnFieldChanged += EditContext_OnFieldChanged;
}

private void EditContext_OnFieldChanged(object sender, FieldChangedEventArgs e)
{
    // identify field, retreive the set value from the model and populate the cities collection
}

Nb777
  • 1,658
  • 8
  • 27
olabacker
  • 1,232
  • 1
  • 16
  • 27
6

Note: @onchange is a compiler directive used with Html elements. It is not applicable to Razor components. The InputSelect component, however, uses the change event internally.

The following code sample demonstrates how you should populate your two InputSelect components, how they interact with each other, and how they interact with the rest of the form. Note also that I use validation to force the user to select a country and then a city, without which the form cannot be "submitted." Copy the code into your Index page and test.

Index.razor

@page "/"


@using System.ComponentModel.DataAnnotations;

<EditForm EditContext="@EditContext" OnValidSubmit="HandleValidSubmit"
          Context="NewContext">
    <DataAnnotationsValidator />

    <div class="form-group">
        <label for="name">Enter your Name: </label>
        <InputText Id="name" Class="form-control" @bind-Value="@person.Name">
        </InputText>
        <ValidationMessage For="@(() => person.Name)" />

    </div>
    <div class="form-group">
        <label for="body">Select your country: </label>
        <InputSelect ValueExpression="@(() => person.CountryID)"
                     Value="@person.CountryID"
                     ValueChanged="@((int? args) => { person.CountryID = args; SelectCities(args); })">
            <option value="">Select country...</option>
            @foreach (var country in countryList)
            {
                <option value="@country.ID">@country.Name</option>
            }
        </InputSelect>
        <ValidationMessage For="@(() => person.CountryID)" />
    </div>
    @if (cityList != null && cityList.Any())
    {
    <div class="form-group">
        <label for="body">Select your city: </label>
        <InputSelect ValueExpression="@(() => person.CityID)"
                     Value="@person.CityID" ValueChanged="@((int? args) =>
                                                person.CityID = args)">
            <option value="">Select city...</option>
            @foreach (var city in cityList)
            {
                <option value="@city.ID">@city.Name</option>
            }
        </InputSelect>
        <ValidationMessage For="@(() => person.CityID)" />
    </div>
    }
    <p>
        <button type="submit">Submit</button>
    </p>
</EditForm>


@code
    {

    private EditContext EditContext;
    private Person person = new Person() { };

    private List<Country> countryList = new List<Country>
    {
        new Country
        {
            ID = 1, Name="USA",
            Cities = new List<City> {  new City { ID = 1, Name = "City1" },
                        new City { ID = 2, Name = "City2" } }
        },
        new Country
        {
            ID =2, Name="Germany",
            Cities =  new List<City> {  new City { ID = 3, Name = "City3" },
                        new City { ID = 4, Name = "City4" } }
        },
       new Country {ID =3, Name="France",
            Cities = new List<City>  {  new City { ID = 5, Name = "City5" },
                        new City { ID =6, Name = "City6" } } },
       new Country {ID =4, Name="UK",
           Cities = new List<City>  {
                       new City { ID = 7, Name = "City7" },
                       new City { ID =8, Name = "City8" },
                       new City { ID = 9, Name = "City9" },
                       new City { ID = 10, Name = "City10" }} },
       new Country {ID =5, Name="Russia",
           Cities =  new List<City>  {  new City { ID = 11, Name = "City11" } }}
    };


    private IQueryable<City> cityList;

    private void HandleValidSubmit()
    {
        Console.WriteLine("Submitting");
    }
    protected override void OnInitialized()
    {
        EditContext = new EditContext(person);

        base.OnInitialized();
    }


    private void SelectCities(int? id)
    {
        person.CityID = null;

        cityList = (from country in countryList
                    where country.ID == id
                    from city in country.Cities
                    select city).AsQueryable();
    }

    public class Country
    {
        public int ID { get; set; }
        public string Name { get; set; }

        public List<City> Cities { get; set; }
    }

    public class City
    {
        public int ID { get; set; }
        public string Name { get; set; }


    }

    public class Person
    {
        public int ID { get; set; }

        [Required]
        public string Name { get; set; }

        [Required(ErrorMessage ="Please, select your country.")]
        public int? CountryID { get; set; }
        [Required(ErrorMessage = "Please, select your city.")]
        public int? CityID { get; set; }

    }

} 

UPDATE:

ValueChanged="@((int? args) => { person.CountryID = args; SelectCities(args); })" do

ValueChanged is defined in the InputSelect component's class as an EventCallback. When you select an item in the select element, the `change` event is triggered. In JavaScript you can access the newly selected value through the event object. In Blazor (JSInterop is behind the scene), you code is passed the selected value in the parameter args (int? args), and your code should assign it to the bound filed (person.CountryID). This is equivalent to what you were trying to do by using `@onchange="countyClicked"`, which is correct when you use html element, but the InputSelect is a component, not html element.

SelectCities(args) is a method that is called when you select a value in the Country InputSelect, whose parameter is the selected value; that is, the id of the selected country; and whose role is to filter the cities according to country selected.

Read the question When to use ValueChanged and ValueExpression in Blazor? and my answer Read my answer here See my answer here and here

enet
  • 41,195
  • 5
  • 76
  • 113
  • Thank you so much it worked for me, just another help please can you tell me what does value, value expression and valuechanged do here I have searched about them but did not understand much and what does ValueChanged="@((int? args) => { person.CountryID = args; SelectCities(args); })" do . – sudip chand Feb 05 '21 at 10:37