2010年4月28日星期三

ADF_094:如何实现下载文件之二:动态文件

实验环境:JDeveloper 11.1.2.0.0。
说明:本文改自本人旧作,使用了目前最新的JDeveloper 11.1.2.0.0重新开发验证。(2011-8-3)

文件的内容是动态生成的。

1. 页面代码
这里使用了af:fileDownloadActionListener 组件。
<af:form id="f1">
<af:commandButton text="Download" id="cb1">
<af:fileDownloadActionListener filename="hello.txt" contentType="text/plain; charset=utf-8"
method="#{myBackingBean.writeContent}"/>
</af:commandButton>
</af:form>

2. 对应的Managed Bean代码
public void writeContent(FacesContext facesContext, OutputStream out) {
try {
OutputStreamWriter w = new OutputStreamWriter(out, "UTF-8");
w.write("Even a brick wants to be something.");
w.close();
facesContext.responseComplete();
} catch (Exception e) {
e.printStackTrace();
}
}

Project下载:DownloadFile.7z

ADF_093:如何实现下载文件之一:静态文件

实验环境:JDeveloper 11.1.2.0.0。
说明:本文改自本人旧作,使用了目前最新的JDeveloper 11.1.2.0.0重新开发验证。(2011-8-3)

这里假设文件保存在服务器的某个目录下:比如C:/Temp。

1. 页面代码。
这里使用了一个参数:fileName,并给其赋了一个常量值。实际应用中,可以根据情况动态赋值。
注意在Managed Bean中如何获取该参数。
如果要使用f:param传递参数,只能使用CommandLink,而不能使用CommandButton。
<af:form id="f1">
<h:commandLink value="Download" id="cb1" actionListener="#{myBackingBean.downloadButton_actionListener}">
<f:param value="1.pdf" name="fileName" id="p1"/>
</h:commandLink>
</af:form>

