🔧 مشکل عملکرد در Tree ناحیهی Oracle APEX
در Oracle APEX، استفاده از ناحیه Tree ممکنه باعث کندی بشه. دلیلش اینه که:
-
ناحیه Tree همه رکوردهایی که از کوئری برمیگردن رو یکجا بارگذاری میکنه.
-
ولی توی نواحی دیگه مثل گزارش تعاملی یا گرید تعاملی، فقط اطلاعات همون صفحه لود میشن و بقیه دادهها وقتی نیاز باشه (مثلاً با رفتن به صفحه بعد) بارگذاری میشن.
📉 نتیجه:
اگه دادهها زیاد باشن، لود شدن ناحیه Tree ممکنه چند ثانیه طول بکشه و صفحه دیر باز بشه.
🕒 راهحل اول: Lazy Loading
با فعال کردن گزینهی Lazy Loaded، اول صفحه نمایش داده میشه و بعد ناحیه Tree بارگذاری میشه.
اما حتی با این روش هم همه نودهای درخت یکباره بارگذاری میشن. پس باز هم با تأخیر روبهرو هستیم، مخصوصاً اگه درخت خیلی بزرگ باشه.
✅ راهحل بهتر: بارگذاری تدریجی نودها
در این روش:
-
فقط نودهای ریشه (Root) موقع لود صفحه بارگذاری میشن.
-
وقتی کاربر روی یک نود کلیک میکنه تا اون رو باز کنه، فقط فرزندان همون نود بهصورت داینامیک (پویا) از سرور دریافت میشن.
📌 ولی توی نواحی دیگه مثل گزارش تعاملی یا گرید تعاملی، فقط اطلاعات همون صفحه لود میشن و بقیه دادهها وقتی نیاز باشه (مثلاً با رفتن به صفحه بعد) بارگذاری میشن.
مراحل پیادهسازی
در این مثال از جدول EMP برای پیادهسازی بارگذاری تنبل (Lazy Load) استفاده شده.
کوئری قسمت Tree
در قسمت Region Source ناحیه Tree، فقط نودهای ریشه باید بازیابی بشن.
ستونهای کلیدی موردنیاز:
- VAL
- برای ارسال پارامترها به Callback آژاکس استفاده میشه. در اینجا مقدار کلید اصلی جدول (EMPNO) به همراه یک چکسام برای امنیت ارسال میشه.
- LVL
- مقدار ۱ به اون اختصاص داده میشه تا به عنوان نود ریشه مشخص بشه.
- LEAF_STATUS
- مقدار ۱ بهش داده میشه تا مشخص کنه این نود برگ نیست (یعنی ممکنه فرزند داشته باشه).
select ename as display
, ‘fa-user ‘ as css_class
, ‘javascript:alert(“‘ || ename || ‘”);’ as link
, empno ||’:’|| APEX_UTIL.get_hash(apex_t_varchar2(empno, ‘PXXX_EMP_TREE’)) as val
, mgr as parent_id
, 1 lvl — makes it root
, 1 leaf_status — tells apex that this node is not a leaf
from emp
where mgr is null
۲. تنظیمات ناحیه Tree
اطمینان حاصل بشه که خصوصیات زیر برای ناحیه Tree بهدرستی تنظیم شدن:
- Node Value Column:
مقدار ستون VAL
- Hierarchy (Not Computed)
- روی «Not Computed» تنظیم بشه
- Node Status Column (LEAF_STATUS)
- Hierarchy Level Column (LVL)

