ASP.NET MVC: DataSource на JavaScript или обертка на Web API сервис (часть 2)
Что к чему
В прошлой статье есть ссылка на проект, его мы и будем доводить до ума. В противном случае, вы можете скачать уже обновленный проект и поэкспериментировать с ним (ссылка в конце статьи)
Pager
Чтобы заработал пейджинг, надо добавить функционал разбития на страницы на стороне Web API. Добавим обработку Index и Size:
1: public HttpResponseMessage GetPersons(JsonQueryParams query) {
2: var size = query.Size.HasValue ? query.Size.Value : 10;
3: var items = _listOfPerson.OrderBy(x => x.Name).AsQueryable();
4: if (query.Index.HasValue) {
5: items = items.Skip(size * query.Index.Value).Take(size);
6: }
7:
8: return Request.CreateResponse(HttpStatusCode.OK,
9: new {
10: success = "все данные",
11: total = _listOfPerson.Count,
12: items = items.ToList()
13: });
14: }
После доработки наша страница отобразила только 10 записей.
Чтобы а нас появился пейджер, надо просто немного поправить html-разметку. Я добавил одну строку под таблицу:
1: @{
2: ViewBag.Title = "DataSource: Master/Details";
3: }
4:
5: <spandata-bind="text: clock.time"></span>
6:
7: <table>
8: <thead>
9: <tr>
10: <th>Name</th>
11: <th>Age</th>
12: <th>Weight</th>
13: </tr>
14: </thead>
15: <tbodydata-bind="foreach: dsPerson.items">
16: <tr>
17: <tddata-bind="text: name"></td>
18: <tddata-bind="text: age"></td>
19: <tddata-bind="text: weight"></td>
20: </tr>
21: </tbody>
22: </table>
23:
24: <divdata-bind="pager: dsPerson"></div>
25:
26: @section scripts
27: {
28: <scriptsrc="~/Scripts/app/site.m.person.js"></script>
29: <script src="~/Scripts/app/site.vm.homeIndex.js"></script>
30: }
Строка 24 подключила пейджер на страницу:
Внешний вид или ода Twitter Bootstrap
Немного не приглядный вид, давайте подключим какой-нибудь стиль или несколько. Я воспользуюсь Twitter Bootstrap. Я скачал bootstrap.zip распаковал его в папку bootstrap и добавил ссылки на файлы в BundleConfig. Запистил проект, нажал F5 и …:
BusyIndicator
Теперь более приглядный вид. Добавим немного WEB 2.0, то есть увеличим индекс дружелюбности :) Для этого выведем индикатор обработки запроса.
Для того чтобы заработал BusyIndicator, я немного поправил Index.cshtml:
1: <divdata-bind="blockUI: dsPerson.indicator">
2:
3: <tableclass="table table-bordered">
4: <thead>
5: <tr>
6: <th>Name</th>
7: <th>Age</th>
8: <th>Weight</th>
9: </tr>
10: </thead>
11: <tbodydata-bind="foreach: dsPerson.items">
12: <tr>
13: <tddata-bind="text: name"></td>
14: <tddata-bind="text: age"></td>
15: <tddata-bind="text: weight"></td>
16: </tr>
17: </tbody>
18: </table>
19:
20: <divdata-bind="pager: dsPerson"></div>
21: </div>
А если быть точнее, то я просто обернул весь (смотри строка 1 и 21) контент в div, который блокируется, чтобы избежать команд пользователя во время выполнения запроса.
Управления размером страниц (Pager size)
Я добавил еще немного html-разметки:
1: <spanclass="pull-right"data-bind="if: dsPerson.hasItems()">
2: <spanclass="icon-eye-open"></span>
3: <selectdata-bind="options: site.cfg.pageSizes,
value: dsPerson.queryParams.size"></select>
4: <spanclass="icon-filter"></span><spandata-bind="
text: dsPerson.queryParams.total"></span>
5: </span>
И после этого, у меня появилась возможность выбрать размер страницы, а так же смотреть общее количество записей. Вот установлен размер страницы равный 5:
Простая фильтрация на основе QueryParams
А теперь добавим возможность фильтровать пользователей по имени. Для это надо доработать метод сервиса:
1: public HttpResponseMessage GetPersons(JsonQueryParams query) {
2: var items = _listOfPerson.OrderBy(x => x.Name).AsQueryable();
3: if (query != null) {
4: var size = query.Size.HasValue ? query.Size.Value : 10;
5: if (query.Filters != null && query.Filters.FilterParams.Select(x => x.Name).Contains("Name")) {
6: var param = query.Filters.FilterParams.FirstOrDefault(x => x.Name.Equals("Name"));
7: if (param != null && param.Value != null) {
8: var filter = param.Value.ToString();
9: if (!string.IsNullOrEmpty(filter)) {
10: items = items.Where(x => x.Name.Contains(filter));
11: }
12: }
13: }
14: if (query.Index.HasValue && items.Count() > size) {
15: items = items.Skip(size * query.Index.Value).Take(size);
16: }
17: }
18: return Request.CreateResponse(HttpStatusCode.OK,
19: new {
20: success = "все данные",
21: total = items.Count(),
22: items = items.ToList()
23: });
24: }
Следует обратить внимание на строки 5-13, где проверяется наличие параметра в списке фильтров. После этого надо расширить queryParams для DataSource:
1: site.vm.homeIndex = function () {
2: var clock = new site.controls.Clock(),
3: queryParamsFilter = {
4: "filters": {
5: "logicalOperator": "And",
6: "filterParams": [
7: {
8: "Name": "Name",
9: "Operator": "Contains",
10: "Value": ko.observable(),
11: "DisplayName": "Имя"
12: }
13: ]
14: }
15: },
16: dsPerson = new site.controls.DataSource({
17: autoLoad: true,
18: service: site.services.person
19: }, queryParamsFilter);
20:
21: dsPerson.queryParams.filters.filterParams[0].Value.subscribe(function () {
22: dsPerson.getData();
23: });
24:
25: return {
26: dsPerson: dsPerson,
27: clock: clock
28: };
29: }();
Строка 3-15: Создаем объект для переопределения настроек по умолчанию для QueryParams, который является структурированным параметром для DataSource.
Строка 10: Указываем, что параметр должен ko.observable().
Строка 21-23: Подписываемся на обновления параметра. При обновлении происходит перезагрузка данных. Если учесть, что измененный параметр (в силу магии KnockoutJs) сразу же применяется к QueryParams, то нам достаточно просто перезапросить новый набор данных с учетом фильтра.
Осталось добавить поле для ввода значения фильтра:
1: <input type="text" data-bind="value: dsPerson.queryParams.filters.filterParams[0].Value,
2: valueUpdate: 'afterkeydown'" />
Поле ввода напрямую привязываю к параметру фильтрации и запускаю приложение. Для того чтобы обновить скрипты на странице, нажимаю F5 и вводу букву “J” в поле фильтра:
Master/Details
Как известно, в связки “Master/Details” используется два источника данных. А зависимость между ними сводится к простой формуле: “Обновился главный – обнови зависимые”. В нашем примере уже есть один источник данных, для второго придется сделать практически те же самые манипуляции: Web API сервис, JavaScript обертку и всё остальное.
Хорошим примером для построения такой зависимости, я построю связку на двух классах из пакета SampleData. Класс Person и класс Department связаны по типу связи “мастер/детализация”. Создадим Web API контролер для класса Department:
1: publicclass DepartmentApiController : ApiController {
2:
3: privatereadonly List<Department> _listOfPerson = new List<Department>();
4:
5: public DepartmentApiController() {
6: _listOfPerson.AddRange(People.GetDepartments());
7: }
8:
9: public HttpResponseMessage GetDepartments(JsonQueryParams query) {
10: var items = _listOfPerson.OrderBy(x => x.Name).AsQueryable();
11: var total = _listOfPerson.Count;
12: if (query != null) {
13: var size = query.Size.HasValue ? query.Size.Value : 10;
14: if (query.Filters != null
15: && query.Filters
16: .FilterParams
17: .Select(x => x.Name)
18: .Contains("Name")) {
19: var param = query.Filters.FilterParams
20: .FirstOrDefault(x => x.Name.Equals("Name"));
21: if (param != null && param.Value != null) {
22: var filter = param.Value.ToString();
23: if (!string.IsNullOrEmpty(filter)) {
24: items = items.Where(x => x.Name.Contains(filter));
25: total = items.Count();
26: }
27: }
28: }
29: if (query.Index.HasValue && items.Count() > size) {
30: items = items.Skip(size * query.Index.Value).Take(size);
31: }
32: }
33: return Request.CreateResponse(HttpStatusCode.OK,
34: new {
35: success = "все данные",
36: total,
37: items = items.ToList()
38: });
39: }
40:
41: public HttpResponseMessage GetDepartment(int id) {
42: return Request.CreateResponse(HttpStatusCode.OK,
43: new {
44: success = "один по идентификатору",
45: item = _listOfPerson.Where(x => x.Id.Equals(id))
46: });
47: }
48:
49: public HttpResponseMessage PostDepartment(Department department) {
50: thrownew NotImplementedException();
51: }
52:
53: public HttpResponseMessage PutDepartment(Department department) {
54: thrownew NotImplementedException();
55: }
56:
57: public HttpResponseMessage DeleteDepartment(Department department) {
58: thrownew NotImplementedException();
59: }
60: }
Web API cервис успешно запустился, теперь сделаем JavaScript-обертка сервиса. Я не буду приводить его код потому что, практические нет никакого отличия от сервиса для Web API Person. Я также добавил упоминание о нем в файл BundleConfig.cs (строка 7).
1: bundles.Add(new ScriptBundle("~/bundles/site").Include(
2: "~/Scripts/app/site.core.js",
3: "~/Scripts/app/site.core.js",
4: "~/Scripts/app/site.controls.js",
5: "~/Scripts/app/site.bindingHandlers.js",
6: "~/Scripts/app/site.services.person.js",
7: "~/Scripts/app/site.services.department.js",
8: "~/Scripts/app/site.utils.js"));
В сервисе site.services.department.js упоминается site.m.Department, и мне потребуется создать класс ViewModel на JavaScript для Department:
1: (function (site, ko) {
2:
3: site.m.Department = function (dto) {
4: var me = this, data = dto || {};
5:
6: me.id = ko.observable(data.Id);
7: me.name = ko.observable(data.Name);
8:
9: me.selected = ko.observable(false);
10:
11: return me;
12: };
13: })(site, ko)
Для того чтобы показать два источника данных рядом, я немного поправил html-разметку, предварительно добавив код для отображения dsDepartment:
1: <div data-bind="blockUI: dsDepartment.indicator"class="span6">
2: <div class="pull-left">
3: <i class="icon-filter"></i>
4: <input type="text"
5: data-bind="value: dsDepartment.queryParams.filters.filterParams[0].Value,
6: valueUpdate: 'afterkeydown'" class="span2" />
7: </div>
8:
9: <div class="pull-right" data-bind="if: dsDepartment.hasItems()">
10: <i class="icon-eye-open"></i>
11: <select data-bind="options: site.cfg.pageSizes,
12: value: dsDepartment.queryParams.size" class="span1"></select>
13: <i class="icon-filter"></i>
14: <span data-bind="text: dsDepartment.queryParams.total"></span>
15: </div>
16:
17: <table class="table table-bordered">
18: <thead>
19: <tr>
20: <th>Name</th>
21: </tr>
22: </thead>
23: <tbody data-bind="foreach: dsDepartment.items">
24: <tr>
25: <td data-bind="text: name"></td>
26: </tr>
27: </tbody>
28: </table>
29:
30: <div data-bind="pager: dsDepartment"></div>
31: </div>
Отличия от dsPerson вообще никакого нет. (В будущем планируется сделать контрол типа GridView для DataSource). После всех нововведений мне остается во ViewModel страницы добавить еще один DataSource, тот самый – dsDepartment:
1: site.vm.homeIndex = function () {
2: var clock = new site.controls.Clock(),
3: queryParamsFilter = {
4: "filters": {
5: "logicalOperator": "And",
6: "filterParams": [
7: {
8: "Name": "Name",
9: "Operator": "Contains",
10: "Value": ko.observable(),
11: "DisplayName": "Имя"
12: }
13: ]
14: }
15: },
16: queryParamsFilter0 = {
17: "filters": {
18: "logicalOperator": "And",
19: "filterParams": [
20: {
21: "Name": "Name",
22: "Operator": "Contains",
23: "Value": ko.observable(),
24: "DisplayName": "Имя"
25: }
26: ]
27: }
28: },
29: dsPerson = new site.controls.DataSource({
30: autoLoad: true,
31: service: site.services.person
32: }, queryParamsFilter),
33: dsDepartment = new site.controls.DataSource({
34: autoLoad: true,
35: service: site.services.department
36: }, queryParamsFilter0);
37:
38: dsPerson.queryParams.filters.filterParams[0].Value.subscribe(function () {
39: dsPerson.getData();
40: });
41:
42: dsDepartment.queryParams.filters.filterParams[0].Value.subscribe(function () {
43: dsDepartment.getData();
44: });
45:
46: return {
47: dsDepartment: dsDepartment,
48: dsPerson: dsPerson,
49: clock: clock
50: };
51: }();
Строка 16-28: Создаем параметр для dsDepartment.
Строка 33-36: Создаем еще один DataSource, параметром для service передаем наш свежеиспеченный site.services.department.
Строка 47: Не забываем “вытащить” наружу объект для UI.
В результате можно увидеть следующее:
У нас на данный момент два DataSource, которые не связаны ни коим образом между собой, но у обоих уже работает постраничная выборка и минимальная фильтрация по полю name.
Selected = true? Легко!
1. Для начала надо отключить автоматическую загрузку записей для dsPerson установив свойство autoLoad в значение false.
2. Теперь надо немного подправить разметку для dsDepartment. Надо сделать чтобы при клике на запись Department эта запись становилась выбранной, то есть свойство selected получало значение true.
1: <table class="table table-bordered">
2: <thead>
3: <tr>
4: <th>Name</th>
5: </tr>
6: </thead>
7: <tbody data-bind="foreach: dsDepartment.items">
8: <tr data-bind="css: {'info':selected}, click: $parent.dsDepartment.select">
9: <td data-bind="text: name"></td>
10: </tr>
11: </tbody>
12: </table>
Строка 8: Привязывает событие click на изменение свойства selected. А также визуально подсвечиваем выбранную строку устанавливая CSS для этой строки в значение info.
Осталось совсем немного: надо добавить параметр DepartmentId в dsPerson и подписаться на изменение этого значения у dsDepartment.
Новый параметр для dsPerson выглядит таким образом (строка 11-16):
1: queryParamsFilter = {
2: "filters": {
3: "logicalOperator": "And",
4: "filterParams": [
5: {
6: "Name": "Name",
7: "Operator": "Contains",
8: "Value": ko.observable(),
9: "DisplayName": "Имя"
10: },
11: {
12: "Name": "DepartmentId",
13: "Operator": "IsEqualTo",
14: "Value": ko.observable(),
15: "DisplayName": "Идентифиактор подразделения"
16: }
17: ]
18: }
19: },
У DataSource (dsDepartment) подписываемся на событие выбора Department (строка 5):
1: dsDepartment = new site.controls.DataSource({
2: autoLoad: true,
3: service: site.services.department,
4: events: {
5: selectedHandler: reloadPersons
6: }
7: }, queryParamsFilter0);
Конструкция работает так, как и предполагалось: При выборе подразделения, происходит обновление dsPerson.
Кстати, не забудьте добавить обработку параметра DepartmentId в Web API сервисе.
Заключение
DataSource достаточно гибкий контрол для работы на UI. Он может очень многое, например:
- добавлять
- удалять
- редактировать
- получать список
- получать под ID
- выбирать
- работать с коллекцией объектов (не Web API)
В настоящий момент уже существуют некоторые вспомогательные контролы, которые дополняют функционал DataSource:
- FormView – контрол для отображения модального окна с подменяемым шаблоном.
- FormEdit – контрол для редактирования в модальном окне сущности с отслеживанием статуса изменения свойств сущности.
- TreeView – контрол для отображение древовидной структоры.
- DbLookUp – контрол подбора комплексных сущностей при редактировании.
Ссылки
Скачать проект для экспериментов
Подробнее: http://feedproxy.google.com/~r/blogmusor/~3/8rn6zxzcsIQ/134
Дайджест новых статей по интернет-маркетингу на ваш email
Новые статьи и публикации
- 2024-11-15 » Перенос сайта на WordPress с одного домена на другой
- 2024-11-08 » OSPanel 6: быстрый старт
- 2024-11-08 » Как установить PhpMyAdmin в Open Server Panel
- 2024-09-30 » Как быстро запустить Laravel на Windows
- 2024-09-25 » Next.js
- 2024-09-05 » OpenAI рассказал, как запретить ChatGPT использовать содержимое сайта для обучения
- 2024-08-28 » Чек-лист: как увеличить конверсию интернет-магазина на примере спортпита
- 2024-08-01 » WebSocket
- 2024-07-26 » Интеграция с Яндекс Еда
- 2024-07-26 » Интеграция с Эквайринг
- 2024-07-26 » Интеграция с СДЕК
- 2024-07-26 » Интеграция с Битрикс-24
- 2024-07-26 » Интеграция с Travelline
- 2024-07-26 » Интеграция с Iiko
- 2024-07-26 » Интеграция с Delivery Club
- 2024-07-26 » Интеграция с CRM
- 2024-07-26 » Интеграция с 1C-Бухгалтерия
- 2024-07-24 » Что такое сторителлинг: техники и примеры
- 2024-07-17 » Ошибка 404: что это такое и как ее использовать для бизнеса
- 2024-07-03 » Размещайте прайс-листы на FarPost.ru и продавайте товары быстро и выгодно
- 2024-07-01 » Профилирование кода в PHP
- 2024-06-28 » Изучаем ABC/XYZ-анализ: что это такое и какие решения с помощью него принимают
- 2024-06-17 » Зачем вам знать потребности клиента
- 2024-06-11 » Что нового в работе Яндекс Метрики: полный обзор обновления
- 2024-06-11 » Поведенческие факторы ранжирования в Яндексе
- 2024-06-11 » Скорость загрузки сайта: почему это важно и как влияет на ранжирование
- 2024-05-27 » Подборка сервисов для расшифровки аудио в текст
- 2024-05-27 » PostgreSQL 16. Изоляция транзакций. Часть 2
- 2024-05-06 » Как настраивать конверсионные стратегии: работа над ошибками
- 2024-04-22 » Комментирование кода и генерация документации в PHP
Великие умы обсуждают идеи, средние - обсуждают поступки, а малые - людей Индийская пословица |
Мы создаем сайты, которые работают! Профессионально обслуживаем и продвигаем их , а также по всей России и ближнему зарубежью с 2006 года!
Как мы работаем
Заявка
Позвоните или оставьте заявку на сайте.
Консультация
Обсуждаем что именно Вам нужно и помогаем определить как это лучше сделать!
Договор
Заключаем договор на оказание услуг, в котором прописаны условия и обязанности обеих сторон.
Выполнение работ
Непосредственно оказание требующихся услуг и работ по вашему заданию.
Поддержка
Сдача выполненых работ, последующие корректировки и поддержка при необходимости.