Vapor 大型教程
编辑缩略语页面
本章学习如何给缩略语做「编辑」功能:用同一个表单页显示已有数据,提交后把改动写回数据库。
为什么用 POST 而不是 PUT
浏览器原生表单只能发送 GET 和 POST 请求,无法直接发送 PUT 更新请求。因此我们的「编辑」也走 POST 路由,提交后再在服务器端更新模型。
上下文与路由
在 WebsiteController.swift 中定义 EditAcronymContext,相比创建上下文多了一个 acronym 和 editing = true 标记,模板借此区分「新建 / 编辑」状态。
struct EditAcronymContext: Encodable {
let title = "Edit Acronym"
let acronym: Acronym
let users: Future<[User]>
let editing = true
}
注册两条路由:显示编辑页用 GET,提交用 POST,两者都带 Acronym.parameter 以定位要编辑的对象。
router.get("acronyms", Acronym.parameter, "edit", use: editAcronymHandler)
router.post("acronyms", Acronym.parameter, "edit", use: editAcronymPostHandler)
显示编辑页面
editAcronymHandler 先用 req.parameters.next(Acronym.self) 取出目标缩略语,再用 flatMap(to: View.self) 在拿到结果后查询全部用户,最后渲染与创建页共用的 createAcronym 模板(靠 editing 标记填充旧值)。
func editAcronymHandler(_ req: Request) throws -> Future<View> {
return try req.parameters.next(Acronym.self)
.flatMap(to: View.self) { acronym in
let context = EditAcronymContext(acronym: acronym, users: User.query(on: req).all())
return try req.view().render("createAcronym", context)
}
}
提交并保存修改
提交处理方法用 flatMap(to: Response.self, req.parameters.next(Acronym.self), req.content.decode(Acronym.self)) 同时拿到「数据库里的旧对象」和「表单解码出的新数据」,把新值赋给旧对象后保存,再重定向回详情页。
func editAcronymPostHandler(_ req: Request) throws -> Future<Response> {
return try flatMap(to: Response.self,
req.parameters.next(Acronym.self),
req.content.decode(Acronym.self)
) { acronym, data in
acronym.short = data.short
acronym.long = data.long
acronym.userID = data.userID
return acronym.save(on: req).map(to: Response.self) { savedAcronym in
guard let id = savedAcronym.id else {
throw Abort(HTTPResponseStatus.internalServerError)
}
return req.redirect(to: "/acronyms/\(id)")
}
}
}
模板与详情页
编辑页复用 createAcronym.leaf,通过 #if(editing) 给输入框填入旧值、下拉框标记选中项,并把按钮文案在 Update 与 Submit 间切换。
#set("content"){
<h1>#(title)</h1>
<form method = "post">
<div class="form-group">
<label for = "short">Acronym</label>
<input type="text" name="short" class="form-control" id = "short" #if(editing) { value = "#(acronym.short)" }/>
</div>
<div class="form-group">
<label for = "long">Meaning</label>
<input type="text" name="long" class="form-control" id="long" #if(editing) { value = "#(acronym.long)"}/>
</div>
<div class="form-group">
<label for="userID">User</label>
<select name="userID" class="form-control" id="userID">
#for(user in users) {
<option value="#(user.id)" #if(editing){ #if(acronym.userID == user.id) { selected }}>
#(user.name)
</option>
}
</select>
</div>
<button type="submit" class = "btn btn-primary">
#if(editing) { Update } else { Submit }
</button>
</form>
}
#embed("base")
详情页 acronym.leaf 提供一个指向编辑页的链接。
#set("content"){
<h1>#(acronym.short)</h1>
<h2>#(acronym.long)</h2>
<p>Created by <a href="/users/#(user.id)/">#(user.name)</a></p>
<a class="btn btn-primary" href="/acronyms/#(acronym.id)/edit" role="button">Edit</a>
}
#embed("base")
提示:
req.content.decode(Acronym.self) 与 req.parameters.next(Acronym.self) 的配合,是 Vapor 3 中「先取旧值、再合并新值」的常见写法。