В первом посте я уже описал начальную настройку веб-приложения. Логика работы с БД уже готова, теперь мы займёмся описанием представлений и контроллеров.
Контроллеры
Для
начала создадим пару контроллеров, в которых будет реализована вся
бизнес-логика для работы с заявками и комментариями к ним.
TicketController:
@Controller
@RequestMapping("/ticket")
public class TicketController {
@Autowired
private TicketRepository ticketRepository;
@Autowired
private TicketCommentRepository ticketCommentRepository;
/**
* Список заявок.
*
* @param all Отображать все заявки
* @return
*/
@RequestMapping({"", "/", "/index"})
public ModelAndView index(@RequestParam(required = false, defaultValue = "false") boolean all) {
return new ModelAndView("ticket/index")
.addObject("all", all)
.addObject("tickets", all
? ticketRepository.findAll(new Sort(Sort.Direction.DESC, "createDate"))
: ticketRepository.findByResolveDateIsNull(new Sort(Sort.Direction.DESC, "createDate")));
}
/**
* Создание новой заявки.
*
* @param ticket Заявка
* @return
*/
@RequestMapping(method = RequestMethod.POST)
public ModelAndView create(@ModelAttribute Ticket ticket) {
ticket.setCreateDate(new Date());
ticket = ticketRepository.save(ticket);
return new ModelAndView("redirect:/ticket/" + ticket.getId() + "/view");
}
/**
* Просмотр заявки.
*
* @param id Идентификатор запрашиваемой заявки
* @return
*/
@RequestMapping("/{id:\\d+}/view")
public ModelAndView view(@PathVariable long id) {
Ticket ticket = ticketRepository.findOne(id);
return new ModelAndView("ticket/view")
.addObject("ticket", ticket)
.addObject("ticketComments", ticketCommentRepository.findByTicketId(id));
}
/**
* Закрытие заявки.
*
* @param id Идентификатор запрашиваемой заявки
* @return
*/
@RequestMapping("/{id:\\d+}/resolve")
public ModelAndView resolve(@PathVariable long id) {
ticketRepository.resolveTicket(id);
return new ModelAndView("redirect:/ticket/" + id + "/view");
}
/**
* Повторное открытие заявки.
*
* @param id Идентификатор запрашиваемой заявки
* @return
*/
@RequestMapping("/{id:\\d+}/reopen")
public ModelAndView reopen(@PathVariable long id) {
ticketRepository.reopenTicket(id);
return new ModelAndView("redirect:/ticket/" + id + "/view");
}
}
Человек, знакомый с Spring WebMVC, ничего нового не увидит в этом контроллере.
Аналогичным образом выглядит и контроллер для работы с комментариями к заявкам, TicketCommentController:
@Controller
@RequestMapping("/ticket/{ticketId:\\d+}/ticketComment")
public class TicketCommentController {
@Autowired
private TicketCommentRepository ticketCommentRepository;
@Autowired
private TicketRepository ticketRepository;
@RequestMapping(method = RequestMethod.POST)
public ModelAndView create(@PathVariable long ticketId, @ModelAttribute TicketComment ticketComment) {
ticketComment.setTicket(ticketRepository.findOne(ticketId));
ticketComment.setCreateDate(new Date());
ticketCommentRepository.save(ticketComment);
return new ModelAndView("redirect:/ticket/" + ticketId + "/view");
}
}
Отображения с использованием Thymeleaf
Как уже было сказано, по умолчанию Thymeleaf в связке с Spring Boot будет искать шаблоны в classpath:/templates. Создадим поддиректорию ticket, в которой будут находиться отображения для заявок.
Страница со списком заявок:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Bootdesk :: Tickets</title>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"/>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-lg-4" th:include="ticket/create :: #_ticket_form"></div>
<div class="col-lg-8">
<h1>Tickets list
<small>
<a th:if="!${all}" th:href="@{/ticket(all=true)}">All</a>
<a th:if="${all}" th:href="@{/ticket}">Not resolved</a>
</small>
</h1>
<table class="table table-striped">
<thead>
<tr>
<th>
ID
</th>
<th>
Issue
</th>
<th>
Opened
</th>
</tr>
</thead>
<tbody>
<tr th:each="ticket : ${tickets}">
<td th:text="${ticket.id}">ID</td>
<td>
<a th:style="${ticket.resolveDate != null}?'text-decoration:line-through;':''" th:href="@{/ticket/{id}/view(id=${ticket.id})}" th:text="${ticket.issue}"></a>
</td>
<td th:text="${ticket.createDate}">Opened</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
В качестве небольшого украшения дизайна я использовал Twitter Bootstrap. В глаза сразу бросаются Thymeleaf-аттрибуты в HTML-тэгах. Пробежимся по ним вкратце:
- th:include вкладывает в текущий шаблон часть другого. В качестве значения указывается путь к вкладываемому файлу отображения. Если нужно вложить не весь файл, а лишь его часть, то после имени добавляется :: и CSS-селектор вкладываемого компонента. В данном случае будет вложен элемент с id="_ticket_form".
- th:if - условный аттрибут. В качестве значения используется SPEL-выражение. Если выражение возвращает true, то элемент будет отображён.
- th:href - аттрибут, генерирующий значение аттрибута href у ссылки. С тонкостями этого аттрибута лучше ознакомиться в официальной документации Thymeleaf.
- th:text - определяет тектовое содержимое элемента.
- th:style - настройки CSS-стиля элемента.
Вкладываемое отображение выглядит следующим образом:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Bootdesk :: Create New Ticket</title>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
<div id="_ticket_form">
<form method="post" th:action="@{/ticket}">
<div class="form-group">
<label for="_ticket_issue">Issue</label>
<input type="text" class="form-control" name="issue" placeholder="Type your problem" id="_ticket_issue"/>
</div>
<div class="form-group">
<label for="_ticket_description">Description</label>
<textarea placeholder="Input a detailed description of your problem here ..." id="_ticket_description" name="description" class="form-control"></textarea>
</div>
<button class="btn btn-primary btn-block"><i class="glyphicon glyphicon-send"></i> Submit</button>
</form>
</div>
</body>
</html>
Thymeleaf в связке с Spring Boot ищет вкладываемые отображения в той же самой директории - classpath:/templates.
В этом отображении используется аттибут th:action у тэга form, он задаёт URL, который будет указан в аттрибуте action. Его поведение аналогично th:href у ссылок.
Осталось отображение заявки с её обсуждением:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Bootdesk :: Open Tickets</title>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"/>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-lg-4" th:include="ticket/create :: #_ticket_form"></div>
<div class="col-lg-8">
<a th:href="@{/ticket}">← All tickets</a>
<h1 th:style="${ticket.resolveDate != null}?'text-decoration:line-through;':''">Ticket #<span th:text="${ticket.id}"></span>: <span th:text="${ticket.issue}"></span></h1>
<div>Created: <span th:text="${ticket.createDate}"></span></div>
<p th:text="${ticket.description}"></p>
<br/>
<div>
<a th:if="${ticket.resolveDate == null}" th:href="@{/ticket/{ticketId}/resolve(ticketId=${ticket.id})}">Resolve</a>
<a th:if="${ticket.resolveDate != null}" th:href="@{/ticket/{ticketId}/reopen(ticketId=${ticket.id})}">Re-open</a>
</div>
<hr/>
<h3>Discussion</h3>
<div th:each="ticketComment : ${ticketComments}" class="well well-sm">
<strong th:text="${ticketComment.createDate}"></strong>
<p th:text="${ticketComment.comment}"></p>
</div>
<form th:action="@{/ticket/{ticketId}/ticketComment(ticketId=${ticket.id})}" method="post">
<div class="form-group">
<label>Your comment:</label>
<textarea name="comment" placeholder="Input your comment here..." class="form-control"></textarea>
</div>
<button class="btn btn-primary pull-right"><i class="glyphicon glyphicon-comment"></i> Comment</button>
</form>
</div>
</div>
</div>
</body>
</html>
В следующем посте я опишу работу со Spring Security в связке со Spring Boot, а так же добавлю в демонстрационное приложение аутентификацию и авторизацию.

Шикарный блог!
ОтветитьУдалитьСпасибо большое!
А где продолжение?
ОтветитьУдалить+
ОтветитьУдалить