EBS FORM开发全过程(3)
基于EBS的FORM开发全过程(3)
描述性弹性域、Key弹性域、Key弹性域查询
描述性弹性域开发步骤
关于描述性弹性域
描述性弹性域的实质就是系统预留自定字段,不过Oracle做的很成功,提供一套完整的“自定义”机制,可以用值集来验证字段、字段间可以设置依赖关系等等。
我们以基于基表的最简单例子来演示开发步骤。
基表要求:基表中需含有1个结构字段和若干个自定义字段
结构字段通常起名ATTRIBUTE_CATEGORY,长度为30;自定义字段通常15个,起名ATTRIBUTE1..n,长度240:
点击查看代码
-- Create table
create table SECOM.CUX_FLEXFIELD_DEMO
(
FLEXFIELD_DEMO_ID NUMBER not null,
CODE_COMBINATION_ID NUMBER not null,
DESCRIPTION VARCHAR2(240),
CREATION_DATE DATE not null,
CREATED_BY NUMBER not null,
LAST_UPDATED_BY NUMBER not null,
LAST_UPDATE_DATE DATE not null,
LAST_UPDATE_LOGIN NUMBER,
ATTRIBUTE_CATEGORY VARCHAR2(30),
ATTRIBUTE1 VARCHAR2(240),
ATTRIBUTE2 VARCHAR2(240),
ATTRIBUTE3 VARCHAR2(240),
ATTRIBUTE4 VARCHAR2(240),
ATTRIBUTE5 VARCHAR2(240),
ATTRIBUTE6 VARCHAR2(240),
ATTRIBUTE7 VARCHAR2(240),
ATTRIBUTE8 VARCHAR2(240),
ATTRIBUTE9 VARCHAR2(240),
ATTRIBUTE10 VARCHAR2(240),
ATTRIBUTE11 VARCHAR2(240),
ATTRIBUTE12 VARCHAR2(240),
ATTRIBUTE13 VARCHAR2(240),
ATTRIBUTE14 VARCHAR2(240),
ATTRIBUTE15 VARCHAR2(240)
)
tablespace SECOMD;
-- Create/Recreate indexes
create unique index SECOM.CUX_FLEXFIELD_DEMO_U1 on SECOM.CUX_FLEXFIELD_DEMO (FLEXFIELD_DEMO_ID)
tablespace SECOMD;
-- Create/Recreate sequence
CREATE SEQUENCE SECOM.CUX_FLEXFIELD_DEMO_S;
-- Create/Recreate synonum
CREATE SYNONYM CUX_FLEXFIELD_DEMO_SS FOR SECOM.CUX_FLEXFIELD_DEMO_S;
CREATE SYNONYM CUX_FLEXFIELD_DEMO_AA FOR SECOM.CUX_FLEXFIELD_DEMO;
注册要求:注册表和字段到EBS中
用户客制开发的表通常不需要注册到EBS中,依然可以正常使用。
但如果需要使用预警、审计或值集功能,则需要将客制化的表注册到EBS里。
在应用开发员→用户产品→数据库→表中可以查看到已经注册到EBS中的表,但该模块不能添加,如果需要注册,需要使用EBS提供的API:AD_DD
调用EBS标准过程完成表的注册:
点击查看代码
BEGIN
--注册表
--('应用产品','表名','T自动扩展/S非自动扩展','下一区','自由','已使用')
ad_dd.register_table('SECOM', 'CUX_FLEXFIELD_DEMO', 'T', 2, 10, 40);
--注册字段
--('应用产品','表名', '字段名',序号,'类型',字段宽度,是否为空,是否可以转换)
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'FLEXFIELD_DEMO_ID',
1,
'NUMBER',
38,
'N',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'CODE_COMBINATION_ID',
2,
'NUMBER',
38,
'N',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'DESCRIPTION',
3,
'VARCHAR2',
240,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'CREATION_DATE',
4,
'DATE',
9,
'N',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'CREATED_BY',
5,
'NUMBER',
38,
'N',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'LAST_UPDATED_BY',
6,
'NUMBER',
38,
'N',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'LAST_UPDATE_DATE',
7,
'DATE',
9,
'N',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'LAST_UPDATE_LOGIN',
8,
'NUMBER',
38,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE_CATEGORY',
9,
'VARCHAR2',
30,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE1',
10,
'VARCHAR2',
240,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE2',
11,
'VARCHAR2',
240,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE3',
12,
'VARCHAR2',
240,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE4',
13,
'VARCHAR2',
240,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE5',
14,
'VARCHAR2',
240,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE6',
15,
'VARCHAR2',
240,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE7',
16,
'VARCHAR2',
240,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE8',
17,
'VARCHAR2',
240,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE9',
18,
'VARCHAR2',
240,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE10',
19,
'VARCHAR2',
240,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE11',
20,
'VARCHAR2',
240,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE12',
21,
'VARCHAR2',
240,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE13',
22,
'VARCHAR2',
240,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE14',
23,
'VARCHAR2',
240,
'Y',
'N');
ad_dd.register_column('SECOM',
'CUX_FLEXFIELD_DEMO',
'ATTRIBUTE15',
24,
'VARCHAR2',
240,
'Y',
'N');
--注册主键
--('应用产品','键名','表名','描述','S','Y审计','Y启用');
--ad_dd.register_primary_key('###', '###', '###', '###', 'S', 'Y', 'Y');
--注册主键字段
--AD_DD.REGISTER_PRIMARY_KEY_COLUMN('应用产品','键名','表','字段',序列)
--ad_dd.register_primary_key_column('###', '###', '###', '###', 1);
COMMIT;
END;
查看是否注册成功
EBS中查看是否注册成功,路径:应用开发员→用户产品→数据库→表

