AJAX+JSF组件实现高性能的文件上载(4)

AJAX+JSF组件实现高性能的文件上载(4)

[@more@]四、处理AJAX请求

AJAX请求的生成是在这个组件的解码方法中处理的。我们需要检查这是否是一个实际的AJAX请求(为了区别于正常的编译行为),然后基于由ProgressMonitorFileItemFactory类的SessionUpdatingProgressObserver实例设置在会话中的值把一个XML响应发送回客户端。

public void decode(FacesContext context, UIComponent component) {

 UIFileUpload input = (UIFileUpload) component;

 //检查是否这是一个上传进度请求,或是一个实际的上传请求.

 ExternalContext extContext = context.getExternalContext();

 Map parameterMap = extContext.getRequestParameterMap();

 String clientId = input.getClientId(context);

 Map requestMap = extContext.getRequestParameterMap();

 if(requestMap.get(clientId) == null){

return;//什么也不做,返回

 }

 if(parameterMap.containsKey(PROGRESS_REQUEST_PARAM_NAME)){

//这是一个在该文件请求中的得到进度信息的请求.

//得到该进度信息并把它生成为XML

HttpServletResponse response =

(HttpServletResponse)context.getExternalContext().getResponse();

//设置响应的头信息

response.setContentType("text/xml");

response.setHeader("Cache-Control", "no-cache");

try {

 ResponseWriter writer = FacesUtils.setupResponseWriter(context);

 writer.startElement("progress", input);

 writer.startElement("percentage", input);

 //从会话中获得当前进度百分数(由过滤器所设置).

 Double progressCount = (Double)extContext.getSessionMap().

 get("FileUpload.Progress." +input.getClientId(context));

 if(progressCount != null){

writer.writeText(progressCount, null);

 }else{

writer.writeText("1", null);//我们还没有收到上传

 }

 writer.endElement("percentage");

 writer.startElement("clientId", input);

 writer.writeText(input.getClientId(context), null);

 writer.endElement("clientId");

 writer.endElement("progress");

} catch(Exception e){

 //做一些错误记录...

}

}else{

 //正常的译码请求.

...

五、 正常的译码行为

在正常的编译期间,文件上传生成器从请求属性中检索FileItem,正是在此处它被过滤器所设置,并且更新该组件的值绑定。然后,该会话中的进度被更新到100%,这样在页面上的JavaScript就可以把组件送入第3个阶段。

//正常的译码请求.

if(requestMap.get(clientId).toString().equals("file")){

try{

 HttpServletRequest request = (HttpServletRequest)extContext.getRequest();

 FileItem fileData = (FileItem)request.getAttribute(clientId);

 if(fileData != null) input.setSubmittedValue(fileData);

 //现在我们需要清除与该项相关的任何进度

 extContext.getSessionMap().put(

"FileUpload.Progress." + input.getClientId(context),new Double(100));

}catch(Exception e){

 throw new RuntimeException("不能处理文件上传" +" - 请配置过滤器.",e);

}

}

客户端JavaScript负责向服务器发出进度请求并通过不同阶段来移动组件。为了简化处理所有的浏览器特定的XMLHttpRequest对象的问题,我选用了Matt Krause提供的AjaxRequest.js库。该库最大限度地减少我们需要编写的JavaScript代码的数量,同时可以使这个组件正常工作。也许把这部分JavaScript代码打包为该组件的一部分,然后从PhaseListener生成它更好一些,但是,我已经通过定义一个到JSP页面上的JavaScript库的链接来尽力使得它简单。

组件中的getProgressBarJavaScript方法被调用以生成JavaScript。使JavaScript正常工作通常是实现AJAX组件最困难的部分;不过我想,下面的代码已经非常清晰易于理解了。尽管在我的示例中JavaScript是嵌入到Java代码中的,但是把它放到一个外部独立的文件中也许更好一些。在本文中,我只是想使问题更为简单些且只关心本文的主题。下面是一个将由组件生成的JavaScript的示例。其中假定,fileUpload1是被赋值到该文件组件的客户端JSF Id,而uploadForm是HTML表单的Id。

function refreshProgress(){

 // 假定我们正在进入到阶段2.

 document.getElementById('fileUpload1_stage1').style.display = 'none';

 document.getElementById('fileUpload1_stage2').style.display = '';

 document.getElementById('fileUpload1_stage3').style.display = 'none';

 //创建AJAX寄送

 AjaxRequest.post(

 {

//指定正确的参数,以便

//该组件在服务器端被正确处理

'parameters':{ 'uploadForm':'uploadForm',

'fileUpload1':'fileUpload1',

'jsf.component.UIFileUpload':'1',

'ajax.abortPhase':'4' } //Abort at Phase 4.

//指定成功处理相应的回调方法.

,'onSuccess':function(req) {

var xml = req.responseXML;

if( xml.getElementsByTagName('clientId').length == 0) {

 setTimeout('refreshProgress()',200); return;

}

var clientId = xml.getElementsByTagName('clientId');

clientId = clientId[0].firstChild.nodeValue + '_progressBar';

//从XML获取百分比

var percentage =

xml.getElementsByTagName('percentage')[0].firstChild.nodeValue;

var innerSpans =

document.getElementById(clientId).getElementsByTagName('span');

document.getElementById(clientId + 'label').innerHTML

= Math.round(percentage) + '%';

//基于当前进度,设置这些span的式样类。

for(var i=0;i<innerSpans.length;i++){

 if(i < percentage){

innerSpans[i].className = 'active';

 }else{

innerSpans[i].className = 'passive';

 }

}

//如果进度不是100,我们需要继续查询服务器以实现更新.

if(percentage != 100){

 setTimeout('refreshProgress()',400);

} else {

 //文件上传已经完成,我们现在需要把该组件送入到第3个阶段.

 document.getElementById('fileUpload1_stage1').style.display = 'none';

 document.getElementById('fileUpload1_stage2').style.display = 'none';

 document.getElementById('fileUpload1_stage3').style.display = '';

}

 }

});

}

return builder.toString();

六、 结论

我很希望,本文能够在有关如何使得文件上传更具有用户友好性,并且把AJAX和JavaServer Faces用于实现高级用户接口组件的可能性方面引发你的进一步思考。毫无疑问,本文中的方案比较冗长并且有可能得到进一步的改进。我希望你能详细地分析一下本文中所提供的完整的源代码来深入理解本文中所讨论的概念。