Задача
Мне требуется сделать следующую, довольно простую штуку: есть два комбо, в одном перечислены страны, а в другом надо обновлять список городов для данной страны. Т.е. выбрали мы, например, Russia в первом комбо, во втором должны появиться Moscow, SPb и так далее. И естественно, без перезагрузки страницы.
План
Задача на первый взгляд простая и даже тривиальная, и решается она действительно просто. Но давайте в учебных целях подробно рассмотрим план решения.
1. БД - в базе должны были страны и города
2. Entity Framework - интерфейс для доступа к данным.
3. Модель - должен обеспечивать как выдачу списка доступных стран, так и списка городов для страны.
4. Контроллер - должен иметь методы действий для отображения всей страницы (на которой размещаются комбо) и части (обновление второго комбо).
5. Представления - отображение данных.
6. Ajax (prototype) - код для подтягивания данных по событию комбо.
1. База данных
Заведем две таблицы - города и страны. Я не очень доверяю числовым идентификаторам, т.е. я им доверяю, но связывать такие справочные таблицы предпочитаю с помощью более долговечных вещей. Поэтому я ввожу поле кода для страны, а в таблице городов делаю ссылку на это поле (FK т.е.).
Тут есть еще дистрикты, но они нам в данный момент не интересны.
После этого можно забить нужные вам данные (в дальнейшем это, конечно, надо делать с помощью админского сайта).
2. Entity Framework
После создания таблиц надо сгенерировать код для доступа к ним. Можно просто апдейтить нашу модель, но у меня, например, студия сглюкнула и пришлось удалять модель полностью, а затем создавать заново. Чтобы обновить модель, надо перейти в режим Model browser (дважды щелкнуть на файле .edmx - мне такой способ не очень нравится, но другого я не знаю). Затем выбрать корневой элемент в дереве и выполнить команду Update model from database. Далее вы просто выбираете новые таблицы и жмете Finish. Если все пройдет нормально, вы получите обновленную модель и обновленный код доступа к данным.
3. Модель
В каталоге Models проекта создадим два класса - с помощью первого будем извлекать статические данные для первого комбобокса (страны), с помощью второго - динамического для комбо с городами.
Итак, первый класс - StaticData - содержит одно публичное свойство и приватный метод, который забирает из базы список стран.
public class StaticData
{
public List<Country> CountryList
{
get
{
return GetAllCountries();
}
}
private static List<Country> GetAllCountries()
{
List<Country> countries = null;
using (DataCoreConnection context = new DataCoreConnection())
{
countries = (from c in context.Country
select c).ToList();
}
return countries;
}
}
Код простейший, добавлю лишь, что DataCoreConnection - это класс типа System.Data.Objects.ObjectContext из файла, сгенерированного при построении модели.
Второй класс аналогичен первому, за исключением конструктора, в который передается код страны:
public class Cities
{
private string countryCode;
public Cities(string CountryCode)
{
countryCode = CountryCode;
}
public List<City> CitiesForCountry
{
get
{
return GetCitiesForCountry(countryCode);
}
}
private List<City> GetCitiesForCountry(string CountryCode)
{
List<City> list = null;
using (DataCoreConnection context = new DataCoreConnection())
{
list = (from c in context.City
where c.Country.Code == CountryCode
select c).ToList();
}
return list;
}
}
4. Контроллер
Контроллер у нас уже есть - HomeController (поскольку наши комбобоксы располагаются на главной странице). Сюда мы добавим один-единственный метод:
public void UpdateCities(string code)
{
Cities cities = new Cities(code);
RenderView("SelectCity", cities);
}
Здесь мы всего лишь получаем наши города и передаем экземпляр объекта представлению SelectCity.
Не забываем о роутинге - в файле Global.asax добавим строку:
routes.Add(new Route("Home/UpdateCities/{code}", new RouteValueDictionary(new { controller = "Home", action = "UpdateCities" }),
new MvcRouteHandler()));
Обратите внимание на последний элемент - {code} - это код города, который передается контроллеру. Контроллер принимает его как string code.
5. Представления - отображение данных.
Контролы помещаются на странице (т.е. представлении) Index.aspx. Поскольку страница с момента загрузки должна содержать некоторые статические данные, это должна быть не просто ViewPage, а в нашем случае ViewPage<StaticData>. Сначала отобразим комбобокс со списком доступных стран:
Как видим, мы получаем данные напрямую из ViewData, как будто ViewData - это объект типа StaticData (так оно и есть). Очень удобно. Кого-то может смутить тот факт, что разметка перемешивается с кодом. Как же так? Ведь обещали полное отделение кода! Мммм... ну.... в-общем, ничего страшного. Кода все-таки очень мало, и мне он кажется логическим дополнением к разметке.
Обращаю внимание на объявление обработчика:
onchange="UpdateCitiesList();"
Это функция JavaScript, которая будет подтягивать список городов при изменении значения комбобокса со странами (эту функцию рассмотрим чуть позже).
А пока займемся представлением, обновляющим второй комбобокс. Да-да, комбобокс будет обновлен целым представлением! Добавляем новое представление SelectCity, которое, конечно, объявляем так:
public partial class SelectCity : ViewPage<Cities>
{
}
А вот код, генерирующий содержимое комбобокса:
6. Ajax
Ну а теперь посмотрим, какая функция подтягивает список городов (вернемся к Index.aspx):
1: <script type="text/javascript" language="javascript">
2: function UpdateCitiesList()
3: {
4: if ($('selCountry').value == "0")
5: return;
6: var url = '/Home/UpdateCities/' + $('selCountry').value;
7: new Ajax.Updater('selCities', url,
8: {
9: method: 'get'
10: }
11: );
12: }
13: </script>
В строке 4 мы проверяем, не выбран ли нулевой элемент выпадающего списка (тот, который содержит текст "Select ..."). В этом случае просто покидаем функцию. Далее формируем url, к которому будем обращаться - это обычный адрес в MVC, нам надо вызвать контроллер Home, метод UpdateCities, и передать код страны. В строке 7 мы вызываем очень удобный метод Ajax.Updater - он обновляет содержимое нужного нам контрола. Сигнатура метода проста:
new Ajax.Updater(container, url[, options])
Container - это контрол, который требуется обновить; url - вызываемый url; options - мы указали только метод передачи запроса (полный список параметров см. на сайте Prototype).
Как только мы выбираем страну, срабатывает событие onchange выпадающего списка, и Ajax.Updater обновляет комбобокс городов. Все! По-моему, все выглядит просто и красиво.