better way to load 2 dropdown in mvc
Asked Answered
V

5

7

This is how i am loading on page load state and city dropdown:

My Controller method:

This is the first method which is calling when page is loaded.

public ActionResult Index()
{
    var states = GetStates();
    var cities =  Enumerable.Empty<SelectListItem>();
    ViewBag.States = states;
    ViewBag.Cities = cities;
}

private IEnumerable<SelectListItem> GetStates()
{
    using (var db = new DataEntities())
    {
        return db.States.Select(d => new SelectListItem { Text = d.StateName, Value =d.Id.ToString() });
    }
}

[HttpGet]
public ActionResult GetCities(int id)
{
    using (var db = new DataEntities())
    {
        var data = db.Cities.Where(d=>d.StateId==id).Select(d => new { Text = d.CityName, Value = d.Id }).ToList();
        return Json(data, JsonRequestBehavior.AllowGet);
    }
}

My View:

IEnumerable<SelectListItem> States = ViewBag.States;
IEnumerable<SelectListItem> Cities = ViewBag.Cities;

@Html.DropDownList("State", States, "Select State", new { onchange="loadCities(this)"})
@Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id="ddlCity"})
function loadCities(obj) {
            $.ajax({
                url: "/Home/GetCities",
                data: { id: $(obj).val() },
                contentType:"application/json",
                success:function(responce){                   
                    var html = '<option value="0">Select City</option>';
                    $(responce).each(function () {
                        html += '<option value="'+this.Value+'">'+this.Text+'</option>'
                    });
                    $("#ddlCity").html(html);
                }
            });
        }

Any better way then this to load state and city dropdown?

public class HomeController : Controller
    {
        public ActionResult Index(int id=0)
        {
            Person model = null;
            var states = GetStates().ToList();
            var cities =  Enumerable.Empty<SelectListItem>();
            if (id > 0)
            {
                using (var db = new  DataEntities())
                {
                    model = db.People.Include("City").FirstOrDefault(d => d.Id == id);
                    if (model == null)
                        model = new Person();
                    else
                    {
                       states.First(d => d.Value == model.City.StateId.ToString()).Selected = true;
                       cities = db.Cities.Where(d => d.StateId == model.City.StateId).ToList().Select(d => new SelectListItem { Text = d.CityName,Value=d.Id.ToString(),Selected=d.Id==model.CityId });
                    }

                }
            }
            else
            {
                model = new Person();
            }
            ViewBag.States = states;
            ViewBag.Cities = cities;
            ViewBag.Persons = GetPersons();
            return View(model);
        }

        [HttpGet]
        public ActionResult GetCities(int id)
        {
            using (var db = new DataEntities())
            {
                var data = db.Cities.Where(d=>d.StateId==id).Select(d => new { Text = d.CityName, Value = d.Id }).ToList();
                return Json(data, JsonRequestBehavior.AllowGet);
            }
        }

        public ActionResult SavePersonDetail([Bind(Exclude = "Id")] Person model)
        {

            // var employeeDal= new Emploee();
            //employee.firstname=model.

            if (ModelState.IsValid)
            {
                var Id = model.Id;
                int.TryParse(Request["Id"], out Id);
            using (var db = new DataEntities())
            {
                if (Id > 0)
                {
                    var person = db.People.FirstOrDefault(d => d.Id == Id);
                    if (person != null)
                    {
                        model.Id = Id;
                        db.People.ApplyCurrentValues(model);
                    }
                }
                else
                {
                    db.People.AddObject(model);                    
                }
                db.SaveChanges();
                }               
            }
            if (!Request.IsAjaxRequest())
            {
                ViewBag.States = GetStates();
                ViewBag.Persons = GetPersons();
                ViewBag.Cities = Enumerable.Empty<SelectListItem>();
                return View("Index");
            }
            else
            {
                return PartialView("_personDetail",GetPersons());
            }
        }

        public ActionResult Delete(int id)
        {
            using (var db = new DataEntities())
            {
                var model = db.People.FirstOrDefault(d => d.Id == id);
                if (model != null)
                {
                    db.People.DeleteObject(model);
                    db.SaveChanges();
                }                
            }
            if (Request.IsAjaxRequest())
            {
                return Content(id.ToString());
            }
            else
            {
                ViewBag.States = GetStates();
                ViewBag.Persons = GetPersons();
                ViewBag.Cities = Enumerable.Empty<SelectListItem>();
                return View("Index");
            }
        }

        private IEnumerable<SelectListItem> GetStates()
        {
            using (var db = new DataEntities())
            {
               return db.States.ToList().Select(d => new SelectListItem { Text = d.StateName, Value =d.Id.ToString() });
            }
        }

        private IEnumerable<Person> GetPersons()
        {
            using (var db = new DataEntities())
            {
                return db.People.Include("City").Include("City.State").ToList();
            }
        }

        public ActionResult HomeAjax()
        {
            ViewBag.States = GetStates();
            ViewBag.Cities = Enumerable.Empty<SelectListItem>();
            using (var db = new DataEntities())
            {
                var data = db.States.Include("Cities").Select(d => new { Id = d.Id, Name = d.StateName, Cities = d.Cities.Select(x => new { Id=x.Id,Name=x.CityName}) }).ToList();
                ViewBag.CityStateJson = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(data);
            }
            ViewBag.Persons = GetPersons();
            return View();
        }
    }