۳. تعریف TreeNodeAdapter
برای کنترل نحوهی بارگذاری نودها توسط ویجت TreeView، باید از یک TreeNodeAdapter سفارشی استفاده بشه.
تابع childCount
این تابع، تعداد فرزندان هر نود رو مشخص میکنه.اگه مقدار برگشتی null باشه، تابع fetchChildNodes اجرا میشه.
adapter.childCount = function(pNode) {
if(pNode.children && pNode.children != null) {
return pNode.children.length;
}
else
{
return null;
}
}
تابع fetchChildNodes
این تابع اصلی، فرزندان یک نود رو بهصورت داینامیک از طریق AJAX دریافت میکنه.
.
adapter.fetchChildNodes = function(pNode, pCallback) {
va r nodeInfo = pNode.id.split(“:”);
v ar empno = nodeInfo[0];
var checksum = nodeInfo[1];
apex.server.process( // ajax to get children of a node
“GET_TREE_CHILDREN”
, {
x01: empno
, x02: checksum
}
, {
success: function(data){
var children = data.children;
pNode.children = [];
for(let i = 0; i < children.length; i++) {
var childJson = children[i];
var childNode = {
id: childJson.id
, label: childJson.label
, icon: childJson.icon
, link: childJson.link
}
childNode.children = [{
label: “Fetching infos”
, icon: “fa fa-refresh fa-anim-spin”
}] // placeholder so that we have the option to expand
pNode.children.push(childNode);
}
pCallback(children.length); // return result to treeview widget
},
error: function( jqXHR, textStatus, errorThrown ) {
pCallback(false);
}
}
);
}
تابع setExpanded
چون APEX فقط یکبار تابع fetchChildNodes رو اجرا میکنه، باید وقتی یک نود (گره) رو باز میکنیم، خودمون دوباره این تابع رو صدا بزنیم.
adapter.setExpanded = function(pTreeId, pNode, pExpanded) {
if(pExpanded && pNode.lazyLoaded != true) { // we fetch async on expansion and it hasnt been loaded yet
adapter.fetchChildNodes(pNode, function(pData){
// get the JQuery object reference of the node for TreeView function calls
var newNode = apex.region(options.regionStaticId).widget().treeView(“find”, {
depth: -1
, match: function(n) {return n.id == pNode.id;}
});
pNode.lazyLoaded = true; // flag to only load data once
// we have to refresh to see the new children nodes
apex.region(options.regionStaticId).widget().treeView(“refresh”, newNode);
newNode = apex.region(options.regionStaticId).widget().treeView(“find”, {
depth: -1
, match: function(n) {return n.id == pNode.id;}
});
// Refresh causes the node to collapse so we expand the node again
// have to find node again because refreshes changes the jquery object
apex.region(options.regionStaticId).widget().treeView(“expand”, newNode);
۴. تعریف Callback AJAX
این پروسه AJAX دادههای فرزندان نود فعلی رو به صورت JSON تولید میکنه.
declare l_empno apex_application.g_x01 % type := apex_application.g_x01;
l_checksum apex_application.g_x02 % type := apex_application.g_x02;
l_calculated_checksum varchar2(4000);
l_node_json JSON_OBJECT_T;
begin l_calculated_checksum := APEX_UTIL.get_hash(
apex_t_varchar2(l_empno, ‘PXXX_EMP_TREE’)
);
if l_calculated_checksum != l_checksum then — security check
return;
end if;
APEX_JSON.open_object();
APEX_JSON.open_array(‘children’);
for node_child in (
select
ename as display ,
‘fa-user’ as icon_class ,
‘javascript:alert(“‘ || ename || ‘”);’ as link ,
empno || ‘:’ || APEX_UTIL.get_hash(
apex_t_varchar2(empno, ‘PXXX_EMP_TREE’)
) as val
from
emp e1 where mgr = l_empno
) loop APEX_JSON.open_object();
APEX_JSON.write(‘label’, node_child.display);
APEX_JSON.write(‘icon’, node_child.icon_class);
APEX_JSON.write(‘link’, node_child.link);
APEX_JSON.write(‘id’, node_child.val);
APEX_JSON.close_object();
end loop;
APEX_JSON.close_array();
–children
APEX_JSON.close_object();
end;
قابلیتهای قابل توسعه
نمایش شرطی گزینهی گسترش:
با کمی تغییر در منطق، فقط زمانی نود Dummy اضافه بشه که نود واقعاً فرزند داشته باشه.
استفاده از کلیدهای مرکب (Compound Keys):
اگه کلیدها از چند ستون بیان، میتونیم اونها رو با “:” به هم بچسبونیم یا به شکل JSON در ستون VAL ذخیره کنیم.
