68

I want to create two DropDownList in a cascade using MVC3 (preferably Razor) with C#.

I would like to have one dropdown where you can choose the year and another one where you can choose a specific set of months depending on the selected year.

Let's put it simple. When I choose the current year (i.e. 2011) in the dropdown list "year", the dropdown list "month" gets populated with the months until the current month (i.e. March). For the other cases (other years) no restriction is given. Moreover it would be nice to "block" the dropdown list "month" before any element in the dropdown list "year" is selected.

I already looked in the Internet for some solutions, using jQuery or even homemade approaches, but they all refer to past versions of MVC and some commands are deprecated in MVC3.

ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
CiccioMiami
  • 8,028
  • 32
  • 90
  • 151

1 Answers1

139

As always you start with a model:

public class MyViewModel
{
    public int? Year { get; set; }
    public int? Month { get; set; }

    public IEnumerable<SelectListItem> Years
    {
        get
        {
            return Enumerable.Range(2000, 12).Select(x => new SelectListItem
            {
                Value = x.ToString(),
                Text = x.ToString()
            });
        }
    }
}

then a controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new MyViewModel();
        return View(model);
    }

    public ActionResult Months(int year)
    {
        if (year == 2011)
        {
            return Json(
                Enumerable.Range(1, 3).Select(x => new { value = x, text = x }), 
                JsonRequestBehavior.AllowGet
            );
        }
        return Json(
            Enumerable.Range(1, 12).Select(x => new { value = x, text = x }),
            JsonRequestBehavior.AllowGet
        );
    }
}

and finally a view:

@model AppName.Models.MyViewModel

@Html.DropDownListFor(
    x => x.Year, 
    new SelectList(Model.Years, "Value", "Text"),
    "-- select year --"
)

@Html.DropDownListFor(
    x => x.Month, 
    Enumerable.Empty<SelectListItem>(),
    "-- select month --"
)

<script type="text/javascript">
    $('#Year').change(function () {
        var selectedYear = $(this).val();
        if (selectedYear != null && selectedYear != '') {
            $.getJSON('@Url.Action("Months")', { year: selectedYear }, function (months) {
                var monthsSelect = $('#Month');
                monthsSelect.empty();
                $.each(months, function (index, month) {
                    monthsSelect.append($('<option/>', {
                        value: month.value,
                        text: month.text
                    }));
                });
            });
        }
    });
</script>

Obviously you will notice that in my example I have hardcoded all the values. You should improve this logic by using notions like current year, current month, probably even fetch those values from a repository, etc... but for the purpose of the demonstration this should be enough to put you on the right track.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • do I need to add any special AJAX references inside the program? Thanks – CiccioMiami Mar 31 '11 at 10:48
  • 3
    @Francesco, yes, you need jquery. – Darin Dimitrov Mar 31 '11 at 11:22
  • 1
    Whats with the ? I don't get it. – Roberto Bonini May 09 '11 at 18:30
  • 1
    Roberto Bonini, is the selectlistitem. It is appended inside the list element. – Shawn Mclean May 10 '11 at 21:30
  • This example produces empty elements. This is the only place I've seen this notation for the append() method. Does this require an additional jQuery plugin? Thanks! – davidbitton Jul 11 '12 at 20:54
  • Thanks Darin, is it the shortest type of binding two dropdownlist? Can't we do anything special in the model code first, so that this applies automatically ? – Mohsen Afshin Jul 13 '12 at 14:40
  • Perfect! The only thing I needed to improve is to restore "-- select month --" label. It gets loaded initially, but then is removed in monthsSelect.empty(); – Stan Bashtavenko Jun 18 '13 at 19:06
  • Simple example that really works. Darin, can you explain why we have to add the $ in front of ', { value: month.value, text: month.text })); – Sugar Bowl Apr 15 '14 at 21:05
  • Straight to the point answer. Upvoted, my friend! Correct me if I'm wrong but the action method should return a JsonResult, e.g. public JsonResult Months(int year) – OpcodePete Aug 16 '15 at 23:39