@model IEnumerable<Person>
<div>
    <table>
        <tr>
            <th>
                First Name
            </th>
            <th>
                Last Name
            </th>
            <th>
                Email
            </th>
            <th>
                City
            </th>
            <th>
                State
            </th>
            <th>
                Edit
            </th>
        </tr>
    @if (Model.Count() == 0)
    {
        <tr>
            <td colspan="6">
                <h3>No data available</h3>
            </td>
        </tr>
    }
    else { 
    foreach (var item in Model) { 
        <tr data-id="@item.Id">
            <td data-id="fn">@item.FirstName</td>
            <td data-id="ln">@item.LastName</td>
            <td data-id="email">@item.Email</td>
            <td data-id="cn">@item.CityName<input type="hidden" value="@item.CityId" /></td>
            <td>@item.StateName</td>
            <td>
                @if (ViewBag.Title == "Home Ajax" || Request.IsAjaxRequest())
                {
                    <a href="javascript:void(0);" onclick="Edit(this,@item.Id);">Update</a>
                    <span>@Ajax.ActionLink("Delete", "Delete", new { id = item.Id }, new AjaxOptions {OnSuccess="deleteSuccess",OnBegin="showLoader",OnComplete="hideLoader" })</span>

                }
                else { 
                    <span>@Html.ActionLink("Update", "Index", new { id = item.Id })</span>
                    <span>@Html.ActionLink("Delete", "Delete", new { id = item.Id })</span>
                }

            </td>
        </tr>
    }

    }
        </table>
</div>

@model Person

@{
    ViewBag.Title = "Home Ajax";
    IEnumerable<Person> persons = ViewBag.Persons;
    IEnumerable<SelectListItem> States = ViewBag.States;
    IEnumerable<SelectListItem> Cities = ViewBag.Cities;
    IEnumerable<State> fullStates=ViewBag.CityStates;

}

@section featured {
    <section class="featured">
        <div class="content-wrapper">
            <hgroup class="title">
                <h1>@ViewBag.Title.</h1>                
            </hgroup>            
        </div>
    </section>
}

@section styles{
    <style type="text/css">
       td,th {
            border:1px solid;
            padding:5px 10px;
        }

        select {
           padding:5px 2px;
           width:310px;
           font-size:16px;
        }
    </style>
}

