Здравствуйте, уважаемое читатели!
Я занимаюсь разработкой веб-сайтов. Как правило, это решения под индивидуальные потребности заказчиков. Поэтому я не использую готовые CMS, а предпочитаю складывать кирпичики самостоятельно. Конечно и админскую часть приходиться писать самостоятельно, поскольку она должна выполнять те функции, которые нужны заказчику, но и ничего лишнего не должно быть. И если написать несколько методов для редактирования данных это пол беды, то приходилось ещё и верстать приятный и удобный интерфейс.
Долгое время я использовал Twitter Bootstrap, но он не мог удовлетворить все потребности. Приходилось верстать дополнительные кнопочки и писать скрипты. Но вот однажды, я познакомился с замечательным UI-фреймворком KendoUI от Telerik. Что из этого получилось под катом.
О самом фреймворке уже писалась статья на Хабре. Мы будем использовать библиотеку Web-контролов KendoUI Web. Чтобы понимать, что можно создавать с её помощью можно посетить страничку с демо. Для построения контролов можно использовать как хелперы для ASP.NET, JSP или PHP, так и javascript-виджеты. Последние распространяются по лицензии GPL v3 License, поэтому я использовал именно их. Скачать тот или иной пакет можно здесь. На серверной стороне я использую ASP.NET MVC 4 с пакетом Microsoft ASP.NET Web API OData 4.0.0, уставить который можно с помощью команды PM> Install-Package Microsoft.AspNet.WebApi.OData
Для демонстрации создадим простой класс Article, и добавим ему три свойства разного типа
public class Article
{
public int ID { get; set; }
public string Title { get; set; }
public bool Hidden { get; set; }
}
Далее создадим ApiController для работы с данными. В своем примере я использую Entity Framework, поэтому сразу указываю Scaffolding options
После этого я изменяю только действие GetArticles, так показано в примере ниже. Прежде всего теперь оно возвращает ODataResult. А в качестве параметра принимает ODataQueryOptions, это коллекция сериализованных параметров строки запроса. В самом действии мы получаем коллекцию и общее количество элементов в ней. А после применяем к ней входящие параметры. В результате мы возвращаем коллекцию после применение к ней параметров и общее количество, оно необходимо для пагинации.
public class ArticlesController : ApiController
{
private Storage db = new Storage();
// GET api/Articles
public ODataResult<Article> GetArticles(ODataQueryOptions options)
{
var items = db.Articles;
var count = items.Count();
var res = (IEnumerable<Article>)options.ApplyTo(items);
return new ODataResult<Article>(res, null, count);
}
// GET api/Articles/5
public Article GetArticle(int id)
{
Article article = db.Articles.Find(id);
if (article == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return article;
}
// PUT api/Articles/5
public HttpResponseMessage PutArticle(int id, Article article)
{
if (ModelState.IsValid && id == article.ID)
{
db.Entry(article).State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
// POST api/Articles
public HttpResponseMessage PostArticle(Article article)
{
if (ModelState.IsValid)
{
db.Articles.Add(article);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, article);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = article.ID }));
return response;
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
// DELETE api/Articles/5
public HttpResponseMessage DeleteArticle(int id)
{
Article article = db.Articles.Find(id);
if (article == null)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
db.Articles.Remove(article);
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK, article);
}
}
Вот и все. Самое время переходить к клиентской части. Для начала подключим KendoUI и JQuery на страницу.
<link href="~/Content/kendo/kendo.common.min.css" rel="stylesheet" />
<link href="~/Content/kendo/kendo.default.min.css" rel="stylesheet" />
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
<script src="~/Scripts/kendo/kendo.web.min.js"></script>
После этого установим и настроим виджет. Подробнее о настройках виджета можно прочитать здесь.
<div id="grid"></div>
<script>
$(document).ready(function () {
$("#grid").kendoGrid({
dataSource: {
pageSize: 3,
serverSorting: true,
serverFiltering: true,
serverPaging: true,
type: 'odata',
transport: {
read: {
url: "/api/articles",
dataType: "json",
type: "GET"
},
create: {
url: "/api/articles",
dataType: "json",
type: "POST"
},
update: {
url: function (article) {
return "/api/articles/" + article.ID
},
dataType: "json",
type: "PUT"
},
destroy: {
url: function (article) {
return "/api/articles/" + article.ID
},
dataType: "json",
type: "DELETE"
}
},
schema: {
data: function (data) {
return data.Items;
},
total: function (data) {
return data.Count;
},
model: {
id: "ID",
fields: {
ID: { editable: false },
Title: { type: "string", editable: true, nullable: false, validation: { required: true } },
Hidden: { type: "boolean", editable: true }
}
}
}
},
height: 250,
filterable: true,
sortable: true,
pageable: true,
toolbar: ["create"],
editable: "popup",
columns: [
{ field: "ID", filterable: false, width: 50 },
{ field: "Title", title: "Название", width: 300 },
{ field: "Hidden", title: "Скрыт", width: 100 },
{ command: ["edit", "destroy"], title: " ", width: "210px" }
]
});
});
</script>
Готово! Теперь можно запускать и проверять. Менее чем за 10 минут мы создали полноценный интерфейс для управления данными, с возможностью пагинации, сортировки и фильтрации. Удобный и приятный. Спасибо KendoUI и формату OData, а Вам за внимание.
Р.S. Кстати есть возможность локализации виджетов. Для этого необходимо подключить соответствующий скрипт из папки js/cultures, который поставляется в архиве с фреймворком.
Автор: aleksey_bober