пятница, 30 июля 2010 г.

GridView в MVC - jQuery плагины для манипуляции с табличными данными

Не секрет, что важным правилом для любого уважающего себя веб-мастера является - избегать использование тегов <table></table> для построения интерфейса пользователя. В то же время нет более подходящего набора элементов, чем <table>,<th>, <tr>, <td> для структурирования и удобного отображения табличных данных.

Простота генерации табличного контента и возможность назначения CSS-стилей заглавным строкам либо же строкам данных делает этот тег незаменимым при отображении значений, полученных из источников данных. Но простота использования – и преимущество и недостаток данного тега. Если же встает необходимость отображать большие объемы данных, требующие разбиение на страницы, сортировку или фильтрацию - необходимо всю эту логику реализовывать самостоятельно либо же прибегать к компонентам сторонних разработчиков.

Данная статья как раз и поможет подобрать наиболее подходящий компонент для генерации и манипулирования табличными данными.

Для сравнения возмем привычный серверный компонент для ASP.NET WebForms - <asp:GridView>, позволяющий очень просто отобразить табличные данные на странице.

   1: <asp:GridView ID="gvEmployee" runat="server" DataSourceID="dsEmployee" />
   2:  
   3: <asp:SqlDataSource ID="dsEmployee" runat="server" 
   4:     ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>" 
   5:     SelectCommand="SELECT [EmployeeID], [FirstName], [LastName] FROM [Employees]">
   6: </asp:SqlDataSource>

В результате получим следующий контент на странице:


image


За простой записью декларации скрываются сложные механизмы рендеринга контента, получения данных из источника данных, привязки данных к контролу для последующего отображения. Прелесть WebForms для начинающего программиста в том, что сложное может создаваться простой декларацией на странице. Сложные механизмы авторизации можно добавлять к сайту, не вникая в детали их работы; отображение вложенности страниц легко создается простой декларацией .sitemap-файла и многое другое. Для ASP.NET MVC многие тривиальные задачи для WebForms могут превратится в сложные нетривиальные задачи, требующие глубокого освоения клиентских и серверных языков.


На примере все же данной статьи хотелось бы проверить – насколько сложно будет повтрорить огромную функциональность серверного контрола <asp:GridView />в рамках модели MVC.


Хорошо, просто отобразить табличные данные подобно примеру выше не составляет особого труда – выводим их на страницу с помощью foreach-перебора.



<table>
    <tr>
        <th>EmployeeID</th>
        <th>FirstName</th>
        <th>LastName</th>
    </tr>
 
<% foreach (var item in Model) { %>
    <tr>
        <td><%= Html.Encode(item.EmployeeID) %></td>
        <td><%= Html.Encode(item.FirstName) %></td>
        <td><%= Html.Encode(item.LastName) %></td>
    </tr>
<% } %>
 
</table>

Получаем похожий результат:


image


Вполне приемлемый результат. К тому же в этом примере мы имеем полный контроль над генерируемым кодом, так как сами определяем структуру таблицы. Тем самым задаем стили отображения таблицы просто, без особого штудирования MSDN (как в примере с <asp:GridView />)


В реальных приложениях зачастую выводить все содержимое таблицы нецелесообразно, особенно если число записей исчисляется сотнями и тысячами. В этом случае необходимо добавлять пейжинг и выводить лишь небольшую часть данных.


Привычный контрол <asp:GridView> справляется с этой задачей на все 100% без особых усилий – добавляем лишь пару атрибутов:



<asp:GridView ID="gvEmployee" runat="server" DataSourceID="dsEmployee" AllowPaging="true" PageSize="5" />

Результат:


image


MVC-модель в свою очередь заствляет разработчика остановится на данном этапе, откинуться на спинку кресла и предаться размышлениям. Увы, столь простого механизма нету. Делаем чай, ходим по комнате, внимательно протирая глазами дырку в полу – таки да, столь простого механизма нету.


Ну что же – самому реализовывать логику пейжинга слишком лениво, в то время как много плагинов сторонних разработчиков присутствует в сети. Врезультате несложно поиска выделяем для себя сразу несколько компонентов, чтобы сравнивать функциональность.


