вторник, 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>

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

четверг, 4 августа 2011 г.

В Уфе Яндекс запустит в небо дирижабль

4 августа интернет-компания «Яндекс» начинает съемку городских панорам с высоты птичьего полета. Съемка будет производиться с беспилотного дирижабля.
Как отмечают в компании, это крупнейший беспилотный радиоуправляемый дирижабль в России: его длина – 12 метров, объем – 57 кубических метров.
Первым городом, в которой запустят дирижабль станет Уфа.
Фотосъемка будет вестись с высоты 150-200 метров. На аппарате установлены пять специальных фотокамер, на которые попадут главные улицы и площади города, а также сад имени Салавата Юлаева.
Вскоре, все снимки мы сможем увидеть на сервисе «Яндекс.Карты».

среда, 3 августа 2011 г.

MVC. Поиск на карте средствами API Яндекс.Карт


Итак, у нас есть карта с кучей объектов, все работает, и все вроде бы прекрасно. Но объектов становится все больше, и ориентироваться в них все сложнее. Поэтому юзеру рано или поздно захочется воспользоваться хотя бы простеньким поиском. Вот и попробуем сделать простенький поиск по названию объекта. Для этого нам нужно окошко для ввода поискового слова, кнопка для запуска поиска и контейнер для списка найденных объектов (у нас это будет просто DropDownList). При выборе в списке найденных объектов какого-либо элемента на карте будет отображаться соответствующий балун. Все это будет сделано на чистом jQuery с использованием API YandexMaps.
Для начала создадим необходимые контролы (листинг 1).
Листинг 1 – Контролы для поиска
    <table>
        <tr>
            <td>
                @Html.Editor("findWord")
            </td>
            <td>
                <input type="button" value="Find" id="findButton" />
            </td>
        </tr>
        <tr>
            <td>
                @Html.DropDownList("ObjName", Enumerable.Empty<SelectListItem>())
            </td>
            <td>
            </td>
        </tr>
    </table>

Теперь пишем функцию поиска объектов по введенному слову (листинг 2).
Листинг 2 – jQuery – функция поиска
        // Функция для поиска объектов среди имеющихся на карте
        // по названию объекта и добавления их в список
        function getFindList() {
           
            // поисковая стркоа
            var searchName = document.getElementById('findWord').value.replace(new RegExp(' ', 'i'), '.+');

            // "регулярка" для поиска
            re = new RegExp(searchName, 'i');

            // произведем поиск
            var result = group["new"].filter(function (overlay) {

                var i = overlay.name.search(re);

                if (i != -1) {

                    return overlay.name;
                }
             });

            // ищем список
            var findChild = $('#ObjName');

            findChild.empty();

            // заполняем
            $.each(result, function (index, resultItem) {

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

                    value: resultItem.name,

                    text: resultItem.name
                }));
            });
        };

Остановимся подробнее на этой функции. Сначала мы получаем наше искомое слово, попутно избавляясь от лишним пробелов в нем. Далее составляем регулярное выражение, по которому будет происходить поиск (подробнее ознакомиться с регулярными выражениями можно тут, ну и Google в помощь). Далее, используя полученную «регулярку», осуществляем поиск методом API Яндекс.Карт. В переменной result у нас будут объекты, названия которых удовлетворяет нашей поисковой строке. Далее уже знакомый код по добавлению найденных значений в выпадающий список. Осталось вызвать эту функцию при клике по кнопке поиска (листинг 3).
Листинг 3 – Вызов поиска при клике по кнопке
        $('#findButton').click(function () {

            getFindList();

        });

Мы получили список объектов, которые удовлетворяют поисковой строке (рисунок 1).

Рисунок 1 – Список найденных объектов

Идем дальше. Нам нужно снова осуществить поиск на карте, но теперь уже по найденным названиям объектов. И теперь совпадение имен должно быть стопроцентным, т.е. никаких регулярных выражений. И, кроме того, необходимо показывать соответствующий балун (листинг 4).
Листинг 4 – Отображение балуна найденного объекта
        // Функция для отображения объекта
        // из списка найденных
        function showObject() {

            var findChild = $('#ObjName');

            var searchName = findChild.val();

            var result = group["new"].filter(function (overlay) {
                return overlay.name == searchName; // Точное совпадение
            });

            if (result[0] != null) {
                result[0].openBalloon();
            }
        };

Думаю, тут все понятно. Осталось вызвать эту функцию при выборе элемента списка (листинг 5).
Листинг 5 – Отображение балуна объекта при выборе его в списке
        $('#ObjName').change(function () {

            showObject();

        });
Собственно, все! Удачи!