2. 对应的Managed Bean代码。
public void downloadButton_actionListener(ActionEvent actionEvent) {
FacesContext facesContext = FacesContext.getCurrentInstance();
String fileName = (String)JSFUtils.getManagedBeanValue("param.fileName");
if (fileName != null) {
File file = new File(fileLocation + fileName);
String fileType = "aplication/octet-stream";
if (fileName.endsWith(".pdf")) {
fileType = "application/PDF";
} else if (fileName.endsWith(".doc")) {
fileType = "aplication/msword";
} else if (fileName.endsWith(".txt")) {
fileType = "text/plain";
} else if (fileName.endsWith(".ppt")) {
fileType = "application/vnd.ms-powerpoint";
} else if (fileName.endsWith(".rar")) {
fileType = "aplication/octet-stream";
} else if (fileName.endsWith(".zip")) {
fileType = "aplication/zip";
} else if (fileName.endsWith(".jpg")) {
fileType = "aplication/jpg";
} else {
fileType = "aplication/octet-stream";
}

if (file.exists()) {
ExternalContext extContext = facesContext.getExternalContext();
HttpServletResponse response = (HttpServletResponse)extContext.getResponse();
response.setHeader("Content-Disposition", "attachment;filename=\"" + fileName + "\"");
response.setContentLength((int)file.length());
response.setBufferSize(1024);
response.setContentType(fileType);
//response.setContentType("application/x-download");

try {
InputStream in = new FileInputStream(file);
OutputStream out = response.getOutputStream();
writeInputStreamToOutputStream(in, out);
in.close();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
facesContext.responseComplete();
}
}

3. 运行页面。
(1)IE提示下载的窗口:


(2)Firefox提示打开和保存的窗口

(3)如果你的IE关联了迅雷这样的浏览器默认下载工具,你需要取消这个关联,方法是:工具->配置->监视设置->监视浏览器。

(4)facesContext.responseComplete();这行代码很重要,表明response结束了。

Project下载:DownloadFile2.7z

参考文献:
1. http://thepeninsulasedge.com/frank_nimphius/2007/08/18/adf-faces-direct-file-download-through-managed-bean/
2. http://blogs.oracle.com/smuenchadf/examples/#85
3. http://log-cd.javaeye.com/blog/415844
4. http://www.javaeye.com/topic/100066
5. http://pity1115.iteye.com/blog/336426

ADF_092:上传文件到服务器方式之二:使用Button的ActionListener

开发环境:JDeveloper 11.1.2.0.0。
说明:本文改自本人旧作,使用了目前最新的JDeveloper 11.1.2.0.0重新开发验证。(2011-8-1)

1. 新增页面:upload_file.jspx
<af:form id="f1" usesUpload="true">
<af:inputFile label="File Name:" id="if1"
valueChangeListener="#{myBackingBean2.inputFile_valueChangeListener}"
binding="#{myBackingBean2.inputFileComponent}"/>
<af:commandButton text="Upload" id="cb1" actionListener="#{myBackingBean2.doUpload}"/>
</af:form>
说明:
(1)核心处理代码是myBackingBean.uploadButton_actionListener方法,
myBackingBean.inputFile_valueChangeListener方法只是负责给file赋值。
(2)点击Upload按钮会触发ValueChangeEvent发生,然后处理文件上传逻辑。

2. 对应的Managed Bean代码
// 点击Upload按钮时,先调用此方法,因为ValueChange事件先于actionListener事件 。
// 该方法主要作用是给file赋值。
public void inputFile_valueChangeListener(ValueChangeEvent event) {
file = (UploadedFile)event.getNewValue();
}

// 点击Upload按钮时,执行完valueChangeListener后,调用此方法
public void uploadButton_actionListener(ActionEvent event) {
if (!(new File(fileUploadLocation).exists())) {
(new File(fileUploadLocation)).mkdirs();
}
if (file != null && file.getLength() > 0) {
try {
InputStream in = file.getInputStream();
FileOutputStream out = new FileOutputStream(fileUploadLocation + "/" + file.getFilename());
writeInputStreamToOutputStream(in, out);
in.close();
out.close();

String message =
"Successfully uploaded file '" + file.getFilename() + "' (" + file.getLength() + " bytes)";
popupMessage(event, message);
} catch (Exception e) {
e.printStackTrace();
}
// 清空InputFile,这样符合中国人的习惯。
inputFileComponent.setValue(null);
} else {
popupMessage(event, Not_Valid_FileName_Message);
}
}

3. 思考:哪种方式更好一些?
感觉第2种方式更科学和自然一些。
因为点击Upload按钮时,才是真正确认要上传文件,所以主要逻辑应该写在这里。
而ValueChangeEvent发生时,只是选择了一个文件而已,还不能确定用户是否真的要上传。
从代码角度看,第2种方式略微比第一种方式复杂一点点:多了一个私有变量:private UploadedFile file;。

Project下载:UploadFile2.7z

ADF_091:上传文件到服务器方式之一:使用InputFile的ValueChangeListener

开发环境:JDeveloper 11.1.2.0.0。
说明:本文改自本人旧作,使用了目前最新的JDeveloper 11.1.2.0.0重新开发验证。(2011-8-1)

1. 新增页面:upload_file.jspx
<af:form id="f1" usesUpload="true">
<af:inputFile label="File Name:" id="if1"
valueChangeListener="#{myBackingBean.inputFile_valueChangeListener}"
binding="#{myBackingBean.inputFileComponent}"/>
<af:commandButton text="Upload" id="cb1" actionListener="#{myBackingBean.uploadButton_actionListener}"/>
</af:form>

说明:
(1)核心处理代码是myBackingBean.inputFile_valueChangeListener方法,
myBackingBean.uploadButton_actionListener方法只是检查文件名是否为空。
(2)点击Upload按钮的主要作用是触发ValueChangeEvent发生。

2. 对应的Managed Bean代码
// 点击Upload按钮时,先调用此方法,因为ValueChange事件先于actionListener事件 。
public void inputFile_valueChangeListener(ValueChangeEvent event) {
UploadedFile file = (UploadedFile)event.getNewValue();
if (!(new File(fileUploadLocation).exists())) {
(new File(fileUploadLocation)).mkdirs();
}
if (file != null && file.getLength() > 0) {
try {
InputStream in = file.getInputStream();
FileOutputStream out = new FileOutputStream(fileUploadLocation + "/" + file.getFilename());
writeInputStreamToOutputStream(in, out);
in.close();
out.close();

String message =
"Successfully uploaded file '" + file.getFilename() + "' (" + file.getLength() + " bytes)";
popupMessage(event, message);
} catch (Exception e) {
e.printStackTrace();
}
}
}

// 点击Upload按钮时,执行完valueChangeListener后,调用此方法
public void uploadButton_actionListener(ActionEvent actionEvent) {
if (this.getInputFileComponent().getValue() != null) {
// 清空InputFile,这样符合中国人的习惯。
inputFileComponent.setValue(null);
} else {
popupMessage(actionEvent, Not_Valid_FileName_Message);
}
}

Project下载:UploadFile.7z

参考文献:
1. http://gergerconsulting.blogspot.com/2007/04/adf-faces-progressindicator-example-for.html
2. http://groundside.com/blog/DuncanMills.php?title=the_progress_indicator&more=1&c=1&tb=1&pb=1
3. http://technology.amis.nl/blog/2297/adf-faces-file-uploading-it-is-really-that-simple
4. http://thepeninsulasedge.com/blog/?p=6
5. http://log-cd.javaeye.com/blog/415844
6. http://cn.forums.oracle.com/forums/thread.jspa?messageID=4013464
7. http://forums.oracle.com/forums/thread.jspa?threadID=983109

2010年4月27日星期二

ADF_090:Task Flow使用指南之五:捕获异常 (3)

开发环境:JDevloper 11.1.2.2.0+ Oracle Database 10g Express Edition 10.2.0.1。

本实验接着上一个实验,除了可以在TaskFlow中定义ExceptionHandler捕捉异常之外,还可以自定义全局的ExceptionHandler。

重要步骤说明:

1. 新建自定义的ExceptionHandler类
package view;

import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseId;

import oracle.adf.view.rich.context.ExceptionHandler;


public class CustomExceptionHandler extends ExceptionHandler {
    public CustomExceptionHandler() {
        super();
    }

    public void handleException(FacesContext facesContext, Throwable throwable, PhaseId phaseId) throws Throwable {
        String error_message;
        error_message = throwable.getMessage();
        System.out.println("%%%%%%%%%%%%%%%%%%%%%%%%% handleException " + error_message);
        if (error_message != null && error_message.indexOf("ADF_FACES-30108") > -1) {
            ExternalContext ectx = facesContext.getExternalContext();
            ectx.redirect("faces/SessionExpired.jspx");
        } else {
            facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, error_message, null));
            //Code to execute if the if condition is not met
            //throw throwable;
        }
    }
}

2. 创建oracle.adf.view.rich.context.ExceptionHandler文件,注意没有扩展名
该文件位于ADF_TaskFlow_PageToTaskFlow\.adf\META-INF\services目录下,其中services是要你创建的。
该文件的内容为view.CustomExceptionHandler,即指向自定义的ExceptionHandler。