DataTables – плагин для библиотеки jQuery. Чем особенно интересен плагин, так это возможностью делать гибкую таблицу с пейжингом, сортировками и т.д. из уже сгенерированного кода. Точнее – сначала мы генерируем таблицу, а потом используем плагин для придания этой таблице необходимых нам свойств.


Попробуем на примере.


Подключаем .js библиотеки:


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


Создаем таблицу:


<table id="tblEmployees">
<thead>
<tr>
<th>EmployeeID</th>
<th>FirstName</th>
<th>LastName</th>
</tr>
</thead>
<tbody>
<% foreach (var item in Model) { %>
<tr>
<td><%= Html.Encode(item.EmployeeID) %></td>
<td><%= Html.Encode(item.FirstName) %></td>
<td><%= Html.Encode(item.LastName) %></td>
</tr>
<% } %>
</tbody>
</table>


Обязательно необходимо полно описывать структуру данных, используя теги <thead>, <tbody> – данный плагин использует их для разбиения на заголовок и контент. Пропустив теги <thead> и <tbody>, мы получим ошибку выполнения функции скрипта.


Добавляем следущий код на страницу:


<script type="text/javascript">
$(document).ready(function () {
$("#tblEmployees").dataTable();
});
</script>



Немного корректируем стили отображения элементов:


<style type="text/css">
#tblEmployees_wrapper { border:solid 2px #a4a4a4; width:450px }
#tblEmployees_length { float:left; padding:5px 30px 5px 5px }
#tblEmployees_filter { padding:5px; background-color:#d4d4d4 }
#tblEmployees_info { padding:5px 30px 5px 5px; background-color:#d4d4d4; float:left; font-size:10px }
#tblEmployees_paginate { padding:5px; background-color:#d4d4d4 }
#tblEmployees_paginate span { padding:2px; font-size:11px; color:#474d7c }
#tblEmployees_paginate span.paginate_button { cursor:pointer }
#tblEmployees_paginate span.paginate_button:hover { text-decoration:underline }
#tblEmployees_paginate span.paginate_active { font-weight:bold }
</style>


И получаем следующий результат:


image



Превосходно. И при этом совсем минимум дополнительного кода, а на выходе – разбиение на страницы, сортировка по убыванию-возрастанию каждой колонки, выбор количества выводимых элементов на страницу, поиск. И самое важное – построили этот контрол на базе обычной таблицы.


TableSorter – также плагин к популярной JavaScript-библиотеки jQuery, который превращает стандартную таблицу с тегами THEAD и TBODY в удобный контрол с возможностью сортировки по колонкам без перезагрузки страницы.


Пробуем на примере:


Подключаем необходимые библиотеки



   1: <script src="../../Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
   2: <script src="../../Scripts/jquery.tablesorter.min.js" type="text/javascript"></script>
   3: <script src="../../Scripts/jquery.tablesorter.pager.js" type="text/javascript"></script>


Последняя библиотека необходима для придания нашей таблице возможностей пейжинга.


Генерируем таблицу предыдущим способом:



   1: <table id="tblEmployees">
   2:     <thead>
   3:         <tr>
   4:             <th>EmployeeID</th>
   5:             <th>FirstName</th>
   6:             <th>LastName</th>
   7:         </tr>
   8:     </thead>
   9:     <tbody>
  10:         <% foreach (var item in Model) { %>
  11:             <tr>
  12:                 <td><%= Html.Encode(item.EmployeeID) %></td>
  13:                 <td><%= Html.Encode(item.FirstName) %></td>
  14:                 <td><%= Html.Encode(item.LastName) %></td>
  15:             </tr>
  16:         <% } %>
  17:     </tbody>
  18: </table>


Добавляем следующий код на страницу:



   1: $(function () {
   2:     $("#tblEmployees").tablesorter({ widthFixed: true }).tablesorterPager({ container: $("#pager") });
   3: });


и изменяем стиль отображения контрола:



   1: #tblEmployees th { text-align: left; padding: 5px; background-color: #6E6E6E; color:#ffffcc; cursor:pointer }
   2: #tblEmployees td { color: #FFF; padding: 5px; }
   3: #tblEmployees { font-size: 12px; background-color: #4D4D4D; border: 1px solid #000; }
   4: #pager .pagedisplay { width:50px }