@section scripts{
    @Scripts.Render("~/bundles/jqueryval")
    <script type="text/javascript">

        var jsonArray = @Html.Raw(ViewBag.CityStateJson)

        function clearValues() {
            $("input[type='text'],select").val('');
            $("input[type='hidden'][name='Id']").val(0);
        }

        function loadCities(obj) {

            for (var i = 0; i < jsonArray.length; i++) {
                if (jsonArray[i].Id == parseInt($(obj).val())) {
                    fillCity(jsonArray[i].Cities);
                    break;
                }
            }
        }

        function Edit(obj, Id) {
            //  alert("hi")
            $("input[type='hidden'][name='Id']").val(Id);
            var tr = $(obj).closest("tr");
            $("#txtfirstName").val($("td[data-id='fn']", tr).text().trim());
            $("#txtlastName").val($("td[data-id='ln']", tr).text().trim());
            $("#txtemail").val($("td[data-id='email']", tr).text().trim());
            var city = $("td[data-id='cn'] input[type='hidden']", tr).val();
            var state;
            for (var i = 0; i < jsonArray.length; i++) {
                for (var j = 0; j < jsonArray[i].Cities.length; j++) {
                    if (jsonArray[i].Cities[j].Id == parseInt(city)) {
                        state = jsonArray[i].Id;
                        break;
                    }
                }
                if (state) {
                    fillCity(jsonArray[i].Cities);
                    break;
                }
            }
            $("#ddlState").val(state);
            $("#ddlCity").val(city);
        }

        function fillCity(obj) {
            var html = '<option value="0">Select City</option>';
            $(obj).each(function () {
                html += '<option value="' + this.Id + '">' + this.Name + '</option>'
            });
            $("#ddlCity").html(html);
        }

        function deleteSuccess(responce) {
            alert("record deleted successfully");
            $("tr[data-id='" + responce + "']").remove();
        }

        function insertSuccess() {
            alert("Record saved successfully");
            clearValues();
        }

        function showLoader() {
            $("#overlay").show();
        }
        function hideLoader() {
            $("#overlay").hide();
        }
    </script>
}

<h3>Add Personal Detail</h3>
@using (Ajax.BeginForm("SavePersonDetail", "Home", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "personList" ,OnSuccess="insertSuccess",OnBegin="showLoader",OnComplete="hideLoader"}))
{
    @Html.HiddenFor(m => m.Id);
<ol class="round">
    <li>       
        @Html.LabelFor(m => m.FirstName)
        @Html.TextBoxFor(m => m.FirstName, new { id = "txtfirstName" })
        @Html.ValidationMessageFor(m => m.FirstName)
    </li>
    <li>
    @Html.LabelFor(m => m.LastName)
        @Html.TextBoxFor(m => m.LastName, new { id = "txtlastName" })
        @Html.ValidationMessageFor(m => m.LastName)
        </li>
    <li>
       @Html.LabelFor(m => m.Email)
        @Html.TextBoxFor(m => m.Email, new { id = "txtemail" })
        @Html.ValidationMessageFor(m => m.Email)
    </li>

    <li>
        @Html.Label("State")
       @Html.DropDownList("State", States, "Select State", new { onchange = "loadCities(this)", id = "ddlState" })       
    </li>
    <li>
         @Html.LabelFor(m => m.CityId)
        @Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id = "ddlCity" })
        @Html.ValidationMessageFor(m => m.CityId)
    </li>
</ol>
    <input type="submit" value="Save" />
    <input type="button" value="Cancel" onclick="clearValues();"/>
}

   <h2>
        Person List
    </h2>
<div style="position:fixed;text-align:center;top:0;bottom:0;left:0;right:0;z-index:10;background-color:black;opacity:0.6;display:none;" id="overlay">
    <img style="position:relative;top:370px" src="~/Images/ajax-loader.gif" />
</div>
<div id="personList">
    @Html.Partial("_personDetail", persons)
    </div>
Vendace answered 20/2, 2015 at 11:2 Comment(15)
Define "better". Does this not work in some way? It seems reasonable to use AJAX to fetch the contents of the second list when the first list has been selected. You might try codereview.stackexchange.comBander
Assuming your list of cities isn't too big, I'd load it all at runtime and not have an ajax call back. Have a Dictionary with keys for each state and a list of cities as the value, not that into javascript objects on page load then your loadCities function can refresh the list without need for another call back.Northamptonshire
yup this is working but i thought there must be some better way like you you professional guys always have some better way then fresher like meVendace
@Ben: yes thats what i was telling that is it possible that i can get all city and state data at once and then fill dropdown on client side wihtout having an additional ajax callVendace
You shouldn't need .ToList() when populating your collections using .Select().Robbegrillet
@Sippy:Can you tell me what all the correction do i need to make in my code so that it looks some what better?Vendace
I don't particularly understand why you need ajax at all? Can you not just do both the same way?Robbegrillet
@Sippy thats what i am asking forVendace
Ohhh, you want to know which method is better? Do it without AJAX. Keep it simple.Robbegrillet
do it without ajax.but how thats what i am askingVendace
Ah my bad didn't realise what you were actually doing, you do need AJAX for your cities list. Hold on.Robbegrillet
@MariaPithia, Your approach is fine and fairly standard. The only other option is to create a javascript array from a model property containing a collection of all cities (that includes the state ID) and loop though it to update the second dropdown - but that just slows the initial rendering of the page.Buddha
@StephenMuecke:thank you sir.your comments and answer are always great and helpfullVendace
but still in case some one knowing other method for this do answer so that i can learn some new things atleastVendace
There are other ways but they kinda require the re-engineering of your solution. You should look at the concept of using ViewModels to deal with view-specific data, as well as creating a data layer using either a generic repository pattern (undesirable as technically antipattern) or an EF wrapper.Robbegrillet
B
17

