创建书本表单 - 学习 Web 开发 编辑
Edit 此子文档显示如何定义页面/表单以创建Book
对象。这比相同的作者Author
或种类Genre
页面稍微复杂一点,因为我们需要在我们的书本表单中,获取并显示可用的作者和种类记录。
导入验证和清理方法
打开 /controllers/bookController.js,并在文件顶部添加以下行:
const { body,validationResult } = require('express-validator/check');
const { sanitizeBody } = require('express-validator/filter');
控制器—get 路由
找到导出的book_create_get()
控制器方法,并将其替换为以下代码。
// Display book create form on GET.
exports.book_create_get = function(req, res, next) {
// Get all authors and genres, which we can use for adding to our book.
async.parallel({
authors: function(callback) {
Author.find(callback);
},
genres: function(callback) {
Genre.find(callback);
},
}, function(err, results) {
if (err) { return next(err); }
res.render('book_form', { title: 'Create Book', authors: results.authors, genres: results.genres });
});
};
这使用异步模块 async(在教程5:显示数据库中的数据),来获取所有作者和种类对象。然后将它们作为名为authors
和genres
的变量(以及页面标题title
),传递给视图book_form.pug
。
控制器—post 路由
找到导出的book_create_post()
控制器方法,并将其替换为以下代码。
// Handle book create on POST.
exports.book_create_post = [
// Convert the genre to an array.
(req, res, next) => {
if(!(req.body.genre instanceof Array)){
if(typeof req.body.genre==='undefined')
req.body.genre=[];
else
req.body.genre=new Array(req.body.genre);
}
next();
},
// Validate fields.
body('title', 'Title must not be empty.').isLength({ min: 1 }).trim(),
body('author', 'Author must not be empty.').isLength({ min: 1 }).trim(),
body('summary', 'Summary must not be empty.').isLength({ min: 1 }).trim(),
body('isbn', 'ISBN must not be empty').isLength({ min: 1 }).trim(),
// Sanitize fields (using wildcard).
sanitizeBody('*').trim().escape(),
sanitizeBody('genre.*').escape(),
// Process request after validation and sanitization.
(req, res, next) => {
// Extract the validation errors from a request.
const errors = validationResult(req);
// Create a Book object with escaped and trimmed data.
var book = new Book(
{ title: req.body.title,
author: req.body.author,
summary: req.body.summary,
isbn: req.body.isbn,
genre: req.body.genre
});
if (!errors.isEmpty()) {
// There are errors. Render form again with sanitized values/error messages.
// Get all authors and genres for form.
async.parallel({
authors: function(callback) {
Author.find(callback);
},
genres: function(callback) {
Genre.find(callback);
},
}, function(err, results) {
if (err) { return next(err); }
// Mark our selected genres as checked.
for (let i = 0; i < results.genres.length; i++) {
if (book.genre.indexOf(results.genres[i]._id) > -1) {
results.genres[i].checked='true';
}
}
res.render('book_form', { title: 'Create Book',authors:results.authors, genres:results.genres, book: book, errors: errors.array() });
});
return;
}
else {
// Data from form is valid. Save book.
book.save(function (err) {
if (err) { return next(err); }
//successful - redirect to new book record.
res.redirect(book.url);
});
}
}
];
此代码的结构和行为,几乎与创建种类Genre
或作者Author
对象完全相同。首先,我们验证并清理数据。如果数据无效,那么我们将重新显示表单,以及用户最初输入的数据,和错误消息列表。如果数据有效,我们将保存新的Book
记录,并将用户重定向到Book
详细信息页面。
与其他表单处理代码相关的第一个主要区别,是我们使用通配符,一次修剪和转义所有字段(而不是单独清理它们):
sanitizeBody('*').trim().escape(),
与其他表单处理代码相关的下一个主要区别,是我们如何清理种类Genre
信息。表单返回一个Genre
项的数组(而对于其他字段,它返回一个字符串)。为了验证信息,我们首先将请求转换为数组(下一步需要)。
// Convert the genre to an array.
(req, res, next) => {
if(!(req.body.genre instanceof Array)){
if(typeof req.body.genre==='undefined')
req.body.genre=[];
else
req.body.genre=new Array(req.body.genre);
}
next();
},
然后,我们在清理器中使用通配符(*)来单独验证每个种类数组条目。下面的代码显示了 - 这转换为 “清理关键种类genre
下的每个项目”。
sanitizeBody('genre.*').trim().escape(),
与其他表单处理代码的最终区别,在于我们需要将所有现有的种类和作者传递给表单。为了标记用户已经检查过的种类,我们遍历所有种类,并将checked='true'
参数,添加到我们的 POST 数据中(如下面的代码片段中所示)。
// Mark our selected genres as checked.
for (let i = 0; i < results.genres.length; i++) {
if (book.genre.indexOf(results.genres[i]._id) > -1) {
// Current genre is selected. Set "checked" flag.
results.genres[i].checked='true';
}
}
视图
创建 /views/book_form.pug,并复制下面的文本。
extends layout block content h1= title form(method='POST' action='') div.form-group label(for='title') Title: input#title.form-control(type='text', placeholder='Name of book' name='title' required='true' value=(undefined===book ? '' : book.title) ) div.form-group label(for='author') Author: select#author.form-control(type='select', placeholder='Select author' name='author' required='true' ) for author in authors if book option(value=author._id selected=(author._id.toString()==book.author ? 'selected' : false) ) #{author.name} else option(value=author._id) #{author.name} div.form-group label(for='summary') Summary: input#summary.form-control(type='textarea', placeholder='Summary' name='summary' value=(undefined===book ? '' : book.summary) required='true') div.form-group label(for='isbn') ISBN: input#isbn.form-control(type='text', placeholder='ISBN13' name='isbn' value=(undefined===book ? '' : book.isbn) required='true') div.form-group label Genre: div for genre in genres div(style='display: inline; padding-right:10px;') input.checkbox-input(type='checkbox', name='genre', id=genre._id, value=genre._id, checked=genre.checked ) label(for=genre._id) #{genre.name} button.btn.btn-primary(type='submit') Submit if errors ul for error in errors li!= error.msg
视图结构和行为与 genre_form.pug 模板几乎相同。
主要区别在于,我们如何实现选择类型字段:作者Author
和种类Genre
。
- 种类集合显示为复选框,使用我们在控制器中设置的检查值
checked
,来确定是否应该选中该框。 - 作者集合显示为单选下拉列表。在这种情况下,我们通过比较当前作者选项的 id 与用户先前输入的值(作为
book
变量传入),来确定要显示的作者。这在上面突出显示!注意: 如果提交的表单中存在错误,那么,当要重新呈现表单时,新的书本作者仅使用字符串(作者列表中选中选项的值)进行标识。相比之下,现有的书本作者的
_id
属性不是字符串。因此,要比较新的和现有的,我们必须将每个现有书本作者的_id
,强制转换为字符串,如上所示。
它看起來像是?
运行应用程序,将浏览器打开到http://localhost:3000,然后选择Create new book链接。如果一切设置正确,您的网站应该类似于以下屏幕截图。提交有效的图书后,应将其保存,然后您将进入图书详细信息页面。
下一步
继续教程 6 的下一个部分: 创建书本实例表单
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论