ADF, JSF and FLEX

Friday, April 13, 2007

An ADF Faces ProgressIndicator Example for File Upload

This post demonstrates how an ADF Faces ProgressIndicator can be used to display the progress of a file upload.

The sample application is built in JDeveloper 10.1.3.2. You can download the sample workspace from here. The workspace does not need a database connection to run.

Sample Application

The sample application consists of one page that allows the user to upload files to the server. When the upload starts, a progress indicator displays the current status of the upload operation. The uploaded files are not stored anywhere.

Controlling the progressIndicator
<f:view>
<afh:html>
<afh:head title="FileUpload">
<meta http-equiv="Content-Type"
content="text/html; charset=windows-1252"/>
<afh:script source="progressIndicator.js"/>
</afh:head>
<afh:body onload="deactivateProgressIndicators();">
<af:messages/>
<af:form usesUpload="true">
<af:inputHidden id="fileUploadStatus" value="#{MyFileUploadBean.fileUploadStatus}"/>
<af:panelBox>
<af:inputFile valueChangeListener="#{MyFileUploadBean.fileUploaded}"/>
<af:commandButton text="start upload"
id="startButton"
actionListener="#{MyFileUploadBean.doUpload}"
onclick="reactivateProgressIndicators();"/>
</af:panelBox>
<af:panelBox id="panelBox" inlineStyle="display:none">
<af:panelHeader text="File is being uploaded"/>
<af:progressIndicator id="progressIndicator"
value="#{MyProgressRangeModel}"
partialTriggers="pollid">
<af:outputFormatted styleUsage="instruction"
value="Task status not known"
rendered="#{MyProgressRangeModel.value == -1}"/>
<af:outputFormatted styleUsage="instruction"
value="#{MyProgressRangeModel.value} of #{MyProgressRangeModel.maximum} Completed"
rendered="#{MyProgressRangeModel.value > -1}"/>
</af:progressIndicator>
<af:poll id="pollid" interval="1000"/>
</af:panelBox>
</af:form>
</afh:body>
</afh:html>
</f:view>

The fileUpload.jsp page contains a <af:progressIndicator/> tag and a <af:poll/> tag. The poll tag is used as a timer by the progressIndicator to check the status of the file upload periodically using partial page rendering (PPR). However, this operation is not necessary if the user is not uploading any files. If the polling is not deactivated, the progressIndicator is going to perform PPR even if there is no file being uploaded. This constant PPR creates an undesired effect on the page.

At client side, a PollManager object is used to regulate the poll objects on the page. The PollManager provides two methods to activate and deactivate the poll objects; reactivateAll() and deactivateAll(). Using these two methods the PPR can be started when file upload begins and stopped when it ends. Below is the JavaScript code that accomplishes these tasks;

function deactivateProgressIndicators()
{
if (self._pollManager)
{
if (document.getElementById('fileUploadStatus').value=='noUpload')
{
_pollManager.deactivateAll();
}
}
}

function reactivateProgressIndicators()
{
if (self._pollManager)
{
document.getElementById('panelBox').style.display='';
document.getElementById('fileUploadStatus').value='uploading';
_pollManager.reactivateAll();
}
}
Server Side Code

The MyFileUploadBean and MyProgressRangeModel beans handle the file upload and progressIndicator server side operations respectively.

You can find a detailed explanation of the ProgressRangeModel and how it is used with the progressIndicator at Duncan Mills blog.

Below is the source code for the MyFileUploadBean and the MyProgressRangeModel classes in the sample application.

