我正在尝试使用 hx-put 来处理表单上的更新。我的项目使用 Spring 3.3.2、thymeleaf-extras-springsecurity6 和 htmx 2.0.3 (webjars)。
这是我用于处理更新的百里香片段:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<div th:fragment="email-form(email, personId)">
<h4 th:text="${edit} ? 'Edit Email' : 'Add Email'">add or edit Email</h4>
<form th:action="@{/person/{personId}/email/save(personId=${personId})}"
th:object="${email}"
method="post">
<input type="hidden" th:field="*{emailId}"/>
<div class="mb-3">
<label for="emailType" class="form-label">Type</label>
<select id="emailType" th:field="*{type}" class="form-select">
<option value="PERSONAL">Personal</option>
<option value="WORK">Work</option>
<option value="HOME">Home</option>
<option value="SCHOOL">School</option>
<option value="OTHER">Other</option>
</select>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email Address</label>
<input id="email" type="email" th:field="*{emailAddress}" class="form-control"
placeholder="[email protected]" required>
</div>
<div class="d-flex justify-content-end">
<button type="button" class="btn btn-success me-2"
th:if="${edit}"
th:hx-put="@{/person/{personId}/email/update(personId=${personId})}"
hx-target="#email-parent"
hx-swap="innerHTML transition:true"
th:attr="data-csrf-header=${_csrf.headerName}, data-csrf-token=${_csrf.token}, data-email-id=${email.emailId"
hx-trigger="click">
Update Email
</button>
<button type="button" class="btn btn-success me-2"
th:if="${!edit}"
th:hx-post="@{/person/{personId}/email/save(personId=${personId})}"
hx-target="#email-parent"
hx-swap="innerHTML transition:true"
hx-trigger="click">
Save
</button>
<button type="button" class="btn btn-secondary"
th:hx-get="@{/person/view/{personId}(personId=${personId})}"
hx-target="body" hx-swap="outerHTML transition:true">Cancel
</button>
</div>
</form>
</div>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<div th:fragment="email-form(email, personId)">
<h4 th:text="${edit} ? 'Edit Email' : 'Add Email'">add or edit Email</h4>
<form th:action="@{/person/{personId}/email/save(personId=${personId})}"
th:object="${email}"
method="post">
<input type="hidden" th:field="*{emailId}"/>
<div class="mb-3">
<label for="emailType" class="form-label">Type</label>
<select id="emailType" th:field="*{type}" class="form-select">
<option value="PERSONAL">Personal</option>
<option value="WORK">Work</option>
<option value="HOME">Home</option>
<option value="SCHOOL">School</option>
<option value="OTHER">Other</option>
</select>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email Address</label>
<input id="email" type="email" th:field="*{emailAddress}" class="form-control"
placeholder="[email protected]" required>
</div>
<div class="d-flex justify-content-end">
<button type="button" class="btn btn-success me-2"
th:if="${edit}"
th:hx-put="@{/person/{personId}/email/update(personId=${personId})}"
hx-target="#email-parent"
hx-swap="innerHTML transition:true"
th:attr="data-csrf-header=${_csrf.headerName}, data-csrf-token=${_csrf.token}"
hx-trigger="click">
Update Email
</button>
<button type="button" class="btn btn-success me-2"
th:if="${!edit}"
th:hx-post="@{/person/{personId}/email/save(personId=${personId})}"
hx-target="#email-parent"
hx-swap="innerHTML transition:true"
hx-trigger="click">
Save
</button>
<button type="button" class="btn btn-secondary"
th:hx-get="@{/person/view/{personId}(personId=${personId})}"
hx-target="body" hx-swap="outerHTML transition:true">Cancel
</button>
</div>
</form>
</div>
</html>
此表单用于添加新电子邮件和更新现有电子邮件。 正在尝试使用 hx-put 进行更新。
在我的 user-layout.html 中,我包含一个 htmx.configRequest 调用:
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
<head>
<meta charset="UTF-8">
<title>Records Management - User Dashboard</title>
<link rel="stylesheet" th:href="@{/css/style.css}"/>
<link rel="stylesheet" th:href="@{/webjars/bootstrap/5.3.3/css/bootstrap.min.css}"/>
<link rel="stylesheet" th:href="@{/webjars/font-awesome/6.5.2/css/all.min.css}"/>
</head>
<body class="gradient-custom" hx-boost="true">
<header th:insert="~{fragments/header :: userHeader}"></header> <!-- User-specific header -->
<div class="container">
<div th:insert="~{fragments/messages :: messages}"></div>
<div layout:fragment="content"></div> <!-- Content will be injected here -->
</div>
<footer th:insert="~{fragments/footer :: userFooter}"></footer>
<script th:src="@{/webjars/bootstrap/5.3.3/js/bootstrap.bundle.min.js}"></script>
<script type="text/javascript" th:src="@{/webjars/htmx.org/2.0.3/dist/htmx.min.js}"></script>
<script>
document.body.addEventListener('htmx:configRequest', function(event) {
const target = event.target;
const csrfHeader = target.getAttribute('data-csrf-header');
const csrfToken = target.getAttribute('data-csrf-token');
// If CSRF attributes are found, add them to the request headers
if (csrfHeader && csrfToken) {
event.detail.headers[csrfHeader] = csrfToken;
}
});
</script>
</body>
</html>
需要此代码来解决 Spring Security 的 CORS 要求。
我的控制器代码方法是:
@PutMapping(value = "/person/{personId}/email/update")
public String updateEmail(@ModelAttribute Email email,
@PathVariable("personId") Long personId,
Model model) {
// Verify that 'email' here contains the ID correctly and not the email string.
LOGGER.info("Updating email for personId: {}, emailId: {}", personId, email.getEmailId());
Person person = personService.getPersonById(personId);
emailService.saveOrUpdateEmail(person, email);
model.addAttribute("emails", person.getEmails());
model.addAttribute("personId", personId);
return "person/emails-info :: emails-info";
// return "person/email-item :: email-item";
}
我在提交表单时看到的是控制器中的
@ModelAttribute Email email
已实例化,但对象中的所有属性都是空的。 当我查看“网络”选项卡中的浏览器流量时,我看到了有效负载:
_csrf: aemCvGmY8p2U6i0t2sysF7dEPI5eZPLq-NAbPY6WC-bNKYycD4zghFCgyq25iEsbuOGYc9UmEbduVpTHyuAuW7f0b97-HLyu
emailId: 2
type: WORK
emailAddress: [email protected]
但是,在日志中,emailId 为空,type 为空,emailaddress 为空。 我开始认为 hx-put 不适用于 Spring boot/thymeleaf。 有什么想法吗?
我发现了两件事:
application/x-www-form-urlencoded
进行 POST 请求,但对于其他 HTTP 方法(如 PUT),它可能默认为 text/plain。 不知道为什么,但只是观察到确实如此。hx-encoding="application/x-www-form-urlencoded"
添加到了我的按钮中。这确保了我的 PUT 请求不会打开内容类型。@Bean
public FormContentFilter formContentFilter() { //added with hx-pu
return new FormContentFilter();
}
深入研究 FormContentFilter 的文档,我发现:
解析 HTTP PUT、PATCH 和 DELETE 请求的表单数据的过滤器 并将其公开为 Servlet 请求参数。默认情况下,Servlet 规范仅要求 HTTP POST 这样做。
我的百里香叶片段的最终状态如下所示:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<div th:fragment="email-form(email, personId)">
<h4 th:text="${edit} ? 'Edit Email' : 'Add Email'">add or edit Email</h4>
<form th:action="@{/person/{personId}/email/save(personId=${personId})}"
th:object="${email}"
method="post">
<input type="hidden" th:field="*{emailId}"/>
<div class="mb-3">
<label for="emailType" class="form-label">Type</label>
<select id="emailType" th:field="*{type}" class="form-select">
<option value="PERSONAL">Personal</option>
<option value="WORK">Work</option>
<option value="HOME">Home</option>
<option value="SCHOOL">School</option>
<option value="OTHER">Other</option>
</select>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email Address</label>
<input id="email" type="email" th:field="*{emailAddress}" class="form-control"
placeholder="[email protected]" required>
</div>
<div class="d-flex justify-content-end">
<button type="button" class="btn btn-success me-2"
th:if="${edit}"
th:hx-put="@{/person/{personId}/email/update(personId=${personId})}"
hx-target="#email-parent"
hx-swap="innerHTML transition:true"
th:attr="data-csrf-header=${_csrf.headerName}, data-csrf-token=${_csrf.token}"
hx-trigger="click"
hx-encoding="application/x-www-form-urlencoded">
Update Email
</button>
<button type="button" class="btn btn-success me-2"
th:if="${!edit}"
th:hx-post="@{/person/{personId}/email/save(personId=${personId})}"
hx-target="#email-parent"
hx-swap="innerHTML transition:true"
hx-trigger="click">
Save
</button>
<button type="button" class="btn btn-secondary"
th:hx-get="@{/person/view/{personId}(personId=${personId})}"
hx-target="body" hx-swap="outerHTML transition:true">Cancel
</button>
</div>
</form>
</div>
</html>
我的控制器方法:
@PutMapping(value = "/person/{personId}/email/update")
public String updateEmail(@ModelAttribute Email email,
@PathVariable("personId") Long personId,
Model model) {
// Verify that 'email' here contains the ID correctly and not the email string.
LOGGER.info("Updating email for personId: {}, emailId: {}", personId, email.getEmailId());
LOGGER.info("email address = {}", email.getEmailAddress());
Person person = personService.getPersonById(personId);
emailService.saveOrUpdateEmail(person, email);
model.addAttribute("emails", person.getEmails());
model.addAttribute("personId", personId);
return "person/emails-info :: emails-info";
// return "person/email-item :: email-item";
}
花了很多天才弄清楚这一点。 希望它对其他人有帮助!