3. 运行deparmentList2页面,最后点击ProcessData2按钮
注意,employees-taskflow2-btf没有继承error-handler-task-flow,因此不会执行controllerExceptionHandler方法。
而是会执行CustomExceptionHandler中的handleException方法。

同时,Console日志输出如下:
%%%%%%%%%%%%%%%%%%%%%%%%% handleException java.lang.Exception: Exception! Data Processing Failed

Project 下载:ADF_TaskFlow_PageToTaskFlow(ExceptionHandler3).7z

参考文献:
1. http://blog.csdn.net/qingqingxuelang/article/details/5576564
2. http://blog.csdn.net/zdwzzu2006/article/details/6568600
3. https://blogs.oracle.com/jdevotnharvest/entry/extending_the_adf_controller_exception_handler

ADF_089:Task Flow使用指南之五:捕获异常 (2)

开发环境:JDevloper 11.1.2.2.0+ Oracle Database 10g Express Edition 10.2.0.1。

本实验接着上一个实验,由于使用Method Activity 绑定抛出异常的方法,ADF会在页面直接抛出异常,用户友好性较差,因此需要开发人员编写异常处理逻辑。
我将定义一个Task Flow Template,并在其中定义一个Exception Handler,然后让其它BTF继承该TaskFlow Template。

重要步骤说明:

1. 新建一个ADF TaskFlow Template:error-handler-task-flow
(1)拖放一个Method Activity,并Mark为Exception Handler。

(2)Method Activity指向Managed Bean中的方法:controllerExceptionHandler

public void controllerExceptionHandler() {
System.out.println("######################## controllerExceptionHandler ");
ControllerContext context = ControllerContext.getInstance();
ViewPortContext currentRootViewPort = context.getCurrentRootViewPort();
Exception exceptionData = currentRootViewPort.getExceptionData();

if (currentRootViewPort.isExceptionPresent()) {
exceptionData.printStackTrace();
currentRootViewPort.clearException();

FacesContext facesContext = FacesContext.getCurrentInstance();
facesContext.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, exceptionData.getMessage(), null));
}
}


2. 让employees-taskflow-btf继承ADF TaskFlow Template:error-handler-task-flow


3. 运行,点击Go to ProcessData Method按钮
这次页面上不再直接抛出异常,而是会调用controllerExceptionHandler,该方法使用弹出窗口显示异常:

同时,在Console日志中输出如下:
######################## controllerExceptionHandler
oracle.jbo.JboException: JboException! Data Processing Failed
at model.AppModuleImpl.processData(AppModuleImpl.java:98)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at oracle.adf.model.binding.DCInvokeMethod.invokeMethod(DCInvokeMethod.java:655)
at oracle.adf.model.binding.DCDataControl.invokeMethod(DCDataControl.java:2162)
at oracle.adf.model.bc4j.DCJboDataControl.invokeMethod(DCJboDataControl.java:3088)
at oracle.adf.model.binding.DCInvokeMethod.callMethod(DCInvokeMethod.java:266)
at oracle.jbo.uicli.binding.JUCtrlActionBinding.doIt(JUCtrlActionBinding.java:1626)
at oracle.adf.model.binding.DCDataControl.invokeOperation(DCDataControl.java:2169)
at oracle.jbo.uicli.binding.JUCtrlActionBinding.invoke(JUCtrlActionBinding.java:731)
at oracle.adf.controller.v2.lifecycle.PageLifecycleImpl.executeEvent(PageLifecycleImpl.java:402)
at oracle.adfinternal.view.faces.model.binding.FacesCtrlActionBinding._execute(FacesCtrlActionBinding.java:252)
at oracle.adfinternal.view.faces.model.binding.FacesCtrlActionBinding.execute(FacesCtrlActionBinding.java:210)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.sun.el.parser.AstValue.invoke(Unknown Source)
at com.sun.el.MethodExpressionImpl.invoke(Unknown Source)

Project 下载:ADF_TaskFlow_PageToTaskFlow(ExceptionHandler2).7z

ADF_088:Task Flow使用指南之五:捕获异常 (1)

开发环境:JDevloper 11.1.2.2.0+ Oracle Database 10g Express Edition 10.2.0.1。

本实验基于《Task Flow使用指南之四:获取Task Flow返回值(1)》。

重要步骤说明:

1. 定制AM,增加一个方法,抛出异常,并发布到Client Interface

public void processData() {
throw new JboException("JboException! Data Processing Failed");
}


2. 修改employees-taskflow-btf
(1)增加一个Method Activity,绑定到processData

(2)修改employeeList页面,增加两个按钮:一个直接绑定到processData,一个导航到Method Activity。

<af:inputText label="Label 1" id="it1" value="#{pageFlowScope.deptId}"/>
<af:commandButton text="Go to ProcessData Method" id="cb2" action="toProcessData"/>
<af:commandButton actionListener="#{bindings.processData.execute}" text="Call processData"
disabled="#{!bindings.processData.enabled}" id="cb3" action="toReturn"/>


3. 运行
(1)点击Call ProcessData按钮

页面弹出一个窗口,提示异常发生:

同时,在Console日志中,输出如下信息:
<Utils> <buildFacesMessage> ADF: Adding the following JSF error message: JboException! Data Processing Failed
oracle.jbo.JboException: JboException! Data Processing Failed
at model.AppModuleImpl.processData(AppModuleImpl.java:98)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at oracle.adf.model.binding.DCInvokeMethod.invokeMethod(DCInvokeMethod.java:655)
at oracle.adf.model.binding.DCDataControl.invokeMethod(DCDataControl.java:2162)
at oracle.adf.model.bc4j.DCJboDataControl.invokeMethod(DCJboDataControl.java:3088)
at oracle.adf.model.binding.DCInvokeMethod.callMethod(DCInvokeMethod.java:266)
at oracle.jbo.uicli.binding.JUCtrlActionBinding.doIt(JUCtrlActionBinding.java:1626)
at oracle.adf.model.binding.DCDataControl.invokeOperation(DCDataControl.java:2169)
at oracle.jbo.uicli.binding.JUCtrlActionBinding.invoke(JUCtrlActionBinding.java:731)
at oracle.adf.controller.v2.lifecycle.PageLifecycleImpl.executeEvent(PageLifecycleImpl.java:402)
at oracle.adfinternal.view.faces.model.binding.FacesCtrlActionBinding._execute(FacesCtrlActionBinding.java:252)
at oracle.adfinternal.view.faces.model.binding.FacesCtrlActionBinding.execute(FacesCtrlActionBinding.java:185)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
小结:使用按钮直接绑定抛出异常的方法,ADF会自动以弹出窗口的方式显示异常,用户友好性较好,开发人员不用做特别处理。

(2)点击Go to ProcessData Method按钮

页面上直接抛出500异常:
oracle.jbo.JboException: JBO-29114 ADFContext is not setup to process messages for this exception. Use the exception stack trace and error code to investigate the root cause of this exception. Root cause error code is JBO-.
at model.AppModuleImpl.processData(AppModuleImpl.java:98)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at oracle.adf.model.binding.DCInvokeMethod.invokeMethod(DCInvokeMethod.java:655)
at oracle.adf.model.binding.DCDataControl.invokeMethod(DCDataControl.java:2162)
at oracle.adf.model.bc4j.DCJboDataControl.invokeMethod(DCJboDataControl.java:3088)
at oracle.adf.model.binding.DCInvokeMethod.callMethod(DCInvokeMethod.java:266)
at oracle.jbo.uicli.binding.JUCtrlActionBinding.doIt(JUCtrlActionBinding.java:1626)
at oracle.adf.model.binding.DCDataControl.invokeOperation(DCDataControl.java:2169)
at oracle.jbo.uicli.binding.JUCtrlActionBinding.invoke(JUCtrlActionBinding.java:731)
at oracle.adf.controller.v2.lifecycle.PageLifecycleImpl.executeEvent(PageLifecycleImpl.java:402)
at oracle.adfinternal.view.faces.model.binding.FacesCtrlActionBinding._execute(FacesCtrlActionBinding.java:252)
at oracle.adfinternal.view.faces.model.binding.FacesCtrlActionBinding.execute(FacesCtrlActionBinding.java:210)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.sun.el.parser.AstValue.invoke(Unknown Source)
at com.sun.el.MethodExpressionImpl.invoke(Unknown Source)
同时,Console中输出如下信息:
<LifecycleImpl> <_handleException> ADF_FACES-60098:Faces lifecycle receives unhandled exceptions in phase INVOKE_APPLICATION 5
oracle.jbo.JboException: JboException! Data Processing Failed
at model.AppModuleImpl.processData(AppModuleImpl.java:98)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at oracle.adf.model.binding.DCInvokeMethod.invokeMethod(DCInvokeMethod.java:655)
at oracle.adf.model.binding.DCDataControl.invokeMethod(DCDataControl.java:2162)
at oracle.adf.model.bc4j.DCJboDataControl.invokeMethod(DCJboDataControl.java:3088)
at oracle.adf.model.binding.DCInvokeMethod.callMethod(DCInvokeMethod.java:266)
at oracle.jbo.uicli.binding.JUCtrlActionBinding.doIt(JUCtrlActionBinding.java:1626)
at oracle.adf.model.binding.DCDataControl.invokeOperation(DCDataControl.java:2169)
at oracle.jbo.uicli.binding.JUCtrlActionBinding.invoke(JUCtrlActionBinding.java:731)
at oracle.adf.controller.v2.lifecycle.PageLifecycleImpl.executeEvent(PageLifecycleImpl.java:402)
at oracle.adfinternal.view.faces.model.binding.FacesCtrlActionBinding._execute(FacesCtrlActionBinding.java:252)
at oracle.adfinternal.view.faces.model.binding.FacesCtrlActionBinding.execute(FacesCtrlActionBinding.java:210)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.sun.el.parser.AstValue.invoke(Unknown Source)
小结:使用Method Activity 绑定抛出异常的方法,ADF会在页面直接抛出异常,用户友好性较差,需要开发人员编写异常处理逻辑。

Project 下载:ADF_TaskFlow_PageToTaskFlow(ExceptionHandler1).7z

参考文献:
1. http://andrejusb.blogspot.jp/2011/03/exception-handler-for-method-calls_19.html
2. http://andrejusb.blogspot.com/2011/03/exception-handler-for-method-calls.html

ADF_087:Task Flow使用指南之四:获取Task Flow返回值(3)

和TaskFlow传入参数一样,TaskFlow的传出参数也是定义在TaskFlow定义中的。

1. 打开TaskFlow定义,定义Return Value Definitions。


说明:这里定义了一个返回参数LocationId,其值来自于pageFlowScope.locationId。
那么,谁给pageFlowScope.locationId赋值呢?