На выходе получаем следующий результат:


image


Как видим – полученный результат вполне удовлетворяет нашим потребностям: пейжинг, сортировка по колонкам.


После непродолжительных поисков в сети находим еще несколько плагинов, подходящих для построения удобных таблиц с дополнительными возможностями: сортировка, пейжинг.


FlexiGrid – легкий, но богатый функциональностью плагин для создания таблиц с пейжингом, сортировками и возможностью подключаться к внешним источникам данных с помощью AJAX-запросов.


image


Ingrid – еще один интересный jQuery плугин для придания таблицам свойств: сортировка, пейжниг, назначение стилей таблицам, колонкам, подгрузка данных с помощью AJAX-запросов и многое другое.


image


TablePagination – очень простой jQuery плагин, добавляющий возможности пейжинга к существующей таблице.


image


Итак, судя по анализу, существует несколько способов создать MVC-контрол, аналогичный WebForms контролу – asp:GridView. Но не полноценный аналог, а лишь повторяющий некоторую функциональность данного контрола – сортировка по столбцам, пейжинг.


В реальных приложениях, обрабатывающих большие массивы данных, загрузка всего контента на страницу для последующего построения удобной таблицы с пейжингом – это явно нехороший тон программирования. Большая загрузка сервера для вычитки всех данных, большая загрузка сервера приложений для построения страницы, загрузка сети для передачи больших объемов данных, увеличенное время ожидания пользователем загрузки и генерации страницы браузером. Все эти негативные моменты заставляют разработчиков использовать другие методы для построения страницы – подгрузка только определенного среза данных, в зависимости от запроса.


Вышеупомянутый WebForms серверный контрол – asp:GridView и в этом случае радуем программиста, так как имеет механизмы для отображения только небольшого объема данных с возможностью подгружать новые, переходя по страницам пейжинга. Для этого необходимо в качестве источника данных использовать контрол:


<asp:ObjectDataSource ID="odsEmployee" runat="server" SelectMethod="GetAll" SelectCountMethod="GetTotalCount" TypeName="Employee" />



и реализовав 2 метода для получения теущего набора данных и общего количесва данных:



public static List<Employee> GetAll()
{
...
}

public static int GetTotalCount()
{
...
}


можно подгружать только отображаемые в данных момент данные, а переходя по страницам пейжинга, подгружать новые.


Попробуем создать подобный контрол для ASP.NET MVC. Задача – загружать только те данные, которые пользователь просматривает в данный момент и подгружать по мере необходимости новые.


Сканируя документацию по вышеперчесленным jQuery-плагинам, находим следующие полезные возможности:


DataTables – возможность задавать конструктору локальный набор данных в виде JSON-объекта, либо URL – который возвращает JSON-объект. Используем данный плагин для создания контрола, отображающего только запрашиваемые в данный момент данные, не подгружая при этом ничего лишнего.


Редактируем существующий javascript-код на странице:


<script type="text/javascript">
   1:  
   2:     $(document).ready(function () {
   3:         $("#tblEmployees").dataTable({ "sPaginationType": "full_numbers",
   4:             "bProcessing": true,
   5:             "bServerSide": true,
   6:             "sAjaxSource": "../Employee/GetAllEmployees"
   7:         });
   8:     });
</script>


В конструкторе плагина мы указываем, что собираемся получать данные для построения таблицы из сервера и задаем URI для получения JSON-объекта.


В контроллере Employee создаем новый метод – GetAllEmployees:


public JsonResult GetAllEmployees()
{
//загружаем всех Employee. Если хотим совсем правильно делать, то создаем процедуру, возвращающую
//только запрашиваемый в данный момент набор
var data = EmployeeModel.GetAll();

//получаем текущее состояние пейжинга
var start = int.Parse(Request.QueryString["iDisplayStart"]);

//количество отображаемых элементов на странице
var pageCount = int.Parse(Request.QueryString["iDisplayLength"]);

var json = new { iTotalRecords = pageCount, iTotalDisplayRecords = data.Count, aaData = (from d in data.Skip(start).Take(pageCount) select new string[] { d.EmployeeID.ToString(), d.FirstName, d.LastName }) };
return Json(json, JsonRequestBehavior.AllowGet);
}


