现在,图片管理就剩上传文件功能没有完成了。这需要用到swfupload,在《CMS之图片管理(1)》中有它的下载地址和加入项目的说明。
使用swfupload最麻烦的地方是要有一个HTML元素让它嵌入加载Flash的HTML代码,而且这个HTML元素必须覆盖住Flash来实现功能,这个有点类似做单按钮的上传按钮。
现在,先为swfupload生成一个HTML元素来实现它的功能。实现方法是在显示图片的视图底部添加一个工具栏,然后将工具栏分成两部分,第一部分显示一个SPAN元素,第二部分显示一个进度条来指示上传进度。
在PicManager.js文件中,找到me.items这句代码,在它上面创建一个进度条,代码如下:
me.progress=Ext.widget("progressbar",{text:"上传进度",flex:1});
因为在swfupload的处理方法中还要直接调用进度条,因而这里将它绑定到progress属性,会方便很多。因为水平工具栏默认是使用HBox布局的,因而在进度条上设置flex为1,就会让它占满整工具栏余下的宽度。
接着在图片文件的面板内添加一个dockedItems配置项,在面板底部放置一个工具栏,并在工具栏上放置一个SPAN元素和进度条,代码如下:
dockedItems: [
{ xtype:"toolbar", dock: "bottom",
items: [
{ xtype: "tbtext", text:"<span></span>", 70 },
me.progress
]
}
]
代码中,使用了一个TextItem来显示SPAN元素,宽度为70像素。
现在,要解决的是SPAN的id问题,这也是swfupload的要求,它需要根据该id来获取元素。如果统一一个id,那就会有冲突,因而必须想办法设置成不同的id,这个就需要用Ext的id方法,它看返回一个唯一的id。
在创建进度条的代码下添加以下代码获取一个id:
me.spanid = Ext.id();
修改一下SPAN元素的代码,为它加上id,代码如下:
"<span id='" + me.spanid +"'></span>"
现在要做的是监听扩展的afterrender事件,在callParent代码之前添加以下代码:
me.on("afterrender",me.onAfterRender);
接着要完成的就是onAfterRender方法了,在该方法内,主要工作是完成swfupload的初始化操作,代码如下:
onAfterRender: function(){
var me =this;
me.swfu= new SWFUpload({
upload_url: "/File/Upload",
file_size_limit: "10 MB",
file_types:"*.jpg;*.png;*.gif;*.bmp;",
file_types_description: "图片文件",
file_upload_limit: 100,
file_queue_limit: 0,
file_post_name: "Filedata",
swfupload_preload_handler: me.UploadPreLoad,
swfupload_load_failed_handler: me.UploadLoadFailed,
swfupload_loaded_handler: me.UploadLoaded,
file_queued_handler: me.fileQueued,
file_queue_error_handler: me.fileQueueError,
file_dialog_complete_handler: me.fileDialogComplete,
upload_start_handler: me.uploadStart,
upload_progress_handler: me.uploadProgress,
upload_error_handler: me.uploadError,
upload_success_handler: me.uploadSuccess,
upload_complete_handler: me.uploadComplete,
queue_complete_handler: me.queueComplete,
//Button settings
button_window_mode: SWFUpload.WINDOW_MODE.TRANSPARENT,
button_image_url: '',
button_placeholder_id: me.spanid,
button_ 60,
button_height: 20,
button_text: '上传图片',
button_text_style: '',
button_text_top_padding: 0,
button_text_left_padding: 0,
//Flash Settings
flash_url: "Scripts/swfupload/swfupload.swf", // Relative tothis file
flash9_url: "Scripts/swfupload/swfupload_FP9.swf", // Relativeto this file
custom_settings: { scope: me },
//Debug Settings
debug: false
});
}
以下是swfupload的配置项的说明:
q upload_url:上传文件的地址,代码中是File控制器的Upload方法。
q file_size_limit:限制上传文件的大小,代码中限制了只能上传小于10M的文件。
q file_types:允许上传的文件类型,代码中允许的类型是jpg、png、gif和bmp格式的文件。
q file_types_description:这个是显示在文件选择对话框中的描述。
q file_upload_limit:一次允许上传的文件数量,这里设置一次最多只能上传100个文件。
q file_queue_limit:文件队列的限制,这里设置为0表示没有限制。
q file_post_name:文件提交后,服务器端可根据该参数获取文件。
q swfupload_preload_handler:监听预加载事件。
q swfupload_load_failed_handler:监听上传失败事件。
q swfupload_loaded_handler:监听上传成功事件。
q file_queued_handler:监听上传队列事件。
q file_queue_error_handler:监听队列错误事件。
q file_dialog_complete_handler:监听文件选择对话框关闭事件。
q upload_start_handler:监听开始上传事件。
q upload_progress_handler:监听上传进度事件。
q upload_error_handler:监听上传错误事件。
q upload_success_handler:监听上传成功事件。
q upload_complete_handler:监听上传完成事件。
q queue_complete_handler:监听上传队列完成事件。
q button_window_mode:按钮的样式,这里设置了窗口系统模式。
q button_image_url:按钮图片的路径,因为没有,所以设置了为空。
q button_placeholder_id:就是SPAN元素的id了。
q button_width:按钮的宽度,要比TextItem小点。
q button_height:按钮的高度。
q button_text:按钮显示的文本,这里要显示的是“上传图片”。
q button_text_style:按钮文本的样式,这里不需要。
q button_text_top_padding:按钮文本的顶部内补丁。
q button_text_left_padding:按钮文本的左边内补丁。
q flash_url:flash文件所在的路径。
q flash9_url:flash 9版本的flash文件所在路径。
q custom_settings:自定义配置,这里一定要添加scope配置项,且值为me,这样就可在swfupload的事件内找到扩展自身,从而使用扩展的属性和方法。
q debug:是否开启调试模式,false表示不开启。
现在要完成的就是swfupload的监听事件了,这个基本可从swfupload包中的示例代码复制过来,具体代码如下:
UploadPreLoad:function(){
},
UploadLoadFailed:function(){
},
UploadLoaded:function(){
},
fileQueued:function(){
},
fileQueueError:function(file,errorCode, message){
try{
vardlg=Ext.Msg.alert;
if(errorCode === SWFUpload.QUEUE_ERROR.QUEUE_LIMIT_EXCEEDED) {
dlg("选择的文件太多。\n一次最多上传100个文件,而你选择了" +message + "个文件。");
return;
}
switch(errorCode) {
caseSWFUpload.QUEUE_ERROR.FILE_EXCEEDS_SIZE_LIMIT:
dlg("文件超过了10M.");
this.debug("错误代码: File too big, File name: " +file.name + ", File size: " + file.size + ", Message: " +message);
break;
caseSWFUpload.QUEUE_ERROR.ZERO_BYTE_FILE:
dlg("不允许上传0字节文件。");
this.debug("ErrorCode: Zero byte file, File name: " + file.name + ", File size: "+ file.size + ", Message: " + message);
break;
caseSWFUpload.QUEUE_ERROR.INVALID_FILETYPE:
dlg("非法的文件类型。");
this.debug("ErrorCode: Invalid File Type, File name: " + file.name + ", File size:" + file.size + ", Message: " + message);
break;
default:
if(file !== null) {
dlg("未知错误。");
}
this.debug("ErrorCode: " + errorCode + ", File name: " + file.name + ", Filesize: " + file.size + ", Message: " + message);
break;
}
}catch (ex) {
this.debug(ex);
}
},
fileDialogComplete:function(numFilesSelected,numFilesQueued){
try {
if(numFilesQueued > 0) {
varme=this.customSettings.scope,sels=me.tree.getSelectionModel().getSelection(),path="/";
if(sels.length>0){
path=sels[0].data.id;
SimpleCMS.postParams.path=path;
this.setPostParams(SimpleCMS.postParams);
this.startUpload();
}
else{
Ext.Msg.alert("请先选择文件夹。");
}
}
}catch (ex) {
this.debug(ex);
}
},
uploadStart:function(file){
try{
varme=this.customSettings.scope;
me.progress.updateProgress(0);
me.progress.updateText("正在上传文件"+file.name+"...");
}
catch(ex) {}
returntrue;
},
uploadProgress:function(file,bytesLoaded, bytesTotal){
try{
varpercent = bytesLoaded / bytesTotal;
varme=this.customSettings.scope;
me.progress.updateProgress(percent);
me.progress.updateText("正在上传文件"+file.name+"...");
}catch (ex) {
this.debug(ex);
}
},
uploadError:function(file,errorCode, message){
try{
varme=this.customSettings.scope;
me.progress.updateText("正在上传文件"+file.name+"...");
switch (errorCode) {
caseSWFUpload.UPLOAD_ERROR.HTTP_ERROR:
me.progress.updateText("上传错误:" + message);
this.debug("ErrorCode: HTTP Error, File name: " + file.name + ", Message: " +message);
break;
caseSWFUpload.UPLOAD_ERROR.UPLOAD_FAILED:
me.progress.updateText("上传失败。");
this.debug("ErrorCode: Upload Failed, File name: " + file.name + ", File size: "+ file.size + ", Message: " + message);
break;
caseSWFUpload.UPLOAD_ERROR.IO_ERROR:
me.progress.updateText("Server(IO) 错误");
this.debug("ErrorCode: IO Error, File name: " + file.name + ", Message: " +message);
break;
caseSWFUpload.UPLOAD_ERROR.SECURITY_ERROR:
me.progress.updateText("安全错误");
this.debug("ErrorCode: Security Error, File name: " + file.name + ", Message: " +message);
break;
caseSWFUpload.UPLOAD_ERROR.UPLOAD_LIMIT_EXCEEDED:
me.progress.updateText("文件大小超出限制。");
this.debug("ErrorCode: Upload Limit Exceeded, File name: " + file.name + ", File size:" + file.size + ", Message: " + message);
break;
caseSWFUpload.UPLOAD_ERROR.FILE_VALIDATION_FAILED:
me.progress.updateText("验证失败。");
this.debug("ErrorCode: File Validation Failed, File name: " + file.name + ", Filesize: " + file.size + ", Message: " + message);
break;
caseSWFUpload.UPLOAD_ERROR.FILE_CANCELLED:
break;
caseSWFUpload.UPLOAD_ERROR.UPLOAD_STOPPED:
me.progress.updateText("停止");
break;
default:
me.progress.updateText("未知错误:" + errorCode);
this.debug("ErrorCode: " + errorCode + ", File name: " + file.name + ", Filesize: " + file.size + ", Message: " + message);
break;
}
}catch (ex) {
this.debug(ex);
}
},
uploadSuccess:function(file,serverData){
try{
varme=this.customSettings.scope;
me.progress.updateProgress(1);
me.progress.updateText(serverData);
}catch (ex) {
this.debug(ex);
}
},
uploadComplete:function(file){
try {
if(this.getStats().files_queued > 0) {
this.startUpload();
}else {
varme=this.customSettings.scope;
me.progress.updateProgress(1);
me.progress.updateText("所有文件已上传。");
me.filestore.load();
}
} catch(ex) {
this.debug(ex);
}
},
queueComplete:function(numFilesUploaded){
varme=this.customSettings.scope;
me.progress.updateProgress(1);
me.progress.updateText("已上传"+ numFilesUploaded + "个文件");
if(numFilesUploaded>0){
me.filestore.load();
}
}
代码中UploadPreLoad、UploadLoadFailed、UploadLoaded和fileQueued么有使用到,其实可以在配置中去掉,在这里写出来是为了说明一下。要具体了解清楚这些事件,可仔细阅读一下swfupload的API文档。
先来看看fileQueueError方法,该方法会在队列出现出错时触发。代码中,涉及dlg的代码是提示给用户看的,debug则是在开启了调试模式时使用的。
方法fileDialogComplete会在文件选择对话框关闭后触发,在这里就意味着开始上传文件了,因而,当检测到队列中有文件(numFilesQueued大于0),就从customSettings中获取扩展自身,然后从树中获取选择模型,看是否有选择节点,如果有,就把选择节点(目录)作为上传路径,否则,提示用户选择一个节点(目录)。
在这里一定会很奇怪,为什么会有一个SimpleCMS. postParams的东西,它有什么用?这主要是验证问题,因为Flash上传并不会把当前页面的验证作为其验证,因而要在服务器端验证上传文件的用户是否已经登录且符合权限要求,就要求通过添加验证方式办法来实现,它的具体代码如下:
SimpleCMS.postParams = {
path:null,
"ASPSESSID": "@Session.SessionID",
"AUTHID": "@Request.Cookies[FormsAuthentication.FormsCookieName].Value"
};
代码中,path是用来设置上传路径的,后面两项就是用来填写验证信息用的。笔者在第一次做这个的时候,上传老是不成功,然后在调试模式下(设置debug为true),看到的提示是权限不足,奇怪了,然后google一下,发现原来Flash上传文件的验证信息不能和页面的同步,要加这两个东西来实现。
要完成这个,还要打开Global.asax文件,在里面加入以下代码:
protected void Application_BeginRequest()
{
varRequest = HttpContext.Current.Request;
varResponse = HttpContext.Current.Response;
try
{
string session_param_name = "ASPSESSID";
string session_cookie_name = "ASP.NET_SESSIONID";
if(HttpContext.Current.Request.Form[session_param_name] != null)
{
UpdateCookie(session_cookie_name,HttpContext.Current.Request.Form[session_param_name]);
}
elseif (HttpContext.Current.Request.QueryString[session_param_name] != null)
{
UpdateCookie(session_cookie_name,HttpContext.Current.Request.QueryString[session_param_name]);
}
string auth_param_name = "AUTHID";
string auth_cookie_name = FormsAuthentication.FormsCookieName;
if(HttpContext.Current.Request.Form[auth_param_name] != null)
{
UpdateCookie(auth_cookie_name,HttpContext.Current.Request.Form[auth_param_name]);
}
elseif (HttpContext.Current.Request.QueryString[auth_param_name] != null)
{
UpdateCookie(auth_cookie_name,HttpContext.Current.Request.QueryString[auth_param_name]);
}
}
catch(Exception e)
{
Response.StatusCode = 500;
Response.Write("Error Initializing Session");
}
}
private static void UpdateCookie(stringcookie_name, string cookie_value)
{
HttpCookie cookie =HttpContext.Current.Request.Cookies.Get(cookie_name);
if(cookie == null)
{
cookie = new HttpCookie(cookie_name);
//SWFUpload 的Demo中给的代码有问题,需要加上cookie.Expires 设置才可以
cookie.Expires = DateTime.Now.AddYears(1);
HttpContext.Current.Request.Cookies.Add(cookie);
}
cookie.Value = cookie_value;
HttpContext.Current.Request.Cookies.Set(cookie);
}
其实这代码,在swfupload的示例中也是有的,只是笔者没留意。
回到fileDialogComplete方法,调用swfupload的setPostParams方法就可将参数复制到swfupload中了,然后调用startUpload方法就可以开始上传文件了。
方法uploadStart会在文件开始上传的时候执行,在这里要做的就是更新进度条了。(这里说明一下,swfupload是一个个文件传的,并不是一次把所有文件都传过去的)。
方法uploadProgress就是用来更新进度的,主要功能就是更新进度条了。
方法uploadError是用来显示上传错误的,复制过来根据自己想法修改提示方式就行了。
方法uploadSuccess会在一个文件上传成功后执行,这里要做的就是将进度条显示到100%,并显示服务器端返回的信息。
文件上传完也会执行uploadComplete方法,在这里可检查队列中是是否还有文件,如果有,就调用startUpload继续上传,如果没有,则更新进度条显示,说明文件已经全部上传完毕。
队列中的文件都上传后会执行queueComplete方法,这个和uploadComplete方法检查队列中没有文件后的处理有点重叠,看你怎么取舍了。
最后,别忘了在Index.html加入加载swfupload.js的代码,代码如下:
@if (Request.IsAuthenticated)
{
<script type="text/javascript"src="@Url.Content("Scripts/swfupload/swfupload.js")"></script>
}
这个脚本只有在登录后才会加载。
现在切换到File控制器,完成Upload方法,这个不难,就是接收一个文件,然后保存而已,代码如下:
[AjaxAuthorize(Roles = "普通用户,系统管理员")]
public string Upload()
{
stringpath = Request["path"] ?? "";
string[]filetypes = { "jpg", "gif", "png","bmp" };
stringdir = Server.MapPath(root + path);
if(Directory.Exists(dir))
{
HttpPostedFileBase file = Request.Files["Filedata"];
string filename = file.FileName.ToLower();
string extname = filename.Substring(filename.LastIndexOf(".")+ 1, 3);
if(file.ContentLength <= 0)
{
return "不允许上传0字节文件。";
}
elseif (file.ContentLength > 10485760)
{
return "文件超过了10M。";
}
elseif (!filetypes.Contains(extname))
{
return "文件类型错误。" + extname;
}
else
{
try
{
file.SaveAs(dir + "\\" + filename);
return "文件已上传。" + dir + "\\" + newfilename;
}
catch (Exception e)
{
return e.Message;
}
}
}
else
{
return "保存目录不存在或已被删除。";
}
}
代码没有对重名文件做检查,这个有需要可以自己加上去。
现在可以测试一下了。生成解决方案后,在浏览器打开首页,将看到如图38所示的效果,在视图底部已经有了一个上传图片按钮(实际效果不像按钮,如果想类似按钮,最好是做一下图片)和进度条了。切换到目录1,然后单击上传图片按钮,上传一下图片后将看到如图39所示的效果。
图38 添加了上传图片按钮和进度条的图片管理界面
图39 上传文件后的显示
最后讨论一下扩展的Store问题,如果想所有的扩展都共享一个TreeStore和视图的Store,可以把它们独立出来,写到App的Store目录里,这样就可共享了。这还是个人喜好问题。
至此,图片管理的功能就完成了,余下就是文章管理的功能了。