public class MyFileUploadBean
{
private UploadedFile file;
private long sizeOfFile;
private int availableBytes;
private String fileUploadStatus = "noUpload";


public MyFileUploadBean() {
}

public void fileUploaded(ValueChangeEvent valueChangeEvent)
{
file = (UploadedFile)valueChangeEvent.getNewValue();
if(file != null)
{
sizeOfFile = file.getLength();
}
}

public void doUpload(ActionEvent actionEvent)
{
if(file != null)
{
InputStream is;
OutputStream os;
FacesContext fctx = FacesContext.getCurrentInstance();
FacesMessage message = new FacesMessage("succesfully uploaded file" +
" " + file.getFilename() + " (" +
file.getLength() + " bytes)");
fctx.addMessage(actionEvent.getComponent().getClientId(fctx),
message);

try
{
is = file.getInputStream();
int data;
getMyRangeModel().setMaximumBytes(sizeOfFile);
while((data = is.read()) != -1)
{
availableBytes = is.available();
getMyRangeModel().setAvailableBytes(availableBytes);
if(availableBytes == 0)
{
setFileUploadStatus("noUpload");
}
}
is.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}

private MyProgressRangeModel getMyRangeModel()
{
FacesContext fctx = FacesContext.getCurrentInstance();
MyProgressRangeModel rangeModel = (MyProgressRangeModel) fctx.getApplication()
.createValueBinding("#{MyProgressRangeModel}").getValue(fctx);
return rangeModel;
}

public void setFileUploadStatus(String fileUploadStatus) {
this.fileUploadStatus = fileUploadStatus;
}

public String getFileUploadStatus() {
return fileUploadStatus;
}
}
public class MyProgressRangeModel extends oracle.adf.view.faces.model.BoundedRangeModel
{
private long availableBytes;
private long maximumBytes;

public MyProgressRangeModel()
{
}

public long getMaximum()
{
long result;
long maxByte = getMaximumBytes();
if(maxByte==0)
result=-1;
else
result = maxByte;
return result;
}

public long getValue()
{
long result;
long availableByte = getMaximumBytes() - getAvailableBytes();
if(availableByte == 0 || availableByte==getMaximumBytes())
result=-1;
else
result = availableByte;
return result;
}

public void setAvailableBytes(long availableBytes)
{
this.availableBytes = availableBytes;
}

public long getAvailableBytes()
{
return availableBytes;
}

public void setMaximumBytes(long maximumBytes)
{
this.maximumBytes = maximumBytes;
}

public long getMaximumBytes()
{
return maximumBytes;
}
}

Wednesday, April 4, 2007

A Custom ShowDetailFrame that does not require a WebCenter License

This post demonstrates a custom showDetailFrame that is built using regular ADF Faces components. Hence no additional license is needed to use it. (You need a WebCenter license if you use the new showDetailFrame component. The issue is explained in this OTN thread )

The sample application is built in 10.1.3.2. It does not require a database connection to run. You can download the workspace from here.

Sample Application

The application contains a page built with custom ShowDetailFrame and another page built with the original component.


Custom ShowDetailFrameOriginal ShowDetailFrame

Below is the JSP and the JavaScript code of the custom showDetailFrame.

<f:view>
<afh:html>
<afh:head title="Show Custom Detail Frame">
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"/>
<script type="text/javascript" src="customFrame.js"></script>
</afh:head>
<afh:body onload="onLoad();">
<af:form>
<af:inputHidden id="frameStatus" value="#{processScope.frameStatus}"/>
<af:panelForm rows="2" maxColumns="1">
<af:panelBox width="400">
<af:panelHorizontal halign="left">
<af:commandLink onclick="minimize();" id="minimizer"
partialSubmit="true" immediate="true"
inlineStyle="#{processScope.frameStatus == 'minimized' ? 'display: none;' : ''}">
<af:setActionListener from="#{'minimized'}" to="#{processScope.frameStatus}"/>
<af:objectImage source="/minimize.gif"
id="minimizeObj" />
</af:commandLink>
<af:commandLink onclick="maximize();" id="maximizer"
partialSubmit="true" immediate="true"
inlineStyle="#{processScope.frameStatus == 'minimized' ? '' : 'display: none;'}">
<af:setActionListener from="#{'maximized'}" to="#{processScope.frameStatus}"/>
<af:objectImage source="/expand.gif" id="extendObj"/>
</af:commandLink>
<af:outputLabel value="Custom showDetailFrame"/>
</af:panelHorizontal>
</af:panelBox>
<afh:tableLayout width="400" id ="frameTableLayout"
inlineStyle="border: 1px solid #D2D8B0; border-top: 0; height:200.0px;"
#{processScope.frameStatus == 'minimized' ? ' display: none;' : ''}">
<afh:rowLayout>
<afh:cellFormat halign="center">
<af:goLink destination="http://www.gergerconsulting.com">
<af:objectImage source="/GC.gif" shortDesc="Gerger Consulting"/>
</af:goLink>
</afh:cellFormat>
</afh:rowLayout>
<afh:rowLayout>
<afh:cellFormat>
<af:inputText label="Required Field" required="true" value="#{processScope.requiredField1}"
columns="10" maximumLength="10"/>
</afh:cellFormat>
</afh:rowLayout>
</afh:tableLayout>
</af:panelForm>
<af:panelForm rows="1" maxColumns="1">
<af:objectSpacer width="10" height="10"/>
<af:commandButton text="Go To The Next Page" action="goPage1"/>
</af:panelForm>
</af:form>
</afh:body>
</afh:html>
</f:view>

function minimize(){
var frameStatus=document.getElementById('frameStatus');
frameStatus.value='minimized';
var minimizer=document.getElementById('minimizer');
minimizer.style.display='none';
var maximizer=document.getElementById('maximizer');
maximizer.style.display='';
var frameTableLayout=document.getElementById('frameTableLayout');
frameTableLayout.style.display='none';
return true;
}

function maximize(){
var frameStatus=document.getElementById('frameStatus');
frameStatus.value='maximized';
var minimizer=document.getElementById('minimizer');
minimizer.style.display='';
var maximizer=document.getElementById('maximizer');
maximizer.style.display='none';
var frameTableLayout=document.getElementById('frameTableLayout');
frameTableLayout.style.display='';
return true;
}

function onLoad(){
var frameStatus=document.getElementById('frameStatus');
if (frameStatus.value=='maximized' || frameStatus.value=='')
{
maximize();
}
else if (frameStatus.value=='minimized')
{
minimize();
}
return true;
}

The original showDetailFrame component is not Back/Forward Button friendly. The custom showDetailFrame shown in this sample application preserves its state even though the page is viewed using the Back/Forward buttons.

If this behavior is not important or not desired, use the following JSP below;

<f:view>
<afh:html>
<afh:head title="Show Custom Detail Frame">
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"/>
<script type="text/javascript" src="customFrame.js"></script>
</afh:head>
<afh:body onload="onLoad();">
<af:form>
<af:inputHidden id="frameStatus" value="#{processScope.frameStatus}"/>
<af:panelForm rows="2" maxColumns="1">
<af:panelBox width="400">
<af:panelHorizontal halign="left">
<af:commandLink onclick="minimize(); return false;" id="minimizer">
<af:objectImage source="/minimize.gif"
id="minimizeObj" />
</af:commandLink>
<af:commandLink onclick="maximize(); return false;" id="maximizer">
<af:objectImage source="/expand.gif" id="extendObj"/>
</af:commandLink>
<af:outputLabel value="Custom showDetailFrame"/>
</af:panelHorizontal>
</af:panelBox>
<afh:tableLayout width="400" id ="frameTableLayout"
inlineStyle="border: 1px solid #D2D8B0; border-top: 0; height:200.0px;">
<afh:rowLayout>
<afh:cellFormat halign="center">
<af:goLink destination="http://www.gergerconsulting.com">
<af:objectImage source="/GC.gif" shortDesc="Gerger Consulting"/>
</af:goLink>
</afh:cellFormat>
</afh:rowLayout>
<afh:rowLayout>
<afh:cellFormat>
<af:inputText label="Required Field" required="true" value="#{processScope.requiredField1}"
columns="10" maximumLength="10"/>
</afh:cellFormat>
</afh:rowLayout>
</afh:tableLayout>
</af:panelForm>
<af:panelForm rows="1" maxColumns="1">
<af:objectSpacer width="10" height="10"/>
<af:commandButton text="Go To The Next Page" action="goPage1"/>
</af:panelForm>
</af:form>
</afh:body>
</afh:html>
</f:view>

The modified custom showDetailFrame will not support the following scenario;

  1. Run the customShowDetailFrame page

  2. Click the “Go To The Next Page” button

  3. Click the Back Button

  4. Minimize the frame

  5. Click Forward Button

  6. Click the “Go To The Previous Page” button

The frame will appear as maximized even though it was minimized in step 4.