EBS 11.5.10.2
需求
下图是Oracle标准页面,有一个简单的开发需求,当Expense Template等于“管理费用”时,Approver的LOV输入框默认为“XXX”,并且LOV 只读,不能进行选择。
分析
这个看似极其简单的开发,其背后所隐藏的技术知识却是不那么简单的。
当Expense Template选择为“管理费用”时,Approver LOV 输入框只读,这里牵涉到的开发基本为给Expense Template加上FireAction 的功能,
然后在Expense Template的Value Change事件 中去修改Approver LOV 的只读属性。
因为开发规范是不允许直接修改Oracle的代码,所以此开发将通过集成标准的控制器类,在客户化的控制器类中实现添加Expense Template的FireAction功能,和修改Approver LOV 只读属性,
然后通过个性化,将控制器类替换成客户化的控制器类。
技术要点
1,修改Approver LOV 的只读属性
刚刚进行OAF 开发的开发人员可能觉得这是很简单的事,只要在客户化的控制器类的processFormRequest ()方法中调用以下代码:
1 2 |
OAMessageLovInputBean lovBean = ( OAMessageLovInputBean) webBean.findChildRecursive ( "Approver" ) ; lovBean.setReadOnly ( true ) ; |
就可以实现了,如果这样做开发,那么就错了,在运行时会抛出异常:
1 2 3 |
The OA passivation framework coding standard has been violated. Web bean properties cannot be modified in the controller processFormData or processFormRequest method. Web bean properties should be modified in the processRequest method only. |
(这里有两个方法是例外,setValue()和setText(),因为Value并不影响控件树)为什么把LOV 设为只读会出错呢?这是因为OAF 在运行时,表现为一棵控件树。
当进入页面时,控件树被初始化创建,而当用户事件触发POST请求时,控件树并不会被重新生成(提高性能),要改变控件的属性只有两个办法,一个是重新生成控件树,二是使用PPR。
方法一:重构控件树
重构控件树就意味着在processFormRequest()方法处理事件时,需要Forward 到本页,并通过参数控制来设置Approver LOV 只读,这样会刷新整个页面,而且对编码也带来困难。
方法二:使用PPR
实现方法是利用OAF 数据绑定机制 ,将Approver LOV的ReadOnly属性绑定到视图对象的属性,通过改变视图对象属性的值来控制Approver LOV 的只读。
这里又引出第2个技术要点:
2,动态创建视图对象
创建视图对象需要首先创建OAViewDef 对象,然后利用OAViewDef对象来创建视图对象。
这里谈一下Oracle的一个开发规范,OAViewDef接口在oracle.apps.fnd.framework.server 包下,按照Oracle的规范,任何webui下的类不允许import任何server下的类。我是一个规范的严格遵守者,这样就会使开发变得更复杂。
要在server包下引用OAViewDef接口,就必须创建应用程序模块,将动态创建视图对象的代码移到应用程序模块中实现。在客户化控制器类中,动态创建应用程序模块,然后调用应用程序模块的方法。
实现步骤
假设开发人员对Oracle标准的页面结构都已经非常了解了,包括需要集成哪个标准的控制器,页面上所使用的视图对象。
1、创建客户化应用程序模块cux.oracle.apps.ap.oie.entry.server.CuxPVOAM
2、创建视图对象cux.oracle.apps.ap.oie.entry.server.CuxPVO,视图对象中只有一个临时属性ReadOnlyFlag,类型为Boolean(此步可选)
3、创建客户化控制器类,继承标准的控制器类
4、在CuxPVOAMImpl.java中创建两个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import oracle.apps.fnd.framework.OAViewObject ; import oracle.apps.fnd.framework.server.OAViewDefImpl ; import oracle.jbo.AttributeDef ; import oracle.jbo.Row ; …… public void initPVO( ) { OAApplicationModuleImpl rootAM = ( OAApplicationModuleImpl) getRootApplicationModule( ) ; // 创建视图对象 OAViewDefImpl viewDef = ( OAViewDefImpl viewDef) getOADBTransaction( ) .createViewDef ( ) ; viewDef.setFullName ( "CuxPVODef" ) ; viewDef.setViewObjectClass ( "oracle.apps.fnd.framework.server.OAViewObjectImpl " ) ; viewDef.addTransientAttrDef ( "ReadOnlyFlag" , "java.lang.Boolean" , null , false , AttributeDef.UPDATEABLE ) ; OAViewObject pvo = ( OAViewObject) rootAM.createViewObject ( "CuxViewPVO" , viewDef) ; // 初始化PVO if ( ! pvo.isPreparedForExecution ( ) ) { pvo.executeQuery ( ) ; } pvo.setMaxFetchSize ( 1 ) ; Row row = pvo.createRow ( ) ; pvo.insertRow ( row) ; row.setAttribute ( "ReadOnlyFlag" , Boolean .FALSE ) ; } public void handleTemplateChange( ) { OAApplicationModuleImpl rootAM = ( OAApplicationModuleImpl) getRootApplicationModule( ) ; OAViewObject vo = ( OAViewObject) rootAM.findViewObject ( "XxxVO1" ) ; //标准的VO OAViewObject pvo = ( OAViewObject) rootAM.findViewObject ( "CuxViewPVO" ) ; if ( < expense template的值等于X> ) { pvo.first ( ) .setAttribute ( "ReadOnlyFlag" , Boolean .TRUE ) ; vo.first ( ) .setAttribute ( "Xxx" , < value> ) ; // 设置Approver LOV绑定的视图对象属性值 } else { pvo.first ( ) .setAttribute ( "ReadOnlyFlag" , Boolean .FALSE ) ; } } } |
5、在客户化控制器中添加相应代码
processRequest代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public void processRequest( OAPageContext pageContext, OAWebBean webBean) { super .processRequest ( pageContext, webBean) ; // 启用Expense Template的FireAction功能 OAMessageChoiceBean choiceBean = ( OAMessageChoiceBean) webBean.findChildRecursive ( "ExpenseTemplate" ) ; choiceBean.setFireActionForSubmit ( "change" , null , null , true ) ; // 创建客户化应用程序模块 OAApplicationModule rootAM = pageContext.getRootApplicationModule ( ) ; OAApplicationModule pvoAM = ( OAApplicationModule) rootAM.findApplicationModule ( "CuxPVOAM" ) ; if ( pvoAM == null ) { rootAM.createApplicationModule ( "CuxPVOAM" ,"cux.oracle.apps.ap.oie.entry.server.CuxPVOAM" ) ; pvoAM = HssCustomizeHelper.getNestedAMInstance ( rootAM, "CuxPVOAM" ) ; } pvoAM.invokeMethod ( "initPVO" ) ; // 将ReadOnly属性绑定到视图对象属性 OAMessageLovBean approverLov = ( OAMessageChoiceBean) webBean.findChildRecursive ( "Approver" ) ; approverLov.setAttributeValue ( READ_ONLY_ATTR, new OADataBoundValueViewObject( approverLov,"ReadOnlyFlag" ,"CuxPVO" ) ) ; } |
将开发测试后的代码发布到服务器。(测试可以在本地,将页面上的控制器类替换成客户化的控制器来进行)
processFormRequest代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public void processFormRequest( OAPageContext pageContext, OAWebBean webBean) { super .processFormRequest ( pageContext, webBean) ; OAApplicationModule rootAM = pageContext.getRootApplicationModule ( ) ; OAApplicationModule pvoAM = ( OAApplicationModule) rootAM.findApplicationModule ( "CuxPVOAM" ) ; if ( pvoAM == null ) { rootAM.createApplicationModule ( "CuxPVOAM" ,"cux.oracle.apps.ap.oie.entry.server.CuxPVOAM" ) ; pvoAM = HssCustomizeHelper.getNestedAMInstance ( rootAM, "CuxPVOAM" ) ; } if ( "change" .equals ( pageContext.getParameter ( EVENT_PARAM) ) ) { pvoAM.invokeMethod ( "handleTemplateChange" ) ; } } |
HssCustomizeHelper.getNestedAMInstance()的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public static OAApplicationModule getNestedAMInstance( OAApplicationModule parentAM, String nestedAMName) { OAApplicationModule am = null ; String [ ] amNames = parentAM.getApplicationModuleNames ( ) ; for ( int i= 0 ; i& lt; amNames.length ; i++ ) { if ( amNames[ i] .endsWith ( nestedAMName) ) { return ( OAApplicationModule) parentAM.findApplicationModule ( amNames[ i] ) ; } else { am = getNestedAMInstance( ( OAApplicationModule) parentAM.findApplicationModule ( amNames[ i] ) ,nestedAMName) ; if ( am != null ) { return am; } } } return am; } |
7、重启Apache
8、进入页面,设置个性化,替换标准的控制器类为客户化控制器类。