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

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


четверг, 28 июля 2011 г.

MVC - часть3. CRUD логика (Detail)


Итак, в прошлой статье мы получили список объектов карты. Возле каждого объекта у нас имеется ссылки, в том числе и «Details». Вот о ней и поговорим. Сама по себе страница с подробностями об объекте не так интересна, но у нас же карта, так давайте вместе с подробностями отобразим небольшую карту, где бы отображался этот самый объект.
Сперва пишем экшен контроллера (листинг 1), а затем генерируем представление (листинг 2).
Листинг 1Экшен контроллера
        [HttpGet]
        public ActionResult MapObject_Details(long id)
        {
            if (id != 0)
            {
                DataManager db = new DataManager();

                return View(db.GetMapObject(id));
            }
            else
            {
                return View();
            }
        }
Листинг 2Представление Details
<fieldset>
    <legend>Map</legend>

    <div class="display-label">ObjName</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.ObjName)
    </div>

    <div class="display-label">ObjAddress</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.ObjAddress)
    </div>

    <div class="display-label">ObjType</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.ObjType_id)
    </div>

    <div class="display-label">Description</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Description)
    </div>

    <div class="display-label">Contact</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Contact)
    </div>

    <div class="display-label">Longitude</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Longitude)
    </div>

    <div class="display-label">Latitude</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Latitude)
    </div>

    <div class="display-label">ObjType_id</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.ObjType_id)
    </div>

    <div class="display-label">Author</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Author)
    </div>

    <div class="display-label">CreateDate</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.CreateDate)
    </div>
</fieldset>

Дополним эту разметку еще одним полем – скрытым:
@Html.HiddenFor(model => model.MapObj_id)

В этом скрытом поле на нашей странице будет присутствовать идентификатор объекта. Зачем он нам? А мы по его значению будем на карту грузить его балун. Рассмотрим, как. Для начала сделаем метод нашего менеджера данных, который по идентификатору будет отдавать данные об объекте (листинг 3).
Листинг 3 – Получаем данные объекта по идентификатору
        public List<object> GetMapObjectById(long mapObjId)
        {
            List<object> list = new List<object>();

            Map item = GetMapObject(mapObjId);

            var data = new
            {
                Name = item.ObjName,
                Style = item.MapObjType.Style,
                Latitude = item.Latitude,  //широта
                Longitude = item.Longitude, //долгота
                Html = "<table class='info'>" +
                        "<tr><th>Название</th><td>" + item.ObjName + "</td></tr>" +
                        "<tr><th>Адрес</th><td>" + item.ObjAddress + "</td></tr>" +
                        "<tr><th>Описание</th><td>" + item.Description + "</td></tr>" +
                        "<tr><th>Контакты</th><td>" + item.Contact + "</td></tr>" +
                        "<tr><th>Оценка</th><td>" + ((item.MiddleRating != 0) ? item.MiddleRating.ToString() : "нет") + "</td></tr>" +
                        "<tr><th><a href='/Home/Comment/" + item.MapObj_id + "'>Отзывы</a></th><td></td></tr>" +
                        "</table>",      //код для балуна
            };

            list.Add(data);

            return list;
        }

Как видно, тут мы формируем код балуна, который позже загрузим в js-скрипте.
Теперь пишем экшен, который запакует это все дело в JSON (листинг 4).
Листинг 4 – Экшен, передающий данные в представление
        [HttpGet]
        public JsonResult ObjectDetails(long mapObjId)
        {
            DataManager db = new DataManager();

            return Json(db.GetMapObjectById(mapObjId), JsonRequestBehavior.AllowGet);
        }

Остался скрипт, который загрузит наш JSON на карту (листинг 5).
Листинг 5 – Скрипт отображения данных на карте
<script src="http://api-maps.yandex.ru/1.1/index.xml?key=AAFDuU0BAAAAWmDlWgMAn8sg2dtfRwaCuNPYn0etp5qnk6QAAAAAAAAAAAAwqKOA3GzAs8EObkeouS3eI9xZMA=="
        type="text/javascript">
    </script>

    <script type="text/javascript" src="../../Scripts/jquery-1.5.1.min.js">
    </script>

    <script type="text/javascript" >
        var group = [];
        var map;

        $(document).ready(function () {
            // Найдем на странице html элемент в котором будет размещаться карта
            map = new YMaps.Map(YMaps.jQuery("#YMapsID")[0]);

            map.setCenter(new YMaps.GeoPoint(55.975669, 54.732613), 10);

            // добавим контролы
            map.addControl(new YMaps.ToolBar());
            map.addControl(new YMaps.Zoom());
            map.addControl(new YMaps.TypeControl());
            map.enableScrollZoom();

            var searchControl = new YMaps.SearchControl({
                resultsPerPage: 2,  // Количество объектов на странице
                useMapBounds: 1     // Объекты, найденные в видимой области карты
                // будут показаны в начале списка
            });
            map.addControl(searchControl);

            getObject(document.getElementById('MapObj_id').value);
        });

        // объект по идентификатору
        function getObject(mapObjId) {

            $.getJSON("/Home/ObjectDetails", { mapObjId: mapObjId }, function (mapObj) {

                var s;

                group["new"] = new YMaps.GeoObjectCollection("default#");

                $.each(mapObj, function (i) {

                    // Создание стиля для значка метки
                    s = new YMaps.Style();
                    s.iconStyle = new YMaps.IconStyle();
                    s.iconStyle.href = this.Style;
                    s.iconStyle.size = new YMaps.Point(18, 18);
                    s.iconStyle.offset = new YMaps.Point(-9, -9);
                    s.balloonContentStyle = new YMaps.BalloonContentStyle(
                            new YMaps.Template("<div>$[description]</div>")
                    );

                    var point = new YMaps.GeoPoint(this.Longitude, this.Latitude);
                    var placemark = new YMaps.Placemark(point, { hasHint: 1, style: s });
                    placemark.name = this.Name;

                    //тут засовываем html
                    placemark.description = this.Html + "<br />";

                    group["new"].add(placemark);
                });

                map.addOverlay(group["new"]);
            });
        };

    </script>

В общем то, схема уже нам знакома, комментировать нечего.
Результат на рисунке 1.

Рисунок 1 – Подробности об объекте карты