我們將實現一個具有AJax能力的組件——它不僅實現把文件上傳到服務器,而且"實時地"監視文件上傳的實際過程。
(四) ProgressMonitorFileItem類
public class ProgressMonitorFileItem extends DiskFileItem {
private ProgressObserver observer;
private long passedInFileSize;
...
private boolean isFormFIEld;
...
@Override
public OutputStream getOutputStream() throws IOException {
OutputStream baSEOutputStream = super.getOutputStream();
if(isFormFIEld == false){
return new BytesCountingOutputStream(baSEOutputStream);
}else{return baSEOutputStream;}
}
...
private class BytesCountingOutputStream extends OutputStream{
private long previousProgressUpdate;
private OutputStream base;
public BytesCountingOutputStream(OutputStream ous){ base = ous; }
...
private void fireProgressEvent(int b){
bytesRead += b;
...
double progress = (((double)(bytesRead)) / passedInFileSize);
progress *= 100.0
observer.setProgress();
}
}
}
ProgressMonitorFileItem把DiskFileItem的缺省OutputStream包裝到一個BytesCountingOutputStream中,這可以在每次讀取一定數目的字節後更新相關的ProgressObserver。
(五) 支持AJax的JavaServer Faces(JSF)上傳組件
這個組件負責生成Html文件上傳標簽,顯示一個進度條以監視文件上傳,並且生成一旦文件上傳成功需要被顯示的組件。使用JavaServer Faces實現這個組件的一個主要優點是,大多數復雜性被隱藏起來。開發人員只需要把組件標簽添加到JSP,而後由組件負責所有的AJax及相關的進度條監控細節問題。下面的JSP代碼片斷用於把上傳組件添加到頁面上。
<comp:fileUpload
value="#{uploadPageBean.uploadedFile}"
uploadIcon="images/upload.png"
styleClass="progressBarDiv"
progressBarStyleClass="progressBar"
cellStyleClass="progressBarCell"
activeStyleClass="progressBarActiveCell">
<%--下面是一旦文件上傳完成將成為可見的組件--%>
<h:panelGrid columns="2" cellpadding="2" cellspacing="0" width="100%">
<f:facet name="header">
<h:outputText styleClass="text"
value="文件上傳成功." />
</f:facet>
<h:panelGroup style="text-align:left;display:block;width:100%;">
<h:commandButton action="#{uploadPageBean.reset}"
image="images/reset.png"/>
</h:panelGroup>
<h:panelGroup style="text-align:right;display:block;width:100%;">
<h:commandButton action="#{uploadPageBean.nextPage}"
image="images/continue.png"/>
</h:panelGroup>
</h:panelGrid>
</comp:fileUpload>
文件上傳組件的value屬性需要用一個擁有一個FileItem的屬性綁定到一個bean上。組件只有在該文件被服務器成功收到時才顯示。
三、 實現AJax文件上傳組件 實質上,上載組件或者生成一個完整的自已,或者在一個AJax請求的情況下,只生成部分XML以更新在頁面上進度條的狀態。為了防止JavaServer Faces生成完整的組件樹(這會帶來不必要的負荷),我們還需要實現一個PhaseListener(PagePhaseListener)以取消該faces的請求處理的其它部分-如果遇到一個AJax請求的話。我在本文中略去了所有的關於標准配置(faces-config.XML和標簽庫)的討論,因為它們相當直接且已經在以前討論過;而且這一切都包含在隨同本文的源碼中,你可以詳細分析。
(一) AJax文件上傳組件生成器
該組件和標簽類的實現比較簡單。大量的邏輯被包含到生成器中,具體地說,它負責以下:
· 編碼整個的上傳組件(和完整的Html文件上傳標簽)、文件被上傳完成後要顯示的組件,還有實現AJax請求的客戶端JavaScript代碼。
· 適當地處理部分AJax請求並且發送回必要的XML。
· 解碼一個文件上傳並且把它設置為一個FileItem實例。
(二) 編碼整個上傳組件
前面已經提及,文件上傳組件由三個階段組成。在該組件的整個編碼期間,我們將詳細分析這三個階段的編碼。注意,在頁面上的該組件的可視化(使用CSS顯示)屬性將由AJax JavaScript來控制。
(三) 階段一
圖5顯示了該上傳組件的第一個階段。
圖5.選擇文件上傳
在第一階段中,我們需要生成Html文件Upload標簽和點擊Upload按鈕時相應的執行代碼。一旦用戶點擊了Upload按鈕,表單將被一個IFRAME(為防止頁面阻塞)提交並初始化第二個階段。下面是生成代碼的一部分:
//文件上傳組件
writer.startElement("input", component);
writer.writeAttribute("type", "file", null);
writer.writeAttribute("name", component.getClIEntId(context), "id");
writer.writeAttribute("id", component.getClIEntId(context),"id");
if(input.getValue() != null){
//如果可用,則生成該文件名.
FileItem fileData = (FileItem)input.getValue();
writer.writeAttribute("value", fileData.getName(), fileData.getName());
}
writer.endElement("input");
String iconURL = input.getUploadIcon();
//生成圖像,並把JavaScript事件依附到其上.
writer.startElement("div", component);
writer.writeAttribute("style","display:block;width:100%;text-align:center;", "style");
writer.startElement("img", component);
writer.writeAttribute("src",iconURL,"src");
writer.writeAttribute("type","image","type");
writer.writeAttribute("style","cursor:hand;cursor:pointer;","style");
UIForm form = FacesUtils.getForm(context,component);
if(form != null) {
String getFormJS = "document.getElementById('" + form.getClIEntId(context) + "')";
String JSFriendlyClientID = input.getClIEntId(context).replace(":","_");
//設置表單的編碼為multipart以用於文件上傳,並且通過一個IFRAME
//來提交它的內容。該組件的第二個階段也在500毫秒後被初始化.
writer.writeAttribute("onclick",getFormJS + ".encoding='multipart/form-data';" +
getFormJS + ".target='" + iframeName + "';" + getFormJS + ".submit();" +
getFormJS + ".encoding='application/x-www-form-urlencoded';" +
getFormJS + ".target='_self';" +
"setTimeout('refreshProgress" + JSFriendlyClIEntID + "();',500);",null);
}
...
writer.endElement("img");
//現在實現我們將要把該文件/表單提交到的IFRAME.
writer.startElement("iframe", component);
writer.writeAttribute("id", iframeName, null);
writer.writeAttribute("name",iframeName,null);
writer.writeAttribute("style","display:none;",null);
writer.endElement("iframe");
writer.endElement("div");
writer.endElement("div"); //階段1結束
(四) 階段二
第二階段是顯示當前百分比的進度條和標簽,如圖6所示。該進度條是作為一個具有100個內嵌span標簽的div標簽實現的。這些將由AJax JavaScript根據來自於服務器的響應進行設置。
圖6.上傳文件到服務器
writer.startElement("div",component);
writer.writeAttribute("id", input.getClIEntId(context) + "_stage2", "id");
...
writer.writeAttribute("style","display:none", "style");
String progressBarID = component.getClIEntId(context) + "_progressBar";
String progressBarLabelID = component.getClIEntId(context) + "_progressBarlabel";
writer.startElement("div", component);
writer.writeAttribute("id",progressBarID,"id");
String progressBarStyleClass = input.getProgressBarStyleClass();
if(progressBarStyleClass != null)
writer.writeAttribute("class",progressBarStyleClass,"class");
for(int i=0;i<100;i++){
writer.write("<span> </span>");
}
writer.endElement("div");
writer.startElement("div",component);
writer.writeAttribute("id",progressBarLabelID,"id");
...
writer.endElement("div");
writer.endElement("div"); //階段2結束
(五) 階段三
最後,作為階段三,一旦文件成功上傳,需要被顯示的組件即被生成,見圖7。這些是在生成器的encodeChildren方法中實現的。
圖7.上傳完成
public void encodeChildren(FacesContext context,
UIComponent component) throws IOException {
ResponseWriter writer = context.getResponseWriter();
UIFileUpload input = (UIFileUpload)component;
//一旦文件上傳成功,處理將被顯示的子結點
writer.startElement("div", component);
writer.writeAttribute("id", input.getClIEntId(context) + "_stage3", "id"); //階段3.
if(input.getValue() == null){
writer.writeAttribute("style","display:none;",null);
}else{
writer.writeAttribute("style","display:block",null);
}
List<UIComponent> children = input.getChildren();
for(UIComponent child : children){
FacesUtils.encodeRecursive(context,child);
}
writer.endElement("div"); //階段3結束
}