En esta entrada vamos a ver como introducir un control de tipo árbol en las paginas de Peoplesoft. Anteriormente para cumplir este requerimiento el Application Designer proveía un control llamado tree. En la últimas versiones de las PeopleTools se recomienda no usar más este control, en cambio Peoplecode posee una función llamada GenerateTree que permite cargar los árboles en un control de tipo HTML Area.
En los peoplebooks se encuentra un ejemplo de como cargar los árboles creados por el gestor de árboles en una página creada por nosotros. La ruta del ejemplo en los PeopleBooks es Home > PeopleBooks > Enterprise PeopleTools 8.51 PeopleBook: PeopleCode Developer's Guide > Using HTML Trees and the GenerateTree Function.
El ejemplo que vamos a describir en esta entrada nos permitirá crear un árbol a partir de cualquier estructura jerárquica que se cree dentro de peoplesoft.
El primer paso es crear nuestra estructura jerarquica, para eso creamos un componente que permita insertar los empleados de una organización de tal forma que estén relacionados con su jefe.
A continuación podemos ver la estructura del registro PE_TREE_EMPLE que vamos a utilizar para ingresar la estructura jerárquica de los empleados.
Luego creamos la página PE_TREE_EMPLEADOS para administrar los datos del registro PE_TREE_EMPLE. La página debe verse como se muestra a continuación
Luego creamos un componente con registro de búsqueda INSTALLATION y un menú que nos permita registrar el componente dentro del portal.
En nuestro nuevo componente ingresamos unos datos de prueba como se ven en la ilustración:
El siguiente paso es crear un Application Package que nos permita administrar los datos ingresado por el componente PE_TREE_EMPLEADOS en forma de nodos de un árbol. Para lo anterior crearemos la clase es pe_nodoEmpleado dentro del paquete PE_TREE_EMPLEADOS. Está clase nos permitirá manejar como un nodo cada uno de los registros ingresados por el componente PE_TREE_EMPLEADOS. La clase también poseerá un arreglo de objetos de está misma clase que representará los hijos del nodo.
A continuación podemos ver el código completo de la clase descrito línea por línea.
/*Lo primero es declarar nuestra clase*/
class pe_nodoEmpleado
/*El método constructor recibirá compo parámetros el nombre del nodo (En nuestro caso el nombre del nodo será el id del empleado que se creo), la descripción (El nombre del empleado que se creo) y un indicador que indica si el nodo raíz del árbol o no*/
method pe_nodoEmpleado(&str_nombreNodo_par As string, &str_descNodo_par As string, &bol_nodoRaiz_par As boolean);
/*El siguiente método permite cargar los hijos del nodo actual en un arreglo */
method pe_cargarHijos();
/*El método getNodo permite retornar un nodo que se encuentre dentro de la estructura de nodos que se está procesando. */
method pe_getNodo(&str_nombreNodo_par As string) Returns PE_TREE_EMPLEADOS:pe_nodoEmpleado;
/*El método getNodo permite retornar un nodo que se encuentre dentro de la estructura de nodos que se está procesando. */
method pe_getNodoNumber(&nbr_idNodo As number) Returns PE_TREE_EMPLEADOS:pe_nodoEmpleado;
/*Método que retorna el total de hijos del nodo*/
method pe_totalHijos() Returns number;
/*Propiedad para consultar el nombre del nodo*/
property string NOMBRE_NODO get;
property string DESCR_NODO get;
property boolean CARGO_HIJOS get;
/*Propiedad que indica si el nodo tiene hijos o no*/
property boolean TIENE_HIJOS get;
property boolean NODO_RAIZ get;
private
/*variable para almacenar el nombre del nodo*/
instance string &str_nombreNodo;
/*variable para almacenar la descripción del nodo*/
instance string &str_descNodo;
/*variable para almacenar si ya se cargaron los hijos*/
instance boolean &bol_cargoHijos;
/*variable para almacenar si el nodo es el nodo raíz*/
instance boolean &bol_nodoRaiz;
/*variable para almacenar el listado de nodos hijos del nodo actual*/
instance array of PE_TREE_EMPLEADOS:pe_nodoEmpleado &obj_hijos;
end-class;
method pe_nodoEmpleado
/+ &str_nombreNodo_par as String, +/
/+ &str_descNodo_par as String, +/
/+ &bol_nodoRaiz_par as Boolean +/
/*Se inicializan las variables privadas de la clase*/
&str_nombreNodo = &str_nombreNodo_par;
&str_descNodo = &str_descNodo_par;
&bol_cargoHijos = False;
&bol_nodoRaiz = &bol_nodoRaiz_par;
end-method;
method pe_cargarHijos
/*Se inicializa el arreglo &obj_hijos en el que se almacenarán los nodos hijos del nodo actual*/
Local PE_TREE_EMPLEADOS:pe_nodoEmpleado &obj_hijo = create PE_TREE_EMPLEADOS:pe_nodoEmpleado(" ", " ", False);
Local string &str_cod, &str_nombre;
Local SQL &sql_hijos;
&obj_hijos = CreateArrayRept(&obj_hijo, 0);
/*Si el nodo actual está marcado como el nodo raíz entonces se cargan todos los empleados que no tengan ningún jefe*/
/*Si el nodo actual está marcado como el nodo raíz entonces se cargan todos los empleados que no tengan ningún jefe*/
If &bol_nodoRaiz Then
&sql_hijos = CreateSQL("SELECT PE_CODIGO_EMPL, PE_NOMBRE_EMPLEADO FROM PS_PE_TREE_EMPLE WHERE PE_CODIGO_JEFE = ' '");
Else
/*Si el nodo actual no es el nodo raíz se cargan todos los empleados cuyo jefe sea el nodo actual*/
/*Si el nodo actual no es el nodo raíz se cargan todos los empleados cuyo jefe sea el nodo actual*/
&sql_hijos = CreateSQL("SELECT PE_CODIGO_EMPL, PE_NOMBRE_EMPLEADO FROM PS_PE_TREE_EMPLE WHERE PE_CODIGO_JEFE = :1", &str_nombreNodo);
End-If;
/*Para cada uno de los empleados hijos del nodo actual, se creará un nodo y se agrega al arreglo de nodos hijos del nodo actual*/
While &sql_hijos.Fetch(&str_cod, &str_nombre)
&obj_hijo = create PE_TREE_EMPLEADOS:pe_nodoEmpleado(&str_cod, &str_nombre, False);
&obj_hijos.Push(&obj_hijo);
End-While;
/*Se marca el nodo para que indique que ya fueron cargados los nodos*/
&bol_cargoHijos = True;
end-method;
get NOMBRE_NODO
/+ Returns String +/
Return &str_nombreNodo;
end-get;
get DESCR_NODO
/+ Returns String +/
Return &str_descNodo;
end-get;
get CARGO_HIJOS
/+ Returns Boolean +/;
Return &bol_cargoHijos;
end-get;
get TIENE_HIJOS
/+ Returns Boolean +/;
Local number &nbr_hijos = 0;
/*si ya fueron cargados los hijos se valida que el arreglo de nodos hijos tenga hijos*/
If &bol_cargoHijos Then
If &obj_hijos.Len = 0 Then
Return False;
Else
Return True;
End-If;
Else
/*Si no han sido cargados los hijos se consulta la tabla de empleados para verificar si tienes nodos hijos*/
/*Si no han sido cargados los hijos se consulta la tabla de empleados para verificar si tienes nodos hijos*/
If &bol_nodoRaiz Then
SQLExec("select count(1) from ps_PE_TREE_EMPLE where PE_CODIGO_JEFE = ' '", &nbr_hijos);
Else
SQLExec("select count(1) from ps_PE_TREE_EMPLE where PE_CODIGO_JEFE = :1", &str_nombreNodo, &nbr_hijos);
End-If;
If &nbr_hijos = 0 Then
Return False;
Else
Return True;
End-If;
End-If;
end-get;
get NODO_RAIZ
/+ Returns Boolean +/
Return &bol_nodoRaiz
end-get;
method pe_getNodo
/+ &str_nombreNodo_par as String +/
/+ Returns PE_TREE_EMPLEADOS:pe_nodoEmpleado +/
Local number &nbr_nodos;
Local PE_TREE_EMPLEADOS:pe_nodoEmpleado &obj_hijo;
/*Retorna el nodo buscado, si es el actual, si es algún nodo hijo o si es algún nodo hijo de los nodos hijos del nodo actual*/
/*Retorna el nodo buscado, si es el actual, si es algún nodo hijo o si es algún nodo hijo de los nodos hijos del nodo actual*/
If &str_nombreNodo_par = &str_nombreNodo Then
Return %This;
Else
If &bol_cargoHijos Then
For &nbr_nodos = 1 To &obj_hijos.Len
If &str_nombreNodo_par = &obj_hijos [&nbr_nodos].NOMBRE_NODO Then
Return &obj_hijos [&nbr_nodos];
Else
&obj_hijo = &obj_hijos [&nbr_nodos].pe_getNodo(&str_nombreNodo_par);
If &obj_hijo <> Null Then
Return &obj_hijo;
End-If;
End-If;
End-For;
Return Null
Else
Return Null;
End-If;
End-If;
end-method;
method pe_getNodoNumber
/+ &nbr_idNodo as Number +/
/+ Returns PE_TREE_EMPLEADOS:pe_nodoEmpleado +/
Return &obj_hijos [&nbr_idNodo];
end-method;
method pe_totalHijos
/+ Returns Number +/
Return &obj_hijos.Len;
end-method;
Con algunas modificaciones a la clase anterior se puede tener una clase que administre cualquier tabla que almacene una estructura jerárquica.
La función GenerateTree funciona utilizando 2 records, uno que almacena los parámetros de generación del árbol y otro que contiene cada uno de los nodos que pertenecen al árbol. El primer registro lo llamaremos PE_TREE_HDR y se debe crear utilizando el subrecord TREECTL_HDR_SBR y el segundo registro los llamaremos PE_TREE_NDE y se debe crear utilizando el subrecord TREECTL_NDE_SBR.
El siguiente paso en nuestro ejemplo es crear un registro derived que almacene el campo tipo LONG que tendrá el código HTML que generará la función GenerateTree y el campo Char(50) que controlará los eventos que se generen en el HTML creado por la función GenerateTree.
En la siguiente imagen se puede ver la estructura de los 4 registro que estamos utilizando en nuestro proyecto.
El siguiente paso es crear la página PE_TREE_EXAMPLE en la cual mostraremos el árbol de empleados. A la página se le debe agregar un objeto HTMLAREA asociado al campo HTML_AREA_01 del registro PE_TREE_EXP. También se debe agregar un Editbox asociado al campo PE_EVENT_FIELD del registro PE_TREE_EXP que debe ser invisible, debe tener marcada la propiedad Modifiable by JavaScript y en la propiedad Page Field Name debe tener PE_EVENT_FIELD.
La página debemos agregarla a un componente cuyo registro de búsqueda sea INSTALLATION. El componente lo llamamos PE_TREE_EXAMPLE y se debe registrar en el portal para poder acceder a la página.
En el evento PostBuild del componente debemos agregar el código que inicializa nuestro árbol. A continuación se lista el código que debe ir en el postbuild:
import PE_TREE_EMPLEADOS:pe_nodoEmpleado;
/*Declaramos la variable que tendrá el nodo raíz de nuestro árbol*/
Component PE_TREE_EMPLEADOS:pe_nodoEmpleado &obj_nodos;
/*Declaramos el rowset que contendrá la estructura que se utilizará para ejecutar la función GenerateTree*/
Component Rowset &TREECTL;
&NODE_ROWSET = CreateRowset(Record.PE_TREE_NDE);
&TREECTL = CreateRowset(Record.PE_TREE_HDR, &NODE_ROWSET);
&TREECTL.InsertRow(1);
&REC = &TREECTL.GetRow(2).GetRecord(1);
/* Set the HDR options:
/*Asignamos el nombre de la página en la que va a ser mostrado el árbol*/
&REC.GetField(Field.PAGE_NAME).Value = "PE_TREE_EXAMPLE";
/*Asignamos el nombre del campo que usaremos para controlar los eventos generados en el árbol*/
&REC.GetField(Field.PAGE_FIELD_NAME).Value = "PE_EVENT_FIELD";
/*Los siguientes campos se asignan por defecto*/
&REC.GetField(Field.PAGE_SIZE).Value = 15;
&REC.GetField(Field.DISPLAY_LEVELS).Value = 8;
&REC.GetField(Field.COLLAPSED_IMAGE).Value = "PT_TREE_COLLAPSED";
&REC.GetField(Field.EXPANDED_IMAGE).Value = "PT_TREE_EXPANDED";
&REC.GetField(Field.END_NODE_IMAGE).Value = "PT_TREE_END_NODE";
&REC.GetField(Field.LEAF_IMAGE).Value = "PT_TREE_LEAF";
&REC.GetField(Field.IMAGE_WIDTH).Value = 15;
&REC.GetField(Field.IMAGE_HEIGHT).Value = 12;
&REC.GetField(Field.INDENT_PIXELS).Value = 20;
/*Se crea el nodo raíz de nuestro árbol de empleados*/
&obj_nodos = create PE_TREE_EMPLEADOS:pe_nodoEmpleado("Empleados", "Todos los Empleados", True);
/*Si el nodo tiene hijos en la variable &PARENT_FLAG X que indica que el nodo tiene hijos y no estos no se han desplegado*/
If &obj_nodos.TIENE_HIJOS Then
&PARENT_FLAG = "X";
Else
&PARENT_FLAG = "Y";
End-If;
&NODE_ROWSET = &TREECTL.GetRow(2).GetRowset(1);
&NODE_ROWSET.InsertRow(1);
&REC = &NODE_ROWSET.GetRow(2).GetRecord(1);
/*Se le indica al nodo que no es una hoja*/
&REC.GetField(Field.LEAF_FLAG).Value = "N";
/*Se asigna el nombre del nodo*/
&REC.GetField(Field.TREE_NODE).Value = "Empleados";
/*Se asigna la descripción del nodo*/
&REC.GetField(Field.DESCR).Value = "Todos los Empleados";
/*Se asignan los siguiente valores por defecto*/
&REC.GetField(Field.RANGE_FROM).Value = "";
&REC.GetField(Field.RANGE_TO).Value = "";
&REC.GetField(Field.DYNAMIC_FLAG).Value = "N";
&REC.GetField(Field.ACTIVE_FLAG).Value = "Y";
&REC.GetField(Field.DISPLAY_OPTION).Value = "B";
&REC.GetField(Field.STYLECLASSNAME).Value = "PSHYPERLINK";
/*Se asigna el valor de la variable PARENT_FLAG*/
&REC.GetField(Field.PARENT_FLAG).Value = &PARENT_FLAG;
/*Se indica que el nodo está a nivel 1 dentro del árbol*/
&REC.GetField(Field.TREE_LEVEL_NUM).Value = 1;
&REC.GetField(Field.LEVEL_OFFSET).Value = 0;
/*Se ejecuta la función GenerateTree y el resultado se asigna al control HTMLAREA que agregamos a la página */
PE_TREE_EXP.HTML_AREA_01 = GenerateTree(&TREECTL);
El último paso de nuestro ejemplo es agregar el código necesario al evento FieldChange del campo PE_TREE_EXP.PE_FIELD_EVENT. El código que agregaremos a este evento controla cuando se despliegan o se colapsan los nodos y la carga de los nodos hijos cada vez que sea necesario.
import PE_TREE_EMPLEADOS:pe_nodoEmpleado;
/*Se declara el rowset que contiene la información de los nodos que serán generados por la función GenerateTree*/
Component Rowset &TREECTL;
/*Se declara el objeto que contiene el nodo raíz del árbol de empleados*/
Component PE_TREE_EMPLEADOS:pe_nodoEmpleado &obj_nodos, &obj_nodoActual;
Local PE_TREE_EMPLEADOS:pe_nodoEmpleado &obj_nodoActual, &obj_Hijo;
/*Si el evento es de despliegue de un nodo*/
If Left(PE_TREE_EXP.PE_EVENT_FIELD, 1) = "X" Then
/*Se obtiene la fila que representa el nodo sobre el cual fue ejecutado el evento*/
&ROW = Value(Right(PE_TREE_EXP.PE_EVENT_FIELD, Len(PE_TREE_EXP.PE_EVENT_FIELD) - 1)) + 1;
&NODE_ROWSET = &TREECTL.GetRow(2).GetRowset(1);
/*Se obtiene el registro que corresponde al nodo sobre el que se ejecutó el evento*/
&PARENT_REC = &NODE_ROWSET.GetRow(&ROW).GetRecord(1);
/*Se valida el nivel del nodo sobre el que se ejecuto el evento */
&PARENT_LEVEL = &PARENT_REC.GetField(Field.TREE_LEVEL_NUM).Value;
&ROW = &ROW + 1;
/*Se obtiene el objeto nodo que corresponde al nodo del árbol sobre el que se ejecuto el evento*/
&obj_nodoActual = &obj_nodos.pe_getNodo(&PARENT_REC.GetField(Field.TREE_NODE).Value);
/*Si el nodo tiene hijos se cargan los hijos al árbol*/
If &obj_nodoActual.TIENE_HIJOS Then
&obj_nodoActual.pe_cargarHijos();
Local number &nbr_nodos;
/*Se recorren los hijos del nodo y se agregan al rowset que se está usando de base para el árbol*/
For &nbr_nodos = 1 To &obj_nodoActual.pe_totalHijos()
&obj_Hijo = &obj_nodoActual.pe_getNodoNumber(&nbr_nodos);
&LEVEL_OFFSET = 0;
&NODE_ROWSET.InsertRow(&ROW - 1);
&REC = &NODE_ROWSET.GetRow(&ROW).GetRecord(1);
If &obj_Hijo.TIENE_HIJOS Then
&PARENT_FLAG = "X";
&REC.GetField(Field.LEAF_FLAG).Value = "N";
&REC.GetField(Field.TREE_NODE).Value = &obj_Hijo.NOMBRE_NODO;
&REC.GetField(Field.DESCR).Value = &obj_Hijo.DESCR_NODO;
&REC.GetField(Field.RANGE_FROM).Value = "";
&REC.GetField(Field.RANGE_TO).Value = "";
Else
/*Si el nodo no tiene hijos se marca como una hoja para diferenciarlo en el árbol */
&PARENT_FLAG = "N";
&REC.GetField(Field.LEAF_FLAG).Value = "Y";
&REC.GetField(Field.TREE_NODE).Value = " ";
&REC.GetField(Field.DESCR).Value = " ";
&REC.GetField(Field.RANGE_FROM).Value = &obj_Hijo.NOMBRE_NODO;
&REC.GetField(Field.RANGE_TO).Value = &obj_Hijo.DESCR_NODO;
End-If;
&REC.GetField(Field.DYNAMIC_FLAG).Value = "N";
&REC.GetField(Field.ACTIVE_FLAG).Value = "Y";
&REC.GetField(Field.DISPLAY_OPTION).Value = "B";
&REC.GetField(Field.STYLECLASSNAME).Value = "PSHYPERLINK";
&REC.GetField(Field.PARENT_FLAG).Value = &PARENT_FLAG;
&REC.GetField(Field.TREE_LEVEL_NUM).Value = &PARENT_LEVEL + 1;
&REC.GetField(Field.LEVEL_OFFSET).Value = &LEVEL_OFFSET;
&ROW = &ROW + 1;
End-For;
/* Se indica al nodo actual que ya se cargaron los nodos*/
&PARENT_REC.GetField(Field.PARENT_FLAG).Value = "Y";
End-If;
Else
/* Se procesa el evento de selección de un nodo*/
If Left(PE_TREE_EXP.PE_EVENT_FIELD, 1) = "S" Then
Local number &nbr_filas;
/*Se recupera el id del nodo sobre el cual se genero el evento*/
&ROW = Value(Right(PE_TREE_EXP.PE_EVENT_FIELD, Len(PE_TREE_EXP.PE_EVENT_FIELD) - 1)) + 1;
&NODE_ROWSET = &TREECTL.GetRow(2).GetRowset(1);
/* se marca como no seleccionado el nodo que había sido seleccionado previamente*/
For &nbr_filas = 1 To &NODE_ROWSET.ActiveRowCount
&REC = &NODE_ROWSET.GetRow(&nbr_filas).GetRecord(1);
If &REC.STYLECLASSNAME.value = "PSTREENODESELECTED" Then
&REC.STYLECLASSNAME.value = "PSHYPERLINK";
End-If;
End-For;
&REC = &NODE_ROWSET.GetRow(&ROW).GetRecord(1);
/*Se marca el nodo seleccionado como seleccionado*/
&REC.STYLECLASSNAME.value = "PSTREENODESELECTED";
Else
rem Procesa los eventos adicionales;
End-If;
End-If;
/*Se ejecuta nuevamente la función GenerateTree para actualizar el estado del árbol*/
PE_TREE_EXP.HTML_AREA_01 = GenerateTree(&TREECTL, PE_TREE_EXP.PE_EVENT_FIELD);
PE_TREE_EXP.PE_EVENT_FIELD = "";
Con el código anterior finalizamos el desarrollo de nuestro proyecto, en la imagen siguiente podemos ver el resultado.
Con la anterior imagen me despido por esta vez, espero que le encuentren utilidad a la información anterior. Hasta la próxima entrada.