删除注册信息
点击查看代码
DECLARE
CURSOR cur_table IS
SELECT at.table_name
FROM all_tables at
WHERE at.table_name IN ('CUX_FLEXFIELD_DEMO') AND at.owner = 'APPS';
CURSOR cur_col(p_table_name IN VARCHAR2) IS
SELECT c.column_name,c.data_type,c.data_length,c.nullable,c.table_name
FROM dba_tab_columns c
WHERE c.table_name = p_table_name;
l_application_short_name VARCHAR2(30) := 'SECOM';
BEGIN
FOR t IN cur_table
LOOP
FOR c IN cur_col('EMPLOYEE_TEST')
LOOP
ad_dd.delete_column(l_application_short_name,t.table_name,c.column_name);
END LOOP;
ad_dd.delete_table(l_application_short_name,t.table_name);
END LOOP;
commit;
END;
2、通过Application Developer职责/Flexfield(弹性域)/Descriptive(说明性)/Register(注册)注册弹性域,通常起名和表名一致:
3、保存后,点击Columns,可以看到,系统自动选中了所有Attribute字段。

字段要求:一个非数据库项
在块中手工创建一个字段,名字通常叫DESC_FLEX,子类为TEXT-ITEM-DESC-FLEX,Prompt为一对大括号,布局时通常放在最后,但不随滚动条滚动:

触发器要求:Form级
WHEN-NEW-FORM-INSTANCE中追加:
fnd_descr_flex.define(BLOCK => 'BLOCKNAME',
field => 'DESC_FLEX',
appl_short_name => '###',--Application 应用产品名
desc_flex_name => 'CUX_FLEXFIELD_DEMO');
触发器要求:块级
PRE-INSERT中追加:
fnd_flex.event('PRE-INSERT');
PRE-UPDATE中追加:
fnd_flex.event('PRE-UPDATE');
PRE-QUERY中追加:
fnd_flex.event('PRE-QUERY');
POST-QUERY中追加:
fnd_flex.event('POST-QUERY');
WHEN-VALIDATE-RECORD中追加:
fnd_flex.event('WHEN-VALIDATE-RECORD');
注:可以把这些触发器写在Form级,这样不需要每个块都写,不过如果为了其它功能在块级写了同名触发器,执行层次需要改为Before。
触发器要求:Item级
WHEN-NEW-ITEM-INSTANCE中追加:
fnd_flex.event('WHEN-NEW-ITEM-INSTANCE');
WHEN-VALIDATE-ITEM中追加:
fnd_flex.event('WHEN-VALIDATE-ITEM');
注:可以把这些触发器写在Form级,这样不需要每个Item都写,不过如果为了其它功能在块级写了同名触发器,执行层次需要改为Before。
启用弹性域
通过Application Developer职责/Flexfield(弹性域)/Descriptive(说明性)/Segments(段)启用弹性域:


上传&编译&运行
Key弹性域开发步骤
关于Key弹性域
Key弹性域通常用来处理有层次结构的编码,比如账户、类别等,极少自行客户化开发,一般都是使用系统标准的Key弹性域而已。
我们以基于基表的最简单例子来演示如何使用系统标准的Key弹性域。
基表要求:基表中需含有1个ID字段
Key弹性域对应的表都有一个ID字段,如果我们在客户化开发中需要使用该表作为外键,当然需要一个外键ID字段,名字根据需要起即可,比如我们的CUX_FLEXFIELD_DEMO.CODE_COMBINATION_ID,实际上将用来保存账户ID。

字段要求:一个Key代码组合字段+一个可选的Key描述组合字段
这两个字段可以是数据库项,也可以不是。
代码组合字段子类是Text_Item,描述组合字段子类通常是Text_Item_Display_Only;注意它们的长度要足够,不然可能放不下组合。
对代码组合字段,需要设置其Lov属性,和日期字段一样:

触发器要求:Form级
WHEN-NEW-FORM-INSTANCE中追加:
fnd_key_flex.define(BLOCK => 'BLOCKNAME',
field => 'ACCOUNT_CODE',
description => 'ACCOUNT_DESCRIPTION',
appl_short_name => 'SQLGL',
code => 'GL#',
id => 'CODE_COMBINATION_ID',
required => 'N',
usedbflds => 'N',
validate => 'FULL',
vrule => '\\nSUMMARY_FLAG\\nI\\nAPPL=SQLGL;NAME=GL_NO_PARENT_SEGMENT_ALLOWED\\nN\\0GL_GLOBAL\\nDETAIL_POSTING_ALLOWED\\nE\\nAPPL=INV;NAME=INV_VRULE_POSTING\\nN',
num => 101);
这个定义比较复杂,因为不同公司启用的Structure不同,实际开发中不要写死Num值了。
简单的查询不同Key弹性域的定义:
SELECT app.application_short_name,
app.application_name,
flx.id_flex_code,
flx.id_flex_name,
str.id_flex_num,
str.id_flex_structure_code,
str.id_flex_structure_name
FROM fnd_id_flexs flx,
fnd_id_flex_structures_vl str,
fnd_application_vl app
WHERE flx.application_id = str.application_id
AND flx.id_flex_code = str.id_flex_code
AND flx.application_id = app.application_id
ORDER BY 1, 3, 5
触发器要求:块级
同描述性弹性域。
触发器要求:Item级
同描述性弹性域。
上传&编译&运行
运行结果如下:

Folder JTF Grid
Folder开发步骤
本节标题说明:标准指做Folder都要做而且是一样的步骤,可以考虑做个模版了;普通指和做普通Form一样;特殊指做Folder都要做但需要根据实际内容作修改。
什么是Folder
Folder不是Form的标准功能,而是Oracle自己在EBS开发中总结出来的“动态界面”:不同用户可以根据自己的需要,设置块中哪些字段需要显示以及顺序;而开发人员则免于被布局折腾的痛苦。
对于开发来说,要做的事情就是用“遵循Folder规范”换取“布局零工作量”。
拷贝标准Folder对象 标准
打开APPSTAND.fmb,把对象组“STANDARD_FOLDER”拖到我们自己的Form中,并选择“Subclass”而非“Copy”,这个和前面讲的查询块不同。
这样会自动产生一系列用于Folder的对象:块、画布、Lov/记录组、参数、Property Classes、Window,这些都不用修改。
引用Folder的PLL库 标准
选中Attached Libraries,点击“+”,选择APPFLDR.pll,如果本地没有请先从服务器下载。

点击Attach后选择“Yes”移除绝对路径。
创建数据库对象 普通
创建数据库对象,没有任何特殊之处:
-- Create table
create table SECOM.CUX_FLODER_DEMO
(
FLODER_DEMO_ID NUMBER not null,
NUMBER_FIELD1 NUMBER not null,
NUMBER_FIELD2 NUMBER,
NUMBER_FIELD3 NUMBER,
NUMBER_FIELD4 NUMBER,
DATE_FIELD1 DATE NOT NULL,
DATE_FIELD2 DATE,
VARCHAR2_FIELD1 VARCHAR2(100) NOT NULL,
VARCHAR2_FIELD2 VARCHAR2(100),
VARCHAR2_FIELD3 VARCHAR2(100),
VARCHAR2_FIELD4 VARCHAR2(100),
VARCHAR2_FIELD5 VARCHAR2(100),
VARCHAR2_FIELD6 VARCHAR2(100),
CREATION_DATE DATE not null,
CREATED_BY NUMBER not null,
LAST_UPDATED_BY NUMBER not null,
LAST_UPDATE_DATE DATE not null,
LAST_UPDATE_LOGIN NUMBER,
ATTRIBUTE_CATEGORY VARCHAR2(30),
ATTRIBUTE1 VARCHAR2(240),
ATTRIBUTE2 VARCHAR2(240),
ATTRIBUTE3 VARCHAR2(240),
ATTRIBUTE4 VARCHAR2(240),
ATTRIBUTE5 VARCHAR2(240),
ATTRIBUTE6 VARCHAR2(240),
ATTRIBUTE7 VARCHAR2(240),
ATTRIBUTE8 VARCHAR2(240),
ATTRIBUTE9 VARCHAR2(240),
ATTRIBUTE10 VARCHAR2(240),
ATTRIBUTE11 VARCHAR2(240),
ATTRIBUTE12 VARCHAR2(240),
ATTRIBUTE13 VARCHAR2(240),
ATTRIBUTE14 VARCHAR2(240),
ATTRIBUTE15 VARCHAR2(240)
)
tablespace SECOMD;
-- Create/Recreate indexes
create unique index SECOM.CUX_FLODER_DEMO_U1 on SECOM.CUX_FLODER_DEMO (FLODER_DEMO_ID)
tablespace SECOMD;
-- Create/Recreate sequence
CREATE SEQUENCE SECOM.CUX_FLODER_DEMO_S;
-- Create/Recreate synonum
CREATE SYNONYM CUX_FLODER_DEMO_SS FOR SECOM.CUX_FLODER_DEMO_S;
CREATE SYNONYM CUX_FLODER_DEMO FOR SECOM.CUX_FLODER_DEMO;
创建Folder块 普通
按照普通步骤创建数据块,包括块和字段的子类、LOV、On-XXX触发器、行指示符等等,如果有弹性域,那么也需要DF字段和相关的触发器。
1.创建Block数据块
2.设置Block属性及其Subclass
3.设置Item属性及其Subclass
4.创建Canvas画布
5.设置画布属性和子类
6.调整布局
7.编写数据操作Program Unit
8.编写块ON触发器
当然了,从Template开始的常规修改步骤也是要做的。
1、修改Forms级触发器PRE-FORM
2、修改Forms级触发器WHEN-NEW-FORM-INSTANCE
3、修改Program Unit下app_custom中的close_window过程
为规范起见,块名后加“FOLDER”,这里是“DEMO_FOLDER”,这种数据块我们叫“Folder块”。
修改Folder块 标准
1、创建SWITCHER字段
手工添加字段,名字叫FOLDER_SWITCHER,子类为SWITCHER:
并编写触发器WHEN-NEW-ITEM-INSTANCE:
app_folder_move_cursor('1');

