Billy
Billy

Reputation: 2436

How to render multiple groups of drop down lists & submit their values in MVC 3

I have the following view model:

Public Class MyViewModel
    Public Property SelectedIDs As List(Of Integer)

    Public Property FilterListItems As SelectList
    Public Property SelectionListItems As SelectList
End Class

For each integer in the SelectedIDs property I need to display a drop down list populated with the items in the FilterListItems property and a drop down list populated with the items in the SectionListItems property and the SelectedID value needs selected in the SectionListItems drop down. The drop down with the FilterListItems will filter the items in the drop down with the SectionListItems when the user makes a change. This will be done via jQuery so I need the ids of both drop down lists in order to wire that up. The user also has the ability to add another group of drop down lists while on the page. There is no limit to how many groups of drop down lists they can add.

When the user hits the "Save" button the selected item value in each drop down list that is populated with the SectionListItems needs to be sent back to the server and saved off.

What is the best way to render this out on the view? From what I have seen I can do a for each loop & manually render the drop down lists or I can render out a partial view that contains both drop down lists. How do I make sure the IDs are different & wire them up to the jQuery so the filter drop downs work w/ the correct select item drop downs?

How does the user's selections get passed back up to my controller when they submit the form? Should I use jQuery to find all the drop downs, create a JSON object and submit that or can this be done with MVC?

How would the user add another set of drop downs on the page? I've seen you can use the clone method via jQuery but the lists would need reloaded, the IDs would need updated, etc. Same would be true if I used jQuery and some sort of template. Also I've seen you can use Ajax.ActionLink to render out another partial view which seems like a better way to go, but how would it assign the correct IDs & how would I wire those up to the jQuery? Maybe just wire it up inside the partial on document ready?

Upvotes: 0

Views: 1001

Answers (1)

Darin Dimitrov
Darin Dimitrov

Reputation: 1039160

I would probably adapt the view model a little bit:

public class MyViewModel
{
    public IEnumerable<ItemViewModel> Items { get; set; }
}

public class ItemViewModel
{
    public int SelectedFilterId { get; set; }
    public IEnumerable<SelectListItem> FilterListItems { get; set; }

    public int SelectedId { get; set; }
    public IEnumerable<SelectListItem> SelectionListItems { get; set; }
}

Now we have our main view model containing a list of items where each item represents two dropdown lists for filtering and selection respectively. Then we have two notions to address here:

  1. editing a variable length list
  2. cascading dropdown lists

The first point has been addressed by Steve Sanderson in this great post.

Let's address the second point. So we would have a controller with 4 actions:

  • GET Index => called when the page is initially loaded. It will query our DAL and fetch domain models that will be mapped to MyViewModel which we need to pass to the corresponding view
  • POST Index => called when the form is submitted in order to process the user input
  • GET Filter => passed a filter id and returning a JSON object containing a list of key/value pairs corresponding to the respective items of the SelectionListItems drop down list.
  • GET BlankEditorRow => returns a partial view representing a single group of drop down lists (see Steve Sanderson's blog post)

So let's put that into action:

public ActionResult Index()
{
    var filters = ... fetch from repo and map to view model
    var selectionItems = ... fetch from repo and map to view model

    var model = new MyViewModel
    {
        // TOOO: fetch from repo and map to view model
        Items = new[]
        {
            new ItemViewModel
            {
                SelectedFilterId = 1,
                FilterListItems = filters,
                SelectionListItems = selectionItems
            },
            new ItemViewModel
            {
                SelectedFilterId = 3,
                FilterListItems = filters,
                SelectionListItems = selectionItems
            },
        }
    };
    return View(model);
}

[HttpPost]
public ActionResult Index(MyViewModel model)
{
    // TODO: process the selected values here
    ...
}

public ActionResult Filter(int filterId)
{
    return Json(_repository.GetSelectionItems(filterId));
}

public ActionResult BlankEditorRow()
{
    var filters = ... fetch from repo and map to view model
    var selectionItems = ... fetch from repo and map to view model

    var model = new ItemViewModel
    {
        SelectedFilterId = 1,
        FilterListItems = filters,
        SelectionListItems = selectionItems
    };
    return PartialView("~/views/home/editortemplates/itemviewmodel.cshtml", model);
}

The next step would be to define the ~/Views/Home/Index.cshtml view:

@model MyViewModel
@using (Html.BeginForm())
{
    <ul id="editorRows">
        @Html.EditorFor(x => x.Items)
    </ul>
    <button type="submit">OK</button>
}

@Html.ActionLink("Add another group of ddls", "BlankEditorRow", null, new { id = "addItem" })

and the corresponding editor template (~/Views/Home/EditorTemplates/ItemViewModel.cshtml):

@model ItemViewModel
<li>
    <div>   
        @using (Html.BeginCollectionItem("items"))
        {
            @Html.DropDownListFor(
                x => x.SelectedFilterId,
                new SelectList(Model.FilterListItems, "Value", "Text", Model.SelectedFilterId),
                new { @class = "filter", data_filter_url = Url.Action("Filter") }
            )

            @Html.DropDownListFor(
                x => x.SelectedId,
                new SelectList(Model.SelectionListItems, "Value", "Text", Model.SelectedId),
                new { @class = "selection" }
            )
        }
    </div>
</li>

The last step is to wire up the cascading drop down list and the possibility to add groups dynamically using javascript. So in a separate js file:

var filterChange = function () {
    $('ul .filter').unbind('change').bind('change', function () {
        $.ajax({
            url: $(this).data('filter-url'),
            type: 'GET',
            cache: false,
            data: { filterId: $(this).val() },
            context: { selectionDdl: $(this).closest('li').find('.selection') },
            success: function (items) {
                var selectionDdl = this.selectionDdl;
                selectionDdl.empty();
                $.each(items, function (index, item) {
                    selectionDdl.append($('<option/>', {
                        value: item.ID,
                        text: item.Name
                    }));
                });
            }
        });
    });
};

$(function () {
    filterChange();

    $('#addItem').click(function () {
        $.ajax({
            url: this.href,
            cache: false,
            success: function (html) {
                $('#editorRows').append(html);
                filterChange();
            }
        });
        return false;
    });
});

Upvotes: 2

Related Questions