Oracle APEX 5.1层级联动动态选择列表实现技术问询
在Oracle APEX 5.1中实现动态层级联动选择列表
嘿,我在APEX 5.1里折腾过好多次这种多层级联动的需求,给你分享两种最实用的实现思路——分固定层级数和动态层级数两种场景,你可以根据自己的实际情况来选:
场景1:固定层级数(比如3级联动)
假设你要做类似「行业大类→细分品类→具体产品」这种固定3级的联动,数据库里有个关联表(比如CATEGORIES,字段是ID, PARENT_ID, CATEGORY_NAME),咱们一步步来:
第一步:创建基础选择列表
先做第一个顶级列表(比如行业大类):
- 类型选动态选择列表
- 写SQL的时候只捞顶级分类:
SELECT category_name AS display_value, id AS return_value FROM categories WHERE parent_id IS NULL -- 筛选顶级分类 ORDER BY category_name - 给它设个好记的静态ID,比如
P1_TOP_CATEGORY,后面动态动作要用到它
接着做第二个列表(细分品类),初始状态设为隐藏:
- 类型同样是动态选择列表
- SQL要绑定第一个列表的值:
SELECT category_name AS display_value, id AS return_value FROM categories WHERE parent_id = :P1_TOP_CATEGORY -- 绑定上级选择的ID ORDER BY category_name - 静态ID设为
P1_SUB_CATEGORY - 加个服务器端条件:
P1_TOP_CATEGORY IS NOT NULL,确保只有选了上级才显示这个列表
第三个列表(具体产品)同理,初始隐藏,SQL绑定
P1_SUB_CATEGORY,静态IDP1_PRODUCT
第二步:配置动态动作实现联动
给
P1_TOP_CATEGORY加个on change动态动作:- 触发条件选「当值改变时」
- 先加一个刷新动作,目标选
P1_SUB_CATEGORY(这样它的SQL会重新执行,加载对应子分类) - 再加一个显示动作,目标也是
P1_SUB_CATEGORY(把之前隐藏的列表显示出来)
给
P1_SUB_CATEGORY也加个on change动态动作:- 触发条件同样是「当值改变时」
- 动作选刷新+显示,目标换成
P1_PRODUCT就行
场景2:动态层级数(层级数不固定)
如果你的层级是动态的——比如有的分类只有2级,有的能到5级,那就要用动态动作动态生成选择列表了,思路是用AJAX递归加载下一级:
第一步:准备基础容器和顶级列表
- 先创建第一个顶级选择列表,静态ID
P1_ROOT_CAT,SQL和场景1的顶级列表一样 - 拖一个「静态内容」区域当容器,给它设静态ID
P1_CAT_CONTAINER,用来放后续动态生成的列表
第二步:给顶级列表加动态动作
给P1_ROOT_CAT加on change动态动作,选「执行PL/SQL代码」,代码逻辑是清空容器、生成第一级子列表,还要绑定on change事件:
DECLARE l_child_count NUMBER; l_html VARCHAR2(32767); l_parent_id NUMBER := :P1_ROOT_CAT; l_level NUMBER := 1; BEGIN -- 先清空容器里的旧列表 apex_javascript.add_onload_code('$("#P1_CAT_CONTAINER").empty();'); -- 先查有没有子分类,没有就直接退出 SELECT COUNT(*) INTO l_child_count FROM categories WHERE parent_id = l_parent_id; EXIT WHEN l_child_count = 0; l_level := l_level + 1; -- 生成子列表的HTML l_html := '<label for="P1_CAT_' || l_level || '">第' || l_level || '级分类</label>' || '<select id="P1_CAT_' || l_level || '" name="P1_CAT_' || l_level || '" class="apex-item-select">' || '<option value="">请选择</option>'; -- 填充子分类选项,记得转义HTML避免XSS FOR rec IN (SELECT category_name, id FROM categories WHERE parent_id = l_parent_id ORDER BY category_name) LOOP l_html := l_html || '<option value="' || rec.id || '">' || apex_escape.html(rec.category_name) || '</option>'; END LOOP; l_html := l_html || '</select>'; -- 把HTML输出到页面 apex_web_service.print(l_html); -- 给新生成的列表加on change事件,用APEX的AJAX请求加载下一级 apex_javascript.add_onload_code( '$("#P1_CAT_' || l_level || '").change(function() {' || ' var parentId = $(this).val();' || ' if (parentId) {' || ' apex.server.process("LOAD_NEXT_CAT_LEVEL", {x01: parentId, x02: ' || l_level || '}, {' || ' success: function(data) {' || ' // 先删掉之前的下一级列表,避免重复' || ' $("#P1_CAT_CONTAINER").find("#P1_CAT_' || (l_level + 1) || '").remove();' || ' $("#P1_CAT_CONTAINER").append(data);' || ' }' || ' });' || ' } else {' || ' // 清空下一级列表' || ' $("#P1_CAT_CONTAINER").find("#P1_CAT_' || (l_level + 1) || '").remove();' || ' }' || '});' ); END;
第三步:创建AJAX进程处理后续层级
创建一个名为LOAD_NEXT_CAT_LEVEL的AJAX进程,用来处理用户选择后加载下一级的请求:
DECLARE l_parent_id NUMBER := apex_application.g_x01; -- 上级ID l_current_level NUMBER := apex_application.g_x02; -- 当前层级 l_next_level NUMBER := l_current_level + 1; l_html VARCHAR2(32767); l_child_count NUMBER; BEGIN -- 先查有没有子分类 SELECT COUNT(*) INTO l_child_count FROM categories WHERE parent_id = l_parent_id; IF l_child_count = 0 THEN apex_web_service.print(''); RETURN; END IF; -- 生成下一级列表的HTML l_html := '<label for="P1_CAT_' || l_next_level || '">第' || l_next_level || '级分类</label>' || '<select id="P1_CAT_' || l_next_level || '" name="P1_CAT_' || l_next_level || '" class="apex-item-select">' || '<option value="">请选择</option>'; FOR rec IN (SELECT category_name, id FROM categories WHERE parent_id = l_parent_id ORDER BY category_name) LOOP l_html := l_html || '<option value="' || rec.id || '">' || apex_escape.html(rec.category_name) || '</option>'; END LOOP; l_html := l_html || '</select>'; -- 给新列表加on change事件,递归加载下一级 apex_web_service.print(l_html || '<script>' || '$("#P1_CAT_' || l_next_level || '").change(function() {' || ' var parentId = $(this).val();' || ' if (parentId) {' || ' apex.server.process("LOAD_NEXT_CAT_LEVEL", {x01: parentId, x02: ' || l_next_level || '}, {' || ' success: function(data) {' || ' $("#P1_CAT_CONTAINER").find("#P1_CAT_' || (l_next_level + 1) || '").remove();' || ' $("#P1_CAT_CONTAINER").append(data);' || ' }' || ' });' || ' } else {' || ' $("#P1_CAT_CONTAINER").find("#P1_CAT_' || (l_next_level + 1) || '").remove();' || ' }' || '});' || '</script>' ); END;
一些关键注意事项
- 绑定变量要正确用:所有动态列表的SQL一定要用绑定变量(比如
:P1_TOP_CATEGORY),不仅能防SQL注入,还能提升查询性能 - 静态ID不能重复:不管是静态创建还是动态生成的列表,静态ID必须唯一,不然动态动作会找不到目标
- APEX 5.1兼容性:APEX 5.1里的AJAX请求要用
apex.server.process,别用旧的htmldb_Get,不然会出问题 - 用户体验优化:下级列表初始隐藏,选了上级再显示,避免页面乱糟糟的;如果用户清空上级选择,记得把下级列表也隐藏或者清空
内容的提问来源于stack exchange,提问作者Madona
相关产品推荐
相关产品推荐

