Thursday, November 1, 2012
Row Level Security
Oracle delivers a myriad of row level security options for their PeopleSoft HCM Product. But recently we came across a scenario that the product did not have a delivered configuration that meet our need. We needed to secure our Job Data for our regional HR users that would allow them to only see people within there region, but not in there own department. Here in lies the issue, we have departments that cross regions and if we granted them access to these departments via the department security tree (PPLJOB/001), then they would have access to a bunch of people they should not get to see. We tried then to use Job Location (PPLJOB/001), this eliminated the people outside their region, but it prevented us from securing their own department. What we need was Job Department & Location row level security.
So we added a new Security Access Type (PPLJOB/041)-Job-Deptid non Tree-Loc
Security Type SQL tab displays all the sql used by the SJT process to build the row level security. For the most part the default SQL was all I needed. However, I did not need to select from anything other than the Job record so I modified the following SQL Objects that were built.
SEC_041_FLDVAL
, JOB.EMPLID , JOB.POI_TYPE , JOB.PER_ORG , JOB.EMPL_RCD , JOB.BUSINESS_UNIT , JOB.SETID_DEPT , JOB.DEPTID , JOB.SETID_LOCATION , JOB.LOCATION , JOB.COMPANY , JOB.REG_REGION , JOB.APPT_TYPE , JOB.HR_STATUS , JOB.EMPL_STATUS , ' ', JOB.REPORTS_TO , JOB.SUPERVISOR_ID , ' ' , ' ', JOB.SETID_JOBCODE , JOB.JOBCODE ,JOB.MAIN_APPT_NUM_JPN, 0, ' ', JOB.PAY_SYSTEM_FLG, JOB.GP_PAYGROUP, JOB.PAYGROUP, ' ',' ',' ',JOB.EFFDT, JOB.EFFSEQ
SEC_041_FROM
PS_JOB
SEC_041_WHERE
JOB.EFFDT = ( SELECT MAX(EFFDT) FROM PS_JOB JOB2 WHERE JOB.EMPLID = JOB2.EMPLID AND JOB.EMPL_RCD = JOB2.EMPL_RCD AND JOB2.EFFDT <= %CurrentDateIn) AND JOB.EFFSEQ = ( SELECT MAX(EFFSEQ) FROM PS_JOB JOB3 WHERE JOB.EMPLID = JOB3.EMPLID AND JOB.EMPL_RCD = JOB3.EMPL_RCD AND JOB.EFFDT = JOB3.EFFDT)
SEC_041_FUTWHERE
PA.EMPLID = JOB.EMPLID AND PA.EMPL_RCD = JOB.EMPL_RCD AND JOB.EFFDT > %CurrentDateIn AND JOB.EFFSEQ = ( SELECT MAX(EFFSEQ) FROM PS_JOB JOB3 WHERE JOB.EMPLID = JOB3.EMPLID AND JOB.EMPL_RCD = JOB3.EMPL_RCD AND JOB.EFFDT = JOB3.EFFDT)
Now that our Security Access Type is built all we need to do in Navigate to the Security by Permission List Component and configure our Permission List's Access then run the Refresh Trans. SJT table and enjoy our new security access type.
For my next post I plan to demonstrate how to automate the assignment of row level security for this new access type.
Thursday, June 14, 2012
Dynamic IN clause for SQL Object for Oracle (DB >=10g)
We have a table that gets updated every 15 minutes with employee demographic
data and Job Related Data for our Identity Management Tool. Having this table
is very useful for reporting, since it has the latest information in it at all
times. One of the elements within the table is a list of Managed
departments. The list is simply a comma separated string of departments
that look like this: 2000980, 2510901, 9120980, 7280980, 1510980,
1280980, 1200980, 1150980, 7120980. The list is generated using our Department
Security Tree and if a user has access to higher node in the tree, then this
list is populated with all the departments that report to the higher
node. I thought it would be a useful field to utilize when creating
department related content for Pagelets within our portal. So I played
around with creating a pagelet using the Chart API in iScript and using the
managed department list where I manipulated the text to place quotes around
each department and passed it as a bind variable to a SQL object like this
below:
DEPTID IN (:1) (Where :1 = '2000980', '2510901', '9120980', '7280980', '1510980', '1280980', '1200980', '1150980', '7120980')
The SQL did not return any values, nor did it error. I had a SQL trace set and it looked correct, but I assumed the bind functionality was appending quotes to my string literal. So I attempted to remove the first and last quotes on my string of departments and passed it to the SQL object.
DEPTID IN (:1) (Where :1 = 2000980', '2510901', '9120980', '7280980', '1510980', '1280980', '1200980', '1150980', '7120980)
Once again I was meet with futility. I asked our DBA if she could capture the SQL in the database, so I could see what was happening, but like many places a DBA time is a precious commodity. So I was left to my own devices. That is when I found this blog, on how to split a comma separated string and pass to IN clause of select statement. Armed with this information I created a new SQL object and named it SQL.ZZ_LIST, that looked like this:
select regexp_substr(:1,'[^, ]+', 1, level) from dual
connect by regexp_substr(:1, '[^, ]+', 1, level) is not null;
Please note that I had to modify the original regular expression from the blog from '[^,]'+ to '[^, ]+' to account for the space that is in my list of managed departments. Then I was able to modify my SQL object with the department IN list clause and passed it the string literal without adding any quotes around the value: Like this below:
DEPTID IN(%SQL(SQL.ZZ_LIST,:1)) (Where :1 = 2000980, 2510901, 9120980, 7280980, 1510980, 1280980, 1200980, 1150980, 7120980)
And this worked brilliantly!
If you know a way to do this in other databases please leave a comment.
DEPTID IN (:1) (Where :1 = '2000980', '2510901', '9120980', '7280980', '1510980', '1280980', '1200980', '1150980', '7120980')
The SQL did not return any values, nor did it error. I had a SQL trace set and it looked correct, but I assumed the bind functionality was appending quotes to my string literal. So I attempted to remove the first and last quotes on my string of departments and passed it to the SQL object.
DEPTID IN (:1) (Where :1 = 2000980', '2510901', '9120980', '7280980', '1510980', '1280980', '1200980', '1150980', '7120980)
Once again I was meet with futility. I asked our DBA if she could capture the SQL in the database, so I could see what was happening, but like many places a DBA time is a precious commodity. So I was left to my own devices. That is when I found this blog, on how to split a comma separated string and pass to IN clause of select statement. Armed with this information I created a new SQL object and named it SQL.ZZ_LIST, that looked like this:
select regexp_substr(:1,'[^, ]+', 1, level) from dual
connect by regexp_substr(:1, '[^, ]+', 1, level) is not null;
Please note that I had to modify the original regular expression from the blog from '[^,]'+ to '[^, ]+' to account for the space that is in my list of managed departments. Then I was able to modify my SQL object with the department IN list clause and passed it the string literal without adding any quotes around the value: Like this below:
DEPTID IN(%SQL(SQL.ZZ_LIST,:1)) (Where :1 = 2000980, 2510901, 9120980, 7280980, 1510980, 1280980, 1200980, 1150980, 7120980)
And this worked brilliantly!
If you know a way to do this in other databases please leave a comment.
Monday, May 21, 2012
Department Hierarchy Query
Recently, I was asked to create a query to represent our department hierarchy in SQL. The results of the SQL would be used by our business partners to identify nodes on the tree with no manager assigned and to validate our roll-ups looked correct. Since we use our Department Security Tree to represent our organizational structure I knew I would need to write a recursive query against that tree and join in the Manager's name by Manager Position on the department table. I use a custom reporting table that is updated every 15 minutes to get the name of the manager in the position. If you attempt something like this you will have to modify the SQL below to meet your organization's needs. In the past I had used ORACLE'S connect by for recursive joins, but recently I had been turned on to Common Table Expressions.(be extra cautious when creating CTE query's in db2, to my knowledge db2 will not break out of an infinite loop for you like Oracle will). Below is my SQL:
The end result of my query returned the Department ID as the tree node, a column representing the hierarchy with node and description indented with greater than signs and one column indented with spaces, the description of the level from the tree, the tree level number, the manager of the department ( this is from a custom table, you can get the manager's name from the PERSONAL_DATA if you don't use position management), effective status, parent node name, tree node number and the tree node number end and finally the Description from the department table.
Below is an example of the end product in SQL.
COMPANY-Department Descr Manager Name
>1018-Department Descr Manager Name
>>1330-Department Descr Manager Name
>>>1185-Department Descr Manager Name
>>>1186-Department Descr Manager Name
>>>>>>>01000304-Department Descr Manager Name
>>>1385-Department Descr Manager Name
>>>1145-Department Descr Manager Name
>>>>>>>5020916-Department Descr Manager Name
>>>1031-Department Descr Manager Name
>>>>1205-Department Descr Manager Name
>>>>>>>1170980-Department Descr Manager Name
>>>>1331-Department Descr Manager Name
>>>>>>>1110102-Department Descr Manager Name
>>>>>>>1110980-Department Descr Manager Name
While this is pretty decent for SQL it was still difficult to keep inline what departments reported to other departments. So I thought about putting this information into a PeopleSoft page that would allow the user to view this data, however be able to collapse nodes and expand nodes to visualize the hierarchy. I was surprised to find out how easy this was with the use of a jQuery Tree Project found on GITHUB. I created a page with an HTML area and in the page Activate PeopleCode I loaded a view that I created of off the CTE SQL above.