You approach using ajax is fine although I would recommend a few better practices including using a view model with properties for StateID, CityID StateList and CityList, and using Unobtrusive JavaScript rather than polluting you markup with behavior, and generating the first ("please select") option with a null value rather than 0 so it can be used with the [Required] attribute

HTML

@Html.DropDownList(m => m.StateID, States, "Select State") // remove the onchange
@Html.DropDownListFor(m => m.CityID, Cities, "Select City") // why change the default ID?

SCRIPT

var url = '@Url.Action("GetCities", "Home")'; // use the helper (dont hard code)
var cities = $('#CityID'); // cache the element
$('#StateID').change(function() {
  $.getJSON(url, { id: $(this).val() }, function(response) {
    // clear and add default (null) option
    cities.empty().append($('<option></option>').val('').text('Please select'));
    $.each(response, function(index, item) {
      cities.append($('<option></option>').val(item.Value).text(item.Text));
    });
  });
});

If you were rendering multiple items (say you were asking the user to select their last 10 cities they visited), you can cache the result of the first call to avoid repeated calls where their selections may include cities from the same state.

var cache = {};
$('#StateID').change(function() {
  var selectedState = $(this).val();
  if (cache[selectedState]) {
    // render the options from the cache
  } else {
    $.getJSON(url, { id: selectedState }, function(response) {
      // add to cache
      cache[selectedState] = response;
      .....
    });
  }
});

Finally, in response to your comments regarding doing it without ajax, you can pass all the cities to the view and assign them to a javascript array. I would only recommend this if you have a few countries, each with a few cities. Its a matter of balancing the slight extra initial load time vs the slight delay in making the ajax call.

In the controller

model.CityList = db.Cities.Select(d => new { City = d.CountryID, Text = d.CityName, Value = d.Id }).ToList();

In the view (script)