2、 编写触发器
需要编写如下触发器:
WHEN-NEW-RECORD-INSTANCE
WHEN-NEW-BLOCK-INSTANCE
PRE-QUERY
POST-QUERY
PRE-BLOCK
POST-BLOCK
KEY-ENTQRY
KEY-EXEQRY
KEY-PREV-ITEM
KEY-NEXT-ITEM
KEY-PRVREC
KEY-NXTREC
KEY-CLRREC
KEY-CLRBLK
这些触发器的内容都是app_folder.event('触发器名称');
创建Prompt块 标准
手工创建非数据库块,子类仍为Block,为规范起见,块名后加“PROMPT”,这里是“DEMO_PROMPT”,这种数据块我们叫“Prompt块”。
手工创建6个标准Item,名字和子类必须同下表:
| Name | Subclass |
|---|---|
| FOLDER_TITLE | DYNAMIC_TITLE |
| FOLDER_OPEN | FOLDER_OPEN |
| FOLDER_DUMMY | FOLDER_DUMMY |
| ORDER_BY1 | FOLDER_ORDERBY |
| ORDER_BY2 | FOLDER_ORDERBY |
| ORDER_BY3 | FOLDER_ORDERBY |
修改Prompt块和Folder块 特殊
“Folder块”有多少字段要显示,就需要在“Prompt块”创建多少同名字段(除了SWITCHER字段、行指示符(CURRENT_RECORD_INDICATOR)、DF字段,前者是Folder的特殊字段,后两者通常需要固定在内容画布上),并设置这些字段的关键属性:
| 属性 | 值 |
|---|---|
| Subclass | FOLDER_PROMPT_MULTIROW |
| Initial Value | 字段的Prompt |
| Width | 字段的宽度,根据实际需要调整 |
| Prompt | 注:清空 |
注意:对“Folder块”的字段,也需要清空Prompt属性。
Folder自动布局原理
1、需要使用Folder功能的字段必须放在堆叠画布上,Folder功能仅自动布局堆叠画布宽度和在其上的字段顺序。
2、放在内容画布上的所有对象,包括堆叠画布自身在内容画布上的起始位置,需要我们和以前一样手工调整布局;只要布局得当,一个Windows上可以有多个Folder。
3、自动布局的堆叠画布宽度 = 内容画布宽度 – 堆叠画布的X座标 – 0.26,这个0.26啊,正好可以让我们放垂直滚动条!此外,堆叠画布高度也会被自动调整,调整时系统自动回算上水平滚动条的位置!
4、最终界面的字段顺序由Prompt块字段的顺序决定,那么Folder块的字段在界面的排列顺序如何自动和Prompt对应起来呢?原来系统是根据字段名!
5、最终界面Tab键导航的顺序仍然由Folder块字段的顺序决定,所以设计时注意两者要一致。
6、系统并不自动决定字段的Y轴位置!Y轴位置由字段自身属性决定,所以需要手工设置,通常Prompt块的为0,Folder块的为0.25,即等于Prompt块的Item的高度。
7、为操作方便,也为了标准化,通常需要放个文件夹按钮在内容画布的左上角,这个就是Folder_Open字段。
创建堆叠画布、内容画布、窗口 普通
Floder要求字段放置在堆叠画布上,为规范起见,画布名后加“FOLDER_STACK”,View和Canvas的宽度无所谓,运行时将自定根据窗口的大小调整,右边正好会留出滚动条的位置。
另外,为规范起见,内容画布后也可加“FOLDER_CONTENT”。
设置这两个画布的子类,并设置它们的Windows属性相同。
布局Item到画布 特殊
设置如下Item到画布:
1、把FOLDER_OPEN、FOLDER_DUMMY、行指示符、Folder块的垂直滚动条,都设到内容画布上,并设计它们的位置。
Folder_Open按照其子类默认值即可:X为0.1、Y为0。行指示符X我们改为0.1、Y改为0.5。垂直滚动条的Y为0.5,X则需要在Window的Resize事件中设置,还记得那个0.26吗?
2、这样,堆叠画布在内容画布上的位置,应该就是X为0.2,Y为0.25,想想为什么。同时启用堆叠画布的水平滚动条。
3、Folder块的SWITCHER字段设置到堆叠画布。
4、Folder块的其他需要显示的字段都设置到堆叠画布,并且Y座标为0.25。
5、Prompt块的其他字段全部设置到堆叠画布,并且Y座标为0。
注:FOLDER_TITLE字段不知道是干什么的,其宽度设为0。
追加Form级触发器 标准
在FOLDER_ACTION中追加:
app_folder.event(:global.folder_action);
在KEY-CLRFRM中追加:
app_folder.event('KEY-CLRFRM');
追加Form级触发器 特殊
1、 在WHEN-WINDOW-RESIZED中追加,注意BLOCKNAME,要改为你的Folder所在的Window名字:
if :system.event_window in ('MAIN_WIN') then
app_folder.event('WHEN-WINDOW-RESIZED');
end if;
注意必须用代码对内容画布进行调整,因为改变窗口大小时,Form不会自动调整。
2、 在WHEN-NEW-FORM-INSTANCE中追加:
app_folder.define_folder_block(object_name => 'DEMO_FOLDER',
folder_block_name => 'DEMO_FOLDER',
prompt_block_name => 'DEMO_PROMPT',
folder_canvas_name => 'DEMO_FOLDER_STACK',
folder_window_name => 'DEMO_FOLDER',
disabled_functions => '',
tab_canvas_name => '',
fixed_canvas_name => '');
app_folder.event('INSTANTIATE');
第一句是Folder申明,根据参数名给出具体值即可,注意tab_canvas_name,我们不用Tab页,所以为空。
最后一句是因为本例中内容画布上没有可导航的块,所以需要用带码使其显示。
上传&编译&运行
运行结果如下:

可以调整列宽度和顺序、隐藏或显示列,并可以保存布局;调整窗口大小,Folder会自动调整适应。
注,上述触发器代码可以全部组织到一个Program Units中。
JTF Grid开发步骤
本节标题说明:标准指做JTF Grid都要做而且是一样的步骤,可以考虑做个模版了;普通指和做普通Form一样;特殊指做JTF Grid都要做但需要根据实际内容作修改。
什么是JTF Grid
JTF Grid不是Form的标准功能,而是Oracle自己在EBS开发中总结出来的“可配置块字段”:块中有多少字段可以通过专门的界面定义,。
对于开发来说,要做的事情就是用“遵循JTF Grid规范”换取“增删字段无需修改Form代码”
拷贝标准JTF Grid对象 标准
1、对象组
打开JTFSTAND.fmb,把对象组“JTF_GRID”拖到我们自己的Form中,并选择“Subclass”而非“Copy”,这个和前面讲的Folder一样。
这样会自动产生一系列用于JTF_GRID的对象:块、画布、参数、Property Classes、Window,尤其注意Form级触发器JTF_GRID_EVENT。这些都不用修改。
2、过程
从JTFSTAND.fmb拷贝JTF_CUSTOM_GRID_EVENT过程到我们自己的Form中,然后补上事件处理,暂时全部放null:
PROCEDURE jtf_custom_grid_event(gridname IN VARCHAR2,
eventtype IN VARCHAR2) IS
BEGIN
IF p_event_type = jtf_grid_events.hyperlink_event THEN
NULL;
ELSIF p_event_type = jtf_grid_events.new_record_event THEN
NULL;
ELSIF p_event_type = jtf_grid_events.popup_event THEN
NULL;
ELSIF p_event_type = jtf_grid_events.row_selection_event THEN
NULL;
ELSIF p_event_type = jtf_grid_events.end_of_find_event THEN
NULL;
ELSIF p_event_type = jtf_grid_events.doubleclick_event THEN
NULL;
END IF;
END;
引用JTF Grid的PLL库 标准
选中Attached Libraries,点击“+”,选择JTF_GRID.pll,其将自动引用JTF_UTIL、JTFDEBUG。如果本地没有请先从服务器下载。
创建数据库对象 普通
创建数据库对象,没有任何特殊之处,可以使用现成的View和Table,比如我们使用gl_je_headers_v。
定义CRM电子表格 特殊
N: CRM Adminstrator/Spreadtable/Metadata Administraion
输入电子表格名称、源视图、字段定义:

创建Grid块 普通
手工创建非数据库块,规范起见,块名后加“GRID”,这里是“DEMO_GRID”。
当然了,从Template开始的常规修改步骤也是要做的。
修改Grid块 特殊
手工创建非数据库项,并设置这些字段的关键属性:
| 字段名 | Subclass | 说明 |
|---|---|---|
| READONLY_GRID | JTF_GRID_ITEM | 必须,名字随便 |
| FIND | BUTTON | 可选 |
| DETAIL | BUTTON | 可选 |
| 布局Item到画布 普通 | ||
| 把DEMO_GRID布局到画布,什么画布都可以,我们需要设置其在画布的启示位置、高度、宽度,因为设计时在画布上不容易看到,我们可以直接设置属性。 | ||
| 追加Form级触发器 特殊 | ||
| 在WHEN-NEW-FORM-INSTANCE中追加: |
IF NOT jtf_grid.getbooleanproperty('DEMO_GRID.READONLY_GRID',
jtf_grid_property.initialized) THEN
jtf_grid.init(jtf_custom.grid_name, 'GL_JE_HEADERS_V');
jtf_grid.setbooleanproperty(jtf_custom.grid_name,
jtf_grid_property.allow_multiple_row_selection,
FALSE);
END IF;
编写Find Button触发器 特殊
用户点击Find,通常是弹出查询界面,输入完条件再执行查询。
我们这里省去查询条件界面,直接在FIND按钮的WHEN-BUTTON-PRESSED中编写:
jtf_grid.removeallbindvariables('DEMO_GRID.READONLY_GRID');
--jtf_grid.setbindvariable('DEMO_GRID.READONLY_GRID', 'CURRENCY_CODE', 'CNY');
jtf_grid.setcharproperty('DEMO_GRID.READONLY_GRID',
jtf_grid_property.where_clause,
'CURRENCY_CODE=''CNY''');
IF jtf_grid.getbooleanproperty('DEMO_GRID.READONLY_GRID',
jtf_grid_property.is_populated) THEN
jtf_grid.refresh('DEMO_GRID.READONLY_GRID');
ELSE
jtf_grid.populate('DEMO_GRID.READONLY_GRID');
END IF;
处理选择事件 特殊
用户选中某行后,我们可以根据其选中的信息去打开一个普通块,这样首先需要在FIND按钮的WHEN-BUTTON-PRESSED中编写:
jtf_grid.RequestRowSelection('DEMO_GRID.READONLY_GRID');
可以打开该包查看其具体作用。然后在过程jtf_custom_grid_event中响应选择事件:
PROCEDURE jtf_custom_grid_event(gridname IN VARCHAR2,
eventtype IN VARCHAR2) IS
grid_selection JTF_GRID_PROPERTY.ROW_SELECTION_TYPE;
l_startRow number;
BEGIN
IF eventtype = jtf_grid_events.hyperlink_event THEN
NULL;
ELSIF eventtype = jtf_grid_events.new_record_event THEN
NULL;
ELSIF eventtype = jtf_grid_events.popup_event THEN
NULL;
ELSIF eventtype = jtf_grid_events.row_selection_event THEN
grid_selection := jtf_grid.GetRowSelection('DEMO_GRID.READONLY_GRID');
if grid_selection.COUNT > 0 then
l_startRow := grid_selection(1).startRow;
fnd_message.debug(jtf_grid.GetColumnCharValue('DEMO_GRID.READONLY_GRID', l_startRow, 'NAME'));
--Do any thing here
END IF;
ELSIF eventtype = jtf_grid_events.end_of_find_event THEN
NULL;
ELSIF eventtype = jtf_grid_events.doubleclick_event THEN
null;
END IF;
END;
问题:如何响应双击事件呢?
上传&编译&运行
运行结果如下:

注,上述触发器代码通常全部组织到一个名字为“JTF_CUSTOM”的Program Units中,这样就可以定义一个变量GRID_NAME来保存字段名,免得每处代码重复写'DEMO_GRID.READONLY_GRID'。
如果要使Window变化时Grid跟着变,那么需要参考Folder的做法,在WHEN-WINDOW-RESIZED触发器中调整画布大小、Grid的大小。
多语言开发
国际化支持
说明
EBS的国际化支持,也叫多语言支持,包含多个层面:
1、 数据库级别:字符集支持多国语言,如UTF8支持全球所有语言
2、 数据级别:采用_B表+_TL表+ENV(‘LANG’)环境变量+_VL表+“小地球”来实现
3、 消息级别:所有消息,通过分语种维护的消息字典获取
4、 文件级别:采用分语种目录的形式来实现Forms、Reports的国际化支持
Form自身的多语言版本
EBS时运行时“找fmx文件”,实际上是根据用户登录时选择的语言,首先到<应用简称>_TOP/forms/<语言代码>下找fmx文件,如果没有则继续在<应用简称>_TOP/forms/US下找。
也就是说,需要我们维护不同语言的Form编译到不同的目录。不过Oracle提供了工具“Oracle Translation Builder (OTB)”,可以将多个语言的字符串保存入1个fmb文件,这样在设计时根据NLS_LANG 自动显示该语言的内容,编译时、运行时也是同样道理。
数据多语言开发步骤
要求熟练掌握基于Template、基于View的开发过程;要求熟悉EBS中“小地球”的操作。
下面结合例子直接说明开发步骤和注意点,假定只有2个字段,1个需要维护多语言信息,不考虑弹性域字段。
数据库对象的要求: 基表B
create table SCF.CUX_MULTILINGUAL_DEMO_B
(
MULTILINGUAL_DEMO_ID NUMBER not null,
MULTILINGUAL_DEMO_CODE VARCHAR2(30) not null,
CREATED_BY NUMBER(15) not null,
CREATION_DATE DATE not null,
LAST_UPDATED_BY NUMBER(15) not null,
LAST_UPDATE_DATE DATE not null,
LAST_UPDATE_LOGIN NUMBER(15)
);
create unique index SCF.CUX_MULTILINGUAL_DEMO_B_U1 on SCF.CUX_MULTILINGUAL_DEMO_B (MULTILINGUAL_DEMO_ID);
Create Sequence SCF.CUX_MULTILINGUAL_DEMO_B_S;
Create Synonym CUX_MULTILINGUAL_DEMO_B For scf.CUX_MULTILINGUAL_DEMO_B;
Create Synonym CUX_MULTILINGUAL_DEMO_B_S For scf.CUX_MULTILINGUAL_DEMO_B_S;
数据库对象的要求: 多语言表TL
主键字段:基表主键+LANGUAGE。
其他字段:需要维护多语言的字段+Who字段+SOURCE_LANG。
create table SCF.CUX_MULTILINGUAL_DEMO_TL
(
MULTILINGUAL_DEMO_ID NUMBER not null,
DESCRIPTION VARCHAR2(255),
LANGUAGE VARCHAR2(4) not null,
CREATED_BY NUMBER(15) not null,
CREATION_DATE DATE not null,
LAST_UPDATED_BY NUMBER(15) not null,
LAST_UPDATE_DATE DATE not null,
LAST_UPDATE_LOGIN NUMBER(15),
SOURCE_LANG VARCHAR2(4) not null
);
create unique index SCF.CUX_MULTILINGUAL_DEMO_TL_U1 on SCF.CUX_MULTILINGUAL_DEMO_TL(MULTILINGUAL_DEMO_ID, LANGUAGE);
Create Synonym CUX_MULTILINGUAL_DEMO_TL For scf.CUX_MULTILINGUAL_DEMO_TL;
数据库对象的要求:视图VL
该视图根据登录用户的语言过滤数据:
Create Or Replace View CUX_MULTILINGUAL_DEMO_VL As
SELECT b.ROWID row_id,
b.multilingual_demo_id,
b.multilingual_demo_code,
t.description,
b.created_by,
b.creation_date,
b.last_updated_by,
b.last_update_date,
b.last_update_login
FROM scf.cux_multilingual_demo_b b, scf.cux_multilingual_demo_tl t
WHERE b.multilingual_demo_id = t.multilingual_demo_id
AND t.LANGUAGE = userenv('LANG');
数据库对象的要求:表操作API
需要同时操作TL表,同时提供add_language过程,供EBS启用新语言时使用。以下代码仅关注“--Process TL table here”部分即可:
点击查看代码
CREATE OR REPLACE PACKAGE cux_multilingual_demo_pkg AUTHID CURRENT_USER AS
/*=====================================
** PROCEDURE: insert_row()
**=====================================*/
PROCEDURE insert_row(x_row_id IN OUT VARCHAR2,
x_multilingual_demo_id IN OUT NUMBER,
p_multilingual_demo_code IN VARCHAR2,
p_description IN VARCHAR2,
p_creation_date IN DATE,
p_created_by IN NUMBER,
p_last_update_date IN DATE,
p_last_updated_by IN NUMBER,
p_last_update_login IN NUMBER);
/*=====================================
** PROCEDURE: lock_row()
**=====================================*/
PROCEDURE lock_row(p_multilingual_demo_id IN NUMBER,
p_multilingual_demo_code IN VARCHAR2,
p_description IN VARCHAR2 DEFAULT NULL);
/*=====================================
** PROCEDURE: update_row()
**=====================================*/
PROCEDURE update_row(p_multilingual_demo_id IN NUMBER,
p_multilingual_demo_code IN VARCHAR2,
p_description IN VARCHAR2,
p_last_update_date IN DATE,
p_last_updated_by IN NUMBER,
p_last_update_login IN NUMBER);
/*=====================================
** PROCEDURE: delete_row()
**=====================================*/
PROCEDURE delete_row(p_multilingual_demo_id IN NUMBER);
/*=====================================
** PROCEDURE: add_language()
**=====================================*/
PROCEDURE add_language;
END cux_multilingual_demo_pkg;
/
CREATE OR REPLACE PACKAGE BODY cux_multilingual_demo_pkg AS
/*=====================================
** PROCEDURE: insert_row()
**=====================================*/
PROCEDURE insert_row(x_row_id IN OUT VARCHAR2,
x_multilingual_demo_id IN OUT NUMBER,
p_multilingual_demo_code IN VARCHAR2,
p_description IN VARCHAR2,
p_creation_date IN DATE,
p_created_by IN NUMBER,
p_last_update_date IN DATE,
p_last_updated_by IN NUMBER,
p_last_update_login IN NUMBER)
IS
CURSOR c IS
SELECT ROWID
FROM cux_multilingual_demo_b
WHERE multilingual_demo_id = x_multilingual_demo_id;
BEGIN
IF x_multilingual_demo_id IS NULL THEN
SELECT cux_multilingual_demo_b_s.NEXTVAL
INTO x_multilingual_demo_id
FROM dual;
END IF;
INSERT INTO cux_multilingual_demo_b
(multilingual_demo_id,
multilingual_demo_code,
created_by,
creation_date,
last_updated_by,
last_update_date,
last_update_login)
VALUES
(x_multilingual_demo_id,
p_multilingual_demo_code,
p_created_by,
p_creation_date,
p_last_updated_by,
p_last_update_date,
p_last_update_login);
--Process TL table here
INSERT INTO cux_multilingual_demo_tl
(multilingual_demo_id,
description,
created_by,
creation_date,
last_updated_by,
last_update_date,
last_update_login,
LANGUAGE,
source_lang)
SELECT x_multilingual_demo_id,
p_description,
p_created_by,
p_creation_date,
p_last_updated_by,
p_last_update_date,
p_last_update_login,
l.language_code,
userenv('LANG')
FROM fnd_languages l
WHERE l.installed_flag IN ('I', 'B')
AND NOT EXISTS
(SELECT NULL
FROM cux_multilingual_demo_tl t
WHERE t.multilingual_demo_id = x_multilingual_demo_id
AND t.LANGUAGE = l.language_code);
OPEN c;
FETCH c
INTO x_row_id;
IF (c%NOTFOUND) THEN
CLOSE c;
RAISE no_data_found;
END IF;
CLOSE c;
END insert_row;
/*=====================================
** PROCEDURE: lock_row()
**=====================================*/
PROCEDURE lock_row(p_multilingual_demo_id IN NUMBER,
p_multilingual_demo_code IN VARCHAR2,
p_description IN VARCHAR2 DEFAULT NULL)
IS
CURSOR c IS
SELECT multilingual_demo_id, multilingual_demo_code
FROM cux_multilingual_demo_b
WHERE multilingual_demo_id = p_multilingual_demo_id
FOR UPDATE OF multilingual_demo_id NOWAIT;
rec c%ROWTYPE;
--Process TL table here
CURSOR c1 IS
SELECT description
FROM cux_multilingual_demo_tl
WHERE multilingual_demo_id = p_multilingual_demo_id
AND LANGUAGE = userenv('LANG')
FOR UPDATE OF multilingual_demo_id NOWAIT;
tlrec c1%ROWTYPE;
BEGIN
OPEN c;
FETCH c
INTO rec;
IF (c%NOTFOUND) THEN
CLOSE c;
fnd_message.set_name('FND', 'FORM_RECORD_DELETED');
app_exception.raise_exception;
END IF;
CLOSE c;
IF ((rec.multilingual_demo_id = p_multilingual_demo_id) AND
((rec.multilingual_demo_code = p_multilingual_demo_code) OR
((rec.multilingual_demo_code IS NULL) AND
(p_multilingual_demo_code IS NULL)))) THEN
NULL;
ELSE
fnd_message.set_name('FND', 'FORM_RECORD_CHANGED');
app_exception.raise_exception;
END IF;
--Process TL table here
OPEN c1;
FETCH c1
INTO tlrec;
IF (c1%NOTFOUND) THEN
CLOSE c1;
fnd_message.set_name('FND', 'FORM_RECORD_DELETED');
app_exception.raise_exception;
END IF;
CLOSE c1;
IF (((tlrec.description = p_description) OR
((tlrec.description IS NULL) AND (p_description IS NULL)))) THEN
NULL;
ELSE
fnd_message.set_name('FND', 'FORM_RECORD_CHANGED');
app_exception.raise_exception;
END IF;
END lock_row;
/*=====================================
** PROCEDURE: update_row()
**=====================================*/
PROCEDURE update_row(p_multilingual_demo_id IN NUMBER,
p_multilingual_demo_code IN VARCHAR2,
p_description IN VARCHAR2,
p_last_update_date IN DATE,
p_last_updated_by IN NUMBER,
p_last_update_login IN NUMBER)
IS
BEGIN
UPDATE cux_multilingual_demo_b
SET multilingual_demo_id = p_multilingual_demo_id,
multilingual_demo_code = p_multilingual_demo_code,
last_update_date = p_last_update_date,
last_updated_by = p_last_updated_by,
last_update_login = p_last_update_login
WHERE multilingual_demo_id = p_multilingual_demo_id;
IF (SQL%NOTFOUND) THEN
RAISE no_data_found;
END IF;
--Process TL table here
UPDATE cux_multilingual_demo_tl
SET description = p_description,
source_lang = userenv('LANG'),
last_update_date = p_last_update_date,
last_updated_by = p_last_updated_by,
last_update_login = p_last_update_login
WHERE multilingual_demo_id = p_multilingual_demo_id
AND userenv('LANG') IN (LANGUAGE, source_lang);
IF (SQL%NOTFOUND) THEN
RAISE no_data_found;
END IF;
END update_row;
/*=====================================
** PROCEDURE: delete_row()
**=====================================*/
PROCEDURE delete_row(p_multilingual_demo_id IN NUMBER)
IS
BEGIN
DELETE FROM cux_multilingual_demo_b
WHERE multilingual_demo_id = p_multilingual_demo_id;
IF (SQL%NOTFOUND) THEN
RAISE no_data_found;
END IF;
--Process TL table here
DELETE FROM cux_multilingual_demo_tl
WHERE multilingual_demo_id = p_multilingual_demo_id;
IF (SQL%NOTFOUND) THEN
RAISE no_data_found;
END IF;
END delete_row;
/*=====================================
** PROCEDURE: add_language()
**=====================================*/
PROCEDURE add_language IS
BEGIN
INSERT INTO cux_multilingual_demo_tl
(multilingual_demo_id,
description,
created_by,
creation_date,
last_updated_by,
last_update_date,
last_update_login,
LANGUAGE,
source_lang)
SELECT b.multilingual_demo_id,
b.description,
b.created_by,
b.creation_date,
b.last_updated_by,
b.last_update_date,
b.last_update_login,
l.language_code,
b.source_lang
FROM cux_multilingual_demo_tl b, fnd_languages l
WHERE l.installed_flag IN ('I', 'B')
AND b.LANGUAGE = userenv('LANG')
AND NOT EXISTS
(SELECT NULL
FROM cux_multilingual_demo_tl t
WHERE t.multilingual_demo_id = b.multilingual_demo_id
AND t.LANGUAGE = l.language_code);
END add_language;
END cux_multilingual_demo_pkg;
Form对象的要求:2个Form级触发器
1、 PRE-BLOCK,Form级灰掉“小地球”,块级根据需要启用:
app_special.disable('TRANSLATE');
2、 POST-FORMS-COMMIT。
FND_MULTILINGUAL.SAVE;
Form对象的要求:5个Block级触发器
1、 PRE-BLOCK,Form级灰掉“小地球”,块级根据需要启用:
app_special.enable('TRANSLATE');
2、 POST-INSERT,和具体的开发无关:
FND_MULTILINGUAL.KEY;
3、 WHEN-CLEAR-BLOCK,和具体的开发无关:
FND_MULTILINGUAL.FREE_BLOCK;
4、 WHEN-REMOVE-RECORD,和具体的开发无关:
FND_MULTILINGUAL.FREE_RECORD;
5、 TRANSLATIONS,关键代码,和具体的开发相关,下面同时摘录过程参数说明:
-- EDIT- called in WHEN-BUTTON-PRESSED
-- block- name of translated block
-- key_columns- names of primary key columns
-- e.g. 'key1, key2'
-- trans_columns- names of translated columns
-- e.g. 'trans1, trans2'
-- header_columns- the user-visible names of the
-- translated columns. Prefixing
-- with a '*APP:' indicates message
-- dictionary translation, where
-- APP is the application of the msg.
-- e.g. '*FND:ML_KEY1, *FND:ML_KEY2'
-- table_name - name of base table
-- (defaults to <block>)
-- tltable_name - name of translations table
-- (defaults to <block>_TL)
-- validation_callback - optional stored procedure to validate
-- translations. Callback API must be in the form:
-- VALIDATE_CALLBACK(<key_columns> IN VARCHAR2,
-- LANGUAGE IN VARCHAR2,
-- <trans_columns> IN VARCHAR2);
--
--procedure EDIT(block IN VARCHAR2,
-- key_columns IN VARCHAR2,
-- trans_columns IN VARCHAR2,
-- header_columns IN VARCHAR2,
-- table_name IN VARCHAR2 default null,
-- tltable_name IN VARCHAR2 default null,
-- validation_callback IN VARCHAR2 default null)
FND_MULTILINGUAL.EDIT('MULTILINGUAL_DEMO', 'MULTILINGUAL_DEMO_ID',
'DESCRIPTION', '*FND:ML_DESCRIPTION', 'CUX_MULTILINGUAL_DEMO_B', 'CUX_MULTILINGUAL_DEMO_TL');

浙公网安备 33010602011771号