-
1. Re: t:inputFileUpload or s:fileUpload with ajax4jsf
sergeysmirnov Apr 2, 2007 10:50 AM (in response to holgerprause)fileUpload cannot work in Ajax mode. It is a strong limitation from coming from the browser.
-
2. Re: t:inputFileUpload or s:fileUpload with ajax4jsf
holgerprause Apr 2, 2007 11:36 AM (in response to holgerprause)Hello, good to get a clear answer on that.
Well i think i wil have to make an extra step in my wizard
(with page reload) for the fileupload :-/
But thx for the answer,
Bye,
Holger -
3. Re: t:inputFileUpload or s:fileUpload with ajax4jsf
andrew.rw.robinson Apr 3, 2007 11:37 AM (in response to holgerprause)There is a work-around (I just implemented one yesterday). The code is commercial, so I can't share it, but I can tell you what I did.
You can submit the input type=file asynchronously and then wait for the response. Steps:
1) create an input type=file (using the t:inputFileUpload for example)
2) override the onchange event of the control
3) using JavaScript create a hidden IFRAME with src="javascript:false" (to make IE happy under HTTPS), and style="display: none;"
4) set the ID of the IFRAME and then add the IFRAME to the document.body
5) set the Name of the IFRAME to the ID (for some reason in IE6 if you set it before it is added to the document, it loses the value, so you need to set it afterwards)
6) using JavaScript, create a hidden form and add it to the document.body
4) set the form's target to the IFRAME's name
5) set the encoding and enctype of the form to "multipart/form-data" (IE6 needs encoding instead of the standard enctype)
6) set the method of the form to POST
7) set the action property to that of a custom servlet
8) append the INPUT file's ID to the action (client ID of the UIComponent) (optional: append the JBoss-Seam conversationId)
9) move the input file control to the new form (you will want to remember it's original parentNode so that you can move it back later)
10) create a custom servlet and register it with web.xml (use the same name that you used in the form's action)
10b) (optional) register the Seam Servlet Filter so that it picks up the servlet's URL mapping (so you can access the conversation)
11) In that servlet, use the Apache Commons file upload API to parse the request and get the file
12) Get the client ID of the control from the request parameters
13) Get the users HttpSession and put the FileInput control into the session using the client ID as the key
13b) (optional) Instead of 13, get the Seam conversation Context from Contexts and store the FileInput value into the context instead of the session
14) Have the servlet spit back JavaScript in the onload event of the body tag to call the "parent.window.[your function]" to let your page know that it is done
15) Create a custom renderer for this control (instead of using the standard Tomahawk one)
16) Copy the code straight from the Tomahawk renderer
17) Change the decode method to get the value for the FileInput from the Conversation context or user HttpSession and use that value to set the components submitted value.
This will cause the file to be submitted into a different IFRAME, leaving your page untouched. The only thing I haven't coded myself is error handling (if the JavaScript is never written by the servlet).
If I get permission from my company, I may write a blog with the source, but I'm not sure if they would appreciate that. -
4. Re: t:inputFileUpload or s:fileUpload with ajax4jsf
andrew.rw.robinson Apr 3, 2007 11:39 AM (in response to holgerprause)FYI, an easier way is to just put an IFRAME in the page with a form & input file control that submits with the onchange event. That way you don't have to worry about creating temporary forms, IFRAME and such. The reason I didn't do it this way is that it would be harder to style the IFRAME so that it doesn't look like there is an IFRAME (borders, background colors, width, height, etc).
-
5. Re: t:inputFileUpload or s:fileUpload with ajax4jsf
ewfolf Jul 31, 2007 3:03 PM (in response to holgerprause)Hi, I'm implementing an ajax upload over an iframe.
Parsing the request for the uploaded file (tomahawk/apache common fileupload api) works only, if no context filter is setted in components.xml. But without that filter, I won't have access to the various Seam Contexts.<web:context-filter url-pattern="/upload/*" />
How did you solve this issue? -
6. Re: t:inputFileUpload or s:fileUpload with ajax4jsf
ewfolf Jul 31, 2007 3:07 PM (in response to holgerprause)Of course it is only an ajax-like upload ;-)
Sorry. -
7. Re: t:inputFileUpload or s:fileUpload with ajax4jsf
fabmars Aug 1, 2007 5:02 PM (in response to holgerprause)I've tried to implement Andrew's method for an input file + button, that would in return callback and trigger some reRender'ing.
This is a nightmare to have this to work on different browsers. The best is to start from tomahawk's inputfileupload code and a variant of the callToServer() script on this url: http://developer.apple.com/internet/webcontent/iframe.html
I'll post my code when finished. -
8. Re: t:inputFileUpload or s:fileUpload with ajax4jsf
ratondeau Aug 2, 2007 4:47 AM (in response to holgerprause)so is it possible to upload a file inside a modal panel with that method? Would prefer a build-in ajax solution for that.
Appreciate if you post your code if succeeded fabmars. -
9. Re: t:inputFileUpload or s:fileUpload with ajax4jsf
fabmars Aug 2, 2007 5:30 PM (in response to holgerprause)For the moment I managed to make one custom inputFileUpload component based on Tomahawk's which does exactly what Andrew explained.
The problem is I also needed to display a preview of the uploaded file (i'm uploading images and building thumbnails). So I made my component to call a certain js function (myfunction) on upload completion/fail and an <a4j:jsFunction name="myfunction" reRender="..."> placed in the form would take care of the reRender.
Now I'm trying to integrate it all in ONE component, but it's the first time I'm tweaking inside the A4J framework. The funny thing is I've succeeded but the main A4J JS file won't load anymore in my page. No doubt it's an error of mine, I'm working on it. As soon as I've finished, I'll post the full code. -
10. Re: t:inputFileUpload or s:fileUpload with ajax4jsf
ewfolf Aug 8, 2007 3:17 PM (in response to holgerprause)Hi fabmars,
can you please post your code for the ajax-like upload (how Andrew generously explained)?
In my solution, I've still the problem I posted previously.
Thank you. -
11. Re: t:inputFileUpload or s:fileUpload with ajax4jsf
fabmars Aug 8, 2007 9:38 PM (in response to holgerprause)Here we go: I did more or less the same inputFile as you may find into the IceFaces lib, but using Tomahawk's component and integrating A4J into it.
Needless to say this is provided as it without any guarantee whatsoever.
org.apache.myfaces.custom.fileupload.HtmlAjaxFileUploadpackage org.apache.myfaces.custom.fileupload; import javax.faces.context.FacesContext; import javax.faces.el.ValueBinding; import org.apache.myfaces.custom.fileupload.HtmlInputFileUpload; import org.apache.myfaces.shared_tomahawk.util._ComponentUtils; public class HtmlAjaxFileUpload extends HtmlInputFileUpload { public static final String COMPONENT_TYPE = "org.apache.myfaces.HtmlAjaxFileUpload"; public static final String DEFAULT_RENDERER_TYPE = "org.apache.myfaces.AjaxFileUpload"; private String _reRender = null; private String _buttonValue = null; public HtmlAjaxFileUpload() { super(); setRendererType(DEFAULT_RENDERER_TYPE); } public String getReRender() { if (_reRender != null) return _reRender; ValueBinding vb = getValueBinding("reRender"); return vb != null ? _ComponentUtils.getStringValue(getFacesContext(), vb) : null; } public void setReRender(String string) { _reRender = string; } public String getButtonValue() { if (_buttonValue != null) return _buttonValue; ValueBinding vb = getValueBinding("buttonValue"); return vb != null ? _ComponentUtils.getStringValue(getFacesContext(), vb) : null; } public void setButtonValue(String string) { _buttonValue = string; } public Object saveState(FacesContext context) { Object values[] = new Object[3]; values[0] = super.saveState(context); values[1] = _reRender; values[2] = _buttonValue; return ((Object) (values)); } public void restoreState(FacesContext context, Object state) { Object values[] = (Object[])state; super.restoreState(context, values[0]); _reRender = (String)values[1]; _buttonValue = (String)values[2]; } }
org.apache.myfaces.custom.fileupload.HtmlAjaxFileUploadRenderer/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.myfaces.custom.fileupload; import java.io.IOException; import java.util.Map; import javax.faces.component.UIComponent; import javax.faces.component.html.HtmlCommandButton; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.convert.ConverterException; import org.ajax4jsf.ajax.html.HtmlAjaxFunction; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.myfaces.component.UserRoleUtils; import org.apache.myfaces.custom.fileupload.UploadedFile; import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils; import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML; import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRendererUtils; import org.richfaces.renderkit.InputRendererBase; import com.sun.faces.renderkit.html_basic.ButtonRenderer; public class HtmlAjaxFileUploadRenderer extends InputRendererBase { private static final Log log = LogFactory.getLog(HtmlAjaxFileUploadRenderer.class); private final static String NAME_CLEAN_REGEX = "[^a-zA-Z0-9]"; public final static String INPUTFILE_ID = "id"; public final static String INPUTFILE_PREFIX = "upload"; public final static String CALLBACK_SUCCESS_SUFFIX = "cb_success"; public final static String CALLBACK_FAIL_SUFFIX = "cb_fail"; public final static String CALLBACK_RERENDER_SUFFIX = "cb_rerender"; public final static String DEFAULT_BUTTON_VALUE = "Upload"; /** * @see http://jboss.com/index.html?module=bb&op=viewtopic&t=105534 * @see http://www.ibm.com/developerworks/library/wa-facescomp3/ * @see http://developer.apple.com/internet/webcontent/iframe.html */ public void encodeEnd(FacesContext facesContext, UIComponent uiComponent) throws IOException { super.encodeEnd(facesContext, uiComponent); //check for NP ResponseWriter writer = facesContext.getResponseWriter(); HtmlAjaxFileUpload inputFileComponent = (HtmlAjaxFileUpload)uiComponent; String clientId = uiComponent.getClientId(facesContext); String idSuffix = clientId.replaceAll(NAME_CLEAN_REGEX, "_"); //to conform to the jsf spec about ids String spanId = "span_" + idSuffix; writer.startElement(HTML.SPAN_ELEM, uiComponent); writer.writeAttribute(HTML.ID_ATTR, spanId, null); //file input writer.startElement(HTML.INPUT_ELEM, uiComponent); writer.writeAttribute(HTML.TYPE_ATTR, HTML.FILE_ATTR, null); writer.writeAttribute(HTML.ID_ATTR, clientId, null); writer.writeAttribute(HTML.NAME_ATTR, clientId, null); UploadedFile value = (UploadedFile)inputFileComponent.getValue(); if (value != null) { if( value.getName() != null ) { writer.writeAttribute(HTML.VALUE_ATTR, value.getName(), null); } } HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent, HTML.INPUT_FILE_PASSTHROUGH_ATTRIBUTES_WITHOUT_DISABLED); if (isDisabled(facesContext, uiComponent)) { writer.writeAttribute(HTML.DISABLED_ATTR, Boolean.TRUE, null); } writer.endElement(HTML.INPUT_ELEM); String frameFunc = makeFuncName(clientId, "onclick"); String contextPath = facesContext.getExternalContext().getRequestContextPath(); String actionUrl = contextPath + "/uploadHtml?" + INPUTFILE_ID + "=" + clientId; String hFrameId = "hifrm_" + idSuffix; String hFormId = "hform_" + idSuffix; String buttonId = "upbtn_" + idSuffix; String buttonValue = inputFileComponent.getButtonValue(); if(buttonValue == null) { buttonValue = DEFAULT_BUTTON_VALUE; } // Adding the upload button HtmlCommandButton uploadButton = new HtmlCommandButton(); uploadButton.setId(buttonId); uploadButton.setType(HTML.BUTTON_ELEM); uploadButton.setValue(buttonValue); String submitCall = frameFunc + "('" + actionUrl + "','" + hFrameId + "','" + hFormId + "');"; uploadButton.setOnclick(submitCall); ButtonRenderer btRenderer = new ButtonRenderer(); btRenderer.encodeBegin(facesContext, uploadButton); //btRenderer.encodeChildren(facesContext, uploadButton); //not needed btRenderer.encodeEnd(facesContext, uploadButton); writer.endElement(HTML.SPAN_ELEM); writer.write("\n"); writer.write("\n"); // Adding the IFRAME remote scripting trick writer.startElement(HTML.SCRIPT_ELEM, null); writer.writeAttribute(HTML.LANG_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null); writer.writeText("//<![CDATA[", null); // Hidden iframe creation writer.write("\n"); writer.writeText("function " + frameFunc + "(actionUrl, frameId, formId) {", null); writer.writeText("if (!document.createElement) {", null); writer.writeText("alert('Cannot create elements for uploading');", null); writer.writeText("return;", null); writer.writeText("}", null); writer.writeText("var ie = false;", null); writer.writeText("if (navigator.appName == \"Microsoft Internet Explorer\") {", null); writer.writeText("ie = true;", null); writer.writeText("}", null); writer.writeText("var sIframe = (ie)?'<iframe name=\"' + frameId + '\"></iframe>':'iframe';", null); writer.writeText("var hIFrame = document.createElement(sIframe);", null); writer.writeText("hIFrame.setAttribute('id',frameId);", null); writer.writeText("if (!ie) {", null); writer.writeText("hIFrame.setAttribute('name', frameId);", null); writer.writeText("}", null); writer.writeText("hIFrame.src='javascript:false';", null); writer.writeText("hIFrame.style.border = '0px';", null); writer.writeText("hIFrame.style.width = '0px';", null); writer.writeText("hIFrame.style.height = '0px';", null); writer.writeText("var IFrameObj = document.body.appendChild(hIFrame);", null); // this is for IE5 Mac, because it will only // allow access to the document object // of the IFrame if we access it through // the document.frames array writer.writeText("if (document.frames) {", null); writer.writeText("IFrameObj = document.frames[frameId];", null); writer.writeText("}", null); writer.writeText("if (navigator.userAgent.indexOf('Gecko') !=-1) {", null); writer.writeText("while(!IFrameObj.contentDocument) {", null); // we have to wait for NS6 to recognize the new IFrame writer.writeText("}", null); writer.writeText("}", null); // Hidden form creation writer.writeText("var hForm = document.createElement('form');", null); writer.writeText("hForm.id = formId;", null); writer.writeText("hForm.style.width = '0px';", null); writer.writeText("hForm.style.height = '0px';", null); writer.writeText("hForm.style.border = '0px';", null); writer.writeText("document.body.appendChild(hForm);", null); writer.writeText("hForm.target = frameId;", null); writer.writeText("hForm.encoding = 'multipart/form-data';", null); //for IE6 writer.writeText("hForm.enctype = 'multipart/form-data';", null); writer.writeText("hForm.method = 'post';", null); writer.writeText("hForm.action = actionUrl;", null); //passing input file client id writer.writeText("var fSpan = document.getElementById('" + spanId + "');", null); writer.writeText("var fInput = document.getElementById('" + clientId + "');", null); writer.writeText("fSpan.removeChild(fInput);", null); writer.writeText("hForm.appendChild(fInput);", null); writer.writeText("var fButton = document.getElementById('" + buttonId + "');", null); writer.writeText("fSpan.removeChild(fButton);", null); writer.writeText("hForm.appendChild(fButton);", null); writer.writeText("hForm.submit();", null); writer.writeText("hForm.removeChild(fInput);", null); writer.writeText("fSpan.appendChild(fInput);", null); writer.writeText("hForm.removeChild(fButton);", null); writer.writeText("fSpan.appendChild(fButton);", null); writer.writeText("}", null); writer.write("\n"); String reRenderFuncName = makeFuncName(clientId, CALLBACK_RERENDER_SUFFIX); String callbackFuncName = makeFuncName(clientId, CALLBACK_SUCCESS_SUFFIX); writer.writeText("function " + callbackFuncName + "() {", null); //writer.writeText("alert('Upload success');", null); writer.writeText(reRenderFuncName + "();", null); writer.writeText("}", null); String failFuncName = makeFuncName(clientId, CALLBACK_FAIL_SUFFIX); writer.writeText("function " + failFuncName + "(name) {", null); writer.writeText("alert('Upload failed:' + name);", null); writer.writeText(reRenderFuncName + "();", null); writer.writeText("}", null); writer.write("\n"); writer.writeText("//]]>", null); writer.endElement(HTML.SCRIPT_ELEM); writer.write("\n"); HtmlAjaxFunction reRenderFunction = new HtmlAjaxFunction(); inputFileComponent.getChildren().add(reRenderFunction); //must be added downstream reRenderFunction.setParent(inputFileComponent); // and upstream ! reRenderFunction.setName(reRenderFuncName); reRenderFunction.setReRender(inputFileComponent.getReRender()); reRenderFunction.encodeBegin(facesContext); //reRenderFunction.encodeChildren(facesContext); //not needed reRenderFunction.encodeEnd(facesContext); } public static String makeFuncName(String id, String suffix) { return "func_" + id.replaceAll(NAME_CLEAN_REGEX, "_") + '_' + suffix.replaceAll(NAME_CLEAN_REGEX, "_"); } protected boolean isDisabled(FacesContext facesContext, UIComponent uiComponent) { if (!UserRoleUtils.isEnabledOnUserRole(uiComponent)) { return false; } else { if (uiComponent instanceof HtmlAjaxFileUpload) { return ((HtmlAjaxFileUpload)uiComponent).isDisabled(); } else { return RendererUtils.getBooleanAttribute(uiComponent, HTML.DISABLED_ATTR, false); } } } public void decode(FacesContext facesContext, UIComponent uiComponent) { super.decode(facesContext, uiComponent); //check for NP HtmlAjaxFileUpload inputFileComponent = (HtmlAjaxFileUpload) uiComponent; Map sessionMap = facesContext.getExternalContext().getSessionMap(); String key = INPUTFILE_PREFIX + '_' + uiComponent.getClientId(facesContext); Object obj = sessionMap.get(key); if(obj != null) { if(obj instanceof UploadedFile) { inputFileComponent.setSubmittedValue(obj); inputFileComponent.setValid(true); } else { log.warn("Content of key " + key + " was not an UploadedFile"); } } } public Object getConvertedValue(FacesContext context, UIComponent component, Object submittedValue) throws ConverterException { if(submittedValue instanceof UploadedFile) { UploadedFile file = (UploadedFile) submittedValue; if(file.getName() != null && file.getName().length() > 0) { return file; } } return null; } }
org.apache.myfaces.custom.fileupload.HtmlAjaxFileUploadTag/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.myfaces.custom.fileupload; import javax.faces.component.UIComponent; import org.apache.myfaces.custom.fileupload.HtmlInputFileUploadTag; public class HtmlAjaxFileUploadTag extends HtmlInputFileUploadTag { public String getComponentType() { return HtmlAjaxFileUpload.COMPONENT_TYPE; } public String getRendererType() { return HtmlAjaxFileUpload.DEFAULT_RENDERER_TYPE; } private String _reRender; private String _buttonValue; public void release() { super.release(); _reRender=null; _buttonValue=null; } protected void setProperties(UIComponent component) { super.setProperties(component); setStringProperty(component, "reRender", _reRender); setStringProperty(component, "buttonValue", _buttonValue); } public void setButtonValue(String buttonValue) { _buttonValue = buttonValue; } public void setReRender(String reRender) { _reRender = reRender; } }
/WEB-INF/web.xml<context-param> <param-name>org.apache.myfaces.custom.uploadMaxFileSize</param-name> <param-value>1048576</param-value> </context-param> <context-param> <param-name>org.apache.myfaces.custom.uploadDirectory</param-name> <param-value>/upload</param-value> </context-param> <servlet> <servlet-name>UploadServlet</servlet-name> <servlet-class>org.apache.myfaces.custom.fileupload.AjaxFileUploadServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>UploadServlet</servlet-name> <url-pattern>/uploadHtml</url-pattern> </servlet-mapping>
/WEB-INF/tld/customtags.xml (just removed storage and added reRender and buttonValue compared to tomahawk's inputFile descriptor<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib xmlns="http://java.sun.com/JSP/TagLibraryDescriptor"> <tlib-version>1.3</tlib-version> <jsp-version>1.2</jsp-version> <short-name>tc</short-name> <uri>http://myfaces.apache.org/tomahawk/custom</uri> <description> MyFaces subproject that contains components and other goodies to be used with any JSF implementation. </description> <!-- Extended standard components --> <!-- ajaxFileUpload --> <tag> <name>ajaxFileUpload</name> <tag-class>org.apache.myfaces.custom.fileupload.HtmlAjaxFileUploadTag</tag-class> <body-content>JSP</body-content> <description> You must enable the MultiPart Filter to make this component work (see web.xml). Also, don't forget to set the form's attribute "enctype" to "multipart/form-data". See "examples/web/fileupload.jsp" for an example! Unless otherwise specified, all attributes accept static values or EL expressions. </description> <!-- UIInput attributes --> <!-- UIOutput attributes --> <!-- UIComponent attributes --> <attribute> <name>id</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description> The developer-assigned ID of this component. The ID must be unique within the scope of the tag's enclosing naming container (e.g. h:form or f:subview). This value must be a static value. </description> </attribute> <attribute> <name>binding</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description> Identifies a backing bean property (of type UIComponent or appropriate subclass) to bind to this component instance. This value must be an EL expression. </description> </attribute> <attribute> <name>rendered</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description> A boolean value that indicates whether this component should be rendered. Default value: true. </description> </attribute> <attribute> <name>value</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description>The initial value of this component.</description> </attribute> <attribute> <name>converter</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description> An expression that specifies the Converter for this component. If the value binding expression is a String, the String is used as an ID to look up a Converter. If the value binding expression is a Converter, uses that instance as the converter. The value can either be a static value (ID case only) or an EL expression. </description> </attribute> <attribute> <name>immediate</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description> A boolean value that identifies the phase during which value change events should fire. During normal event processing, value change events are fired during the "invoke application" phase of request processing. If this attribute is set to "true", these methods are fired instead at the end of the "apply request values" phase. </description> </attribute> <attribute> <name>required</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description> A boolean value that indicates whether an input value is required. If this value is true, and no input value is provided, the error message javax.faces.component.UIInput.REQUIRED is posted. </description> </attribute> <attribute> <name>validator</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description> A method binding EL expression, accepting FacesContext, UIComponent, and Object parameters, and returning void, that validates the component's local value. </description> </attribute> <attribute> <name>valueChangeListener</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description> A method binding EL expression, accepting a ValueChangeEvent parameter and returning void. The specified method is invoked if this component is modified. The phase that this handler is fired in can be controlled via the immediate attribute. </description> </attribute> <!-- HTML 4.0 universal attributes --> <attribute> <name>dir</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>HTML: The direction of text display, either 'ltr' (left-to-right) or 'rtl' (right-to-left).</description> </attribute> <attribute> <name>lang</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>HTML: The base language of this document.</description> </attribute> <attribute> <name>style</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>HTML: CSS styling instructions.</description> </attribute> <attribute> <name>title</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>HTML: An advisory title for this element. Often used by the user agent as a tooltip.</description> </attribute> <attribute> <name>styleClass</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>The CSS class for this element. Corresponds to the HTML 'class' attribute.</description> </attribute> <!-- HTML 4.0 event-handler attributes --> <attribute> <name>onclick</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>HTML: Script to be invoked when the element is clicked.</description> </attribute> <attribute> <name>ondblclick</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>HTML: Script to be invoked when the element is double-clicked.</description> </attribute> <attribute> <name>onmousedown</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>HTML: Script to be invoked when the pointing device is pressed over this element.</description> </attribute> <attribute> <name>onmouseup</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>HTML: Script to be invoked when the pointing device is released over this element.</description> </attribute> <attribute> <name>onmouseover</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>HTML: Script to be invoked when the pointing device is moved into this element.</description> </attribute> <attribute> <name>onmousemove</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>HTML: Script to be invoked when the pointing device is moved while it is in this element.</description> </attribute> <attribute> <name>onmouseout</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>HTML: Script to be invoked when the pointing device is moves out of this element.</description> </attribute> <attribute> <name>onkeypress</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>HTML: Script to be invoked when a key is pressed over this element.</description> </attribute> <attribute> <name>onkeydown</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>HTML: Script to be invoked when a key is pressed down over this element.</description> </attribute> <attribute> <name>onkeyup</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>HTML: Script to be invoked when a key is released over this element.</description> </attribute> <!-- HTML 4.0 input attributes --> <attribute> <name>accesskey</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description>HTML: Sets the access key for this element.</description> </attribute> <attribute> <name>align</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>HTML: Specifies the horizontal alignment of this element. Deprecated in HTML 4.01.</description> </attribute> <attribute> <name>alt</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description>HTML: Specifies alternative text that can be used by a browser that can't show this element.</description> </attribute> <attribute> <name>disabled</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description>HTML: When true, this element cannot receive focus.</description> </attribute> <attribute> <name>onblur</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description>HTML: Specifies a script to be invoked when the element loses focus.</description> </attribute> <attribute> <name>onfocus</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description>HTML: Specifies a script to be invoked when the element receives focus.</description> </attribute> <attribute> <name>onchange</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description>HTML: Specifies a script to be invoked when the element is modified.</description> </attribute> <attribute> <name>onselect</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description>HTML: Specifies a script to be invoked when the element is selected.</description> </attribute> <attribute> <name>readonly</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description> HTML: When true, indicates that this component cannot be modified by the user. The element may receive focus unless it has also been disabled. </description> </attribute> <attribute> <name>tabindex</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description>HTML: Specifies the position of this element within the tab order of the document.</description> </attribute> <attribute> <name>maxlength</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description>HTML: The maximum number of characters allowed to be entered.</description> </attribute> <attribute> <name>size</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <type>java.lang.String</type> <description>HTML: The initial width of this control, in characters.</description> </attribute> <!-- HTML 4.0 input type="file" attributes --> <attribute> <name>accept</name> <required>false</required> <rtexprvalue>false</rtexprvalue> </attribute> <attribute> <name>reRender</name> <required>false</required> <rtexprvalue>false</rtexprvalue> </attribute> <attribute> <name>buttonValue</name> <required>false</required> <rtexprvalue>false</rtexprvalue> </attribute> </tag> </taglib>
/WEB-INF/facelets/custom.taglib.xml (for those using facelets, dont forget to add this to your web.xml's facelets.LIBRARIES)<?xml version="1.0"?> <!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelet-taglib_1_0.dtd"> <facelet-taglib> <namespace>http://myfaces.apache.org/tomahawk/custom</namespace> <tag> <tag-name>ajaxFileUpload</tag-name> <component> <component-type>org.apache.myfaces.HtmlAjaxFileUpload</component-type> <renderer-type>org.apache.myfaces.AjaxFileUpload</renderer-type> </component> </tag> </facelet-taglib>
And now we can test.
my.test.pack.InputFileBeanpackage my.test.pack.; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.myfaces.custom.fileupload.UploadedFile; /** * Should be session-scoped (Ajax going on here) */ public class InputFileBean { public final static Log log = LogFactory.getLog(InputFileBean.class); private UploadedFile uploadedFile; public UploadedFile getUploadedFile() { return uploadedFile; } public void setUploadedFile(UploadedFile file) { log.info("File uploaded: " + file.getName()); this.uploadedFile = file; } }
Add to /WEB-INF/faces-config.xml<managed-bean> <managed-bean-name>inputFileBean</managed-bean-name> <managed-bean-class>org.psynews.v3.site.framework.InputFileBean</managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean>
/ajaxFileUpload.xhtml (facelets code here)<!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:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:a4j="https://ajax4jsf.dev.java.net/ajax" xmlns:tc="http://myfaces.apache.org/tomahawk/custom"> <head> <meta http-equiv="Content-Type" content="text/xhtml; charset=UTF-8"/> </head> <body> <h:form> <h:panelGrid columns="2"> <h:outputLabel value="Upload: "/> <tc:ajaxFileUpload id="fileUp" value="#{inputFileBean.uploadedFile}" storage="file" reRender="fileName, fileSize"/> <h:outputLabel value="File name: "/> <h:outputText id="fileName" value="#{inputFileBean.uploadedFile.name}"/> <h:outputText value="File size: "/> <h:outputText id="fileSize" value="#{inputFileBean.uploadedFile.size}"/> </h:panelGrid> </h:form> </body> </html>
More details :
all is based on Andrew's solution up there, and these links :
http://www.ibm.com/developerworks/library/wa-facescomp3/
http://developer.apple.com/internet/webcontent/iframe.html
The first step was adding a h:commandButton and an a4j:jsFunction at render time. The second step was adding a reRender attribute to it and "forward" if to the jsFunction. the third step was adding the buttonValue for the button's text. You may add some styling attributes for it too.
A4J js scripts are loaded during the form's encode call. Also, from what I've understood from the A4J code, components are asked for their needs in terms of scripts and styles at the begining of renderView. However, at that time there is no A4J component on my test page, so the Ajax view renderer isn't willing to link the scripts. To tweak it I had to extend my HtmlAjaxFileUploadRenderer from InputRendererBase instead of the usual javax.faces.render.Renderer.
Making a composite component instead would be harder because the inputFile would need to know the a4j:jsFunction all the time, especially during restoreState...where JSF generates it itself without telling !
I had to remove the "storage" attribute from the tag, it's impossible to get it back from the upload servlet.
My managed bean used for testing is session-scoped, but I guess it's possible to use it in request scope in certain conditions.
I hope it helps and I didn't break any license doing that. -
12. Re: t:inputFileUpload or s:fileUpload with ajax4jsf
fabmars Aug 8, 2007 9:42 PM (in response to holgerprause)I can't edit, the good package for the managed bean InputFileBean is of course my.test.pack, as the class itself says.
-
13. Re: t:inputFileUpload or s:fileUpload with ajax4jsf
alsha Aug 16, 2007 8:38 AM (in response to holgerprause)Hi,
where can i find the code for AjaxFileUploadServlet?
Thanx in advance! -
14. Re: t:inputFileUpload or s:fileUpload with ajax4jsf
fabmars Aug 16, 2007 5:40 PM (in response to holgerprause)Owww sorry, here it is.
AxaxFileUploadServlet.javapackage org.apache.myfaces.custom.fileupload; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.List; import javax.servlet.ServletException; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.ProgressListener; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.io.FilenameUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.myfaces.custom.fileupload.UploadedFile; import org.apache.myfaces.custom.fileupload.UploadedFileDefaultFileImpl; public class AjaxFileUploadServlet extends HttpServlet { private static final long serialVersionUID = -2013825834865067717L; private static final String UPLOAD_MAX_FILE_SIZE = "org.apache.myfaces.custom.uploadMaxFileSize"; private static final String UPLOAD_DIRECTORY = "org.apache.myfaces.custom.uploadDirectory"; private static int maxFileSize = 1024 * 1024; private static String uploadPath = System.getProperty("java.io.tmpdir"); private ServletFileUpload upload; private final static Log log = LogFactory.getLog(AjaxFileUploadServlet.class); public void init() throws ServletException { super.init(); String sMaxFileSize = getServletContext().getInitParameter(UPLOAD_MAX_FILE_SIZE); try { maxFileSize = Integer.parseInt(sMaxFileSize); } catch(NumberFormatException e) { log.warn(UPLOAD_MAX_FILE_SIZE + " is not correctly configured, assuming default value: " + maxFileSize); } String sDirectory = getServletContext().getInitParameter(UPLOAD_DIRECTORY); if(sDirectory == null || sDirectory.length() == 0) { log.warn(UPLOAD_DIRECTORY + " is not configured, assuming default value: " + uploadPath); } else { uploadPath = sDirectory; } if(!uploadPath.startsWith("/") && !uploadPath.contains(":")) { uploadPath = getServletContext().getRealPath("/" + uploadPath); } File uploadDir = new File(uploadPath); if(!uploadDir.exists()) { uploadDir.mkdirs(); } // Create a factory for disk-based file items DiskFileItemFactory factory = new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD, uploadDir); // Create a new file upload handler upload = new ServletFileUpload(factory); // Set file size constraint upload.setFileSizeMax(maxFileSize); // Set overall request size constraint //upload.setSizeMax(); //TODO add a progress listener } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { log.debug("doPost Upload servlet"); //TODO implement progess listener calls String inputFileId = request.getParameter(HtmlAjaxFileUploadRenderer.INPUTFILE_ID); String callbackFunc = HtmlAjaxFileUploadRenderer.makeFuncName(inputFileId, HtmlAjaxFileUploadRenderer.CALLBACK_SUCCESS_SUFFIX); String failFunc = HtmlAjaxFileUploadRenderer.makeFuncName(inputFileId, HtmlAjaxFileUploadRenderer.CALLBACK_FAIL_SUFFIX); // Check that we have a file upload request boolean isMultipart = ServletFileUpload.isMultipartContent(request); if(isMultipart) { // Parse the request try { List items = upload.parseRequest(request); if(!items.isEmpty()) { FileItem fileItem = (FileItem)items.get(0); String fileName = getFileNameWithoutPath(fileItem); UploadedFile uploadedFile = new UploadedFileDefaultFileImpl(fileItem); request.getSession().setAttribute(HtmlAjaxFileUploadRenderer.INPUTFILE_PREFIX + '_' + inputFileId, uploadedFile); writeOnLoadCallback(response, callbackFunc, fileName); return; } } catch (FileUploadException e) { log.error("File upload error", e); writeOnLoadCallback(response, failFunc, e.getMessage()); } catch (Exception e) { log.error("File save error", e); writeOnLoadCallback(response, failFunc, e.getMessage()); } } else { String msg = "Not a multipart request"; log.debug(msg); writeOnLoadCallback(response, failFunc, msg); } } private void writeOnLoadCallback(ServletResponse response, String funcName, String msg) throws IOException { if(funcName != null) { PrintWriter writer = response.getWriter(); writer.write("<script type='text/javascript'>"); writer.write("\n"); writer.write("window.parent." + funcName + "('"); if(msg != null) { writer.write(msg); } writer.write("');"); writer.write("\n"); writer.write("</script>"); } } private String getFileNameWithoutPath(FileItem item) { String fileName = item.getName(); if (fileName != null) { fileName = FilenameUtils.getName(fileName); } return fileName; } }