重要说明:在TaskFlow中定义的返回参数LocationId,会自动放到pageFlowScope中!
相关的文档说明我没有找到,是我自己试验发现的:增加新的变量后,所有的EL编辑框下拉列表pageFlowScope节点下都会出现这个变量。



2. 打开TaskFlow,在某个页面中,在command component上设置setActionListener或setPropertyListener把当前页面的某个组件的值传入到pageFlowScope中

<af:commandButton text="OK" id="cb1" action="goReturn">
<af:setPropertyListener type="action" from="#{bindings.LocationId.inputValue}" to="#{pageFlowScope.locationId}"/>
</af:commandButton>

说明:原来pageFlowScope.locationId是在这里赋值的。
3. 拖放TaskFlow到父一级TaskFlow中,定义Task Flow Call Activity上的Return Values

说明:可以把TaskFlow以popup dialog的形式展现,并且设置Dialog Return Value。
注意:这里返回参数LocationId并没有定义Value,因为它通过Dialog Return Value来获取该值了。
如果不通过Dialog Return Value,则必须要设置该值。(有待验证)
我建议为了一致性,还是设置为每个返回参数设置对应的返回值,与步骤1保持一致。(有待验证)

思考:如果在第1步定义多个返回参数,在这里Dialog Return Value只能返回一个参数,那该怎么办?
我的回答:如果有多个返回参数,就不能在Dialog Return Value中全部返回了。此时,可以考虑
(1)使用一个大对象包含所有的对象。
(2)分别使用每个返回参数,因为它们都是在pageFlowScope中的。(有待验证)
4. 在父一级的页面中,在command component上设置returnListener,获取返回值。
<af:commandButton ...... useWindow="true" " action="goPopup" returnListener="#{backingBeanScope.backing_Departments.confirmChoice}" partialSubmit="true"/>
对应的MBean中的方法参数为ReturnEvent,使用returnEvent.getReturnValue(); 可以获取popup dialog的返回值,也即TaskFlow的返回值。

参考文献:
1.http://oracleseeker.com/2009/12/30/adf_dialog_inlinepopup_11g/

2010年4月26日星期一

ADF_086:Task Flow使用指南之四:获取Task Flow返回值(2)

本文最后一次修改时间:2012-03-23。
开发环境:JDevloper 11.1.2.1.0+ Oracle Database 10g Express Edition 10.2.0.1。

TaskFlow可以以inline popup窗口的方式显示,那么如何得到TaskFlow的返回值呢?
回答是通过按钮上的ReturnListener方法。
本实验场景如下:在员工表单修改页面,点击浏览部门按钮,弹出一个popup窗口,选择一个部门,确定后,该部门即为当前员工的部门。

重点步骤说明:
1. employee-taskflow
这是一个使用page fragment的Bounded TaskFlow。


2. employee.jsff

Browse按钮的代码如下:

<af:commandButton text="Browse..." id="cb6" windowHeight="350" windowWidth="430"
returnListener="#{myBackingBean.departmentsTaskFlowReturnListener}" partialSubmit="true"
useWindow="true" action="launch" windowEmbedStyle="window"/>

属性说明:
(1)useWindow=true, 表明下一个导航目的地,使用popup窗口方式展现。
(2)windowEmbedStyle=inlineDocument,popup窗口与原始页面是同一个view id;=window,popup窗口单独使用一个view id。
(3)windowModalityType=applicationModal,modal模式;=modeless,非modal模式。
(4)lanchListener=popup窗口装载时调用的方法,可以用来向popup窗口传递参数。
(5)returnListener=popup窗口的返回时调用的方法,可以用来接收返回值。
(6)partialSubmit=true,防止显示popup窗口时,原始页面被reloading。

3. departments-taskflow
(1)该TaskFlow即是在popup窗口中显示的TaskFlow。
注意,这是一个没有使用page fragment的Bounded TaskFlow。也就是说,在popup窗口中显示的TaskFlow没有要求一定要用page fragment。

(2)departments.jsf

Select Department按钮的代码如下:
                  
<af:commandButton text="Select Department" id="cb1" action="done">
<af:setPropertyListener type="action" from="#{bindings.DepartmentId.inputValue}"
to="#{pageFlowScope.selectedDepartmentId}"/>
</af:commandButton>

注意,为了方便地在页面中引用DepartmentId的值,我手工为页面添加一个AttributeBinding:DepartmentId。
可以看出,点击按钮后,DepartmentId的值将被传递到pageFlowScope.selectedDepartmentId中。

(3)设置departments-taskflow的返回参数值
参数bv_departmentId的值正是来自于前面设置的pageFlowScope.selectedDepartmentId。

那么,最终是如何获得返回值的呢?答案在Managed Bean的代码中。

4. Managed Bean的代码

package view;

import java.util.Map;

import javax.faces.application.Application;
import javax.faces.application.NavigationHandler;
import javax.faces.context.FacesContext;

import oracle.adf.model.BindingContext;

import oracle.adf.model.binding.DCIteratorBinding;
import oracle.adf.view.rich.render.ClientEvent;

import oracle.binding.AttributeBinding;
import oracle.binding.BindingContainer;

import oracle.binding.ControlBinding;

import oracle.jbo.Row;

import org.apache.myfaces.trinidad.context.RequestContext;
import org.apache.myfaces.trinidad.event.ReturnEvent;

public class MyBackingBean {
public MyBackingBean() {
super();
}

public void departmentsTaskFlowReturnListener(ReturnEvent returnEvent) {
BindingContext bindingContext = BindingContext.getCurrent();
BindingContainer bindings = bindingContext.getCurrentBindingsEntry();
ControlBinding control = bindings.getControlBinding("DepartmentId");
AttributeBinding deptId = (AttributeBinding)control;
deptId.setInputValue(returnEvent.getReturnValue());
}
}

