create or replace package body htmltree as /***************************************************************************************** Purpose HTML Tree Navigator Usage Remarks required GIFs Revision history When Who Construct Revision What ------------------------------------------------------------------------------------------ $REVISION_HISTORY$ 16-mar-2001 Lucas Jellema 3.7 Filter carriage returns from Display labels of nodes 09-mar-2001 Lucas Jellema 3.6 - take out hardcode references of /odwa_images/ 06-mar-2001 Lucas Jellema 3.5 - inflate bottom node does not result in duplicate nodes anymore 09-feb-2001 Lucas Jellema 3.4 - style definition corrected: double colon changed in single colon 26-jan-2001 Lucas Jellema 3.3 fixed error: outcommented PL/SQL Code corrupted JavaScript code 26-jan-2001 Lucas Jellema 3.2 - do something about hard-coded image path '||g_image_path||' : added a call to ODWAPREF in package body initialization this is NOT proper for a true reusable component; however, to make the release date of ODWA 6i, is it pragmatical. We need to find a more generic solution: HTMLTREE should not be dependent on ODWA objects 26-jan-2001 Lucas Jellema 3.1 - restore js_refresh_tree - restore the reload node functions 25-jan-2001 Lucas Jellema 3.0 - add STYLE to tree frame - display reload node image for selected node when appropriate 10-nov-2000 Lucas Jellema 2.6 removed all external dependencies (except HTP and HTF) 08-nov-2000 Lucas Jellema 2.5 reloadNode option added (to refresh an inflateable node) hideNode option added (to hide a node/branch to save memory/speed up the tree) 15-sep-2000 Lucas Jellema 2.4 procedure draw_tree: the first node should have the firstopennode image instead of the openFolderImage 28-aug-2000 Lucas Jellema 2.3 New procedure draw_tree (limited) Support for Server Side tree-construction 11-aug-2000 Lucas Jellema 2.2 Support refreshTree() Fixed small problem with inflation giving too many folderTree members 10-aug-2000 Lucas Jellema 2.1 - made generic; no hard-coded frame waSelector in write_inflate_code 27-jul-2000 Lucas Jellema 2.0 many many changes to make the code more generic, robust and efficient added some comments function getParentNodeAbsolute and getParentNodeRelative better support for node inflation To Do: ---------- - self contained (no outgoing dependencies) - more comments in code - strip out things we do not use at all - allow tree-deflation (return to bare tree to clear browser memory - get/set marks/targets - place nodesTree in FRAMESET (window level) instead of one of the frames? 24-jul-2000 Lucas Jellema 1.9 - made generic; no hard-coded frame names - support for node inflation 10-jun-2000 Lucas Jellema 1.8 changed the set define off to set define on at the end of this script (it caused installation to fail) 09-jun-2000 Lucas Jellema 1.7 writeNode: highlighted node is made BOLD; no longer do we use the yellow highlighting 08-jun-2000 Lucas Jellema 1.6 Alt text for collapse and expand Use index properly for 'click on element' and set selectedIdx 07-jun-2000 Lucas Jellema 1.5 Collapse/Expand did not work yet 07-jun-2000 Lucas Jellema 1.4 Better graphics, less HTML tables (just one table), support for LastLeaf Support for Collapse/Expand All Support for highlighting the selected element Questions: - is collapse/expand useful - is highlighting not too bad for performance 06-jun-2000 Lucas Jellema 1.3 Be smart about opening and closing nodes (improve performance) 06-jun-2000 Lucas Jellema 1.2 Use function showPalette in onClick event instead of URL in name Set SelectedIdx and show selected element in yellow (but only after the tree is redrawn for open or close folder) 06-jun-2000 Lucas Jellema 1.1 Use new graphics for Node and Leaf 02-jun-2000 Lucas Jellema 1.0 Creation based on WebDB Edit Module Screen (http://NLG0552.nl.oracle.com/WebDB/WEBDB22.wwv_builder.show?p_module_id=1007430466&p_version=2&p_save_version=2&p_mode=EDIT) 2.1 - made generic; no hard-coded frame waSelector in write_inflate_code 1.9 - made generic; no hard-coded frame names - support for node inflation *****************************************************************************************/ -- -- private constants -- REVISION_LABEL constant varchar2(30) := '$x.y::3.7 $'; PACKAGE_NAME constant varchar2(30) := 'htmltree'; g_image_path varchar2(500); g_col_levels number(3) :=15; -- the maximum levels in the tree g_codeFrameName varchar2(200); -- the frame that contains the Tree JavaScript g_treeFrameName varchar2(200); -- name of the frame that contains the Tree (e.g. '||g_treeFrameName||') g_treeFramePath varchar2(200); -- the frame (full path) that contains the Tree (e.g. top.selector.'||g_treeFrameName||') g_xchangeFramePath varchar2(500); -- the full frame path to the frame that contains the dataXchange procedure type t_node_rec is record ( display_label varchar2(500) , additional varchar2(500) , node_level number(4) , has_children boolean , is_expanded boolean , last_leaf boolean , parent_index number(10) , next_sibling number(10) -- the next node or leaf at the same node_level; only required for nodes (elements with has_children = true) , value varchar2(500) , is_inflatable boolean , is_inflated boolean , classification varchar2(500) ); type t_node_tbl is table of t_node_rec index by binary_integer; g_node_tbl t_node_tbl; -- function return the value assigned to a certain TAG -- written in the following format: {TAG=value} or more generic: -- tag = value function get_tag_value ( p_string in varchar2 , p_tag in varchar2 , p_separator_open in varchar2 default '{' , p_separator_close in varchar2 default '}' ) return varchar2 is l_open_pos number(10):= instr ( replace(upper(p_string),' ','') , p_separator_open||upper(p_tag)||'=' ); l_value_start number(10); l_end_pos number(10); begin if l_open_pos > 0 then l_value_start:= instr( replace(p_string,' ',''), '=', l_open_pos)+1; l_end_pos := instr( p_string, p_separator_close, l_value_start+1); return substr( p_string, l_value_start, l_end_pos - l_value_start); else return ''; end if; end; -- get_tag_value function nbsp ( p_times in number default 1 ) return varchar2 is l_string varchar2(4000); begin for i in 1..p_times loop l_string:= l_string||chr(38)||'nbsp;'; end loop; return l_string; end; -- nbsp function ifThenElse ( p_if in boolean , p_then in varchar2 , p_else in varchar2 default null ) return varchar2 is begin if p_if then return p_then; else return p_else; end if; end; -- ifThenElse procedure reset_tbl is begin g_node_tbl.delete; end; procedure add_node ( p_display_label in varchar2 , p_node_level in number -- start at 1 (root-level) , p_has_children in boolean , p_is_expanded in boolean default false , p_value in varchar2 , p_additional_label in varchar2 default '' , p_is_inflatable in boolean default false , p_classification in varchar2 default '' ) is l_ctr number(10):= g_node_tbl.count+1; function find_parent ( p_node_level in number ) return number is begin -- parent is the nearest node earlier in the tree with a higher level if p_node_level = 1 then return null; -- l_ctr; -- when level 1, the node is its own parent end if; for i in 1..l_ctr-1 loop if g_node_tbl(l_ctr-i).node_level < p_node_level then return l_ctr-i; end if; end loop; return null; end; -- find_parent begin g_node_tbl(l_ctr).display_label := replace(replace(replace( p_display_label, '"','\"'),chr(13),';'),chr(10),''); g_node_tbl(l_ctr).node_level := p_node_level; g_node_tbl(l_ctr).has_children := p_is_inflatable or p_has_children; g_node_tbl(l_ctr).is_expanded := p_is_expanded; g_node_tbl(l_ctr).is_inflatable := p_is_inflatable; g_node_tbl(l_ctr).parent_index := find_parent( p_node_level); if g_node_tbl(l_ctr).parent_index is not null then g_node_tbl(g_node_tbl(l_ctr).parent_index).has_children := true; end if; g_node_tbl(l_ctr).value := p_value; g_node_tbl(l_ctr).additional := replace( p_additional_label, '"','\"'); g_node_tbl(l_ctr).classification := p_classification; end; -- add_node -- this procedure will walk through the tree table and set the has_children property -- where it is not set while in fact children DO exist procedure find_folder_nodes is l_last_node_level number(3):=999; begin -- this can be done much smarter and faster if g_node_tbl.count > 0 then for i in reverse 1..g_node_tbl.count loop if g_node_tbl(i).node_level < l_last_node_level then -- find the first node with a higher level and set its property has_children property for j in reverse 1..i loop if g_node_tbl(j).node_level < g_node_tbl(i).node_level then g_node_tbl(j).has_children:= true; exit; end if; end loop; end if; -- g_node_tbl(i).node_level > l_last_node_level l_last_node_level:= g_node_tbl(i).node_level; end loop; -- i in reverse 1..g_node_tbl.count end if; -- g_node_tbl.count > 0 end; -- find_folder_nodes function get_number_of_nodes return number is begin return g_node_tbl.count; end; -- this procedure will write a JavaScript function that can be called to refresh the indicated node -- in the tree; if the node itself cannot be refreshed, the next higher up that can will be refreshed procedure js_refreshTree ( p_refresh_tree_level in number default null ) is begin if p_refresh_tree_level is null then htp.p (' '); else htp.p (' '); end if; -- p_refresh_tree_level is null end; -- js_refreshTree procedure draw_tree is type t_last_folder is table of boolean index by binary_integer; l_last_folder t_last_folder; openFolderImage varchar2(200):= g_image_path||'nodeop2.jpg'; firstOpenFolderImage varchar2(200):= g_image_path||'nodeop1.jpg'; closedFolderImage varchar2(200):= g_image_path||'nodecl3.jpg'; firstClosedFolderImage varchar2(200):= g_image_path||'nodecl1.jpg'; leafImage varchar2(200):= g_image_path||'leaf5.jpg'; lastLeafImage varchar2(200):= g_image_path||'leaf7a.jpg'; folderLineImage varchar2(200):= g_image_path||'leaf8.jpg'; expandAllImage varchar2(200):= g_image_path||'expall.jpg'; collapseAllImage varchar2(200):= g_image_path||'collall.jpg'; begin -- initialize l_last_folder for i in 1..g_col_levels loop l_last_folder(i):= false; end loop; htp.p( ''); for i in 1..g_node_tbl.count loop htp.p(''); -- node is last if g_node_tbl( g_node_tbl(i).next_sibling).node_level < g_node_tbl(i).node_Level for l in 2..g_node_tbl(i).node_level loop if l_last_folder(l-1) -- if the current folder at level l-1 is the last, no line drawn then htp.p ( '' ); else htp.p ( '' ); end if; -- l_lastFolder(l) end loop; -- l in 1..g_node_tbl(i).node_level htp.p ( ''); htp.p ( ''); htp.p(''); end loop; -- i in 1..g_node_tbl.count htp.p( '
' ||nbsp(1) ||' ' ||'' ||'' ); if g_node_tbl(i).has_children -- IS FOLDER then -- the current folder is the last folder at this level -- if one of the following conditions is met l_last_Folder(g_node_tbl(i).node_level) := g_node_tbl(i).last_leaf or g_node_tbl(i).next_sibling is null --last_leaf -- the last one; that means: no line needs to be drawn at that level or g_node_tbl( g_node_tbl(i).next_sibling).node_level < g_node_tbl(i).node_Level ; if not g_node_tbl(i).is_expanded -- foldersNode is not opened; that means all its leafs and sub-nodes are invisible -- and can be bypassed; lets jump to the next sibling! then if g_node_tbl(i).next_Sibling is not null then null; -- gotoCtr= treeNodeConvertor[foldersNode.nextSibling]; //nextSibling refers to nodeIndex, treeNodeConvertor holds position in nodesTree-array for every nodeIndex else null; -- // if no nextSibling and level = 0, then jump to end of list --if (g_node_tbl(i).last_leaf) // will always be true I think -- gotoCtr = nodesTree.length+1; end if; -- g_node_tbl(i).nextSibling is not null end if; -- if not g_node_tbl(i).is_expanded -- for each level, a column is written (or, if the columns are empty anyway, one column with colspan=level is written) if g_node_tbl(i).is_expanded then htp.p ( '' ); else htp.p ( '' ); end if; -- is_expanded else -- IS LEAF htp.p ( '' ); end if; -- g_node_tbl(i).has_children -- IS FOLDER htp.p('' ); htp.p ( '' ||replace( g_node_tbl(i).display_label, '\"','"') ||'' ); htp.p('
'); end; -- draw_tree -- write the tree-initialization statements (appendChild) for elements in g_node_tbl -- should be preceeded by one or more calls to add_node procedure populate_tree ( p_start_idx in number default 0 -- where in nodesTree array; 0 means at the start , p_node_index_offset in number default 0 -- node index offset , p_frame_locator in varchar2 default null -- if statements are included in other frame than which contains nodesTree and appendChild, the frame locator needs to be provided ) is type t_sibling_rec is record ( sibling_index number(10) ); -- 1.3 type t_sibling_tbl is table of t_sibling_rec index by binary_integer ; l_sibling_tbl t_sibling_tbl; begin -- 1.3 -- first determine the value of g_node_tbl.next_sibling for all nodes (has_children = true) htp.p(' //function appendChild(index, name, level, isFolder, isOpen // , value, additional, nextSibling, lastLeaf, inflatable, inflated )'); for i in reverse 1..g_node_tbl.count loop -- now if has_children, find the the next sibling or higher level (level >= own level) --if g_node_tbl(i).has_children -- we really only need siblings for Nodes (not for leaf elements) --then -- find the index for the next node/leaf at the same or higher level for j in reverse 1..g_node_tbl(i).node_level loop if l_sibling_tbl.exists( j) then -- if the next sibling (remember: we treewalk in reverse order) at this node-level exists -- the we can assign the value of this sibling's index to the current node g_node_tbl(i).next_sibling := l_sibling_tbl( j ).sibling_index; exit; end if; end loop; --end if; -- all lower level sibling_indexes should be set to this one as well for j in g_node_tbl(i).node_level..g_col_levels loop l_sibling_tbl( j ).sibling_index:= i; -- the most recent next sibling at this level is i end loop; -- j if g_node_tbl(i).next_sibling is null -- should only happen to nodes at the end of the tree -- however, if this is just a subtree, the next sibling -- will be the next node in the supertree that this sub-tree -- is added to; only: we do not know the index -- value just after p_node_index_offset then g_node_tbl(i).last_leaf := true; else if g_node_tbl( g_node_tbl(i).next_sibling).node_level < g_node_tbl(i).node_level then g_node_tbl(i).last_leaf := true; end if; end if; htp.p ( p_frame_locator||'nodesTree['||to_char(p_start_idx + i-1)||'] = new '||p_frame_locator||'appendChild' ||' ("'||to_char(i+ p_node_index_offset)||'"' -- index ||', "'||g_node_tbl(i).display_label||'"' -- name ||', "'||to_char(g_node_tbl(i).node_level-1)||'"' -- level ||', "'||ifThenElse( g_node_tbl(i).has_children, '1','0')||'"' -- isFolder (has children) ||', "'||ifThenElse( (g_node_tbl(i).is_expanded), '1','0')||'"' -- isOpen ||', "'||g_node_tbl(i).value||'"' -- 1.2 ||', "'||g_node_tbl(i).additional||'"' -- 1.2 ||', "'||to_char(g_node_tbl(i).next_sibling + p_node_index_offset)||'"' -- 1.3 ||', "'||ifThenElse( g_node_tbl(i).last_leaf, '1','0')||'"' -- lastLeaf ||', "'||ifThenElse( g_node_tbl(i).is_inflatable, '1','0')||'"' -- InFlatable ||', "'||'0'||'"' -- never initially InFlated ||', "'||g_node_tbl(i).classification||'"' ||');' ); end loop; -- i in reverse 1..g_node_tbl.count loop end; -- populate_tree procedure write_inflate_code ( p_node_index in number , p_num_of_nodes in number , p_frame_locator in varchar2 -- path to the frame that contains the nodesTree , p_entry_point in number default null -- typically equal to p_node_index, although sometimes slightly beyond p_node_index ) is begin g_codeFrameName := p_frame_locator; -- from here on, this is generic again htp.htmlOpen; htp.headOpen; -- now write JS function htp.p (' '); htp.headClose; htp.bodyOpen(cattributes=>'onLoad="inflateNode()"'); htp.bodyClose; htp.htmlClose; end; -- write_inflate_code -- this procedure will write all JavaScript code required to draw and manage a navigator tree procedure js_tree ( p_codeFrameName in varchar2 -- e.g. treeLogicFrame , p_treeFrameName in varchar2 -- e.g. folderFrame , p_treeFramePath in varchar2 -- e.g. top.selector.folderFrame , p_xchangeFramePath in varchar2 -- e.g. top.appsBottom.rightSide.dataXchange , p_js_action in varchar2 -- the JavaScript function to be called when a node is selected , p_node_href in varchar2 -- the JavaScript Call to be made to p_js_action when a node is selected -- elId and idx can be referred to in the call; they the node value and index respectively -- name and additionalLabel are also available , p_inflate_request in varchar2 default null -- name of PL/SQL procedure to be called to provide inflation data; e.g. 'odwa_fol.inflate_Node' , p_inflate_parameters in varchar2 default null -- additional parameters to be passed upon inflation (if more than one parameter, they must be separated by Ampersands) -- e.g. 'p_session_id='||to_char(odwactxt.get_session_id)||chr(38)||'p_root_node="+getParentNodeAbsolute( idx, 0) -- the parameters always passed are: -- p_node_index, p_node_level, p_num_of_nodes, p_node_value, p_node_type, p_entry_point , p_refresh_tree in varchar2 default null -- what is the PL/SQL procedure (incl.parameters) to be called to refresh the entire tree -- if not null, this parameter will results in a function refreshTree() -- that performs: p_codeFrameName.location = p_refresh_tree ) is begin g_codeFrameName := p_codeFrameName; g_treeFrameName := p_treeFrameName; g_treeFramePath := p_treeFramePath; g_xchangeFramePath:= p_xchangeFramePath; htp.p (' '); end; -- js_tree procedure tree_frame is begin htp.htmlOpen; htp.bodyopen; -- ( cbackground=> '#c0c0c0'); htp.bodyclose; htp.htmlClose; end; begin --3.2 this line is NOT proper for a true reusable component; however, to make the release date of -- ODWA 6i, is it pragmatical. We need to find a more generic solution: HTMLTREE should not be dependent on ODWA objects g_image_path:= odwapref.get_value(p_preference_name => 'image_path'); end; --htmltree /