Inconsistent rich:fileUpload behavior
gs_noe Nov 3, 2009 7:11 PMUsing: RichFaces 3.3.2 SR1, JSF 1.2, Tomcat 6.0.20
Sorry for the long post, but I'm at the end of my rope. I'm having a few issues with the rich:fileUpload component behaving very inconsistently.
1. The fileUploadListener is called inconsistently. There are no stack traces or errors generated in Firebug. It takes several tries to get a file to upload.
2. The a4j:support function for the onuploadcomplete event is not called consistently, again, no stack trace or error in Firebug. Sometimes you have to upload three or more files for the data table that displays the uploaded to be re-rendered (and I'm not even sure if the onuploadcomplete is being fired then).
3. The inconsistency doesn't seem to be browser specific.
Things to note:
- The form is in a secured (HTTPS) area of the web site.
- The site is still in development so the security certificates on my local machine and our demo server are self-signed.
- The flash module is not being used (doesn't work under HTTPS in browsers other than IE (thanks, Adobe!), perhaps this should be documented somewhere?)
- I have tried to model my backing bean and file upload objects as close to the examples on the RichFaces demo site as I could.
- I have tried using h:form and a4j:form with no difference in behavior.
Thanks for any help.
Here's my HTML, which is used called from a ui:include within an a4j:outputPanel:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:a4j="http://richfaces.org/a4j" xmlns:rich="http://richfaces.org/rich"> <ui:composition> <p> <h:outputText value="Photo to PDF Utility" style="color:#093379;font-size:1.4em;font-weight:bold;"/> </p> <hr/> <a4j:form id="frmImgToPDF"> <h:panelGrid columns="1" style="height:100%;width:100%;" columnClasses="pgColFullWidth"> <h:column id="topSection"> <h:panelGrid columns="2" columnClasses="p2pdfTopLeftColumn, p2pdfTopRightColumn"> <h:column id="topLeftSection"> <h:panelGrid columns="1"> <h:outputLabel value="#{msgs.photo_to_pdf_label_pdf_title}" for="txtPdfTitle" style="font-weight:bold;"/> <h:inputText value="#{photoToPDFBean.pdfTitle}" id="txtPdfTitle" style="width:300px;" maxlength="40" validatorMessage="Maximum 40 characters."> <f:validateLength maximum="40"/> <rich:ajaxValidator event="onblur"/> </h:inputText> <rich:message for="txtPdfTitle" errorClass="errorMessage"> <f:facet name="errorMarker"> <h:graphicImage value="/icons/exclamation.png" class="errorMarker"/> </f:facet> </rich:message> <h:outputLabel value="#{msgs.photo_to_pdf_label_pdf_desc}" for="txtPdfDesc" style="font-weight:bold;"/> <h:inputTextarea style="width:300px;" rows="6" value="#{photoToPDFBean.pdfDescription}" id="txtPdfDesc" validatorMessage="Maximum 500 characters." > <f:validateLength maximum="500" /> <rich:ajaxValidator event="onblur"/> </h:inputTextarea> <rich:message for="txtPdfDesc" errorClass="errorMessage"> <f:facet name="errorMarker"> <h:graphicImage value="/icons/exclamation.png" class="errorMarker"/> </f:facet> </rich:message> </h:panelGrid> </h:column> <h:column id="topRightSection"> <h:panelGrid columns="1" style="width:100%;"> <h:outputLabel value="#{msgs.photo_to_pdf_label_imgs_to_upl}" for="cUpload" style="font-weight:bold;"/> <rich:fileUpload fileUploadListener="#{photoToPDFBean.listener}" addControlLabel="Add Image" clearAllControlLabel="Clear All Images" clearControlLabel="Clear" stopEntryControlLabel="Stop Upload" uploadControlLabel="Upload Images" maxFilesQuantity="#{photoToPDFBean.uploadsAvailable}" id="cUpload" autoclear="false" acceptedTypes="jpg, gif, png, bmp" noDuplicate="true" allowFlash="#{photoToPDFBean.allowFlash}" ontyperejected="alert('Only .jpg, .gif, .png and .bmp files are allowed.');" uploadData="#{photoToPDFBean.uploadData}" listWidth="95%" styleClass="fileUploadBox"> <a4j:support event="onuploadcomplete" reRender="dataTablePanel" limitToList="true"/> </rich:fileUpload> </h:panelGrid> </h:column> </h:panelGrid> </h:column> <h:column id="bottomSection"> <h:panelGrid id="dtContainer" columns="1" styleClass="pgColFullWidth" columnClasses="pgColFullWidth"> <a4j:outputPanel id="dataTablePanel"> <rich:dataTable columns="3" id="dtFileView" value="#{photoToPDFBean.files}" var="file" rowKeyVar="row" style="margin-top:30px;width:95%;margin-left:2.5%;" columnClasses="pgValignTop colCenterAlign leftFUColumn,pgValignTop pgColAlignCenter centerFUColumn,pgValignTop rightFUColumn"> <f:facet name="header"> <rich:columnGroup style="width:100%;"> <rich:column colspan="3" width="100%"> <h:outputText value="#{msgs.photo_to_pdf_label_fview_tbl_hdr_upld_files}"/> </rich:column> <rich:column breakBefore="true"> <h:outputText value="#{msgs.photo_to_pdf_label_fview_tbl_hdr_file_name}"/> </rich:column> <rich:column> <h:outputText value="#{msgs.photo_to_pdf_label_fview_tbl_hdr_description}" /> </rich:column> <rich:column> <h:outputText value="#{msgs.photo_to_pdf_label_fview_tbl_hdr_image}" /> </rich:column> </rich:columnGroup> </f:facet> <rich:column> <h:outputText value="#{file.name}"/> </rich:column> <rich:column> <a4j:outputPanel style="display:#{file.name != 'No Files Uploaded' ? '' : 'none'}"> <h:inputTextarea value="#{file.description}" id="txtFileDesc" style="width:90%;" validatorMessage="#{msgs.validate_max_chars_500}" tabindex="99#{row}"> <f:validateLength maximum="500"/> <a4j:support action="#{photoToPDFBean.updateFileDesc}" event="onblur" limitToList="true"> <a4j:actionparam name="rowId" value="#{row}" assignTo="#{photoToPDFBean.selectedRow}"/> <a4j:actionparam name="imgDesc" value="document.getElementById('bodyContainerInclude:bodyPanelInclude:frmImgToPDF:dtFileView:#{row}:txtFileDesc').value" assignTo="#{photoToPDFBean.imageDescription}" noEscape="true"/> </a4j:support> </h:inputTextarea> <br/> <rich:message for="txtFileDesc" errorClass="errorMessage"> <f:facet name="errorMarker"> <h:graphicImage value="/icons/exclamation.png" class="errorMarker"/> </f:facet> </rich:message> </a4j:outputPanel> </rich:column> <rich:column> <a4j:mediaOutput element="img" mimeType="#{file.mimeType}" createContent="#{photoToPDFBean.paint}" value="#{row}" style="width:100px; height:100px;" cacheable="false"/> </rich:column> <f:facet name="footer"> <rich:columnGroup> <rich:column style="text-align:left;"> <h:commandButton action="#{photoToPDFBean.clearFileAction}" value="#{msgs.photo_to_pdf_label_btn_clear_files}" disabled="#{photoToPDFBean.fileCount gt 0? '' : 'true'}"/> </rich:column> <rich:column colspan="2" style="text-align:right;"> <h:outputLabel value="#{msgs.photo_to_pdf_label_cb_no_img_scaling}" for="boolImgScale" style="padding-right:3px;"> <rich:toolTip value="#{msgs.photo_to_pdf_label_cb_no_img_scaling_desc}" showEvent="onmouseover" style="max-width:200px;text-align:left;" for="boolImgScale"/> </h:outputLabel> <h:selectBooleanCheckbox value="#{photoToPDFBean.noImageScaling}" id="boolImgScale" style="margin-right:15px;"/> <h:commandButton action="#{photoToPDFBean.createPDFAction}" value="#{msgs.photo_to_pdf_label_btn_create_pdf}" disabled="#{photoToPDFBean.fileCount gt 0? '' : 'true'}"/> </rich:column> </rich:columnGroup> </f:facet> </rich:dataTable> </a4j:outputPanel> </h:panelGrid> </h:column> </h:panelGrid> </a4j:form> </ui:composition> </html>
Here's my backing bean (session scope) code (unimportant stuff removed):
public class PhotoToPDFBean { /* * The purpose of the filler file is to keep at least one, VALID * ImageUploadFile in the List<ImageUploadFile> files list. My guess * is that an empty list causes the "Uploaded Files" rich:dataTable or * the a4j:mediaOutput (or associated "paint" method) to wreak havoc * on calls to upload photos or the rerendering of components. Bottom line, * don't remove this. (The file data byte array is a 1px by 1px white 1 bit .bmp image) * Of course, I could be completely wrong about this... */ private static ImageUploadFile FILLER_FILE; static { FILLER_FILE = new ImageUploadFile(); FILLER_FILE.setName("No Files Uploaded"); FILLER_FILE.setData(new byte[]{66,77,68,0,0,0,0,0,0,0,62,0,0,0,40,0,0,0,1,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,6,0,0,0,39,0,0,0,39,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,0}); FILLER_FILE.setLength(FILLER_FILE.getLength()); FILLER_FILE.setMimeType("bmp"); } private static final float MAX_LETTER_IMG_WIDTH = 489f; private static final float MAX_LETTER_IMG_HEIGHT = 633f; private String allowFlash = "false"; private List<ImageUploadFile> files; private List<UploadItem> uploadData; private int uploadsAvailable = 20; private static final String FMT_IMG_NAME_NUM = "Image Name: %s (Image %d of %d)"; private int selectedRow; private String imageDescription; private String pdfTitle; private String pdfDescription; private Boolean noImageScaling; /** * Constructor for PhotoToPDFBean */ public PhotoToPDFBean() { // Keep this setting to allow image manipulation System.setProperty("com.sun.media.jai.disableMediaLib", "true"); } /** * Getter for the files. * @return the List<ImageUploadFile> : files */ public final List<ImageUploadFile> getFiles() { if (null == this.files) { this.files = new ArrayList<ImageUploadFile>(); this.files.add(FILLER_FILE); } return this.files; } /** * Setter for the files. * @param files the files to set */ public final void setFiles(List<ImageUploadFile> files) { this.files = files; } /** * Getter for the uploadsAvailable. * @return the int : uploadsAvailable */ public final int getUploadsAvailable() { return this.uploadsAvailable; } /** * Setter for the uploadsAvailable. * @param uploadsAvailable */ public final void setUploadsAvailable(int uploadsAvailable) { this.uploadsAvailable = uploadsAvailable; } /** * Gets the number of files that have been uploaded. * @return */ public int getFileCount() { if (getFiles().size() > 0) { return getFiles().size(); } else { return 0; } } /** * Upload event listener for adding files to the list. * @param event * @throws Exception */ public void listener(UploadEvent event) throws Exception { try { UploadItem item = event.getUploadItem(); ImageUploadFile file = new ImageUploadFile(); // This way we can switch between use of temp files or memory for item storage. if (item.isTempFile()) { file.setName(FilenameUtils.getName(item.getFileName())); file.setData(FileUtils.readFileToByteArray(item.getFile())); file.setLength(file.getData().length); } else { file.setName(item.getFileName()); file.setData(item.getData()); file.setLength(item.getData().length); } // Removes the filler file if (getFiles().size() == 1) { if (getFiles().get(0).getName().equalsIgnoreCase("No Files Uploaded")) { getFiles().remove(0); } } getFiles().add(file); this.uploadsAvailable--; } catch (Exception e) { e.printStackTrace(); } } /** * Method to get file content. * @param stream * @param object * @throws IOException Because you screwed up. */ public void paint(OutputStream stream, Object object) throws IOException { ImageUploadFile f = getFiles().get((Integer)object); stream.write(f.getData()); } /** * Method to clear uploaded files. */ public String clearFileAction() { getFiles().clear(); getFiles().add(FILLER_FILE); setUploadsAvailable(20); setPdfTitle(""); setPdfDescription(""); return null; } /** * Sets a description of the file. */ public final void updateFileDesc() { if (getSelectedRow() > -1) { getFiles().get(getSelectedRow()).setDescription(getImageDescription()); } } /** * Getter for the selectedRow. * @return the selectedRow */ public final int getSelectedRow() { return this.selectedRow; } /** * Setter for the selectedRow. * @param selectedRow the selectedRow to set */ public final void setSelectedRow(int selectedRow) { this.selectedRow = selectedRow; } /** * Getter for the upload data * @return the uploadData */ public final List<UploadItem> getUploadData() { if (null == this.uploadData) { this.uploadData = new ArrayList<UploadItem>(); } return this.uploadData; } /** * Setter for the upload data. * @param uploadData the uploadData to set */ public final void setUploadData(List<UploadItem> uploadData) { this.uploadData = uploadData; } /** * Getter for allowFlash. * @return the allowFlash */ public final String getAllowFlash() { return this.allowFlash; } /** * Setter for useFlash. * @param allowFlash the useFlash to set */ public final void setAllowFlash(String allowFlash) { this.allowFlash = allowFlash; } }