Here was the use case:
- Display step 1 of a VF page with a commandLink to jump to step 2
- Step 2 was initially rendered invisible
- Step2 included an
apex:inputFile
component
When Step 2 was rendered, the inputFile component appeared.
But, if you select a file and click a button to upload the file to the controller, the controller does not receive the file body but does receive the filename and content type.
Huh?
The issue appears to be that initially unrendered apex:inputFile
components don’t bind as expected to a controller variable declared as transient (as it typically must be to avoid Visualforce viewstate size issues). The upload action method occurs, all other non-transient attributes of inputFile
do transmit but the file body does not.
Here’s proof
The controller
public with sharing class Foo { // Class to investigate rerender issue with inputFile public Boolean hasStep1Completed {get {return this.hasStep1Completed == null ? false : this.hasStep1Completed;} set;} // vis control public transient Blob fileBody {get; set;} // must be transient as view state can't handle > 128KB public String fileDescription {get; set;} public String fileName {get; set;} public Integer fileSize {get; set;} public String fileType {get; set;} public Boolean isFileBodyNullOnXmit {get; set;} // VF feedback for example public Foo() {} public PageReference upload() { this.isFileBodyNullOnXmit = this.fileBody == null ? true : false; return null; } }
This VF page does not work
<apex:page controller="Foo" tabStyle="Account"> <apex:form id="form"> <apex:outputPanel id="step1Op"> <apex:pageBlock title="Step 1" id="step1Pb" rendered="{!NOT(hasStep1Completed)}"> <apex:actionregion > <apex:commandLink value="Go to step2" reRender="step1Op,step2Op"> <apex:param name="hasStep1Completed" value="true" assignTo="{!hasStep1Completed}"/> </apex:commandLink> </apex:actionregion> </apex:pageBlock> </apex:outputPanel> <!-- This doesn't work, initially unrendered inputFile won't transmit file body once rendered --> <apex:outputPanel id="step2Op" > <apex:pageBlock title="Step2" id="step2Pb" rendered="{!hasStep1Completed}" > <apex:pageBlockButtons location="top"> <apex:commandButton value="Upload" action="{!upload}"/> </apex:pageBlockButtons> <apex:inputFile value="{!fileBody}" filename="{!fileName}" contentType="{!fileType}" id="file"/> </apex:pageBlock> </apex:outputPanel> <apex:outputPanel id="resultOp" > <apex:pageBlock rendered="{!hasStep1Completed}"> <apex:outputText value="is FileBody Null On Xmit? {!isFileBodyNullOnXmit}"/> </apex:pageBlock> </apex:outputpanel> </apex:form> </apex:page>
The apex:inputFile
is not initially rendered as Step1 hasn’t completed
Now, by clicking the commandLink
, I render it and then choose a file and click Upload
The results outputPanel shows that no fileBody was transmitted!
So, how to work around this?
The answer was provided in Salesforce StackExchange. Instead of using VF rendered=
attributes, the inputFile must be rendered by made invisible by CSS. So, the revised Visualforce page (no changes to the controller)
<apex:page controller="Foo" tabStyle="Account"> <apex:form id="form"> <apex:outputPanel id="step1Op"> <apex:pageBlock title="Step 1" id="step1Pb" rendered="{!NOT(hasStep1Completed)}"> <apex:actionregion > <apex:commandLink value="Go to step2" reRender="step1Op,step2Op"> <apex:param name="hasStep1Completed" value="true" assignTo="{!hasStep1Completed}"/> </apex:commandLink> </apex:actionregion> </apex:pageBlock> </apex:outputPanel> <!-- This does work, initially rendered by css invisible inputFile will transmit file body once made visible --> <apex:outputPanel id="step2Op" style="display: {!IF(hasStep1Completed, 'inline-block', 'none')};"> <apex:pageBlock title="Step2" id="step2Pb" > <apex:pageBlockButtons location="top"> <apex:commandButton value="Upload" action="{!upload}"/> </apex:pageBlockButtons> <apex:inputFile value="{!fileBody}" filename="{!fileName}" contentType="{!fileType}" id="file"/> </apex:pageBlock> </apex:outputPanel> <apex:outputPanel id="resultOp" > <apex:pageBlock rendered="{!hasStep1Completed}"> <apex:outputText value="is FileBody Null On Xmit? {!isFileBodyNullOnXmit}"/> </apex:pageBlock> </apex:outputpanel> </apex:form> </apex:page>
Only the second outputPanel was changed. Note the use of style="display: {!IF(hasStep1Completed, 'inline-block', 'none')};"
to trick Visualforce into thinking the inputFile is rendered but using display: none to make invisible.
The results outputPanel shows that fileBody was transmitted! Problem solved.