Готово! Теперь при загрузке страницы изначально получаем дружественную надпись, что данные загружаются


image


после чего генерируется привычная нам таблица данных:


image


Сперва я пытался сделать, чтобы URI для получения данных для плагина был ссылкой на веб-метод, но никак не мог добится того, чтобы объект получился в JSON-формате. Даже задавая атрибут [ScriptMethod(ResponseFormat=ResponseFormat.Json)] в результате получал XML-файл. Возможно кто-то подскажет мне – в чем я недоглядел :).



Далее на глаза попадается очень интересный плагин для построения мощных таблиц – FlexiGrid. Пропускаем примеры создания таблиц с возможностями пейжинга и сортировки по столбцам и штудирую документацию по AJAX-подгрузке данных и генерации таблицы на лету. Очень полезна в освоении данного плагина статьюя на CodeProject – советую посмотреть.


Итак, добавляем в проект нужные библиотеки:


<script src="../../Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
<script src="../../Scripts/flexigrid.js" type="text/javascript"></script>
<link href="../../Content/flexigrid.css" rel="stylesheet" type="text/css" />



Помещаем следующий код на страницу:


<table id="tblEmployees"></table>
<script type="text/javascript">
   1:  
   2:  
   3:     $("#tblEmployees").flexigrid({
   4:         url: 'GetFlexigridData',
   5:         dataType: 'json',
   6:         method: 'GET',
   7:         colModel: [
   8:             { display: 'ID', name: 'EmployeeID', width: 40, sortable: true, align: 'center' },
   9:             { display: 'First Name', name: 'FirstName', width: 150, sortable: true, align: 'left' },
  10:             { display: 'Last Name', name: 'LastName', width: 150, sortable: true, align: 'left'}],
  11:         sortname: "id",
  12:         sortorder: "asc",
  13:         usepager: true,
  14:         title: 'Employee List',
  15:         useRp: true,
  16:         rp: 10,
  17:         showTableToggleBtn: true,
  18:         height: 250,
  19:         width: 400
  20:     }
  21:     );
  22:  
</script>


Для получения JSON-данных содержимого таблицы создаем еще один метод в нашем Employee-контроллере, так как структура данных будет отлична от предыдущего примера:


public JsonResult GetFlexigridData()
{
var data = EmployeeModel.GetAll();
var start = int.Parse(Request.QueryString["page"]);
var pageCount = int.Parse(Request.QueryString["rp"]);

var json = new { page = start, total = data.Count, rows = (from d in data.Skip((start - 1) * pageCount).Take(pageCount) select new { id = d.EmployeeID, cell = new string[] { d.EmployeeID.ToString(), d.FirstName, d.LastName } }) };
return Json(json, JsonRequestBehavior.AllowGet);
}


Готово. На выходе получаем довольно красивую и функциональную таблицу, которая подгружает данные по мере запроса (по мере перехода по страницам пейжинга).


image


Итак. Задачей данной статьи было найти примемленые JavaScript-плагины, которые в полной мере заменили бы мне до боли привычный и полностью освоенный контрол <asp:GridView>. Благодаря расширенному jQuery community довольно быстро были найдены и довольно быстро осовены разные плагины. К тому же я имел больше контроля над генерируемым кодом, а значит и больше контроля над присвоением стилей отображения данным контролам. К тому же подгрузка данных производилась с помощью AJAX-запросов, в то время как в обычном <asp:GridView /> я получал постоянно полноценный PostBack, а значит и полный цикл генерации страницы на сервере и передачи ее клиенту.

В общем, мой страх перед сложностями перехода на MVC в доле регенерации аналогов сложных серверных контролов помалу улетучился. Хотелось бы дальше продолжить тематику воссоздания аналогов серверных контролов посредством клиентских JavaScript-библиотек.

Читайте также о том, как сделать не просто пейжинг для контрола, а сделать его SEO-friendly, чтобы страницы грида могли также загружать роботы поисковиков, индексуруя старый контент. Нынешняя же реализация, к сожалению, этого не позволяет.