注意,这里直接通过returnEvent.getReturnValue()获取返回对象,因为只有一个返回值,所以不用根据变量名字查找。
如果是返回多个值,可以使用returnEvent.getReturnParameters();获取所有变量值。

Map params = returnEvent.getReturnParameters();
for (Map.Entry entry : params.entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue();
System.out.println(key.toString() + ":" + value.toString());
}

更进一步,你可以在程序中自己创建一个HashMap对象,为其增加Key和Value,然后返回。比如:
HashMap hm = new HashMap();
hm.put("myreturn","The Value");
AdfFacesContext.getCurrentInstance().returnFromDialog(null,hm);

5. 支持双击选择
点击Select Department按钮选择当前行有些麻烦,如果能够支持双击选择就好了。
关于支持Table双击的方法请参考《Table 组件使用指南之二:基于Table的CRUD》。
这里只重点说明Managed Bean中新增加的方法:handleTableDoubleClick。
(1)使用ViewIterator查找当前行对象。
(2)获取PageFlowScope,并通过程序设置pageFlowScope.selectedDepartmentId变量。
(3)使用NavigationHandler实现程序导航。

public void handleTableDoubleClick(ClientEvent clientEvent) {
Map pageFlowScope = RequestContext.getCurrentInstance().getPageFlowScope();
BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();
DCIteratorBinding iter = (DCIteratorBinding)bindings.get("DepartmentsView1Iterator");
Row row = iter.getCurrentRow();
pageFlowScope.put("selectedDepartmentId", row.getAttribute("DepartmentId"));

FacesContext fctx = FacesContext.getCurrentInstance();
Application application = fctx.getApplication();
NavigationHandler navHandler = application.getNavigationHandler();
ControllerContext controllerContext = ControllerContext.getInstance();
String viewId = controllerContext.getCurrentViewPort().getViewId();
navigationHandler.handleNavigation(fctx, viewId, "done");
}


Project下载:ADF_TaskFlow_ReturnValues.7z

参考文献:
1. http://www.yaosansi.com/post/605.html
2. http://oracleseeker.com/2009/12/30/adf_dialog_inlinepopup_11g/

ADF_085:Task Flow使用指南之四:获取Task Flow返回值(1)

开发环境:JDevloper 11.1.2.2.0+ Oracle Database 10g Express Edition 10.2.0.1。

本实验在《Task Flow使用指南之三:以Dialog方式显示Task Flow》基础之上,获取Task Flow返回值。

1. 选中TaskFlow Call Activity,设置Return参数


2. 双击TaskFlow Call Activity,打开TaskFlow定义,在空白点击一下,设置Return参数



3. 修改departmentList页面中调用TaskFlow的按钮代码,增加returnListener
(1)页面代码

<af:commandButton text="To Employees" id="cb1" action="toEmployees" useWindow="true"
windowEmbedStyle="inlineDocument" windowWidth="600" windowHeight="320"
returnListener="#{myBackingBean.employeesTaskFlow2ReturnListener}">
<af:setPropertyListener from="#{bindings.DepartmentId}" to="#{pageFlowScope.deptId}" type="action"/>
</af:commandButton>

(2)Managed Bean代码

public void employeesTaskFlow2ReturnListener(ReturnEvent returnEvent) {
System.out.println("$$$$$$$$$$$$$$$$$ return Value: " + returnEvent.getReturnValue());
Map params = returnEvent.getReturnParameters();
//System.out.println("$$$$$$$$$$$$$$$$$ return Value: empId " + params.get("empId"));
for (Map.Entry entry : params.entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue();
System.out.println("$$$$$$$$$$$$$$$$$ return Values: " + key.toString() + " : " + value.toString());
}
}


4. 修改employeeList页面中返回按钮的代码,增加setPropertyListener

<af:commandButton text="to Return" id="cb1" action="toReturn">
<af:setPropertyListener from="#{bindings.EmployeeId.inputValue}" to="#{pageFlowScope.empId}" type="action"/>
</af:commandButton>


5. 运行

Console日志中会输出:
$$$$$$$$$$$$$$$$$ return Value: null
$$$$$$$$$$$$$$$$$ return Values: empId : 136

Project 下载:ADF_TaskFlow_PageToTaskFlow(InlinePopupReturnValue).7z

2010年4月25日星期日

ADF_084:Task Flow使用指南之三:以Dialog方式显示Task Flow

开发环境:JDevloper 11.1.2.2.0+ Oracle Database 10g Express Edition 10.2.0.1。

本实验在《Task Flow使用指南之二:传递参数给Task Flow(1)》基础之上,以Dialog方式显示TaskFlow。

重要步骤说明:

1. 修改页面
(1)设置按钮上的属性

(2)设置TaskFlow Call Activity的属性

(3)设置完成后,TaskFlow Call Activity的图标会改变


2. 运行效果


Project 下载:ADF_TaskFlow_PageToTaskFlow(InlinePopup).7z

ADF_083:Task Flow使用指南之二:传递参数给Task Flow(2)

本文最后一次修改时间:2012-03-05。
开发环境:JDevloper 11.1.2.1.0+ Oracle Database 10g Express Edition 10.2.0.1。

