вторник, 9 августа 2011 г.

MVC. Каскадные выпадающие списки


Возникла необходимость добавить в проект MVC каскадные выпадающие списки (Cascade DropDownList). Т.е., чтоб при выборе значения в первом списке, соответственно обновлялся второй список. Рассмотрим этот случай на примере предыдущих статей. Пример будет несколько притянут за уши, но смысл будет понятен, думаю.
Конечно же, нашим спасителем опять станет jQuery, без него никак. Итак, начнем.
В нашей таблице Map есть поле ObjType, которое указывает тип объекта – автосервис/азс. Пусть автосервисы делятся на собственно СТО и пункты замены масла, а азс – на собственно азс и агзс (газовая азс). Тогда в первом списке должно быть «1, 2», а во втором или «СТО, пункт замены масла», или «азс, агзс». Для хранения видов объектов создадим таблицу (листинг 1).
Листинг 1 – Виды объектов
CREATE TABLE [dbo].[MapObjectType] (
  [MapObjType_id] bigint IDENTITY(1, 1) NOT NULL,
  [ObjType] int NOT NULL,
  [TypeName] nvarchar(64) COLLATE Cyrillic_General_CI_AS NULL,
  CONSTRAINT [MapObjectType_pk] PRIMARY KEY CLUSTERED ([MapObjType_id])
)

Никаких внешних ключей делать не будем, т.к. этот пример у нас искусственный, но в боевых условиях ключи делать обязательно.
В MapDataModel.edmx делаем обновление, чтоб сгенерировались сущности для новой таблицы. Теперь в представлении для редактирования нам надо заменить @Html.EditorFor() на @Html.DropDownListFor() для поля ObjType. Для этого нужно сформировать список, в котором «сидят» все возможные ObjType. Для формирования этого списка создадим специальную модель EntitySetToList.cs (листинг 2).
Листинг 2 – Формирования списка для DropDownList
    public static class EntitySetToList
    {
        public static List<SelectListItem> ConvertParent()
        {
            DataManager dm = new DataManager();

            List<SelectListItem> items = new List<SelectListItem>();

            var parent = dm.GetMapObjets;

            foreach (var item in parent)
            {
                items.Add(new SelectListItem
                {
                    Text = item.ObjType.ToString(),
                    Value = item.ObjType.ToString()

                });
            }

            return items;
        }
    }

Тут мы получаем перечень всех объектов карты и в цикле формируем список типа List<SelectListItem>, который будет удобоварим нашему DropDownList'у. Тепер можно сформировать и сам DropDownList (листинг 3). Напомню, что им мы заменяем стандартное поле для редактирования.
Листинг 3DropDownList для типов объектов
        <div class="editor-field">
            @Html.DropDownListFor(model => model.ObjType, MvcApplication3.Models.EntitySetToList.ConvertParent(), "---Выбрать тип объекта---")
        </div>

Отлично! Запускаем проект – и что видим? Выпадающий список есть, а в нем несколько строчек с типом «1» и несколько – «2». Почему? Потому что в нашей таблице Map несколько записей с этими типами. Что делать? Надо как то сгруппировать это все дело. Идем обратно в модель, которая формирует нам список, и вносим изменения (листинг 4).
Листинг 4 – Обновление формирования списка для DropDownList
        public static List<SelectListItem> ConvertParent()
        {
            DataManager dm = new DataManager();

            List<SelectListItem> items = new List<SelectListItem>();

            var parent = dm.GetMapObjets().GroupBy(c => c.ObjType);

            foreach (var item in parent)
            {
                items.Add(new SelectListItem
                {
                    Text = item.Key.ToString(),
                    Value = item.Key.ToString()
                });
            }

            return items;
        }

Видно, что мы сгруппировали список объектов карты. Запускаем проект – видим, что в списке остались только два значения. Прекрасно! Теперь займемся подчиненным списком. Изначально он у нас будет пустым, поэтому в представление ниже родительского списка добавляем пустой подчиненный (листинг 5).
Листинг 5 – Добавление подчиненного списка
        <div class="editor-label">
            @Html.Label("Объект")
        </div>
        <div class="editor-field">
            @Html.DropDownList("TypeName", Enumerable.Empty<SelectListItem>())
        </div>

Обучим теперь наш DataManager получать список видов объектов по его типу (листинг 6).
Листинг 6 – Формирование списка видов объектов
        public List<object> GetMapObjetTypeName(long objType)
        {
            List<object> list = new List<object>();

            foreach (MapObjectType item in _db.MapObjectType)
            {
                if (item.ObjType == objType)
                {
                    var data = new
                    {
                        Id = item.ObjType,
                        Name = item.TypeName
                    };

                    list.Add(data);
                }
            }

            return list;   
        }

Принцип прост – если тип объекта соответствует заданному, то он добавляется в итоговый список. Далее нам нужен экшен, который бы передавал этот список представлению (листинг 7).
Листинг 7 – Экшен для формирования подчиненного списка
        [HttpGet]
        public JsonResult TypeNameById(long objType)
        {
            DataManager db = new DataManager();

            return Json(db.GetMapObjetTypeName(objType), JsonRequestBehavior.AllowGet);
        }

Тут мы получаем от DataManager'а список видов объектов и сериализуем его в JSON. А теперь самое интересное – нам нужно получить этот список из представления и засунуть его в подчиненный список. В дело вступает jQuery (листинг 8).
Листинг 8 – Каскадное обновление
function CascadeDDL(parentDDL, childDDL, actionName) {

    var selectedParent = $(parentDDL).val();

    if (selectedParent != null && selectedParent != '') {

        $.getJSON(actionName, { objType: selectedParent }, function (child) {

            var selectedChild = $(childDDL);

            selectedChild.empty();

            $.each(child, function (index, child) {

                selectedChild.append($('<option/>', {

                    value: child.Id,

                    text: child.Name
                }));
            });
        });
    }
}

Функция получает на вход имя родительского и дочернего списков и имя экшена, который будет с ними работать. Далее определяется выделенный элемент родительского списка и отправляется экшену, который формирует JSON для дочернего списка и отдает его представлению. Далее по очереди все элементы этого JSON добавляются в дочерний список. Все просто! Осталось только задействовать функцию каскадного обновления списков (листинг 9).
Листинг 9 – Подключение каскадного обновления списков
<script src="@Url.Content("../../Scripts/DropDownCascade.js")"></script>
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")"></script>

<script type="text/javascript">

    $('#ObjType').change(function () {

        CascadeDDL("#ObjType", "#TypeName", "/Home/TypeNameById");

    });
   
</script>

Запускаем проект, тестируем, радуемся.
Удачи!

1 комментарий:

Анонимный комментирует...

Опишите пожалуйста, как создать представление Edit, иначе у меня возникает ошибка.