The PeopleCode was probably the most difficult part of this process. We use an unstructured tree for our organization structure. This means that a level three can have a level three as a parent or a level eight department can report to a level three division. So when building the unordered list HTML for the tree I needed to keep track of how many levels to break the list so that the roll-up would work properly. Below is my page PeopleCode to build my unordered list for the HTML area on my page.
Local Rowset &ORG_STRUCTURE;
Local string &new_line = "<li><span><span class=""KNOCKOUT"">";
Local string &sibling = "</span></span></li>" | Char(12) | Char(13);
Local string &child = "</span></span><ul>" | Char(12) | Char(13);
Local string &End_Family = "</ul>" | Char(12) | Char(13) | "</li>" | Char(12) | Char(13);
<* array of parents is used to figure out then number of end family's I need to put in the HTML area. If the tree writes down to
rem level 8, a department, and the next row is a level three, a division, then I need to evaluate the parent of this new row against the array
rem to see how many closing ul and li tags are needed to properly close this group.*>
Local array of string &PARENTS = CreateArrayRept("", 0);
rem a view build on my CTE SQL;
&ORG_STRUCTURE = CreateRowset(Record.ZZ_DEPT_SEC_VW);
&ROWS = &ORG_STRUCTURE.Fill("where EFF_STATUS = 'A'");
REM &tree will be our HTML tree data;
Local string &tree = "";
For &X = 1 To &ROWS
If &X = 1 Then
&tree = &new_line | &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.TREE_NODE.Value;
Else
&value = &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.TREE_LEVEL_NUM.Value - &ORG_STRUCTURE(&X - 1).ZZ_DEPT_SEC_VW.TREE_LEVEL_NUM.Value;
Evaluate &value
When = 0
REM NO CHANGE IN TREE LEVEL BUT NEED TO EVALUATE TWO CONDITIONS;
REM DOES THE NEW ROW REPORT TO THE SAME LEVEL EXAMPLE REGION REPORTS TO A REGION;
REM TWO DOES THIS LEVEL REPORT TO HIGHER LEVEL THAN THE PREVIOUS ROW.
REM EXAMPLE Division reports to Region next division reports to executive;
If &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value = &ORG_STRUCTURE(&X - 1).ZZ_DEPT_SEC_VW.TREE_NODE.Value Then
&tree = &tree | &child;
&PARENTS.Push(&ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value);
Break;
Else
If &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value <> &ORG_STRUCTURE(&X - 1).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value Then
&tree = &tree | &sibling;
&LOOP = True;
While &LOOP
&POP = &PARENTS.Pop();
If &POP <> &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value Then
&tree = &tree | &End_Family;
Else
&PARENTS.Push(&ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value);
&LOOP = False;
End-If;
End-While;
Else
&tree = &tree | &sibling;
End-If;
End-If;
Break;
When > 0
&tree = &tree | &child;
&PARENTS.Push(&ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value);
Break;
When-Other
&tree = &tree | &sibling;
&LOOP = True;
While &LOOP
&POP = &PARENTS.Pop();
If &POP <> &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value Then
&tree = &tree | &End_Family;
Else
&PARENTS.Push(&ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value);
&LOOP = False;
End-If;
End-While;
End-Evaluate;
&tree = &tree | &new_line | &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.TREE_NODE.Value | "-" | &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.DESCR.Value | " <span class=""KNOCKOUT""> [" | &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.MANAGER_NAME.Value | "]</span>";
End-If;
End-For;
REM SERVE UP YOU OWN LOCAL VERSION OF THE LIBRARY IS BEST, TO SEE EXAMPLS OF HOW TO DO THIS CHECK OUT Jim Marion's Blog or his book;
<*
&Jq_CookieJS = GenerateScriptContentURL(%Portal, %Node, Record.WEBLIB_APT_JSL, Field.ISCRIPT1, "FieldFormula", "IScript_Jq_CookieJS");
&Jq_TreeJS = GenerateScriptContentURL(%Portal, %Node, Record.WEBLIB_APT_JSL, Field.ISCRIPT1, "FieldFormula", "IScript_Jq_TreeJS");
&Jq_LibJS = GenerateScriptContentURL(%Portal, %Node, Record.WEBLIB_APT_JSL, Field.ISCRIPT1, "FieldFormula", "IScript_jQuery_1_7_1");
*>
ZZ_ORG_WRK.HTMLAREA.Value = GetHTMLText(HTML.ZZ_ORG_STRUCTURE1, &tree);
WITH TREE (TREE_NODE, TREE_NODE2, TREE_NODE3, TREE_LEVEL_NUM, PARENT_NODE_NAME, TREE_NODE_NUM, TREE_NODE_NUM_END, STATUS, MANAGER_POSN, MANAGER_NAME, DESCR ) AS (
SELECT TREE_NODE
,TREE_NODE
,TREE_NODE
, TREE_LEVEL_NUM
, PARENT_NODE_NAME
,TREE_NODE_NUM
,TREE_NODE_NUM_END
, 'A'
, ' '
, ' '
, 'My Corp. Descr'
FROM pstreenode b
WHERE b.tree_name = 'DEPT_SECURITY'
AND B.SETID = 'SHARE'
AND b.tree_level_num = 1
AND B.EFFDT=(
SELECT MAX(EFFDT)
FROM PSTREENODE PSTREENODE
WHERE PSTREENODE.SETID=B.SETID
AND PSTREENODE.SETCNTRLVALUE=B.SETCNTRLVALUE
AND PSTREENODE.TREE_NAME=B.TREE_NAME
AND PSTREENODE.EFFDT<=TO_DATE(TO_CHAR(SYSDATE,'YYYY-MM-DD'),'YYYY-MM-DD'))
UNION ALL
SELECT B.TREE_NODE
,substr('>>>>>>>>>'
, 1
, (B.TREE_LEVEL_NUM-1))||B.TREE_NODE
,LPAD(' '
, (B.TREE_LEVEL_NUM-1)) ||B.TREE_NODE
, B.TREE_LEVEL_NUM
, B.PARENT_NODE_NAME
,B.TREE_NODE_NUM
, B.TREE_NODE_NUM_END
, C.EFF_STATUS
, C.MANAGER_POSN
, CASE C.MANAGER_POSN WHEN ' ' THEN '' ELSE (
SELECT X.NAME
FROM PS_ZZ_IDM_DATA X
WHERE X.POSITION_NBR = C.MANAGER_POSN
AND X.HR_STATUS IN ('A', 'L')
AND ROWNUM = 1) END CASE,C.DESCR
FROM TREE, pstreenode b, PS_DEPT_TBL C
WHERE b.tree_name = 'DEPT_SECURITY'
AND B.SETID = 'SHARE'
AND TREE.TREE_NODE_NUM = B.PARENT_NODE_NUM
AND B.TREE_NODE = C.DEPTID
AND B.SETID = C.SETID
AND C.EFFDT=(
SELECT MAX(EFFDT)
FROM PS_DEPT_TBL DEPT_TBL
WHERE DEPT_TBL.SETID=C.SETID
AND DEPT_TBL.DEPTID=C.DEPTID
AND DEPT_TBL.EFFDT<=TO_DATE(TO_CHAR(SYSDATE,'YYYY-MM-DD'),'YYYY-MM-DD'))
AND B.EFFDT=(
SELECT MAX(EFFDT)
FROM PSTREENODE PSTREENODE
WHERE PSTREENODE.SETID=B.SETID
AND PSTREENODE.SETCNTRLVALUE=B.SETCNTRLVALUE
AND PSTREENODE.TREE_NAME=B.TREE_NAME
AND PSTREENODE.EFFDT<=TO_DATE(TO_CHAR(SYSDATE,'YYYY-MM-DD'),'YYYY-MM-DD')) )
SELECT TREE_NODE
, TREE_NODE2||'-'||DESCR
, TREE_NODE3||'-'||DESCR
,CASE TREE_LEVEL_NUM WHEN 1 THEN 'Company' WHEN 2 THEN 'Executive' WHEN 3 THEN 'Division' WHEN 4 THEN 'Region' WHEN 5 THEN 'Business' WHEN 6 THEN 'Function' WHEN 7 THEN 'Team' WHEN 8 THEN 'Department' ELSE 'LEVEL9' END CASE
,TREE_LEVEL_NUM
,MANAGER_NAME
,STATUS
,PARENT_NODE_NAME
,TREE_NODE_NUM
,TREE_NODE_NUM_END
,DESCR
FROM TREE
WHERE status = 'A'
ORDER BY TREE_NODE_NUM
SELECT TREE_NODE
,TREE_NODE
,TREE_NODE
, TREE_LEVEL_NUM
, PARENT_NODE_NAME
,TREE_NODE_NUM
,TREE_NODE_NUM_END
, 'A'
, ' '
, ' '
, 'My Corp. Descr'
FROM pstreenode b
WHERE b.tree_name = 'DEPT_SECURITY'
AND B.SETID = 'SHARE'
AND b.tree_level_num = 1
AND B.EFFDT=(
SELECT MAX(EFFDT)
FROM PSTREENODE PSTREENODE
WHERE PSTREENODE.SETID=B.SETID
AND PSTREENODE.SETCNTRLVALUE=B.SETCNTRLVALUE
AND PSTREENODE.TREE_NAME=B.TREE_NAME
AND PSTREENODE.EFFDT<=TO_DATE(TO_CHAR(SYSDATE,'YYYY-MM-DD'),'YYYY-MM-DD'))
UNION ALL
SELECT B.TREE_NODE
,substr('>>>>>>>>>'
, 1
, (B.TREE_LEVEL_NUM-1))||B.TREE_NODE
,LPAD(' '
, (B.TREE_LEVEL_NUM-1)) ||B.TREE_NODE
, B.TREE_LEVEL_NUM
, B.PARENT_NODE_NAME
,B.TREE_NODE_NUM
, B.TREE_NODE_NUM_END
, C.EFF_STATUS
, C.MANAGER_POSN
, CASE C.MANAGER_POSN WHEN ' ' THEN '' ELSE (
SELECT X.NAME
FROM PS_ZZ_IDM_DATA X
WHERE X.POSITION_NBR = C.MANAGER_POSN
AND X.HR_STATUS IN ('A', 'L')
AND ROWNUM = 1) END CASE,C.DESCR
FROM TREE, pstreenode b, PS_DEPT_TBL C
WHERE b.tree_name = 'DEPT_SECURITY'
AND B.SETID = 'SHARE'
AND TREE.TREE_NODE_NUM = B.PARENT_NODE_NUM
AND B.TREE_NODE = C.DEPTID
AND B.SETID = C.SETID
AND C.EFFDT=(
SELECT MAX(EFFDT)
FROM PS_DEPT_TBL DEPT_TBL
WHERE DEPT_TBL.SETID=C.SETID
AND DEPT_TBL.DEPTID=C.DEPTID
AND DEPT_TBL.EFFDT<=TO_DATE(TO_CHAR(SYSDATE,'YYYY-MM-DD'),'YYYY-MM-DD'))
AND B.EFFDT=(
SELECT MAX(EFFDT)
FROM PSTREENODE PSTREENODE
WHERE PSTREENODE.SETID=B.SETID
AND PSTREENODE.SETCNTRLVALUE=B.SETCNTRLVALUE
AND PSTREENODE.TREE_NAME=B.TREE_NAME
AND PSTREENODE.EFFDT<=TO_DATE(TO_CHAR(SYSDATE,'YYYY-MM-DD'),'YYYY-MM-DD')) )
SELECT TREE_NODE
, TREE_NODE2||'-'||DESCR
, TREE_NODE3||'-'||DESCR
,CASE TREE_LEVEL_NUM WHEN 1 THEN 'Company' WHEN 2 THEN 'Executive' WHEN 3 THEN 'Division' WHEN 4 THEN 'Region' WHEN 5 THEN 'Business' WHEN 6 THEN 'Function' WHEN 7 THEN 'Team' WHEN 8 THEN 'Department' ELSE 'LEVEL9' END CASE
,TREE_LEVEL_NUM
,MANAGER_NAME
,STATUS
,PARENT_NODE_NAME
,TREE_NODE_NUM
,TREE_NODE_NUM_END
,DESCR
FROM TREE
WHERE status = 'A'
ORDER BY TREE_NODE_NUM
The end result of my query returned the Department ID as the tree node, a column representing the hierarchy with node and description indented with greater than signs and one column indented with spaces, the description of the level from the tree, the tree level number, the manager of the department ( this is from a custom table, you can get the manager's name from the PERSONAL_DATA if you don't use position management), effective status, parent node name, tree node number and the tree node number end and finally the Description from the department table.
Below is an example of the end product in SQL.
COMPANY-Department Descr Manager Name
>1018-Department Descr Manager Name
>>1330-Department Descr Manager Name
>>>1185-Department Descr Manager Name
>>>1186-Department Descr Manager Name
>>>>>>>01000304-Department Descr Manager Name
>>>1385-Department Descr Manager Name
>>>1145-Department Descr Manager Name
>>>>>>>5020916-Department Descr Manager Name
>>>1031-Department Descr Manager Name
>>>>1205-Department Descr Manager Name
>>>>>>>1170980-Department Descr Manager Name
>>>>1331-Department Descr Manager Name
>>>>>>>1110102-Department Descr Manager Name
>>>>>>>1110980-Department Descr Manager Name
While this is pretty decent for SQL it was still difficult to keep inline what departments reported to other departments. So I thought about putting this information into a PeopleSoft page that would allow the user to view this data, however be able to collapse nodes and expand nodes to visualize the hierarchy. I was surprised to find out how easy this was with the use of a jQuery Tree Project found on GITHUB. I created a page with an HTML area and in the page Activate PeopleCode I loaded a view that I created of off the CTE SQL above.
The PeopleCode was probably the most difficult part of this process. We use an unstructured tree for our organization structure. This means that a level three can have a level three as a parent or a level eight department can report to a level three division. So when building the unordered list HTML for the tree I needed to keep track of how many levels to break the list so that the roll-up would work properly. Below is my page PeopleCode to build my unordered list for the HTML area on my page.
Local Rowset &ORG_STRUCTURE;
Local string &new_line = "<li><span><span class=""KNOCKOUT"">";
Local string &sibling = "</span></span></li>" | Char(12) | Char(13);
Local string &child = "</span></span><ul>" | Char(12) | Char(13);
Local string &End_Family = "</ul>" | Char(12) | Char(13) | "</li>" | Char(12) | Char(13);
<* array of parents is used to figure out then number of end family's I need to put in the HTML area. If the tree writes down to
rem level 8, a department, and the next row is a level three, a division, then I need to evaluate the parent of this new row against the array
rem to see how many closing ul and li tags are needed to properly close this group.*>
Local array of string &PARENTS = CreateArrayRept("", 0);
rem a view build on my CTE SQL;
&ORG_STRUCTURE = CreateRowset(Record.ZZ_DEPT_SEC_VW);
&ROWS = &ORG_STRUCTURE.Fill("where EFF_STATUS = 'A'");
REM &tree will be our HTML tree data;
Local string &tree = "";
For &X = 1 To &ROWS
If &X = 1 Then
&tree = &new_line | &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.TREE_NODE.Value;
Else
&value = &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.TREE_LEVEL_NUM.Value - &ORG_STRUCTURE(&X - 1).ZZ_DEPT_SEC_VW.TREE_LEVEL_NUM.Value;
Evaluate &value
When = 0
REM NO CHANGE IN TREE LEVEL BUT NEED TO EVALUATE TWO CONDITIONS;
REM DOES THE NEW ROW REPORT TO THE SAME LEVEL EXAMPLE REGION REPORTS TO A REGION;
REM TWO DOES THIS LEVEL REPORT TO HIGHER LEVEL THAN THE PREVIOUS ROW.
REM EXAMPLE Division reports to Region next division reports to executive;
If &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value = &ORG_STRUCTURE(&X - 1).ZZ_DEPT_SEC_VW.TREE_NODE.Value Then
&tree = &tree | &child;
&PARENTS.Push(&ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value);
Break;
Else
If &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value <> &ORG_STRUCTURE(&X - 1).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value Then
&tree = &tree | &sibling;
&LOOP = True;
While &LOOP
&POP = &PARENTS.Pop();
If &POP <> &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value Then
&tree = &tree | &End_Family;
Else
&PARENTS.Push(&ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value);
&LOOP = False;
End-If;
End-While;
Else
&tree = &tree | &sibling;
End-If;
End-If;
Break;
When > 0
&tree = &tree | &child;
&PARENTS.Push(&ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value);
Break;
When-Other
&tree = &tree | &sibling;
&LOOP = True;
While &LOOP
&POP = &PARENTS.Pop();
If &POP <> &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value Then
&tree = &tree | &End_Family;
Else
&PARENTS.Push(&ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.PARENT_NODE_NAME.Value);
&LOOP = False;
End-If;
End-While;
End-Evaluate;
&tree = &tree | &new_line | &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.TREE_NODE.Value | "-" | &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.DESCR.Value | " <span class=""KNOCKOUT""> [" | &ORG_STRUCTURE(&X).ZZ_DEPT_SEC_VW.MANAGER_NAME.Value | "]</span>";
End-If;
End-For;
REM SERVE UP YOU OWN LOCAL VERSION OF THE LIBRARY IS BEST, TO SEE EXAMPLS OF HOW TO DO THIS CHECK OUT Jim Marion's Blog or his book;
<*
&Jq_CookieJS = GenerateScriptContentURL(%Portal, %Node, Record.WEBLIB_APT_JSL, Field.ISCRIPT1, "FieldFormula", "IScript_Jq_CookieJS");
&Jq_TreeJS = GenerateScriptContentURL(%Portal, %Node, Record.WEBLIB_APT_JSL, Field.ISCRIPT1, "FieldFormula", "IScript_Jq_TreeJS");
&Jq_LibJS = GenerateScriptContentURL(%Portal, %Node, Record.WEBLIB_APT_JSL, Field.ISCRIPT1, "FieldFormula", "IScript_jQuery_1_7_1");
*>
ZZ_ORG_WRK.HTMLAREA.Value = GetHTMLText(HTML.ZZ_ORG_STRUCTURE1, &tree);
Below is the HTML object I build for my demo:
<script type="text/javascript" src="https://github.com/pioz/jquery-tree/raw/master/jquery.min.js"></script>
<script type="text/javascript" src="https://github.com/pioz/jquery-tree/raw/master/jquery.cookie.js"></script>
<script type="text/javascript" src="https://github.com/pioz/jquery-tree/raw/master/jquery.tree.js"></script>
<script type="text/javascript">
$(document).ready(function( ) {
$('ul.org').tree({default_expanded_paths_string : '0/0,0/1,0/2'});
});
</script>
<table border="0">
<tr>
<td></td>
</tr>
<tr>
<td><div >
<span class="PAPAGETITLE"> Organizational Structure</span>
</div>
</td>
</tr>
<tr>
<td>
<a class="PSHYPERLINK" onclick=jQuery('ul.org').expand()>Expand All</a>
<a class="PSHYPERLINK" onclick=jQuery('ul.org').collapse()>Collapse All</a>
</td>
</tr>
<tr>
<td>
<span class="KNOCKOUT">
<ul class="org" >
%Bind(:1)
</ul>
</span>
</td>
</tr>
</table>
The use of JavaScript Libraries from third party websites is not recommended, however they will work for the purpose of this demo. However, if your looking for cools ways to server up JavaScript and JavaScript libraries to the browser, check out Jim Marion's blog.
Below is example of how the jQuery tree works with a mocked up tree data. You can expand the node of the tree by clicking on the Arrow to the left of the Node number. Other ideas might be to make this tree searchable?
<script type="text/javascript" src="https://github.com/pioz/jquery-tree/raw/master/jquery.cookie.js"></script>
<script type="text/javascript" src="https://github.com/pioz/jquery-tree/raw/master/jquery.tree.js"></script>
<script type="text/javascript">
$(document).ready(function( ) {
$('ul.org').tree({default_expanded_paths_string : '0/0,0/1,0/2'});
});
</script>
<table border="0">
<tr>
<td></td>
</tr>
<tr>
<td><div >
<span class="PAPAGETITLE"> Organizational Structure</span>
</div>
</td>
</tr>
<tr>
<td>
<a class="PSHYPERLINK" onclick=jQuery('ul.org').expand()>Expand All</a>
<a class="PSHYPERLINK" onclick=jQuery('ul.org').collapse()>Collapse All</a>
</td>
</tr>
<tr>
<td>
<span class="KNOCKOUT">
<ul class="org" >
%Bind(:1)
</ul>
</span>
</td>
</tr>
</table>
The use of JavaScript Libraries from third party websites is not recommended, however they will work for the purpose of this demo. However, if your looking for cools ways to server up JavaScript and JavaScript libraries to the browser, check out Jim Marion's blog.
Below is example of how the jQuery tree works with a mocked up tree data. You can expand the node of the tree by clicking on the Arrow to the left of the Node number. Other ideas might be to make this tree searchable?
The use of the arrow to expand and collapse the nodes allows you to turn the tree node description into a hyperlink to the department table for end users to to make updates. Other uses for this type in tree data could be building a reports to tree for position management or even a quick view of supervisor hierarchy. Can you think of any ideas or uses in your organization?
Subscribe to:
Posts (Atom)