本实验实现如下场景:
在Department页面,选中一个部门后,传递DepartmentId给Employee TaskFlow,从而显示该Department下的员工。
这里使用ADF-BC方式默认创建了Department和Employee的EO和VO,然后修改EmployeeView,添加一个Where子句:WHERE Employees.DEPARTMENT_ID = :bv_departmentId。

1. 为了对比开发效果,我这里使用了两种实现方式
(1)在adfc-config.xml中分别创建两组:各自的页面+各自的TaskFlow。

(2)方式一:使用了method节点:ExecuteWithParams,这是ADF推荐的实现方式。

(3)方式二:没有使用method节点,而是在页面中调用ExecuteWithParams。


2. 方式一和方式二的页面department.jsf和department2.jsf代码完全一样。
都是点击DepartmentId链接后,传递departmentId到requestScope中,
<af:commandLink text="#{row.DepartmentId}" id="cl1" action="toEmployees">
<af:setPropertyListener from="#{row.DepartmentId}" to="#{requestScope.deptId}" type="action"/>
</af:commandLink>
使用RequestScope的原因是遵循变量范围最小够用原则。
重要说明:
这里其实还是应该设置成pageFlowScope,具体说明请参考《使用Bounded Task Flow、Region和Router》。

3. 方式一:实现employees-taskflow。
(1)双击打开employees-taskflow定义,点击空白处,设置其输入参数:
在这里定义的输入参数会自动放到pageFlowScope中!
相关的文档说明我没有找到,是在实验中发现的:增加新的变量后,所有的EL编辑框的下拉列表中的pageFlowScope节点下会出现这个变量。
重要说明:
这里其实还是应该获取来自pageFlowScope的变量,具体说明请参考《使用Bounded Task Flow、Region和Router》。


(2)双击打开adfc-config定义,选中employees-taskflow Call,设置其输入参数:
说明:这里的定义要与上一步保持一致。
不知道为啥要定义两次。我感觉是因为这里有其它的参数,如Pass By Value,所以需要再定义一次。

(3)设置ExecuteWithParams节点
这里我使用#{requestScope.deptId}作为参数值,之所以不使用#{pageFlowScope.ip_departmentId},是因为测试结果显示#{pageFlowScope.ip_departmentId}返回值为空。
也就是说taskFlow的inputParameter没有起到预想的作用,原因尚未查明。
没办法,只好另想办法:因为可以读取到requestScope中的变量,所以就使用了#{requestScope.deptId}。
重要说明:
原因已经查明,TaskFlow的输入参数必须使用pageFlowScope,具体说明请参考《使用Bounded Task Flow、Region和Router》。


(4)运行页面department.jsf,效果和预想的一样。

4. 方式二:实现employees2-taskflow。
(1)在employee2.jsf页面装载时,会调用方法ExecuteWithParams。
具体实现方式请参考《使用ADF-BC 实现查询功能之三:如何在页面装载时自动执行查询?》。
(2)为employee2.jsf页面设置Page Parameter:

说明:From Value来自requestScope.deptId,To Value是要在employee2-taskflow中要用到的参数pageFlowScope.departmentId。
(3)那么,在本例中,哪里使用了参数pageFlowScope.departmentId呢?
在employee2.jsf页面中Binding的ExecuteWithParams方法的参数中使用了pageFlowScope.departmentId。
当然,这里我们依然可以使用requestScope.deptId,但是为了能够让taskFlow中其它页面也使用该变量,还是将其复制到了pageFlowScope中。
也就是说,这里是通过设置TaskFlow中的默认页面的Page Parameter把参数传进来。
而不是通过taskFlow的inputParameter传递参数,因为taskFlow的inputParameter没有起到预想的作用,原因待查。

(4)运行department2.jsf,效果和方式一应该一样。

参考文献:
1. http://andrejusb.blogspot.com/2007/12/jdeveloper-11g-and-adf-task-flow.html

Project 下载:ADF_TaskFlow_InputParam.7z

ADF_082:Task Flow使用指南之二:传递参数给Task Flow(1)

开发环境:JDevloper 11.1.2.2.0+ Oracle Database 10g Express Edition 10.2.0.1。

本实验实现如下场景:
在DepartmentList页面,选中一个部门后,传递DepartmentId给EmployeeTaskFlow,显示属于该部门的该的Employee列表。

1. 为了对比开发效果,我这里使用了两种实现方式
(1)在adfc-config.xml中分别创建两组:各自的DepartmentList页面+各自的EmployeeTaskFlow。
其中DepartmentList页面内容相同,不同的是EmployeeTaskFlow。

(2)方式一:使用了method节点:ExecuteWithParams,这是ADF推荐的实现方式。

其中的EmployeeList页面使用了EmployeesView,其中定义了View Criteria,View Criteria的Query部分添加一个Where子句:WHERE DEPARTMENT_ID = :bv_departmentId。

(3)方式二:也使用了method节点:ExecuteWithParams,这是ADF推荐的实现方式。

其中的EmployeeList2页面使用了EmployeesByParam,其中的Query部分添加一个Where子句:WHERE DEPARTMENT_ID = :bv_departmentId。


2. 运行
(1)方式一和方式二效果一样。
(2)方式二运行效果:


小结:方式一比方式二灵活一些。

Project 下载:ADF_TaskFlow_PageToTaskFlow.7z

ADF_081:Task Flow使用指南之一:在页面之间传递参数

开发环境:JDevloper 11.1.2.2.0+ Oracle Database 10g Express Edition 10.2.0.1。

本实验实现如下场景:
在DepartmentList页面,选中一个部门后,传递DepartmentId给DepartmentDetail页面,显示该Department的详细信息。

