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. Он может очень многое, например:

    В настоящий момент уже существуют некоторые вспомогательные контролы, которые дополняют функционал DataSource:

      Ссылки

      Скачать проект для экспериментов

      Подробнее: http://feedproxy.google.com/~r/blogmusor/~3/8rn6zxzcsIQ/134

      Источник: lred.ru

      Оцените статью
      новости для мужчин