express-11 表单处理(2)

时间:2022-01-25 04:36:48

处理AJAX表单###

用Express处理AJAX表单非常简单;甚至可以使用相同的处理程序来处理AJAX请求和常规的浏览器回退。

  • HTML文件 (/views/newsletter.handlebars)
<div class="formContainer">
<form class="form-horizontal newsletterForm" role="form"
action="/process?form=newsletter" method="POST">
<input type="hidden" name="_csrf" value="{{csrf}}">
<div class="form-group">
<label for="fieldName" class="col-sm-2 control-label">Name</label>
<div class="col-sm-4">
<input type="text" class="form-control" id="fieldName" name="name">
</div>
</div>
<div class="form-group">
<label for="fieldEmail" class="col-sm-2 control-label">Email</label>
<div class="col-sm-4">
<input type="email" class="form-control" required id="fieldName" name="email">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-4">
<button type="submit" class="btn btn-default">Register</button>
</div>
</div>
</form>
</div>
{{#section 'jquery'}}
<script>
$(document).ready(function(){
$('.newsletterForm').on('submit', function(evt){
evt.preventDefault();
var action = $(this).attr('action');
var $container = $(this).closest('.formContainer');
$.ajax({
url: action,
type: 'POST',
success: function(data){
if(data.success){
$container.html('<h2>Thank you!</h2>');
} else {
$container.html('There was a problem.');
}
},
error: function(){
$container.html('There was a problem.');
}
});
});
});
</script>
{{/section}}
  • 应用程序文件
app.post('/process', function(req, res){
if(req.xhr || req.accepts('json,html')==='json'){
// 如果发生错误,应该发送 { error: 'error description' }
res.send({ success: true });
} else {
// 如果发生错误,应该重定向到错误页面
res.redirect(303, '/thank-you');
}
});

Express提供了两个方便的属性:req.xhrreq.accepts。如果是AJAX请求(XHR是XML HTTP请求的简称,AJAX依赖于XHR),req.xhr值为truereq.accepts试图确定返回的最合适的响应类型。在此例中,req.accepts('json,html')询问最佳返回格式是JSON还是HTML:这可以根据Accepts HTTP头信息推断出来,它是浏览器提供的可读的、有序的响应类型列表。如果是一个AJAX请求,或者User-Agent明确要求JSON优先于HTML,那么就会返回合适的JSON数据;否则,返回一个重定向。

在这个函数里可以做任何处理:通常会将数据保存到数据库。如果出现问题,则返回一个err属性(而不是success)的JSON对象,或者重定向到一个错误页面(如果不是AJAX请求)。

在此例中,假设所有AJAX请求的是JSON数据,但是并没有要求AJAX通信必须使用JSON(事实上,“X”在AJAX中代表XML)。这个方法是jQuery友好的,因为通常jQuery假定所有数据都是JSON格式的。如果想让AJAX处理程序通用,或者知道AJAX请求使用JSON之外的东西,你应该根据Accepts头信息(可以根据req.accepts辅助方法轻松访问)返回一个适当的响应。如果响应完全基于Accepts头信息,你或许想看看c,这是一个可以根据客户端预期轻松做出适当响应的简便方法。如果这样做,必须保证用jQuery发起AJAX请求时设置dataTypeaccepts属性。

文件上传###

一般,文件上传可以使用Connect的内置中间件multipart来处理。但是,这个中间件已经从Connect中移除了,一旦Express更新了对Connect的依赖项,它也将从Express中消失,所以我强烈建议你不要使用这个中间件。

对于复合表单处理,目前有两种流行而健壮的选择:BusboyFormidable。Formidable要稍微简单一些,因为它有一个方便的回调方法,能够提供包含字段和文件信息的对象。对于Busboy而言,必须对每一个字段和文件事件进行监听。

  • (views/contest/vacation-photo.handlebars)
<form class="form-horizontal" role="form"
enctype="multipart/form-data" method="POST"
action="/contest/vacation-photo/{year}/{month}">
<div class="form-group">
<label for="fieldName" class="col-sm-2 control-label">Name</label>
<div class="col-sm-4">
<input type="text" class="form-control"
id="fieldName" name="name">
</div>
</div>
<div class="form-group">
<label for="fieldEmail" class="col-sm-2 control-label">Email</label>
<div class="col-sm-4">
<input type="email" class="form-control" required
id="fieldName" name="email">
</div>
</div>
<div class="form-group">
<label for="fieldPhoto" class="col-sm-2 control-label">Vacation photo
</label>
<div class="col-sm-4">
<input type="file" class="form-control" required accept="image/*" id="fieldPhoto" name="photo">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-4">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>

注意,我们必须指定enctype="multipart/form-data"来启用文件上传。我们也可以通过accept属性来限制上传文件的类型(这是可选的)。

  • 安装Formidable(npm install --save formidable)并创建一下路由处理程序:
var formidable = require('formidable');

app.get('/contest/vacation-photo',function(req,res){
var now = new Date();
res.render('contest/vacation-photo',{
year: now.getFullYear(),month: now.getMont()
});
}); app.post('/contest/vacation-photo/:year/:month', function(req, res){
var form = new formidable.IncomingForm();
form.parse(req, function(err, fields, files){
if(err) return res.redirect(303, '/error');
console.log('received fields:');
console.log(fields);
console.log('received files:');
console.log(files);
res.redirect(303, '/thank-you');
});
});

(year和month被指定为路由参数)。继续运行,检查控制台日志。你会发现表单字段如你预期的那样:是一个有字段名称属性的对象。文件对象包含更多的数据,但这是相对简单的。对于每一个上传的文件,你会看到属性有文件大小、上传路径(通常是在临时目录中的一个随机名字),还有用户上传此文件的原始名字(文件名,而不是整个路径,出于安全隐私考虑)。

接下来如何处理这个文件就取决于你了:可以将它保存到数据库,将其复制到更持久的位置,或者上传到云端文件存储系统。记住,如果你基于本地存储保存文件,应用程序不能很好地扩展,基于云端存储是一个更好的选择。

jQuery文件上传###

如果想为用户提供真正别出心裁的文件上传,可拖拽,可以看到上传文件缩略图,并查看进度条,那推荐 Sebastian Tschan的jQuery File Upload

要显示文件缩略图,jquery-file-upload-middleware使用[ImageMagick](http://www.imagemagick.org

在Ubuntu和Debian系统中,可以使用apt-get install imagemagick安装ImageMagick;在OS X中,你可以使用brew install imagemagick来安装。对于其他操作系统,请参考[ImageMagick文档](http://www.imagemagick.org/script/binary-releases.php)。

  • 首先,安装jquery-file-upload-middleware包(npm install --save jquery-file-upload-middleware),然后在你的应用文件中添加以下代码:
var jqupload = require('jquery-file-upload-middleware');

app.use('/upload', function(req, res, next){
var now = Date.now();
jqupload.fileHandler({
uploadDir: function(){
return __dirname + '/public/uploads/' + now;
},
uploadUrl: function(){
return '/uploads/' + now;
},
})(req, res, next);
});

除非为所有访问者提供一个共用的文件上传区域,否则可能要将上传文件区分开来。简单的方法是创建一个时间戳目录来存储文件。更实际的做法是使用用户ID或其他唯一ID来创建子目录。

请注意,我们将jQuery File Upload中间件挂载在/upload前缀上。你可以在这里使用任何前缀,但是确保该前缀不用于其他路由或中间件,不然会干扰文件上传操作。

  • 接下来是文件上传的视图,你可以直接复制演示上传代码:你可以在project's GitHub页面上传最新项目包。

如果你只想要一个可构建的最小示例,需要如下脚本:

js/vendor/jquery.ui.widget.jsjs/jquery.iframe-transport.js和js/jquery.fileupload.js。

在这个最小实现中,我们将<input type="file">元素放在<span>中,还有一个<div>用来列出所有已上传文件:

<span class="btn btn-default btn-file">
Upload
<input type="file" class="form-control" required accept="image/*"
id="fieldPhoto" data-url="/upload" multiple name="photo">
</span>
<div id="uploads"></div>
  • 然后我们加上jQuery File Upload:
{{#section 'jquery'}}
<script src="/vendor/jqfu/js/vendor/jquery.ui.widget.js"></script>
<script src="/vendor/jqfu/js/jquery.iframe-transport.js"></script>
<script src="/vendor/jqfu/js/jquery.fileupload.js"></script>
<script>
$(document).ready(function(){ $('#fieldPhoto').fileupload({
dataType: 'json',
done: function(e, data){
$.each(data.result.files, function(index, file){
$('#fileUploads').append($('<div class="upload">' +
'<span class="glyphicon glyphicon-ok"></span>' +
'&nbsp;' + file.originalName + '</div>'));
});
}
});
});
</script>
{{/section}}
  • 为上传按钮添加CSS动态样式:
.btn-file {
position: relative;
overflow: hidden;
}
.btn-file input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 999px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
background: white;
cursor: inherit;
display: block;
}

注意,<input>标签里的data-url属性必须和用于中间件的路由前缀相匹配。在这个简单示例中,当一个文件上传完成后,一个<div class="upload">元素会附加到之前的<div id="uploads">下面。这个列表只显示文件名和大小,不提供删除、运行或者缩略图功能。但这是一个好的开始。定制jQuery File Upload演示程序会让人望而生畏,如果你的视角完全不同,从最小程序开始逐渐向上构建,而不是从演示和定制开始,可能会更简单。不管怎样,你会在jQuery File Upload文档网页找到你想要的资源。