1. 为了对比开发效果,我这里使用了两种实现方式
(1)在adfc-config.xml中分别创建两组:各自的DepartmentList页面+各自的DepartmentDetail页面。
其中DepartmentList页面内容相同,不同的是DepartmentDetail页面。

(2)方式一:DepartmentDetail页面使用的是手工创建的DepartmentsByParamView1,在Query部分添加一个Where子句:WHERE DEPARTMENT_ID = :bv_departmentId。

(3)方式二:DepartmentDetail页面使用的就是DepartmentsView,其中定义了View Criteria,只不过使用了一个新的实例DepartmentsView3,View Criteria的Query部分添加一个Where子句:WHERE DEPARTMENT_ID = :bv_deptId。



2. 运行
(1)方式一运行效果


(2)方式二运行效果和方式一一样。
因为方式二使用的是ViewCriteria,因此比方式一更灵活。

Project 下载:ADF_TaskFlow_PageToPage.7z

参考文献:
1. http://www.mkyong.com/jsf2/4-ways-to-pass-parameter-from-jsf-page-to-backing-bean/
2. https://blogs.oracle.com/shay/entry/passing_parameters_to_an_adf_p
3. https://blogs.oracle.com/shay/entry/passing_value_between_pages_to
4. http://eleven-china.blogspot.com/2009/03/task-flow.html

2010年4月21日星期三

ADF_080:Table 组件使用指南之十:设置宽度和高度

开发环境:JDeveloper 11.1.2.2.0 + Oracle XE Database 10gR2。

尽量不要用绝对值来设置ADF Table的宽度和高度,而应该利用组件的伸缩特性来达到想要的界面效果。
为了能够更好地理解这个问题,首先应该了解一下ADF Table的工作原理。
Table(包括Tree/TreeTable)组件中的符合条件的记录并不是一次从Server端全部取出,而是分批取出,然后展现在Client端。
ADF Table有三个属性与获取和显示行记录有关,它们是:RangeSize,FetchSize,AutoHeightRow。
(1)RangeSize:该值就是Iterator Binding上的属性RangeSize,二者是同一个东西,表示Iterator每次遍历获取的对象数。默认值:25。
(2)FetchSize: 表示Table组件每次从Server端获取的记录数。默认值:#{bindings.JobsView1.rangeSize},即默认和RangeSize值一样。
(3)AutoHeightRow:表示Table组件显示的行数。默认值:-1,表示不自动设置行数,适用于Table的Container可以伸缩子组件,比如panelStretchLayout。
设置为0,表示与FetchSize值一样,这是最理想的情况,每次从Server端取回多少记录就显示多少记录。
注意,AutoHeightRow的值不能大于FetchSize的值。
如果AutoHeightRow大于FetchSize,比如AutoHeightRow=50,FetchSize=25,那么在页面上显示50条记录就需要从Server端“搬运”两次。

1. 设置宽度
(1)这是默认情况下,Table的显示效果:没有横向占满整个空间,并且列宽也不合适。

(2)为Table组件增加columnStretching="multiple"设置,并设置列宽的百分比例。
最终的显示效果如下:横向占满了整个空间,并且列宽也比较合适。

注意,这里的列宽的百分比是设置在Column的width属性上的,如:width="10%",百分比作为各个列之间的比例,不一定要总和等于100%。
(3)允许/不允许用户改变Column的宽度。
在RichTable API中有一个方法setColumnResizing(String str),传入"enabled"表示允许,传入"disabled"表示不允许。
我在Table的属性没有找到这个属性,因此只能通过程序实现,比如:
public void toggleColumnResizing() {
    if (jobsTable.getColumnResizing().equals("enabled")) {
            jobsTable.setColumnResizing("disabled");
    } else {
            jobsTable.setColumnResizing("enabled");
    }
}

2. 设置高度
为了说明前后对比的效果,我这里拖放Data Control生成了三个组件:Query、Table、Form。
(1)这是最初的效果:Table没有横向占满,并且有一个水平滚动条,很难看。

(2)为Table“罩上”一个PanelCollection,水平滚动条消失,但依然没有横向占满。

(3)为PanelCollection增加属性:styleClass="AFStretchWidth",PanelCollection横向占满了,但Table依然没有横向占满。

(4)为Table组件增加columnStretching="multiple"设置,并设置列宽的百分比例。
显示效果如下:Table横向占满了整个空间,并且列宽也比较合适。
但还有一个小缺陷:记录数较少时,Table空间中的空白部分比较难看,能不能根据记录数自动伸缩Table的空间呢?

(5)为Table增加属性autoHeightRows="0",表示与FetchSize相同,我这里就是默认值25。
可以看出记录较少时,Table的空间自动收缩了。

而记录较多时,Table的空间自动扩张了。我这里全部记录是19条,一次全部显示出来了。

(6)当记录很多时,如果你想固定只显示10条记录的空间,那么可以设置autoHeightRows="10"。
可以看出记录超过10个时,右边多了一个垂直滚动条,而Table的显示空间固定为10条记录的大小。


Project 下载:ADF_Table_UI.7z

参考文献:
1. http://www.oracle.com/technetwork/developer-tools/adf/learnmore/march2011-otn-harvest-351896.pdf
2. http://andrejusb.blogspot.kr/2010/07/adf-table-autoheightrows-property.html
3. http://www.adftips.com/2010/11/adf-ui-tips-to-stretch-different-adf.html
4. https://forums.oracle.com/forums/thread.jspa?messageID=3224433
5. https://forums.oracle.com/forums/thread.jspa?threadID=851350#