ajax|js|效能 我們將實現一個具有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顯示了該上傳組件的第一個階段。
在第一階段中,我們需要產生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根據來自於伺服器的響應進行設定。
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方法中實現的。
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結束 } |