// assign all cities to javascript array
var allCities= JSON.parse('@Html.Raw(Json.Encode(Model.CityList))');
$('#StateID').change(function() {
  var selectedState = $(this).val();
  var cities = $.grep(allCities, function(item, index) {
    return item.CountryID == selectedState;
  });
  // build options based on value of cities
});
Buddha answered 21/2, 2015 at 0:14 Comment(13)
Sir if i am having lots of countries and lots of states and cities then for which approach i should go for???Vendace
The first one (i.e. your current approach)Buddha
oh ok thank you sir and i like the approach of your cache although i didnt completely understand it but that concept is goodVendace
The idea is that if your select say "A" in the first dropdown (which calls the server to get the cities for A), then change it to "B" (which calls the server to get the cities for B) then change it back to "A", the server wont be called again because you already have all the cities for "A" cached in the browser - just saves making unnecessary calls.Buddha
Great idea sir.really brilliant.can you edit your answer to show me how ??PleaseVendace
I showed the basics of that that in the second code snippet - have a go yourself so you can get a better understanding (but if you have problems, let me know and I'll update it with the full code)Buddha
What's the type of CityList in the model?Rollback
@franklores, Its a collection of anonymous objects containing 3 properties (City, Text and Value)Buddha
@StephenMuecke but you can't declare the model property to be of anonymous type when you create the model.Rollback
@franklores, you can if the property is typeof object (its not suitable if you were to generate html for its properties, but in this case its just being assigned to a javascript array)Buddha
this is a very elegant soln, in particular with the cache, how would we know if the cache is stale, i.e. out of sync with server? is there a way to force a cache update from the server..Twomey
@transformer, The data is being cached in a javascript variable and only lasts for the lifetime of the page (it just means that if in one row you select a particular country, an ajax call is made and the states are stored on the client so if in another row you pick the same country, you don't need to call the server to get the states again. In this case, its highly unlikely that a new state will be created while the user is editng the data :)Buddha
@StephenMuecke, Many thanks to you. your solution solved my problem.Graniela
T
2

This is a correct approach, but you can simplify your javascript:

function loadCities(obj) {
    $.getJSON("/Home/GetCities", function (data) {
        var html = '<option value="0">Select City</option>';
        $(data).each(function () {
              html += '<option value="'+this.Value+'">'+this.Text+'</option>'
        });
        $("#ddlCity").html(html);
    });
}

Further possible simplification: Add the default item (Select City) server-side, so your javascript will be smaller.

Thrive answered 20/2, 2015 at 13:33 Comment(0)
N
1

Here's how I'd do it without the page refresh, assuming the list of cities isn't too long. I'm assuming you can create a GetStatesAndCities method to return a Dictionary.

public ActionResult Index()
{
  Dictionary<string, List<String>> statesAndCities = GetStatesAndCities();
  ViewBag.StatesAndCities = Json(statesAndCities);
}

Then in the view:

var states = JSON.parse(@ViewBag.StatesAndCities);

function loadCities(obj) {
    var cities = states[$(obj).val()];
    var html = '<option value="0">Select City</option>';
    $(cities).each(function () {
        html += '<option value="'+this.Value+'">'+this.Text+'</option>'
    });
    $("#ddlCity").html(html);
}

This way when the state is changed the cities field with update immediately with no need for callback.

Northamptonshire answered 20/2, 2015 at 16:26 Comment(0)
B
0

disclaimer: This is not a code answer, there are plenty other answers.

I think best way to keep yourself happy to seperate UI pages from data => turn them into API calls:

  • /GetCities
  • /GetStates

Now you can simply leave the select's empty on Razor rendering the page. And use a Jquery/Bootstrap plugin to create an AJAX select box.

This way when the user stops typing his search, this search string can than be send with the AJAX call (eg: /GetStates?search=test) and then a small result set can be send back to the website.

This gives:

  • Better separation in serveside code
  • Better User eXperience.
  • Smaller page loads (since you no longer send all the options to user when he requests the page, only when he opens the select box).
Boor answered 19/10, 2017 at 12:39 Comment(0)
A
-1

How about using Knockout?

Knockout is a JavaScript library that helps you to create rich, responsive display and editor user interfaces with a clean underlying data model

You have to use ajax for your cities. But with knockout you dont need to write

var html = '<option value="0">Select City</option>'; $(responce).each(function () { html += '<option value="'+this.Value+'">'+this.Text+'</option>'}); $("#ddlCity").html(html);

in your javascript.Knockout makes it simple.

You can simply write:

        function CityModel() {
        var self = this; // that means this CityModel
        self.cities = ko.observableArray([]);
        self.getCities = function () {
            $.ajax({
                url: "/Home/GetCities",
                data: { id: $(obj).val() },
                contentType: "application/json",
                success: self.cities
            });
        }
    }
    ko.applyBindings(new CityModel());

thats all. But you have to bind your data into html elements. Instead of using : @Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id="ddlCity"})

You can use:

    <select data-bind="options:cities,optionsValue:"Id",optionsText:"CityName",optionsCaption:"Select City""></select>

or you can mix razor and knockout:

@Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id="ddlCity",data_bind:"options:cities,optionsValue:\"Id\",optionsText:\"CityName\""})

One more thing you have to call GetCities when State changes, you can :

@Html.DropDownList("State", States, "Select State", new {data_bind:"event:\"change\":\"$root.GetCities\""})

Dont be scare with \"\" things this because " is an escape character and we have to say to razor i want to use " by using \ before it.

You can find more info about knockout :Knockout

And mixing with razor: Razor and Knockout

Ps: yes using knockout is suspend us from Razor and Mvc. You have to write another ViewModel . But like this situations ko is helpful. Mixing razor and knockout is another option for you.

Acetometer answered 20/2, 2015 at 12:25 Comment(2)
Seeing as jQuery can do this and there's no point including both libraries why would Knockout be optimal here?Robbegrillet
after your comment i noticed yes knockout is not optimal but is another option. thank for your feedbackAcetometer

© 2022 - 2024 — McMap. All rights reserved.