Curso GeneXus X Año 2011
Curso GeneXus X Año 2011
Objetivo Este curso tienen como objetivo introducir al alumno en los conceptos básicos de GeneXus a través del desarrollo de una aplicación. Como mejor forma de realizar el curso, recomendamos que se desarrolle la misma aplicación en forma paralela. Es decir, intercalando la lectura de cada ejemplo que conforma la construcción de la aplicación con su puesta en práctica.
Página 2 de 143
Curso GeneXus X Año 2011
Temario Capitulo 1: IDE y Creación de la Base de Conocimiento En este capítulo verá cómo personalizar el IDE, cómo crear una Base de Conocimiento (KB), y cómo integrar imágenes y archivos a dicha KB.
Capitulo 2: Transacciones En este capítulo verá cómo diseñar y definir objetos de tipo Transacción, a la vez que se introducen los conceptos de Dominios, Normalización y Tabla Extendida.
Capitulo 3: Ejecución de la Aplicación En este capítulo verá el proceso de ejecución de la aplicación.
Capitulo 4: Patterns En este capítulo se introduce el concepto de Patterns. Verá la aplicación y edición de una instancia del Pattern Work With.
Capitulo 5: Web Panel En este capítulo verá cómo diseñar un objeto de tipo Web Panel, utilizando la Barra de Controles para su edición.
Capitulo 6: Formulas En este capítulo se introducen los conceptos de Fórmulas globales y locales
Capitulo 7: User Controls, SDT y Data provider En este capítulo verá cómo implementar una Galería de Imágenes (User Control), a la vez que se introducen los conceptos de Tipos de Datos Estructurados y Data Providers.
Capitulo 8: Subtipos En este capítulo verá cómo definir atributos diferentes pero que son conceptualmente iguales, a través de Grupos de Subtipos.
Capitulo 9: Data Selectors En este capítulo verá cómo reusar código y navegaciones mediante la definición de Data Selectors. Se introducen los conceptos de comando For each y Tabla Base.
Capitulo 10: Actualización de la Base de Datos
Página 3 de 143
Curso GeneXus X Año 2011
En este capítulo se tratará la actualización interactiva y no interactiva de la base de datos (transacciones, Business components y procedimientos).
Capitulo 11: KB – KnowledgeManager, KB Information y Full Text Search. En este capitulo veremos como importar/exportar objetos a nuestra base de conocimiento. Además como obtener información estadística sobre la base. Además realizaremos búsquedas dentro del IDE utilizando el mecanismo Full Text Search.
Capitulo 12: Objetos Externos Creacion de objetos externos. Consumo de Web Service
Capitulo 13: GX Extensions
Página 4 de 143
Curso GeneXus X Año 2011
Capitulo 1 IDE y Creación de la Base de Conocimiento En este capítulo verá cómo personalizar el IDE, cómo crear una Base de Conocimiento (KB), y cómo integrar imágenes y archivos a dicha KB.
1.1. Generalidades
1.2. Características •
Personalizable: Barras y ventanas pueden tomar diferentes posiciones mediante las acciones Drag & Drop.
Página 5 de 143
Curso GeneXus X Año 2011
Las marcas en la pantalla indican las posibles posiciones que puede tomar una ventana dentro del IDE. El resultado que provocan es el siguiente: • Posición 1: La ventana queda situada en la parte superior ocupando todo el ancho de la pantalla. • Posición 2: La ventana queda situada a la izquierda de la pantalla. • Posición 3: La ventana queda situada en la parte inferior de la pantalla ocpanddo todo el ancho. • Posición 4: La ventana queda situada a la derecha de la pantalla. • Posición 5: La ventana queda situada en la parte superior, inferior, derecha o izquiierda (según se seleccione) dentro de la ventana principal. •
Intuitivo y Contextual: Ofrece las opciones que aplican al objeto o posición seleccionada.
El IDE es contextual. Las opciones que se ofrecen son siempre relativas al objeto o posición seleccionada. En la imaagen de la diapositiva se observa que la ventana de Propiedades muestra las propiedades que aplican all atributo seleccionado. Si por ejemplo estamos situados en el sector de los Eventos, al presionar la opción Insert de la Menubar, el IDE ofrecerá los llamados Code Snippets (trozos de código).
Página 6 de 143
Curso GeneXus X Año 2011
Pero si en cambio estamos situados en el sector de las Rules, entonces la misma opción ofrecerá las reglas más comunes para que puedan ser insertadas.
1.3. Herramientas Integradas • • •
Database Reverse Engineering Application Integration (External Objects) Workflow
Estas herramientas se encuentran integradas al nuevo IDE, bajo la opción Tools de la Menubar.
1.4. Definición de Dominios
1.5. Definición de Atributos Redundantes
Página 7 de 143
Curso GeneXus X Año 2011
1.6. Definición de Variables
Al momento de definir variables, se puede optar por: 1) Definirlas a través del selector Variables presente en todos los objetos GeneXus. 2) Definirlas al momento de declararlas en el source de un objeto, eventos, etc.: Escribir el nombre de la variable, precedido del símbolo “&”, y luego presionar el botón derecho del mousr. Elegir Add Variable. 3) Seleccionar la opción Insert de la menubar: Elegir luego Variable…
Página 8 de 143
Curso GeneXus X Año 2011
1.7. Como Diseñar un Reporte • •
No existen objetos de tipo Reporte. Existen Procedimientos a los cuales se les puede insertar print blocks.
En GeneXus X no existen los objetos de tipo Reporte. Para diseñar un listado, se debe crear un Procedimiento y diseñar su correspondiente Layout insertando print blocks.
Página 9 de 143
Curso GeneXus X Año 2011
1.8. Contextual title property •
Propiedad que aplica a los atributos.
•
Guarda la descripción que es significativa cuando se activa el contexto.
Ejemplo: Atributo CustomerAddress en un web form
La propiedad Contextual Title guarda la descripción significativa según el contexto. Por ejemplo, ¿qué descripción debe mostrar el atributo CustomerAddress en un determinado form? Si el atributo está en el propio form de la transacción Customer (donde es definido) entonces se mostrará simplemente la descripción Address. Pero si el atributo está, por ejemplo, en el form de la transacción Bill, entonces la descripción mostrará Customer Address. El título contextual por defecto se obtiene de la siguiente forma; • Si el nombre del atributo contiene solamente una palabra (las palabras se determinan por las letras mayúsculas): Se utiliza el propio nombre para el título.
Address →Address • Si el nombre contiene dos palabras: Se utiliza la segunda palabra para el título.
CustomerAddress → Address • Si el nombre contiene más de dos palabras: Se utilizan las dos últimas palabras para el título. Página 10 de 143
Curso GeneXus X Año 2011
CustomerBirthDate → Birth Date Se vizualiza el form de la transacción Customer:
Si se visualiza el form de la transacción Bill:
Página 11 de 143
Curso GeneXus X Año 2011
Capitulo 2 Transacción En este capítulo veremos cómo diseñar y definir objetos de tipo Transacción, a la vez que se introducen los conceptos de Dominios, Normalización y Tabla Extendida. El análisis de toda aplicación GeneXus comienza con el diseño de las transacciones. Las transacciones permiten definir los objetos de la realidad. Para identificar cuáles transacciones deben crearse, se recomienda prestar atención a los sustantivos que el usuario menciona cuando describe la realidad. Además de tener por objetivo la definición de la realidad y la consecuente creación de la base de datos normalizada, las transacciones, al igual que la mayoría de los objetos GeneXus que estudiaremos, provocan la generación de programas. En particular los programas correspondientes a las transacciones tienen por objeto permitir dar altas, bajas y modificaciones en forma interactiva en las tablas que tengan implicadas, controlando estos programas la integridad referencial de los datos.
1.9. 2.9.1.
Generalidades Definición Objeto a partir del cual GeneXus creará en forma automática la base de datos en 3era forma normal.
2.9.2. • •
Funciones
Describen las visiones de los usuarios. Contienen toda la información necesaria acerca de los datos de la aplicación y de cómo los usuarios accederán al sistema para su manejo (insertar, modificar y eliminar).
2.9.3.
Elementos que la componen
Algunos elementos de las transacciones, que iremos viendo son: •
Structure: Permite definir los atributos (campos) que componen la transacción y la relación entre ellos. A partir de la estructura de las transacciones, GeneXus inferirá el diseño de la base de datos: tablas, claves, índices, etc.
•
Web Form: Cada transacción contiene un Form (pantalla) Web mediante el cual se realizarán las altas, bajas y modificaciones en ambiente Web.
•
Win Form: ídem, pero para ambiente Win.
•
Rules: Las reglas permiten definir el comportamiento particular de las transacciones. Por ejemplo, permiten definir valores por defecto para los atributos, definir chequeos sobre los datos, etc.
•
Events: Las transacciones soportan la programación orientada a eventos. Este tipo de rogramación permite definir código ocioso, que se activa en respuesta a ciertas acciones provocadas por el usuario o por el sistema.
•
Variables: Permite la definición de variables que serán locales a la Transacción.
•
Help: Permite la inclusión de texto de ayuda, para ser consultado por los usuarios en tiempo de ejecución de la transacción.
•
Documentation: Permite la inclusión de texto técnico, para ser utilizado como documentación del sistema. Página 12 de 143
Curso GeneXus X Año 2011
•
Patterns: (patrones) que pueden ser aplicados a la Transacción con el fin de implementar en forma automática cierta funcionalidad.
•
Propiedades: (se visualizan en ventana aparte) Permiten definir ciertos detalles referentes al comportamiento de la transacción.
Algunos de estos elementos también están asociados a otros tipos de objetos GeneXus.
1.10.
Estructura La estructura de una transacción permite definir qué atributos la integran y cómo están relacionados.
Ejemplo: Se necesita registrar información de proveedores. Se define transacción “Supplier”, con estructura: { SupplierId* Identificador de proveedor SupplierName Nombre de proveedor SupplierAddress Dirección de proveedor SupplierPhone Teléfono de proveedor } A modo de ejemplo, si en una aplicación se necesita registrar información de proveedores, claramente habrá que definir una transacción, a la que podemos dar el nombre “Supplier”, y su estructura podría ser la siguiente: {SupplierId* SupplierName SupplierAddress SupplierPhone } Esta lista de nombres (uno de los cuales está sucedido del símbolo asterisco) corresponde a los atributos que interesa mantener acerca de los proveedores. Entonces, creamos una transacción de nombre “Supplier” cuya estructura se compone de los atributos SupplierId, SupplierName, SupplierAddress y SupplierPhone. Esto significa que cada proveedor se identificará por un código SupplierId (lo que queda determinado por el asterisco a continuación del atributo1), tendrá un nombre SupplierName, una dirección SupplierAddress y un teléfono SupplierPhone. Para cada atributo definido en la estructura, deberemos indicar cosas tales como su tipo de datos, descripción y algunos detalles más que veremos. Vista de la estructura en GeneXus:
a.
Atributos Clave
Página 13 de 143
Curso GeneXus X Año 2011
En la página anterior hemos explicado que el asterisco a continuación del atributo SupplierId indica que se trata del identificador de la transacción. Toda transacción debe tener un identificador, esto es, un atributo o conjunto de atributos que definan la unicidad. En el ejemplo no podrán existir dos proveedores con el mismo valor de SupplierId. En definitiva se trata del concepto de clave primaria, en tanto, para hacer la elección de los atributos que la componen, se deben tener en cuenta los requisitos del objeto de la realidad. En los casos en los cuales no se pueda determinar un identificador, se debe optar por crear un atributo artificial (no existente en la realidad), y que su valor se asigne internamente, por ejemplo, en forma correlativa. Como se puede observar en el editor de transacciones de GeneXus, un ícono de llave representa el asterisco que nosotros utilizamos como notación teórica. b.
Atributo “Descriptor”
El ícono con una lupa representa al atributo que mejor describe o representa a la transacción. En otras palabras sería el atributo que tiene mayor carga semántica en la transacción. Por defecto el primer atributo en la estructura de la transacción que sea de tipo de datos character, se definirá como “atributo descriptor”. Es posible definir a otro atributo como descriptor utilizando el menú popup correspondiente, así como no definir ninguno.
Ejemplo: Se necesita registrar información referente a facturas de venta. Invoice { InvoiceId* Identificador de factura InvoiceDate Fecha de factura CustomerId Identificador de cliente CustomerName Nombre de cliente Detail { ProductId* Identificador de producto ProductDescription Descripción de producto ProductPrice Precio de producto InvoiceDetailQuantity Cantidad de producto llevada en la línea InvoiceDetailAmount Importe de línea de factura } InvoiceAmount Importe total de la factura }
c.
Niveles de una transacción
La transacción “Invoice” consta de dos niveles: el primer nivel queda implícito por lo que no necesita un juego de llaves; el segundo nivel corresponde al conjunto de atributos que se encuentra entre llaves1. El hecho de definir un segundo nivel significa que existen varias instancias del mismo, para cada instancia del nivel anterior. En el ejemplo, un cabezal de factura tiene varios productos. Cada nivel de una transacción define un grupo de atributos que deben operar en conjunto, es decir, se ingresan, se eliminan o se modifican conjuntamente en la base de datos. Llamaremos transacción plana a una transacción de un solo nivel. Así, la transacción “Supplier” es una transacción plana. En cambio, la transacción “Invoice” tiene dos niveles. Es común hablar de “cabezal” para referirnos al primer nivel y de “líneas” para referirnos al segundo. Para cada nivel de la transacción, se debe indicar cuáles de sus atributos actúan como identificador. El identificador de cada nivel puede estar compuesto de un solo atributo, como es el caso de las transacciones que hemos visto hasta ahora, o puede estar conformado por varios atributos. En la transacción “Invoice” el atributo InvoiceId es el identificador del primer nivel, y el atributo ProductId es el dentificador del segundo nivel. Esto último significa que para un número de factura dado, InvoiceId, no puede repetirse el valor del atributo ProductId en distintas líneas. Una transacción puede contener varios niveles paralelos, así como anidados.
Página 14 de 143
Curso GeneXus X Año 2011
En GeneXus queda visualmente claro el nivel correspondiente a las líneas de la factura. A cada nivel de una transacción se le debe asignar un nombre, tipo1 y descripción (salvo al primer nivel, que recibe como nombre el de la transacción).
2.10.1.
Niveles paralelos y anidados
Una transacción puede tener varios niveles de anidación, así como niveles paralelos. Por ejemplo, en el hipotético caso de que una factura pueda abonarse en varios pagos, podríamos definir dos tipos de estructura, dependiendo de lo que se quiera representar: Invoice { Invoice { InvoiceId* InvoiceId* InvoiceDate InvoiceDate CustomerId CustomerId CustomerName CustomerName InvoiceAmount InvoiceAmount Detail Detail {ProductId* {ProductId* ProductDescription ProductDescription ProductPrice ProductPrice InvoiceDetailQuantity InvoiceDetailQuantity InvoiceDetailAmount} InvoiceDetailAmount Payment Payment {InvoicePaymentDate* {InvoicePaymentDate* InvoicePaymentAmount} InvoicePaymentAmount}} }}
Con la estructura de la izquierda se define que una factura tiene muchos productos y muchos pagos, pero no hay una relación directa entre los productos y los pagos (a no ser el hecho de pertenecer a la misma factura). En la estructura de la derecha se registran los pagos por producto llevado. Es sencillo comprender que el segundo y tercer nivel de la transacción de la izquierda, son paralelos. Ambos se encuentran anidados al primer nivel, pero entre ellos, son paralelos. En la estructura de la derecha, son todos niveles anidados.
2.10.2.
Definición del modelo de datos
Página 15 de 143
Curso GeneXus X Año 2011
GeneXus utiliza la estructura de las transacciones para capturar el conocimiento necesario para definir automáticamente cuál es el modelo de datos que debe crear. Para poder realizar la normalización de la base de datos llevándola a 3era. forma normal, GeneXus debe extraer las dependencias funcionales existentes entre los atributos definidos en la base de conocimiento. En la base de conocimiento de nuestro ejemplo, hemos definido a la transacción “Proveedores” y de su estructura GeneXus extrae las siguientes dependencias funcionales: SupplierId → {SupplierName, SupplierAddress, SupplierPhone} Dadas estas dependencias funcionales, GeneXus determina que debe crear una tabla que tendrá por defecto el mismo nombre que la transacción (SUPPLIER)1, y que estará conformada ni más ni menos que por los cuatro atributos anteriores, siendo SupplierId la clave primaria de la misma:
SUPPLIER
SupplierId
SupplierName
SupplierAddress
SupplierPhone
Diremos que la transacción “Supplier” tiene asociada la tabla SUPPLIER en el entendido de que cuando se ingresen, modifiquen o eliminen datos por medio de la transacción, éstos se estarán almacenando, modificando o eliminando físicamente en la tabla asociada. A partir de la estructura de la transacción “Invoice”, GeneXus determina que debe crear dos tablas: Tabla INVOICE, correspondiente al primer nivel de la transacción:
INVOICE
InvoiceId
CustomerId
CustomerName
InvoiceDate
InvoiceAmount
Clave primaria: InvoiceId Tabla INVOICEDETAIL correspondiente al segundo nivel de la transacción:
INVOICEDET AIL
Invoice Id
Product Id
ProductDescript ion
ProductPri ce
InvoiceDetailQua ntity
Clave primaria: {InvoiceId, ProductId} Clave foránea: InvoiceId ya que las dependencias funcionales son: InvoiceId → {CustomerId, CustomerName, InvoiceDate, InvoiceAmount} {InvoiceId, ProductId} ProductPrice, InvoiceDetailQuantity, InvoiceDetailAmount}
InvoicedDetailAm ount
{ProductDescription,
Observemos que la clave primaria de la tabla INOVICELINE es la concatenación del identificador del primer nivel, InvoiceId, con el identificador del segundo nivel, ProductId. El caso es general: la clave primaria de la tabla correspondiente a un nivel n de una transacción se obtiene de concatenar los identificadores de los n-1 niveles anteriores anidados, con el identificador de ese nivel. Página 16 de 143
Curso GeneXus X Año 2011
GeneXus asigna un nombre predeterminado a las tablas que crea. A la tabla asociada al primer nivel de una transacción le asigna el mismo nombre que el de la transacción; y a las tablas de niveles subordinados les asigna la concatenación de los nombres de los niveles. Por esto es que la tabla asociada al segundo nivel de la transacción “Invoice” recibe el nombre INVOICEDETAIL, dado que el nombre del primer nivel es el de la transacción, INVOICE, y el del segundo nivel es DETAIL. Los nombres de las tablas pueden ser modificados por el analista GeneXus cuando así lo desee.
Al definir las nuevas transacciones:
Luego de haber modelado la transacción “Invoice”, nos damos cuenta que hay información de clientes y de productos que nos interesa mantener independientemente de las facturas. Es decir, los clientes y los productos son dos objetos de la realidad independientes de las facturas, por lo tanto creamos las dos nuevas transacciones “Customer” y “Product” detalladas arriba. a.
Dependencias funcionales Con estas nuevas transacciones definidas, aparecen nuevas dependencias funcionales:
CustomerId -> {CustomerName, CustomerAddress, CustomerGender, CustomerStatus} ProductId -> {ProductDescription, ProductPrice, ProductStock} que conducen directamente a la creación de dos nuevas tablas: CUSTOMER
CustomerId
CustomerName
CustomerAddress
CustomerGender
Clave primaria: CustomerId Y PRODUCT
ProductId
ProductDescription
ProductPrice
ProductStock
Clave primaria: ProductId b.
Normailización: Cambios en las Tablas
El conjunto total de dependencias funcionales existentes requiere que deban realizarse algunas modificaciones en las tablas INVOICE e INVOICEDETAIL diseñadas previamente para que el diseño de la base de datos permanezca en 3era. forma normal1. Página 17 de 143
Curso GeneXus X Año 2011
Si representamos sobre las tablas CUSTOMER e INVOICE las dependencias funcionales encontradas para sus atributos:
Podemos ver claramente que INVOICE viola la 3era. forma normal (existe una dependencia funcional transitiva): InvoiceId → CustomerId CustomerId → CustomerName InvoiceId → CustomerName por lo tanto GeneXus normaliza, quitando el atributo CustomerName de la tabla INVOICE:
Ahora CustomerName Ahora veamos las dependencias funcionales encontradas en las tablas PRODUCT e INVOICEDETAIL:
Podemos percibir claramente que la tabla INVOICEDETAIL está violando la 3era. forma normal (existen atributos que están dependiendo en forma parcial de la clave primaria): ProductId → ProductDescription ProductId ProductPrice {InvoiceId, ProductId} → ProductDescription {InvoiceId, ProductId}
ProductPrice
Por lo tanto GeneXus normaliza, quitando los atributos ProductDescription y ProductPrice de la tabla INOVICEDETAIL:
ProductDescription y ProductPrice solo quedarán en la tabla PRODUCT.
c.
Relaciones: establecidas por los nombres de atributos
•
Conceptos iguales deben tener igual nombre
Página 18 de 143
Curso GeneXus X Año 2011
•
Conceptos diferentes NO deben tener igual nombre
Conceptos iguales deben tener el mismo nombre y conceptos diferentes deben ser nombrados diferentes. GeneXus establece las relaciones a través de los nombres de los atributos, de modo que cuando encuentra atributos de igual nombre en distintas transacciones, entiende que se trata del mismo concepto, y mediante dicho conocimiento es que puede normalizar. En el ejemplo que venimos viendo, cuando GeneXus encuentra el atributo de nombre CustomerId tanto en la transacción “Customer” como en la transacción “Invoice”, analiza que: el atributo se llama igual en ambas transacciones, así que se refiere al mismo concepto. En la transacción “Customer”, CustomerId está marcado como identificador, lo que significa que será clave primaria en la tabla física CUSTOMER; entonces en la tabla física INVOICE será clave foránea. El atributo CustomerName, por su parte, también se encuentra tanto en la transacción “Customer” como en la transacción “Invoice”, pero a diferencia de CustomerId, no está marcado como identificador en ninguna transacción del modelo; por tanto GeneXus entiende que se trata de un atributo secundario. Las dependencias funcionales indican que a CustomerName lo determina CustomerId: InvoiceId → CustomerId CustomerId → CustomerName así que GeneXus incluirá CustomerName en la tabla física CUSTOMER (y no en la tabla física INVOICE). Atributos primarios y secundarios Un atributo se califica como primario cuando el mismo es identificador en alguna transacción del modelo. En el ejemplo que venimos viendo, CustomerId es un atributo primario ya que es identificador en la transacción “Customer”. CustomerName, en cambio, es un atributo secundario ya que no es identificador en ninguna transacción del modelo. 38
Atributos almacenados e inferidos Al definir las transacciones “Customer” y “Product”, hemos incluido en ellas ciertos atributos que no hemos eliminado de la transacción “Invoice”. Los atributos CustomerId y ProductId, se incluyeron en las transacciones “Customer” y “Product” respectivamente, y al ser denotados como identificadores de las mismas, pasaron a ser atributos primarios. El atributo CustomerName, por su parte, se agregó en la transacción “Customer”; y los atributos ProductDescription y ProductPrice se incluyeron en la transacción “Product”. Estos son atributos secundarios. Todos estos atributos han quedado en más de una transacción: se han dejado en la transacción “Invoice” tal como se habían definido en un principio, y se han incluido en las transacciones “Customer” y “Product” respectivamente, porque nos hemos percatado de la necesidad de crear estos objetos. Página 19 de 143
Curso GeneXus X Año 2011
A continuación presentamos las 3 estructuras de las transacciones en cuestión, para poder visualizarlas juntas:
Probablemente usted no comprenda la razón por la cual los atributos secundarios CustomerName, ProductDescription y ProductPrice se han dejado en la estructura de la transacción “Invoice”. La explicación es la siguiente: las estructuras de las transacciones no son equivalentes a estructuras de tablas físicas. En las estructuras de las transacciones se pueden incluir ciertos atributos que no estarán en la o las tablas físicas asociadas, ya que a la hora de reorganizar la base de datos GeneXus analizará el conjunto total de dependencias funcionales existentes en la base de conocimiento, y creará -o modificará, según el caso- las tablas físicas, dejándolas en 3ª forma normal. Ahora, ¿con qué finalidad hemos dejado los atributos secundarios CustomerName, ProductDescription y ProductPrice en la estructura de la transacción “Invoice”? Los hemos dejado para poder incluirlos en alguno de los forms (GUI-Windows y/o Web) asociados a la transacción “Invoice” y así poder visualizar los valores de dichos atributos en tiempo de ejecución. Dichos atributos, como hemos explicado, no quedarán almacenados ni en la tabla INVOICE, ni en la tabla INVOICEDETAIL; sin embargo, en tiempo de ejecución cuando el usuario ingrese a través de alguno de los forms (GUIWindows y/o Web) un valor de CustomerId (atributo que sí se almacenará en la tabla INVOICE siendo clave foránea), a continuación se mostrará el CustomerName correspondiente. Decimos que el atributo CustomerName es un atributo inferido en la transacción “Invoice”, ya que su valor no se encuentra almacenado en ninguna de las tablas asociadas a la transacción, sino que se infiere –es decir, se obtiene- de la tabla CUSTOMER, dado el valor del atributo CustomerId. Análogamente, los atributos ProductDescription y ProductPrice también son inferidos en la transacción “Invoice”, ya que no se encuentran almacenados en las tablas asociadas a la misma, sino que sus valores se infieren de la tabla PRODUCT, para ser mostrados en pantalla.
2.10.3.
Nombrado de atributos
Es conveniente usar padrones para los nombres de los atributos. • • •
• Facilitan la tarea de nombrado. • Facilitan la tarea de integración de bases de conocimiento. • Facilitan la lectura del código generado.
Nomenclatura GIK Componente de Entidad + Categoría [+ Calificador + Complemento]
Página 20 de 143
Curso GeneXus X Año 2011
Artech ha definido un estándar para la nomenclatura de atributos: el GeneXus Incremental Knowledge Base (GIK) que es utilizado por la comunidad de usuarios GeneXus. En esta nomenclatura, el nombre de un atributo se forma con 4 componentes (algunos opcionales, señalados entre paréntesis rectos): Componente de Entidad + Categoría [+ Calificador + Complemento] A continuación describimos en qué consiste cada componente: a.
Componente de Entidad (u Objeto):Una Entidad es un actor (ej: Customer), objeto o evento (ej: Vendor Invoice, factura de venta) que interviene en una aplicación dada, representado por una transacción Genexus2. Un Componente de Entidad, representa a cualquier nivel subordinado o paralelo que defina la entidad. Ejemplo: la factura tiene los siguientes componentes, Invoice (cabezal), InvoiceDetail (líneas de la solicitud). Se sugiere un largo de un entorno de 10 caracteres para representar el componente de la Entidad.
b.
Categoría: Es la categoría semántica del atributo, es decir, define el rol que el mismo asume dentro del objeto y dentro del entorno de la aplicación. Se sugiere que no supere los 10 caracteres. Ejemplos: Id (identificador), Code (código), Name (nombre), Date (fecha), Description (descripción), Price (precio), Stock.
c.
Calificador: Es cualquier adjetivo o adverbio, en el entorno de 10 caracteres, que agregue diferenciación conceptual al nombre del atributo para hacerlo único. En general refiere al texto que califica la categoría: Fecha de vencimiento, Ejemplos: Start (inicial), End (final), Due (vencimiento)
d.
Complemento: Texto libre hasta completar la cantidad de caracteres significativos (30) para el nombre. En la transparencia se muestran algunos ejemplos de nombres de atributos.
Nota 1: Las letras mayúsculas permiten establecer fronteras entre los componentes que forman a los nombres de atributos. Nota 2: Para cada componente se pueden utilizar la cantidad de caracteres que se deseen, aunque se sugiere utilizar 10 y que el nombre completo del atributo no supere los 30.
2.10.4.
Demo
Una vez creada la base de conocimiento: creación de las primeras transacciones.
Página 21 de 143
Curso GeneXus X Año 2011
Una vez creada la base de conocimiento (ver introducción teórica), la misma quedará abierta para que se empiecen a crear las transacciones. La creación de objetos, se realiza presionando Ctrl+N. Los objetos creados quedarán en la carpeta Objects que se puede ver en la Folder View de la ventana KB Navigator. Si se desea que el objeto a crear quede guardado en otra carpeta, se deberá posicionar en dicha carpeta y luego hacer clic con el botón derecho del mouse. En el menú elegir New → Object. También se podrá indicar la carpeta en el cuadro de creación de un objeto como se ve en la imagen. Se desplegará un diálogo en el cual se deberá elegir el tipo de objeto que se desea crear (en este caso el tipo de objeto: Transaction), dar un nombre al objeto que se está creando (por ejemplo: “Customer” o “Invoice”), una descripción larga, y la carpeta en la cual guardar el objeto.
Una vez creada la transacción, la misma quedará abierta para que se defina su estructura y dentro de ella sus atributos.
2.10.5.
Definición de atributos
Página 22 de 143
Curso GeneXus X Año 2011
Para definir un atributo, simplemente se debe digitar en el primer campo de una línea (o rama) de la estructura, el nombre del atributo que se desea crear. Mediante la tecla de tabulación se puede pasar a los siguientes campos para indicar el tipo de datos del atributo, así como su descripción, si admitirá valores nulos de la base de datos, y en el caso que el mismo vaya a ser una fórmula (concepto que veremos en breve), cómo calcularla. Con la tecla Enter se puede pasar a la siguiente línea, para definir otro atributo. Una vez definidos los atributos en la estructura de la transacción, solamente restará guardar / salvar los cambios. Si se necesita modificar el nombre de un atributo, su tipo de datos, descripción, nulabilidad, o fórmula, bastará con hacer doble clic sobre el campo implicado en la estructura, y el mismo se habilitará y se podrá editar. Luego se deberán guardar los cambios, nuevamente. Para indicar que uno o más atributos son identificadores en la transacción, se los debe seleccionar y presionar CTRL + K; en consecuencia aparecerán con un símbolo de llave. Para definir que comienza otro nivel en la transacción, se debe digitar CTRL + L, y automáticamente se producirá una indentación y el usuario deberá darle nombre a ese nivel, así como definir el nombre de su tipo de datos1 y una descripción para el nivel. Para indicar que un atributo de uno de los niveles de la transacción será el atributo “descriptor”, se lo debe seleccionar y presionar CTRL+D. GeneXus cuenta con menús pop up, que son menús que se abren cuando el usuario está posicionado en determinado contexto y da clic con el botón derecho del mouse. Por ejemplo, al hacer clic con el botón derecho del mouse sobre un atributo de la estructura, se abre un menú pop up sobre el atributo seleccionado, que ofrece varias utilidades, como por ejemplo indicar que el atributo es clave (opción “Toggle key”), quitarlo de la estructura, pasarlo a un siguiente nivel de anidación, etc. Cada atributo tiene propiedades. Algunas de ellas (las fundamentales y obligatorias) son las que se ofrecen directamente en la estructura para su ingreso “inline”. Para acceder al diálogo que permite configurar todas las propiedades de un atributo, se debe seleccionar el ítem Properties del menú contextual, o presionar la tecla F4. En la figura hemos cambiado la propiedad Autonumber que como veremos oportunamente, permite autonumerar atributos clave primaria. •
Name: Es el nombre del atributo. Se utiliza para identificarlo.
•
Description: La “Descripción” o más propiamente “Nombre largo” es una descripción ampliada del atributo. Debe identificar bien al atributo, con independencia del contexto, y principalmente debe ser entendible por el usuario final. Debe ser un identificador global del atributo, es decir, no se le debe asignar a dos atributos en la base de conocimiento la misma descripción, ya que será a través de esta descripción que el usuario final podrá seleccionar atributos para definir sus propias consultas a la base de datos, con el objeto de tipo query, ejecutando “reportes dinámicos” (tema bastante simple, pero que no se abordará en este curso). Relacionadas a esta propiedad, están las propiedades Title, Column Title y Contextual Title. Las 2 primeras por defecto toman el mismo valor que se especifica en Description, pudiéndose modificar: Página 23 de 143
Curso GeneXus X Año 2011
•
Title: La descripción que se ingrese aquí será colocada por defecto al lado del atributo cada vez que se utilice en salidas “planas” como en el primer nivel de los forms de las transacciones (veremos en breve los forms).
•
Column Title: La descripción que se ingrese aquí será colocada por defecto como título del atributo cada vez que se lo incluya en la columna de un grid (grilla) (en el caso de una transacción solo si se trata de un atributo inferido, como por ejemplo el atributo ProductDescription en la transacción Invoice)1.
•
Contextual Title: Cuando el atributo pertenece al segundo nivel de una transacción, y no es inferido (ejemplo InvoiceDetailQuantity en Invoice), el nombre de la columna del grid de la transacción será tomado de esta propiedad. Obsérvese que por defecto se toma de la propiedad Description, pero quitando el nombre de la transacción, pues se asume del “contexto”.
•
Type Definition
Based on: Permite asociarle un dominio2 al atributo. Al hacerlo, ciertas propiedades del atributo se deshabilitarán (como Data Type y Length) tomando los valores del dominio. En caso de que el atributo no pertenezca a un dominio, el programador dejará el valor ‘(none)’ en esta propiedad, y las correspondientes al tipo de datos del atributo estarán habilitadas para ser ingresadas.
Data Type: Permite indicar el tipo de datos del atributo. Aquí se podrá elegir uno de los tipos de datos soportados por GeneXus. Dependiendo del tipo de datos que se seleccione habrá ciertas propiedades, u otras, para configurar.
Length: Permite indicar el largo del atributo. Si en la propiedad Data Type se indica que el atributo es numérico, entonces se deberá tener en cuenta que el largo incluya las posiciones decimales, el punto decimal y el signo. Esta propiedad estará deshabilitada cuando el tipo de datos elegido no requiera establecer un largo (por ejemplo, si se trata del tipo de datos Date).
Decimals: Si en la propiedad Data Type se indica que el atributo es numérico, se ofrecerá esta propiedad, para que se especifique la cantidad de decimales. El valor 0 en este campo, indicará que se trata de un entero.
Signed: Si en la propiedad Data Type se indica que el atributo es numérico, se ofrecerá esta propiedad para que se indique si manejará signo o no. El valor por defecto es “False”, lo que indica que no se representarán valores negativos.
•
Validation
Value Range: Permite indicar un rango de valores válidos para el atributo. Por ejemplo: · 1:20 30: - significa que los valores válidos son entre 1 y 20; y 30 o mayor. · 1 2 3 4 - significa que los valores válidos son 1, 2, 3 o 4. · 'S' 'N' - significa que los valores válidos son 'S' o 'N'.
•
Picture: Permite indicar el formato de edición para la entrada y salida del atributo. Dependiendo del tipo de datos del atributo, aparecerán determinadas propiedades bajo esta categoría.
GeneXus provee más propiedades para los atributos que las recién mencionadas. Dependiendo del valor que se elija para determinada propiedad, se ofrecerán ciertas propiedades relacionadas, u otras. Recomendamos para la lectura de otras propiedades, acceder al Help de GeneXus. •
Control Info: A un atributo se le puede asociar un tipo de control. Los tipos de controles posibles son: - Edit - Radio Button - Check Box - Combo Box - List Box - Dynamic Combo Box - Dynamic List Box
La asociación de cierto tipo de control a un atributo, sirve para especificar el tipo de control por defecto que se utilizará para el atributo cada vez que se lo incluya en un form. Cuando se define un atributo con un tipo de datos básico, el tipo de control que tiene asociado es Edit, pudiendo el programador cambiarlo a cualquiera de los otros tipos. En general GeneXus elige el tipo de control para un atributo dependiendo de su tipo de datos. Si es un dominio enumerado, como veremos, elegirá Radio Button o Como Box, dependiendo de la cantidad de valores del dominio. Página 24 de 143
Curso GeneXus X Año 2011
En el grupo Control Info del diálogo de edición de las propiedades de un atributo es donde el programador podrá cambiar el tipo de control asociado al atributo y dependiendo del tipo de control seleccionado, se solicitará distinta información complementaria. Luego, cada vez que se agregue el atributo en un form se presentará automáticamente con el tipo de control que tenga asociado aquí. Nota En caso de asociar al atributo el tipo Edit, se podrá especificar que “disfrace” sus valores, mostrando los de otro atributo (propiedad InputType), e incluso que sugiera los valores posibles al usuario, a medida que éste vaya digitando (propiedad Suggest), entre otras. También se puede elegir el color de la fuente de letra que se desea asociar por defecto al atributo, así como el color de fondo, de modo que cada vez que se agregue el atributo en un form, se presente automáticamente con los colores que se le hayan asociado (ver grupo Appearance).
2.10.6. •
Tipos de Datos Numeric: Permite almacenar datos numéricos. Cuando se selecciona este tipo de datos se debe indicar la cantidad total de dígitos del número, la cantidad de posiciones decimales, y si permite signo o no. Ejemplo: Si definimos que el tipo de datos del atributo InvoiceAmount es numérico de largo 10, con decimales 2, y sin signo, estamos especificando que representará números con hasta 7 dígitos en la parte entera y 2 decimales (7 dígitos en la parte entera + punto + 2 dígitos para los decimales = 10 dígitos).
•
Character: Permite almacenar cualquier tipo de texto (caracteres y dígitos). Para este tipo de datos, se debe indicar el largo. Ejemplo: El atributo CustomerName que utilizamos para almacenar el nombre de un cliente, es de tipo Character y si sabemos que nunca un nombre tendrá más de 20 caracteres, podemos fijar el largo: 20.
•
Date: Permite almacenar una fecha. Ejemplo: El atributo InvoiceDate que utilizamos para almacenar la fecha de una factura, será de este tipo de datos. El formato a utilizar para las fechas (día-mes-año, mes-día-año), se configura en forma genérica para todo el modelo dentro de un tipo especial de objeto, el objeto Language correspondiente al lenguaje en el que se generará el modelo. El objeto “language” existe para poder tener una misma aplicación traducida en cualquier lenguaje. La elección de presentar el año con 2 dígitos o 4, se configura con la propiedad Picture de cada atributo.
•
Boolean: permite que un atributo o variable asuma los valores lógicos: True, False.
•
VarChar: Permite almacenar texto de largo variable. Su función, en contraposición al Character, es optimizar el almacenamiento en la base de datos. Equivalente a Character, salvo en la forma en que se almacena en la BD. Cuando se selecciona el tipo de datos VarChar en el diálogo de definición del atributo se agregan las 2 siguientes propiedades: Maximum Length y Average Length. La primera es para indicar el largo máximo de caracteres que se podrán almacenar, mientras que la segunda es para indicar el largo promedio de caracteres que se suele almacenar por lo general. Ejemplo: Cuando se define un atributo de tipo Character y largo 60, si se le ingresa un dato que ocupa 25 caracteres, la capacidad restante de almacenamiento del atributo (35 caracteres) se rellena con blancos. El tipo de datos Varchar, en cambio, optimiza el almacenamiento de la siguiente forma: se le define Average Length (por ejemplo: 25), y Maximum Length (por ejemplo: 60); entonces, si el dato tiene largo menor o igual que 25, se lo almacena en el campo (rellenado con blancos) mientras que en los casos que el dato sea de largo mayor que 25, se almacenan los primeros 25 caracteres en el campo, y el resto en un área de overflow. Como contrapartida a la ventaja de la optimización del almacenamiento, para los casos en que se utilice el área de overflow, será necesario realizar un acceso adicional tanto para la lectura como para la escritura del dato. El tipo de datos Varchar es equivalente al tipo Character en todos los sentidos, salvo en la forma en que se almacena en la base de datos. Se le pueden aplicar todas las funciones y operadores existentes para el tipo de datos Character. Si el DBMS no soporta este tipo de datos, se creará el atributo de tipo Character.
Página 25 de 143
Curso GeneXus X Año 2011
•
Long Varchar: Permite definir un atributo memo; es decir, se utiliza normalmente para almacenar textos largos, comentarios, etc. Al seleccionar este tipo de datos, se debe indicar un largo máximo. Existen dos funciones para manipular este tipo de datos: GXMLines y GXGetMLi. GXMLines retorna la cantidad de líneas que tiene un atributo Long Varchar, teniendo como parámetros el nombre del atributo, y la cantidad de caracteres que se desea considerar por línea. Ejemplo: &cantlin = GXMLines( AtribMemo, 40 ). GXGetMLi por su parte, extrae una línea del atributo Long Varchar (para luego imprimirla, por ejemplo); teniendo como parámetros el nombre del atributo, el número de línea deseado, y la cantidad de caracteres que se desea considerar por línea. Ejemplo: &txt = GXGetMLi( AtribMemo, 1, 40 ). Generalmente se usan estas 2 funciones en combinación, ya que primero se suele consultar la cantidad de líneas que tiene cierto atributo Long Varchar, indicando la cantidad deseada de caracteres por línea, y luego se prosigue extrayendo el contenido de las líneas, utilizando un bucle hasta llegar a la última línea, y de esta forma se imprimen, por ejemplo.
•
DateTime: Permite almacenar una combinación de fecha y hora. La propiedad Picture de este tipo de datos, permite elegir qué se desea mostrar de la fecha, y qué se desea mostrar de la hora. Acerca de la fecha se puede elegir: no manejarla, manejarla y presentar el año con 2 dígitos, o manejarla y presentar el año con 4 dígitos. Acerca de la hora se puede elegir: manejar solo 2 dígitos para la hora (no manejando minutos ni segundos), manejar 2 dígitos para la hora y 2 dígitos para los minutos (no manejando segundos), o manejar 2 dígitos para la hora, 2 dígitos para los minutos y 2 dígitos para los segundos. Los valores anteriores no afectan la forma de almacenar el tipo de datos sino específicamente la forma de aceptar o mostrar su contenido.Nota: En caso de no manejar la fecha, sino solo la hora, el valor de fecha que quedará en el campo será el mínimo soportado por el DBMS, y será reconocido por GeneXus como fecha vacía o nula. En lo que respecta a la hora, los valores no aceptados (minutos y/o segundos) serán almacenados con valor cero.
•
Blob: Ante el creciente manejo de imágenes digitalizadas, videos, planillas así como documentos de todo tipo, las aplicaciones requieren cada vez más mantener y trabajar con este tipo de información. El tipo de datos Blob permite almacenar esta diversidad de información en la propia base de datos, aprovechando así los diferentes mecanismos de integridad y control que proveen los DBMSs. Este tipo de datos solamente se puede utilizar cuando se cuenta con un DBMS.
1.11.
Variables
•
En todo objeto GeneXus es posible definir variables.
•
Las variables son únicamente visibles dentro del objeto; es decir, son locales.
•
Editor similar al de la estructura de las transacciones:
•
Es posible definir variables colección (de cualquier tipo de datos).
Página 26 de 143
Curso GeneXus X Año 2011
Para definir variables en determinado objeto, se debe seleccionar el selector Variables, como se muestra en la figura. Este selector muestra variables definidas por defecto (Standard variables, como por ejemplo la variable Today que contiene la fecha del sistema) para el objeto, y permite definir variables nuevas a través de un editor similar al de las transacciones. También se puede ir a la opción Insert de la menubar y elegir Variables. En el cuadro de diálogo que se desplegará seleccionar New Variable.
Este diálogo solicita el nombre de la variable, su descripción, tipo de datos y largo, o dominio, de forma análoga a cuando se define un atributo.
2.11.1.
Propiedades
La propiedad Dimensions permite indicar si la variable será escalar o si se tratará de un vector (1 dimensión) o matriz (2 dimensiones). El valor que ofrece por defecto esta propiedad es escalar. Comparten varias propiedades con los atributos: •
Data Type: aunque no todo tipo de datos válido para atributo también lo es para variable (ej: Blob) y viceversa (ej: tipos de datos estructurados).
Página 27 de 143
Curso GeneXus X Año 2011
•
Based On: Ofrece una forma rápida de definir una variable. Cuando se selecciona, se despliega un diálogo que muestra todos los atributos (y dominios) definidos en la base de conocimiento; en dicho diálogo, simplemente se debe seleccionar un atributo, y a continuación se definirá automáticamente una variable con el mismo nombre y la misma definición que el atributo.
Por otra parte, es posible definir variables dentro del editor de código de cada objeto (source, eventos, etc.), haciendo uso del menú contextual (botón derecho) luego de escribir el nombre de la variable. Esto es, al escribir &nombreDeVariable y presionar botón derecho del mouse. En el menú contextual luego elegir Add Variable.
2.11.2.
Variables colección
Es posible definir variables colección sobre cualquier tipo de datos ofrecido por GeneXus. Para eso alcanza con clickear la correspondiente casilla en el editor de variables, o indicar el valor True en la propiedad Collection asociada a las variables. Para recorrer luego los items coleccionados en dicha variable, se deberá utilizar la estructura de control “For in”. Por ejemplo, hemos definido la variable &myNumbers de tipo Numeric(4.0) Para recorrer los valores almacenados se deberá crear una nueva variable de tipo Numeric(4.0). Creamos entonces la variable &OneNumber, y declaramos: For &OneNumber in &myNumbers ---------Endfor
2.11.3.
Dominios
• Objetivo: Realizar definiciones genéricas. • ¿Cuándo debemos usar dominios? • Atributos y/o variables con la misma definición.
Ejemplo:
Es común tener en una base de conocimiento atributos que comparten definiciones similares pero que no tienen relación entre sí. Por ejemplo, es común definir todos los nombres como atributos de tipo character y largo 20; o todos los importes, como atributos de tipo numérico y largo 10.2. El objetivo de los dominios es permitir realizar definiciones genéricas. A modo de ejemplo, el atributo InvoiceDetailAmount es de tipo numérico y largo 10.2, y al mismo tiempo, el atributo ProductPrice es del mismo tipo y Página 28 de 143
Curso GeneXus X Año 2011
largo, en la misma base de conocimiento. De modo que podríamos definir un dominio de nombre Price, que sea de tipo numérico con largo 10.2, y luego uno Amount de tipo de datos el dominio Price anterior; a cada uno de los atributos anteriores le asignaríamos estos dominios. La ventaja de hacerlo así es que si en el futuro surge la necesidad de cambiar la definición de los atributos que representan importes, haríamos el cambio una sola vez (en el dominio Price), propagándose éste automáticamente a los atributos InvoiceDetailAmount y ProductPrice. Dominios enumerados: queremos mantener el estado del cliente: Active, On Hold, Closed → dominio Status
Luego atributo CustomerStatus de dominio Status Se trabaja con los nombres en lugar de con los valores: CustomerStatus = Status.Active Existe la posibilidad de trabajar con dominios enumerados (aquellos que representan valores finitos y particulares. Son muy conocidos en los lenguajes de programación. El caso de uso típico es para los días de la semana: cuando se necesita que una variable tome uno de los siguientes valores: Lunes, Martes, Miércoles,Jueves, Viernes, Sábado, Domingo). En nuestro caso, agregaremos a la estructura de la transacción Customer, un atributo que representa el estado del cliente en el sistema en un momento dado. Puede estar active, on hold o closed. Una forma de representarlo es definiendo un dominio Status, que corresponde al tipo de datos Character(1) pero para el que decimos explícitamente que no queremos que pueda tomar todos los valores de ese tipo de datos, sino solo 3: A, H y C, y además le decimos que queremos trabajar dándole nombres a estos 3 valores y trabajar con sus nombres. Así, a A le asociamos el nombre ‘Active’, a H ‘OnHold’ y a C ‘Closed’. Al asociarle al atributo CustomerStatus el dominio Status (observar que GeneXus automáticamente lo sugiere al ingresar el atributo en la estructura luego de haber definido el dominio, debido a la terminación del nombre del atributo) internamente en el atributo de la tabla se guardará uno de los 3 valores: A, H o C, pero nosotros no tendremos que recordar esos valores, sino sus códigos: Active, OnHold y Closed. Ya teníamos otro atributo con dominio enumerado: CustomerGender. En ese caso se componía de solamente 2 valores: Female y Male. Observar cómo el Control Info ofrece para ControlType un Combo Box en lugar de un Edit. Esto tomará sentido cuando veamos el Form de una transacción un poco más adelante.
Página 29 de 143
Curso GeneXus X Año 2011
Así como podemos asociarle a un atributo un dominio (y a otro dominio), también lo podemos hacer para una variable. Un mismo dominio puede asignarse tanto a atributos como a variables, ya que su objetivo es exactamente el mismo. ¿Cómo definir un dominio? Existen varios caminos: 1) Opción Domains de el Folder View: Mediante un editor similar al de las variables, se podrá definir un nuevo dominio. 2) Dado que en la pantalla de configuración de las propiedades de un atributo es donde se le asigna a un atributo un dominio existente, en dicha pantalla se ofrece un botón para crear un dominio nuevo. Ídem con el diálogo de definición de variables. 3) En la estructura de la transacción es posible definir un nuevo domino en el campo de definición del tipo de datos de un atributo, simplemente escribiendo sobre esa misma línea, el nombre del dominio, y asignándole el tipo de datos. Por ejemplo, digitando Price = Numeric(10.2) sobre la columna Type del atributo que se está definiendo, queda también definido el dominio de nombre Price, con el tipo de datos Numeric(10.2).
1.12.
Web Form
Para cada transacción, GeneXus crea un form web, que será la interfaz con el usuario. Es creado por defecto por GeneXus al momento de grabar la estructura de la transacción, y contienen todos los atributos incluidos en la misma, con sus respectivas descripciones, además de algunos botones. Si bien es creado por defecto, es posible modificarlo para dejarlo más vistoso, cambiar por ejemplo controles de tipo edit a otros tipos de controles, agregar y/o quitar botones, etc.
Página 30 de 143
Curso GeneXus X Año 2011
En el ejemplo se muestra el form Web correspondiente a la transacción “Invoice”. A través de este form el usuario final podrá ingresar, modificar y eliminar facturas en la aplicación Web. El control Error Viewer se utiliza para poder ubicar y programar el lugar donde queremos que los mensajes generales le sean desplegados al usuario final de una transacción Web. Podremos especificar entre otras cosas el lugar donde queremos que este control se ubique dentro del form, su tipo de letra, color, etc. Observar que el segundo nivel de la transacción aparece en el form como un control grid. Web Form de la transacción “Customer”
Paletas de herramientas para el diseño de Forms •
Insertar controles (Opción View de la Menubar \ Other Tool Windows \ Toolbox)
Página 31 de 143
Curso GeneXus X Año 2011
•
Cortar, copiar y pegar controles
Podemos definir un control como un área de la interfaz con el usuario, que tiene una forma y un comportamiento determinado. Existen distintos controles: - Texto: Permite colocar texto fijo - Atributo/Variable: Permite colocar atributos o variables. - Línea horizontal - Error Viewer - Table: Insertar tablas en el form - Grid: Permite definir grillas de datos. - Botón: Permite incluir botones en los forms. - Bitmap: Permite definir bitmaps estáticos, etc.
Paleta de herramientas para insertar controles: Cuando se está editando un form, se encuentra disponible una paleta de herramientas (Toolbox) que ofrece los controles posibles de insertar en el mismo. Simplemente se deberá seleccionar el control y arrastrarlo sobre el form. •
Cada control del Web form podrá tener una clase asociada, de un objeto theme (tema) determinado, asociado al objeto. Página 32 de 143
Curso GeneXus X Año 2011
•
Al crear una KB, se crea por defecto el tema “GenexusX” y todos los objetos que se creen tendrán este tema asociado.
•
Esto permitirá independizar el diseño de la interfaz, de la programación.
•
Cada tema tendrá definidas muchas clases para cada tipo de control.
•
El analista solo asocia un tema al objeto, y una clase a cada control del form y puede olvidarse del diseño de los mismos.
•
El control hereda el diseño de la clase del tema al que esté asociado.
Para lograr separar los aspectos de diseño gráfico de un sitio web, de los aspectos de programación propiamente dichos, existe un tipo de objeto llamado Theme (Tema, en español). El objetivo de esta separación es poder paralelizar el desarrollo de un sitio Web, permitiéndole al programador abocarse a las tareas de programación, y apoyarse en un diseñador gráfico para que defina las cuestiones de diseño. De esta manera el diseñador gráfico configurará el “tema” elegido por el programador, y el programador sólo deberá aplicarlo a los objetos de su base de conocimiento. Un objeto “tema” contendrá un conjunto de clases, para cada tipo de control de los que pueden aparecer en el form Web de un objeto GeneXus. Por ejemplo, tendrá varias clases para el control botón, algunas para el control atributo, una clase para el control Error Viewer, etc. Cuando se crea una base de conocimiento, automáticamente aparecerá dentro del Folder View la carpeta “Customization” con objetos Theme predefinidos que podrán importarse en la KB. Por defecto se importa el de nombre “GeneXusX”. Este tema contiene un conjunto de clases asociadas a los distintos controles existentes. Los objetos con form Web que se vayan creando tendrán por defecto asociado este Theme, y cada control que aparezca en sus forms tendrá asociado una clase de ese tema, para este control.
Página 33 de 143
Curso GeneXus X Año 2011
De este modo, cuando el analista crea la transacción “Customer” e ingresa su estructura, al grabar podrá apreciar que el form Web tendrá automáticamente el aspecto que se muestra en la pantalla de arriba a la izquierda. ¿Quién dio formato a todos los controles si no lo hizo el analista explícitamente? Pues bien, todas las transacciones tendrán asociado por defecto el theme “GeneXusX” y todos los controles que se coloquen en el form, tendrán clases de este tema asociadas. Si sobre el Text Block (control de texto) que se muestra arriba (Name), se editan sus propiedades (F4), se puede observar la propiedad Class, que tiene configurada la clase TextBlock. Esta propiedad puede ser cambiada por el analista. Al cliquear el combo box podremos ver una lista de clases posibles para ese control (son las que figuran en el tema asociado). A la derecha hemos cambiado la clase por una de nombre Title y en el form podemos ver la repercusión en el diseño del TextBlock. No entraremos en detalle en este tema en el presente curso.
1.13.
Modos en tiempo de ejecución
Al ejecutar una transacción se pueden distinguir los siguientes modos, dependiendo de la operación que se realice: •
Modo Insert: Indica que se está efectuando una inserción
•
Modo Update: Indica que se está efectuando una actualización
•
Modo Delete: Indica que se está efectuando una eliminación
•
Modo Display: Indica que se está efectuando una consulta
Dependiendo del ambiente de generación, habrá algunas diferencias en lo que se refiere a la operativa de las transacciones en tiempo de ejecución. No obstante, más allá de la plataforma, cada vez que se realice una operación de inserción, actualización, eliminación, o consulta a la base de datos a través de una transacción, habrá un modo asociado.
Página 34 de 143
Curso GeneXus X Año 2011
Siempre se mostrará un número de líneas vacías fijas para ser ingresadas por el usuario (valor configurable por el analista a nivel del grid, en su propiedad “Rows”). Por defecto se muestran 5 líneas vacías. Si el usuario ingresó las 5 líneas y necesita ingresar más, le bastará con presionar New Row., o simplemente presionar Enter y se agregará una nueva línea. Para poder eliminar líneas en ejecución, GeneXus incorpora una columna del lado izquierdo del grid, que aparece con una cruz por cada línea ingresada. Presionando sobre la cruz, se elimina la línea.
1.14.
Master Pages
Las Master Pages proveen una forma de centralizar el layout y el comportamiento común en un solo objeto y reutilizarlo en todo otro objeto sin tener que programar.
Tener un look & feel consistente es hoy en día un deber de toda aplicación Web. Crear y mantener cada página de una aplicación Web asegurando la consistencia con el resto del sitio toma gran tiempo de programación. Al crear una base de conocimiento GeneXus X creará también dos objetos de tipo Master Page: •
AppMasterPage: Para la aplicación.
•
PromptMasterPage: Para los prompts.
Las Master Pages proveen una forma de centralizar el layout y el comportamiento común en un solo objeto y reutilizarlo en todo otro objeto sin tener que programar. Esto significa que la modificación de alguna parte del layout o del comportamiento común es tan fácil como modificarla en un único objeto y ¡listo!. En una misma base de conocimiento se pueden definir tantas Master Pages como se desee.
Página 35 de 143
Curso GeneXus X Año 2011
Cuando estudiemos el objeto Web Panel comprenderemos que una Master Page será en particular un Web Panel categorizado como “Master Page” con todo lo que sea el Layout y comportamiento común a todas las páginas del sitio; dentro de este objeto se dejará un espacio para cargar en cada oportunidad la página que corresponda (el contenido variable del sitio). Eso se hará en el control presente en el form de nombre Content Placeholder. Propiedad Master Page
Las páginas web que implementan el contenido variable, se asocian a la Master Page, de manera que cada vez que se ejecuten, se carguen con ese “contexto” (el de la Master Page).
Página 36 de 143
Curso GeneXus X Año 2011
Capitulo 3 Transacción 3.1. ¿Cómo ejecutar la aplicación? Opción Build \ Run Developer Menu, o presionar la tecla F5.
Recordemos que al momento de creación de la KB, se indicó el generador por defecto a utilizar. Ahora se debe completar la información necesaria para terminar de definir el ambiente de implementación. •
Database name: Nombre de la base de datos que estará asociada a la aplicación.
•
Server name: Nombre del servidor de base de datos que la manejará.
•
Use trusted connection: Yes
No (deberá indicarse usuario y contraseña válido en el DBMS) Consideraciones: Si la base de datos no existiera, GeneXus la creará, siempre y cuando el usuario y contraseña configurados en la ventana de diálogo tengan permiso de DBCreator en el DBMS.
3.2. Proceso de Build El proceso de Build incluye todas las tareas necesarias para la ejecución de la aplicación: Verificación de cambios en la BD, Reorganización (si es necesario), Especificación, Generación y Compilación. No incluye la ejecución.
Página 37 de 143
Curso GeneXus X Año 2011
El proceso de Build se ejecuta en background, permitiendo realizar otras tareas mientras el mismo está corriendo, por ejemplo continuar con el desarrollo. Cada vez que se ejecuta la aplicación (F5), GeneXus realiza una comparación entre las definiciones actuales de todos los objetos y las definiciones de la última ejecución. Esta comparación se llama análisis de impacto. Este nombre es autodescriptivo: GeneXus analiza el impacto causado por las nuevas definiciones, y el resultado del análisis de impacto es un reporte de análisis de impacto (IAR: Impact Analisis Report) que informa al programador qué cambios físicos o estructurales habría que realizar en la base de datos asociada. Si por ejemplo se creó una nueva transacción, esto provocará (tal como es el objetivo de las transacciones) que se creen las tablas correspondientes en la base de datos (ver siguiente página).
3.3. IAR y Reorganización
En el ejemplo, tras crear las 2 transacciones “Customer” e “Invoice” y dar F5, aparece el reporte de análisis de impacto que puede verse, donde se listan entre otras cosas, la estructura que tendrá cada tabla que se creará, con sus atributos, tipos de datos, los índices que se crearán sobre las tablas, las claves foráneas (observar que el reporte de la tabla INVOICE dice que se referenciará la tabla CUSTOMER (esto se debe al atributo CustomerId de la transacción Invoice que se llama igual que el atributo CustomerId de CUSTOMER, constituyendo por tanto una clave foránea a dicha tabla). Si el programador está de acuerdo con los cambios estructurales informados en el reporte de análisis de impacto, podrá proseguir, pasando a reorganizar la base de datos. El término reorganizar refiere a efectuar cambios físicos en la base de datos. Si en cambio el programador encuentra que algo de lo informado en el reporte de análisis de impacto no era lo que pretendía lograr, podrá no efectuar la reorganización, y efectuar en cambio las redefiniciones que crea convenientes. Cuando se decide efectuar una reorganización GeneXus genera programas (en el lenguaje elegido cuando se creó la KB) que implementan las modificaciones a realizar en la base de datos. La ejecución de estos programas tiene por resultado la obtención de una nueva versión de la base de datos con los cambios efectuados. El siguiente paso será obtener los programas de aplicación asociados a los objetos GeneXus. Para ello GeneXus deberá especificar, generar y compilar los programas de aplicación. Especificar un objeto significa que GeneXus analizará todo lo definido en cada uno de los elementos que lo componen: estructura, forms, u otros elementos según corresponda. GeneXus verificará la sintaxis de las definiciones, la validez de lo definido, y como resultado de la especificación mostrará al usuario un listado de navegación, en el cual informará la lógica que ha interpretado, y si hay alguna advertencia o error. Generar un objeto, significa que GeneXus escribirá líneas de código que implementen la programación del mismo, en el lenguaje elegido. Página 38 de 143
Curso GeneXus X Año 2011
Compilar los programas significa que el código escrito (generado) sea convertido a lenguaje máquina (binario) para que puedan ser ejecutados.
3.4. Ejecución
Opción “Build” de la barra de menú de GeneXus
Opciones del ítem “Build”: • Build All y Rebuild All: Estas opciones se utilizan cuando no se está seguro del impacto de los cambios y se necesita tener actualizadas las últimas definiciones. La opción Build All realiza todas las acciones que estén pendientes, mientras que Rebuild All forzará todas ellas. • Las acciones son: Salvar todos los objetos que no estén salvados. Reorganizar la base de datos, si es necesario. Especificar solo los objetos que han sido modificados (si se seleccionó Build All), o forzar la especificación de todos (si se seleccionó Rebuild All). Generar los objetos Compilar los objetos definidos Main Compilar el Developer Menu • Build / Rebuild Developer Menu: Similar a las opciones anteriores pero aplicadas solamente al Developer Menu. No lo ejecuta. • Run Developer Menu: Ejecuta todas las acciones que estén pendientes y ejecuta el Developer Menu. Salva todos los objetos que no estén salvados. Reorganizarla base de datos, si es necesario. Especifica solo los objetos que han sido modificados. Página 39 de 143
Curso GeneXus X Año 2011
•
•
• • •
Genera los objetos Compila y ejecuta el Developer Menu Build / Rebuild / Run options: Opciones que aplican a objetos definidos como Main. Disparan las siguientes acciones: Salvan los objetos que no estén salvados. Reorganizan la base de datos, si es necesario. Se especifican solamente los objetos que sufrieron cambios (si se seleccionó Build), o se fuerza la especificación de todos los objetos pertenecientes al árbol de llamadas del objeto Main (si se seleccionó Rebuild). Generación Compilación del objeto Main Ejecución del objeto Main (si se seleccionó la opción Run) Build / Run with this only options: Build y ejecución del objeto definido como Startup. Por defecto el objeto Startup es el Developer Menu. Salvan todos objetos que no estén salvados. Reorganizan la base de datos, si es necesario. Especifican solamente el objeto seleccionado. Generan solamente el objeto seleccionado. Compilan el objeto Startup Se ejecuta el objeto Startup (si se seleccionó Run). Set As Startup Object: El objeto indicado pasará a ser el objeto Startup de la aplicación, o sea el objeto que finalmente se ejecutará cuando se presione la tecla F5. Por defecto el objeto Startup es el Developer Menu. Create Database Tables: Crea nuevamente las tablas. Se pierden los datos que pudieran estar almacenados previamente. Impact Database Tables: Se realiza un impacto sobre las tablas de la base de datos.
Página 40 de 143
Curso GeneXus X Año 2011
Capitulo 4 Patterns 4.1.
Escenario
Teniendo la transacción Country y la transacción Customer, queremos lograr hacer la aplicación más vistosa, con consultas con vistas más completas y con algún aumento de funcionalidad. Por ejemplo, trabajar con los países de una forma más vistosa y amigable que la que brinda la sola transacción. Visualizar en un grid los países existentes, con la posibilidad de filtrar por nombre de país, y fijando paginado al grid, de forma tal que muestre un número fijo de registros por página...
...así como poder ingresar un nuevo país (mediante la transacción Country), o seleccionar uno de los mostrados en el grid, para poder modificarlo o eliminarlo...
Página 41 de 143
Curso GeneXus X Año 2011
...o incluso ver la información completa de ese país, incluyendo los clientes asociados...
4.2.
Generalidades
Una vez aplicado el patrón, todos los objetos generados quedan como parte de la base de conocimiento. Es natural al desarrollar aplicaciones, tener que resolver partes muy similares pero no exactamente iguales. Por ejemplo, si en una Base de Conocimiento se tienen modelados los objetos de la realidad Customers y Countries, a pesar de ser dichos objetos bien diferentes, los “Work With Customers” y “Work With Countries” respectivamente, tienen muchas cosas en común: un grid en el form, un conjunto de variables para utilizar en filtros, opciones de ordenamiento de la consulta, invocaciones a la transacción correspondiente para actualizar la base de datos, etc.. Surgen entonces los Patterns, que ofrecen la posibilidad de g q p aplicar un patrón (pattern) a las instancias que se deseen de una Base de Conocimiento, y generar todos los objetos GeneXus necesarios para implementar cierta funcionalidad, teniendo en cuenta sus datos específicos. Siguiendo con el ejemplo mencionado inicialmente, es posible aplicar el patrón “Work With” a la Base de Conocimiento, de forma tal que partiendo de las transacciones “Customer” y “Country”, se obtenga todo el desarrollo correspondiente al “Work With Customers” y “Work With Countries” para ambiente web (pantallas vistosas que implementan las consultas, con ordenamientos, filtros, invocaciones a las transacciones correspondientes, y más).
4.3.
Patterns Work With
Página 42 de 143
Curso GeneXus X Año 2011
La pantalla Work With ofrece: •
Consulta interactiva
•
Múltiples ordenamientos
•
Filtros
•
Invocación a la Transacción en los diferentes modos (insert, update, delete, display)
•
Posibilidad de incluir invocadores propios a objetos
•
Link en cada línea de la grilla a la pantalla ‘View’
La pantalla View muestra: •
La información del registro seleccionado en la grilla Work With
•
Un tab control con:
•
Un tab con la información del registro
•
Un tab por cada tabla subordinada a la tabla base del registro en la grilla Work With.:
Aplicación
Para aplicar el pattern sin demoras, alcanzará con editar la instancia (selector “Work With” de la transacción), marcar el check box y grabar ¡Listo! grabar. Con eso se crearán automáticamente los objetos GeneXus que implementan el pattern (en particular la pantalla de selección y filtro y la pantalla de View que mostramos antes). Asimismo se modificará la transacción para que ahora reciba por parámetro el modo (Insert, Update, Delete, Display) y el país. Página 43 de 143
Curso GeneXus X Año 2011
Objetos generados Consecuencia: Se generan en la KB los objetos que ya vimos en ejecución ¿Dónde? En el folder view, bajo la propia transacción
Una vez grabada la instancia, en el folder view, bajo el nombre de la transacción, aparecerá el nombre del pattern aplicado a la misma (en nuestro ejemplo WorkWithCountry) y todos los objetos que GeneXus debe crear para implementarlo. En nuestro caso se crearán 2 objetos de tipo Web Component, y 2 objetos de tipo Web Panel. Son muy similiares. La diferencia es que un Web Component puede incluirse dentro de otro objeto. No entraremos en detalles en este momento. Ejemplo Aplicación del pattern Work With a la transacción Country
Al aplicar el pattern Work With a la transacción Country, se creará: • Pantalla Work With: • Tendrá una grilla con los atributos de la transacción: CountryId y CountryName. • Se podrá ordenar y filtrar por CountryName, por ser CountryName el atributo descriptor. • Se podrá invocar a la transacción en los diferentes modos (Insert, Update, Delete). Página 44 de 143
Curso GeneXus X Año 2011
• Pantalla View: Mostrará dos tabs. El primer tab tendrá la información del país seleccionado, y el segundo tendrá la información de los clientes pertenecientes a dicho país (puesto que existe una relación 1-N entre las tablasCountry yCustomer asociadas a lastransacciones deigual nombre). Comenzando a asociar la pantalla work with
Son muchas las propiedades que se ofrecen en las instancias correspondientes al patrón Work With, para personalizar el comportamiento de los objetos que se generarán. A continuación describimos algunas de ellas. El nodo Selection ofrece las propiedades relacionadas a la pantalla Work With que se generará para la instancia. Sus sub-nodos son: Modes (Ins, Upd, Del, Dis) Este nodo permite definir en cuáles modos se ofrecerá invocar a la transacción. Las posibilidades y sus valores por defecto son: Insert: True Update: True Delete: True Display: False En lainstancia aparece <default> al lado de cada una de las propiedades anteriores. ¿Dónde se configura este valor por defecto? Lo veremos unas páginas más adelante. Para cada modo podrá especificarse una condición. Se proveen las siguientes propiedades para ese propósito: Insert Condition, Update Condition, Delete Condition, Display Condition. Si se define una condición asociada a un modo, la invocación para ese modo solo se habilitará si la evaluación de la condición es verdadera ( Ejemplo: CountryId=10). Personalizar … Pantalla Work With • • • •
Agregar, eliminar, ocultar atributos o variables del grid Especificar modos de invocación de transacción Definir Órdenes y Filtros Agregar, eliminar, modificar acciones.
Página 45 de 143
Curso GeneXus X Aรฑo 2011
Attributes Este nodo permite definir cuรกles atributos se desean mostrar en el grid (y para cada atributo, se pueden personalizar sus propiedades). Por defecto muestra todos los atributos de la estructura de la transacciรณn. Orders Es posible ofrecer al usuario final varios รณrdenes posibles para ver el resultado de la consulta (es decir, las lรญneas mostrando los datos en el grid). Utilizando el botรณn derecho del mouse se puede definir un nuevo orden (su nombre y composiciรณn). Cada orden puede estar compuesto por varios atributos (pudiendo indicar para ada uno de ellos si se desea orden ascendente o descendente). Se presentarรก un combobox en la pantalla Work With ofreciendo todos los รณrdenes posibles de seleccionar, para que el usuario final elija uno y los datos se presenten en el grid ordenados por el mismo. Sin embargo, el control grid ya presenta la posibilidad de ordenar en ejecuciรณn, simplemente cliqueando sobre la columna por la que se desean tener ordenados los datos. Pero hay que tener en cuenta que esa funcionalidad solo permite ordenar en ejecuciรณn los datos de la pรกgina del grid que se estรก mostrando. Filter Este nodo permite definir condiciones de filtro, para que en el grid se muestren solo los registros que cumplan con las mismas. Actions El nodo Actions permite incorporar acciones propias a la pantalla Work With. Es decir, permite agregar botones (dentro o fuera del grid) que invoquen a los objetos que se indiquen, con sus correspondientes parรกmetros. Si bien el nodo Actions no estรก visible por defecto, estando posicionado en el nodo Selection y presionando el botรณn derecho del mouse, se ofrecerรก la opciรณn Add Actions que lo agregarรก. Una vez agregado este nodo, estando posicionado sobre el mismo y presionando el botรณn derecho del mouse, se ofrecerรก la opciรณn Add Action que permitirรก agregar una acciรณn con su nombre de acciรณn, caption, objeto invocado etc Ejemplo: 1)
Ocultar atributo CountryId del grid
Pรกgina 46 de 143
Curso GeneXus X Año 2011
El atributo CountryId, a diferencia del CountryName, no puede ser eliminado del grid, debido a que es el atributo que se envía a la transacción ‘Country’ cuando el usuario desea modificar o eliminar el país mostrado en una línea del grid del Work With. 2)
Eliminar modo Delete desde el grid
Al editar las propiedades estando posicionados en el nodo de la instancia que se muestra, podemos observar que cada uno de los modos en que se puede invocar una transacción (para insertar, modificar, eliminar o incluso deplegar) están listados como propiedades. Podemos ver también que aparece una propiedad Export que permite exportar los datos a una planilla excel. Otra vez aquí podemos apreciar que cada una de las propiedades tiene el valor <default> que aún no sabemos de dónde es tomado. Pero si queremos fijar un valor independiente de cuál sea el default, podemos editar el combo box que presentará tres valores: <default>, ‘true’, o ‘false’. Hemos fijado el valor de la propiedad Delete en ‘false’. Podemos ver en ejecución la repercusión. Ya no aparece en el grid la primera columna que contenía la imagen que permitía eliminar el país. Ahora no se podrá desde esta pantalla eliminar países. Obsérvese también cómo ha desaparecido del grid el atributo CountryId. Lo habíamos ocultado en la página anterior. 3)
Agregar una Acción a la pantalla work with countries que invoque al proceso de facturación: BillingsProcess
Página 47 de 143
Curso GeneXus X Año 2011
Ya habíamos definido el objeto BillingProcess para realizar la facturación del mes a todos los clientes. Aquí estamos agregando un botón fuera del grid, que al presionarlo llama a este otro objeto GeneXus que habíamos creado antes. Asociando .. pantalla view
Página 48 de 143
Curso GeneXus X Año 2011
El nodo View por su parte, ofrece las propiedades relacionadas a la pantalla View que se generará para la instancia Muestra toda la información instancia. de un registro, que fue seleccionado en el grid del Work With (la información del registro es mostrada en una solapa de un tab control, y además hay una solapa con un grid por cada tabla directamente subordinada, para mostrar la información relacionada). 4)
Quitar atributos CustomerGender y CustomerStatus del tab Customer de la pantalla View Country.
En este caso si no queremos que los atributos CustomerGender y CustomerStatus se vean en el grid en ejecución, no necesitamos ocultarlos. Podemos directamente eliminarlos. Agregar un filtro por CustomerName en el tab Customer
Una vez que ejecuta el paso a) y elige ‘Filter’ aparecerá un nuevo nodo Filter inmediatamente después del nodo Attributes, con 2 subnodos: Attributes y Conditions. Luego, en el paso b), deberá posicionarse en el subnodo Attributes y hacer botón derecho, donde se le ofrecerá la posibilidad de agregar un atributo de filtro. Al editar las propiedades, usted deberá presionar el combo box que le desplegará una ventana donde ingresará el atributo (en nuestro caso, CustomerName). Con esto se creará automáticamente una variable de igual nombre que el atributo &CustomerName, que será el control que aparecerá en ejecución para que el usuario digite allí el filtro. Veamos el paso siguiente...
Página 49 de 143
Curso GeneXus X Año 2011
Valores por defecto para las propiedades
El patrón Work With además de generar objetos nuevos, también modifica las transacciones, para que sean invocadas por los objetos generados por el pattern, agregándoles regla parm, etc. Relacionado a esto, cada instancia contiene la propiedad UpdateTransaction, que ofrece los siguientes valores: Do not update: La transacción no será modificada (web form, reglas y eventos serán mantenidos). Only rules and events: Solo las reglas y eventos se modificarán, no se modifica el web form. Apply WW Style: La primera vez que se aplique el patrón, el comportamiento será el mismo que si se hubiese seleccionado el valor Create Default. A partir de la segunda vez que se aplique el patrón, no se modificará la data area del form de la transacción (por si se personalizó y se desea mantener), y sí se modificará el style area, así como los eventos y reglas. Create default: Reglas, eventos y form de la transacción (tanto data area como style area) serán modificados. En lo que respecta al form, será como seleccionar la opción Apply default (Web Form). El valor por defecto para esta propiedad es Only rules and events. En cuanto a las propiedades AfterInsert, AfterUpdate y AfterDelete, permiten definir el comportamiento luego de que se inserta, modifica o elimina un registro. Los valores posibles para cada una de ellas son: • <default> • Return to caller • Go to View Página 50 de 143
Curso GeneXus X Año 2011
• Go to Selection Otra vez el valor <default> Llegó el momento de ver dónde se configuran todos estos valores por defecto que default>. hemos ido encontrando en la instancia... Configuración de Propiedades Generales
En este lugar están centralizados los <default> para toda instancia. Podemos ver que en el nodo Template se ofrecen algunas de las propiedades que mencionamos en la página anterior. El tamaño de página de los grids del work with, que en las imágenes anteriores era de 3 (se mostraban 3 líneas por página del grid) se configura en el nodo Grid. El valor por defecto de esta propiedad es Page.Rows. Es decir, el valor del dominio enumerado Pages creado por GeneXus automáticamente al aplicar el pattern por primera vez. El valor que tiene Page es 10. Nosotros lo habíamos cambiado a 3 para que nos entraran las imágenes completas en estas slides. Dinamismo entre la transacción y Patterns •
Todos los objetos generados por Patterns están basados en el esquema de Defaults. Cada parte de un objeto es generado como un Default.
•
La implementación basada en Defaults permite tener dinamismo entre la transacción y el patrón.
•
El dinamismo se mantiene para todas las partes default del objeto -
Opcion Edit/Apply Default del Menú para volver al default de una parte o todas las partes.
-
Ejemplo: Si agregamos atributo CountryFlag a la estructura de Country, ¿qué pasará con el Work With countries? Si éste conserva el Defaul, será agregada automáticamente columna CountryFlag al grid.
El dinamismo mencionado se mantiene d a s o e c o ado a t e e para todas las partes default de los objetos. Todos los objetos generados por Patterns están basados en el esquema de Defaults de GeneXus. Cada parte (Form, Reglas, Eventos) de cada objeto es generado como Default. Si se modifica alguna parte del objeto, ésta deja de ser Default. Por ejemplo, si se modifica el web form de un WW (no queda como default), y se agrega un nuevo atributo a la transacción, no se va a actualizar automáticamente el grid del WW con ese atributo (o sea, no se agregará dicho atributo). La implementación basada en Defaults permite tener dinamismo entre la Transacción y el patrón • Cambiar propiedades en la definición del patrón (Pattern setting) • Cambios en la instancia (agregar un nuevo filtro) • Cambios en la Transacción (agregar un nuevo atributo) Si se quiere volver al dinamismo, se deberá tener nuevamente las partes como default. Para esto, se debe seleccionar desde el Menú Edit / Apply Default (la parte donde se tiene abierto el objeto) o Página 51 de 143
Curso GeneXus X Año 2011
Apply Default (All parts), lo cual vuelve a default todas las partes que se habían modificado.
Como Borrar los objetos generados por Patterns Seleccionar al instancia en el Folder View, presionar botón derecho/opción “Delete” o presionar la tecla DEL. Aparecerá el mensaje:
Al confirmar el mensaje: •
Se borrarán todos los objetos generados por Patterns asociados a la transacciión.
•
Se borrarán todas las reglas y eventos agregados por Patterns en la transacción.
•
Se desmarcará la opción “Apply this pattern on save” de la Transacción.
Página 52 de 143
Curso GeneXus X Año 2011
Capitulo 5 Web Panel En este capítulo verá cómo diseñar un objeto de tipo Web Panel, utilizando la Barra de Controles para su edición.
5.1. Generalidades Definición
Objetos GeneXus que permiten al usuario realizar consultas interactivas a la base de datos a través de una pantalla en tiempo de ejecución. • • • • •
Son flexibles, por lo que se prestan para múltiples usos. Algunos web panels conocidos: Work With Countries. View Country. Elementos que los componen:
Los elementos de los web panels son: Web Form: Cada web panel contiene un form Web, el cual debe ser diseñado por el analista agregándole variables, atributos, así como otros controles, para que el usuario pueda interactuar con el mismo. Rules: Las reglas de un web panel permiten definir ciertos comportamientos puntuales de dicho objeto. Por ejemplo, declarar qué parámetros recibe. Events: Los web panels emplean la programación orientada a eventos. Este tipo de programación permite definir código ocioso, que se activa en respuesta a ciertas acciones provocadas por el usuario o por el sistema. En esta sección de un web panel es donde se define el código ocioso asociado a los eventos que pueden ocurrir durante la ejecución del web panel. Aquí también pueden programarse subrutinas a ser invocadas por varios eventos. Conditions: Es para definir las condiciones (globales) que deben cumplir los datos a ser recuperados (filtros). Análogo a la solapa de igual nombre del objeto Procedure. Variables: aquí se declaran las variables que serán utilizadas dentro del objeto. Recordar que son locales. Help: Permite la inclusión de texto de ayuda, que los usuarios podrán consultar en tiempo de ejecución del web panel. Documentation: Permite la inclusión de texto técnico en formato tipo wiki, como documentación para el equipo involucrado en el desarrollo de la aplicación. Propiedades: Son características a ser configuradas para definir ciertos detalles referentes al comportamiento general del web panel. Recordar que se acceden con F4.
Página 53 de 143
Curso GeneXus X Año 2011
En el ejemplo, el web panel contiene dos variables &startDate y &endDate como se muestra arriba. En tiempo de ejecución, el usuario podrá ingresar valores en las variables dado que en los web panels las variables son por defecto de entrada (salvo las incluidas en grids, como veremos). En el evento Enter del web panel (asociado al botón Billing Generation), se obtienen para cada cliente las facturas cuyas fechas se encuentren en el rango especificado y se genera recibo para el cliente (invocando al Data Provider que devuelve la colección de Business Components que ya hemos visto oportunamente cuando estudiamos esos temas). De modo que este web panel tiene como finalidad permitir al usuario ingresar un rango de fechas, y presionando el botón Billing Generation, ejecutar el procesamiento de las facturas.
Página 54 de 143
Curso GeneXus X Año 2011
El web panel mostrado arriba ha sido creado para exhibir los datos de un cliente. Se necesita invocarlo desde otro objeto (como ya veremos), pasándole por parámetro el código del cliente del cual se quiere mostrar la información.
Un web panel es un objeto típicamente utilizado para mostrar información. Entonces, cuando GeneXus encuentra atributos en el form, ¿qué intención adjudicará al programador que los puso allí? Pues, que le está pidiendo implícitamente que vaya a buscar los datos correspondientes a la base de datos para desplegarlos. En definitiva, con el web panel anterior, GeneXus irá a la tabla CUSTOMER y filtrando por el atributo recibido por parámetro, CustomerId, mostrará la información del registro encontrado. ¿Qué información? La que reside en los atributos que figuran en el form. Pero el atributo CountryName no se encuentra en la tabla CUSTOMER. Pues sucede lo mismo que con un for each, un grupo de Data Providers, etc., es decir, desde el registro de la tabla base, se accede al registro relacionado de la tabla extendida que se necesite, para obtener el valor del atributo requerido (en nuestro caso accede a la tabla COUNTRY para recuperar el valor de CountryName). Las inferencias que GeneXus realiza son las siguientes: Al tratarse de un web panel, los atributos que figuren serán de consulta1. Entonces GeneXus deberá acceder a la base de datos para recuperar sus valores. ¿A qué tablas? Dependerá de si el Web panel tiene grids o no: • Web panel plano (sin grid): están los atributos “sueltos” en el form, como en el caso que estamos mostrando. Si esto sucede, es porque el analista necesita acceder a información de un registro de una tabla (y eventualmente de los registros relacionados por tabla extendida), como es el caso del ejemplo. En este caso, el web panel estará bien programado si además se agrega un filtro que determine “ese” registro a mostrar. En nuestro caso, tenemos la regla parm que al recibir en atributo PK de la tabla sólo recuperará un registro. En definitiva, GeneXus determina una tabla base del web panel, así como lo hacía para un for each. ¿Cómo? Buscando la mínima tabla extendida que contenga a los atributos... • Web panel con uno o más grids: lo veremos en lo que sigue, pero ya podemos intuirlo... ¿qué sentido tendrá colocar un grid? Mostrar información repetitiva. En el caso general: cada grid mostrará muchos registros de una tabla (y su información asociada).
Para exhibir múltiples registros: Grid (con tabla base)
Ejemplo: Mostrar todos los clientes
Este web panel, a diferencia del anterior no es plano. Cuando se incluye un grid en un form, se está indicando que se va a mostrar una cantidad indefinida de datos (en este caso, clientes). Dado que en este web panel hay involucrados atributos, GeneXus infiere que se desea acceder a la base de datos. Simplemente diciéndole qué atributos se desean desplegar, sin necesidad de más información, ya sabrá qué hacer. Existe un grid, hay atributos, entonces, necesariamente habrá tabla base, esto es, la tabla de la base de datos que se navegará en busca de la información requerida. Página 55 de 143
Curso GeneXus X Año 2011
Si en lugar de mostrar esta información en pantalla, la quisiéramos en un listado, crearíamos un procedimiento con un print block ‘customer’ con los atributos CustomerName y CountryName y en el source programaríamos: for each print customer endfor Esto es análogo, solo que el for each está implícito, no hay que especificarlo. Cada línea del grid que se carga, es como el print que se ejecuta en cada iteración del for each del listado. De las tablas CUSTOMER y COUNTRY y de la relación entre ambas es Na1, GeneXus determina la tabla base de este web panel (aquella tal que su tabla extendida sea la mínima que cumple que contiene a todos los atributos referidos). Obsérvese que este ejemplo solo difiere del anterior, en que los atributos aparecen en un grid, y por tanto, no habrá que filtrar quedándose con un solo registro de CUSTOMER. En este web panel no hay regla parm que establezca filtro alguno. También conseguiremos más adelante que este web panel no solamente se remita a mostrar los clientes de la base de datos en el grid, sino que además permita filtrar los clientes que se deseen ver en cada oportunidad, ya sea por nombre de cliente, por país del cliente, o por ambos. Por eso las variables que aparecen en el form. Load automático
Cuando GeneXus puede determinar automáticamente una tabla a recorrer para cargar las líneas del grid, lo hace, y en ese caso no habrá necesidad de brindarle esa información. Es por ello que decimos que en ese caso hay un for each ‘implícito’. Luego veremos casos en los que esto no ocurre (grids sin tabla base). Obsérvese en el ejemplo, que si en la tabla base existen 4 registros, se cargarán uno a uno los cuatro. Si ahora queremos que el usuario pueda filtrar los clientes que desea ver... ahí entran en juego las variables que hemos definido en la parte fija del form, como veremos en la página siguiente.
Filtrando
Ejemplo: Mostrar clientes que cumplen condiciones
Página 56 de 143
Curso GeneXus X Año 2011
Obsérvese cómo en la ventana de propiedades del control Grid, aparece una de nombre ‘Conditions’. Cliqueando en el combo se abrirá un editor para especificar las condiciones booleanas que deberán cumplir los registros para ser cargados como líneas del grid. Es por esa razón que se han agregado las variables &CustomerName y &CountryName al form del web panel, de manera tal que el usuario pueda ingresar allí valores que operen como filtros sobre los datos a mostrar. En nuestro caso, hemos establecido filtros con el operador like. Las variables en la parte plana del form de web panels son por defecto de entrada. Luego veremos que en principio si están en grids serán por defecto de salida. Obsérvese que las condiciones (separadas con “;”) equivalen a las que aparecían en las cláusulas where de un for each (o grupo repetitivo de data provider). De la misma manera, por cuestiones de optimización, puede determinarse, al igual que se hacía en un for each, criterio de ordenamiento de la tabla a recorrerse, como se muestra arriba en el ejemplo. Nota: Asimismo, igual que en un procedimiento, no sólo pueden establecerse condiciones locales al grid (for each), sino también generales, mediante el selector Conditions. Esto tendrá sentido cuando se tenga más de un grid (for each), para no tener que repetir la misma condición cada vez. Con refresh automático
Página 57 de 143
Curso GeneXus X Año 2011
La propiedad Automatic Refresh que se encuentra a nivel del Web Panel puede tomar los siguientes valores: • When variables in conditions change (valor por defecto): luego de provocarse automáticamente el refresh, se dispara el evento Load cargándose el grid según los nuevos valores de las variables. • No Dependiendo del tipo de datos del filtro, y del control web utilizado para filtrar, la condición será aplicada cuando se está digitando o al abandonar el campo. En el caso de filtros en controles edit, para tipo de datos Character, son aplicados cuando el usuario los va digitando. Para tipo de datos Date, DateTime y Numeric, las condiciones se evalúan cuando se abandona el campo. En el caso de filtros combo boxes o dynamic combos, las condiciones son evaluadas cuando se abandona el campo. Para Check boxes y Radio buttons, las condiciones son evaluadas cuando el valor es cambiado. Ejecución: ¿qué sucede en el cliente y en el servidor al tener la propiedad por defecto y un grid con tabla base? 1ª ejecución (variables vacías): For each CUSTOMER guardar en memoria CustomerName acceder a registro de COUNTRY relacionado guardar en memoria CountryName cargar (load) línea en el grid con ambos valores N-ésima ejecución (cambia valor de variable de las conditions): Automáticamente en el browser del cliente se detecta cambio refesh en el servidor para volver a cargar el grid con los registros que cumplan las condiciones (con los nuevos valores de las variables) load por cada línea. Conclusión: Existen dos eventos del sistema que ocurren en el momento de carga del form (Refresh) y de carga de cada línea del grid (Load). Como veremos, a estos eventos puede asociárseles código para que se ejecute en esos momentos específicos... Programando el evento load Página 58 de 143
Curso GeneXus X Año 2011
Intención: para aquellos clientes con estado ‘On Hold’ poder activarlos marcando check box y presionando botón ‘Activate’. Antes queremos: 1. Solo habilitar check box para los ‘On Hold’: Cuando se carga cada línea del grid, si el valor del atributo CustomerStatus de la tabla CUSTOMER a ser cargado es On Hold, habilitar check box...
Ampliaremos la funcionalidad de nuestro web panel, permitiendo ver el estado de cada cliente, y para aquellos que estén ‘On Hold’, brindando la posibilidad de pasarlos al estado ‘Active’. Para hacer esto, agregaremos al grid el atributo CustomerStatus que muestra el estado, y una variable booleana &Select, que permitirá al usuario seleccionar aquellos clientes que desea ‘activar’. Además un botón para efectivamente activar todos los clientes marcados. Pero esto lo haremos en un segundo paso. Obsérvese que al ser el tipo de datos de la variable &select booleano, por defecto aparece en el grid como un check box. Para asegurar que el usuario solamente intente ‘activar’ clientes ‘On Hold’, deseamos que solamente aparezca habilitado este check box cuando corresponde... para ello necesitaremos programar la carga de cada línea, esto es, el evento Load...
En la sección Events del web panel, programamos el evento Load del grid que vemos arriba. En el ejemplo, customerGrid es el nombre que hemos dado al control grid en el form. Al tratarse de un web panel con un único grid, también podríamos haber programado el evento Load a secas: Event Load Página 59 de 143
Curso GeneXus X Año 2011
if CustomerStatus = Status.OnHold &select.Enabled = 1 else &select.Enabled = 0 endif endevent Es decir, en el caso de un web panel con un único grid, no es necesario calificar el evento con el nombre del grid. Igualmente recomendamos hacerlo, anticipándonos a la posibilidad futura de agregar otro grid al form del web panel. El evento Load se disparará por cada línea que haya de cargarse en el grid, se encuentre éste programado o no. Lo que hacemos en el ejemplo es aprovechar ese momento inmediatamente anterior a la carga, para efectuar una acción. Y ese es el código que incluimos en el evento. Ahora sí, estamos en condiciones de implementar la ‘activación’, para lo que necesitaremos asociar al botón un evento, que podrá ser el evento Enter o uno de usuario... Evento Enter
En el ejemplo, cuando el usuario presiona el botón ‘Confirm’, necesitamos recorrer todas las líneas del grid, y para cada una de ellas, si el check box de la variable booleana &Select fue marcado por el usuario, entonces debemos cambiar el estado del cliente correspondiente, de ‘On Hold’ a ‘Active’. Al insertar el botón en el form del web panel, y editar sus propiedades, podrá observarse que por defecto, el botón está asociado al evento Enter (ver propiedad OnClickEvent). El Enter será un evento del sistema, que se ejecuta tanto cuando el usuario hace clic sobre el control asociado, como cuando presiona la tecla Enter. En este ejemplo veremos a la vez: • Posibilidad de definir eventos de usuario y asociárselos a controles o de utilizar el evento Enter del sistema. • Comando For each line, para recorrer las líneas ya cargadas en un grid. • Variables en un grid pasan de ser Read only (de salida) por defecto, a ser de entrada, cuando se utiliza comando for each line en ese grid (también cuando se programan eventos OnClickEvent, Click, etc., sobre alguna columna del grid). Obsérvese que el comando For each line sólo tiene en común con el comando For each estudiado antes, el hecho de representar una estructura repetitiva. La diferencia más importante: mientras el for each recorre registros de una tabla (base) de la base de datos, el for each line recorre las líneas de un grid. En nuestro ejemplo hemos definido una variable &customer, Business Component Customer, mediante la cuál cambiaremos a estado ‘Active’ todas las líneas del grid marcadas por el usuario. Para ello recorremos el grid con el comando for each line. Evento de Usuario
Página 60 de 143
Curso GeneXus X Año 2011
Otra posibilidad, en lugar de utilizar el evento del sistema Enter, es definir un evento de usuario. Ello se consigue siguiendo los pasos que pueden verse arriba. Como puede apreciarse, la mayoría de los controles presentes en un form tienen la propiedad OnClickEvent asociada. Esa propiedad permite especificar un evento a dispararse cuando el usuario haga clic sobre el control. Podrá ser un evento del sistema (Refresh, Enter) o uno definido por el usuario.
• Por defecto todas las variables de un grid son Read-Only • For each line [in grid] o cualquier evento sobre las líneas o columnas (OnClickEvent, Click, DblClick, IsValid, etc): modifica valor por defecto y todas las variables del grid pasan a ser de entrada. Para que algunas sean de salida:
No así las de la parte fija del web panel. Esas son por defecto de entrada, como ya hemos visto antes para las variables que utilizamos para filtrar los customers mostrados en el grid. Cómo desplegar datos en un grid
Página 61 de 143
Curso GeneXus X Año 2011
Por defecto todo atributo y variable que está dentro de un grid se despliega en ejecución como texto, es decir que es únicamente de lectura y por consiguiente no puede ser modificado. Cómo aceptar datos en un grid Es posible aceptar datos en las variables de un grid dependiendo de la programación de los eventos existentes en el objeto: 1. Si dentro de un evento del web panel se está utilizando el comando For each line, todas las variables que están dentro del grid pasan a ser de entrada. Es posible indicar en este caso cuáles son las variables que no van a poder ser modificadas a través de la propiedad ReadOnly. 2. Si dentro de la fila hay algún control con un evento click, dblClick, etc.. asociado (ó evento de usuario especificado en la propiedad OnClickEvent), sucederá lo mismo.
Seguimos ampliando nuestro ejemplo; ahora queremos que el usuario pueda seleccionar una línea del grid (un cliente) y presionando botón ‘Select customer’ poder invocar al web panel que habíamos implementado antes. En este ejemplo vemos dos funcionalidades en juego: • La necesidad de colocar una columna en el grid oculta (no visible). • Permitir que el usuario seleccione una línea del grid para hacer algo con ella. ¿Por qué colocar la columna correspondiente a CustomerId y ocultarla, en lugar de no agregarla en absoluto? Reflexione sobre lo siguiente: el atributo CustomerId enviado por parámetro al ejecutarse el evento ‘Select customer’, ¿de dónde es extraído? ¿De la base de datos? No, es el que está cargado en el grid. Más específicamente, por cada grid existirá un archivo temporal que contendrá tantas columnas como las del grid, visibles y ocultas. Cuando el usuario selecciona en el grid la segunda línea, y presiona el botón ‘Select customer’, se está posicionado en ese archivo temporal, correspondiente a esa línea (¡y nunca en la base de datos!). Es por esta razón que de no colocar la columna correspondiente a CustomerId en el grid, no estaríamos pasando valor alguno a CustomerView. Para permitir al usuario seleccionar una línea del grid, alcanza con ‘prender’ la propiedad ‘AllowSelection’ del grid. Puesta a punto: Determinación de tabla base
Página 62 de 143
Curso GeneXus X Año 2011
¿Cómo determina GeneXus una tabla base a recorrer automáticamente para cargar el grid del web panel? De existir algún atributo en por lo menos uno de los 3 lugares mencionados, GeneXus podrá encontrar tabla base. Para determinarla extrae los atributos encontrados allí (parte fija del Form, en el Grid, tanto en sus columnas como en las propiedades Order, Conditions o using Data Selector, y en los eventos programados en el selector de Eventos, solamente considerando los atributos que estén ‘sueltos’ dentro del evento, es decir, no dentro de un comando for each de acceso a la base de datos), y determina la mínima tabla extendida que los contiene. La tabla base de esa extendida, será la recorrida automáticamente y cargada con el Load. Observemos que solamente hemos programado el evento Load para tomar la decisión para cada línea a ser cargada en el grid, acerca de si se habilitará o no para la misma la variable &select que permitirá al usuario marcar el check box. Importante: La forma de determinación de la tabla base de un grid dependerá de si existe otro grid en el web panel o es el único. El resumen presentado aquí corresponde a un único grid. En este caso se dice que el propio Web panel tiene tabla base. Es la del grid. Cuando existan más de un grid en el web panel, esto ya no tendrá sentido, y cada grid pasará a tener o no tabla base. Veremos la forma de determinación de las tablas bases en ese caso, cuando tratemos el caso de múltiples grids en un web panel.
Para exhibir múltiples registros: Grid (sin tabla base)...
Página 63 de 143
Curso GeneXus X Año 2011
Aquí presentamos otro ejemplo, para observar un caso en el que surge naturalmente la necesidad de implementar un grid sin tabla base. El caso natural de implementación de un grid con tabla base, es cuando se quiere cargar por cada registro de una tabla, una línea del grid (1 registro – 1 línea). Cuando el caso es que se quiere cargar una línea del grid como producto de recorrer varios registros de la base de datos (N registros – 1 línea), como productos de cálculos, etc., suele ser más natural implementar el grid sin tabla base. Este es el caso del ejemplo: no queremos cargar por cada factura una línea, sino que queremos agrupar facturas por fecha, y por cada grupo, cargar una línea que sumariza sus Amount. Estamos hablando de un corte de control. Si tuviéramos que implementar un listado PDF en lugar de un web panel, sabríamos bien cómo programarlo (como lo hacemos arriba). Veamos cómo implementarlo con un web panel...
El objetivo del comando LOAD es agregar una línea en un grid. Es necesario cuando el grid no tiene tabla base, dado a que en ese caso no se agregará automáticamente línea alguna. El evento Load ocurrirá una vez, y si éste no se programa, el grid resultará vacío. Por tanto en este caso es indispensable programarlo, de acuerdo a la lógica que corresponda. Una vez que se haya asignado valor a cada variable, y se desee agregar una línea al grid, deberá ejecutarse el comando LOAD. Solamente se puede especificar el comando LOAD dentro del evento Load del grid de un web panel. Obsérvese que este caso es el que deja más a cargo del analista la implementación. Frente al caso de grid con tabla base, en este GeneXus realiza muchas menos inferencias.
Página 64 de 143
Curso GeneXus X Año 2011
En el caso de un grid sin tabla base, los filtros sobre los datos a mostrar son programados dentro del código que implementa la carga (el del Load). Aquí no habrá refresh automático. Esto es, cuando el usuario modifique los valores de las variables que intervienen en los filtros, GeneXus no detectará que debe volver a cargar el grid. Para ello deberá, por ejemplo, agregarse un botón asociado al evento Enter, o a un evento de usuario, sin código, debido a que solo lo necesitamos para que se produzca un Refresh, es decir para que se vuelva al servidor a cargar el web panel. Sobre los eventos disponibles y el orden en que se disparan, entraremos en lo que sigue.
Programación dirigida por Eventos • Evento Start • Evento Refresh • Evento Load • Evento Enter • Eventos de Usuario • Evento TrackContext (no lo veremos en el curso) • Asociados a controles del form (dependiendo del tipo de control): • Evento Click, DblClick, RightClick, IsValid, Drag, Drop, etc.
En todo Web panel existen eventos del sistema que pueden programarse. Algunos ocurrirán siempre, en cada ejecución del web panel (como el Start, Refresh, Load), otros si se los declara y el usuario realiza las acciones necesarias para provocarlos (Enter, definidos por el usuario, TrackContext). Asimismo, casi todos los controles que aparecen en el form brindan la posibilidad de disparar un evento cuando el usuario hace clic con el mouse sobre ellos (aparecen como hipervínculos en ejecución); se consigue de dos maneras distintas: 1. Editando las propiedades del control (F4), y definiendo un evento de usuario en la propiedad OnClickEvent, o asociándole el evento Enter o el Refresh. Página 65 de 143
Curso GeneXus X Año 2011
2. Dándole un nombre al control y en la sección de Eventos programando: Event nombreControl.click … Endevent Con esta última alternativa no tendremos que definir un evento de usuario, sino que estaremos programando el evento click del control. Lo mismo ocurre con los eventos DblClick, RightClick, IsValid...(para cuando se hace doble clic, botón derecho, etc.). Sobre los eventos asociados a las acciones sobre los controles (click, dblclick, drag, drop, etc.) no nos explayaremos en el presente curso. En nuestro wiki encontrará información detallada, así como en el Help de GeneXus. Sobre el evento TrackContext solo mencionaremos que será posible detectar cambios en el valor dado a un control (grid, variable, etc.) y en ese caso disparar este evento para en base al valor modificado, tomar una acción. Evento Start • Es un evento del sistema, que ocurre automáticamente siempre que se hace Get o Post y es el primer evento que se ejecuta. • No se conocen valores de atributos, salvo los recibidos por parámetro. Esto se debe a que aún no se ha efectuado la consulta. • Ejemplo: se puede utilizar para que un control del form no aparezca visible, para cargar un bitmap, para asociarle un Link a otro control, etc.: Event Start &var.Visible = 0 &Update = LoadBitmap("images/edit.gif") newControl.Link = Link(TCustomer) endevent Evento Refreh y Load • Eventos del sistema, codificables, asociados a la carga del Web Panel. Se ejecuta primero el Refresh y a continuación siempre el Load. • Si el grid tiene tabla base
• Si el grid no tiene tabla base Load se ejecuta N veces:una por cada línea Load se ejecuta 1 sola vez: Dentro del evento habrá que programar la carga de cada línea del grid. Para cargarla, comando Load.
Cuando el web panel es con tabla base, al producirse el evento Refresh se accede a la base de datos, a esa tabla base (la asociada al web panel), y se la recorre cargando los registros que cumplan las condiciones (conditions del grid y generales). Ocurrirá en ese proceso un evento Load por cada registro en el que se esté posicionado, inmediatamente antes de cargarlo. Esto nos permite realizar alguna operación que requiera de ese registro (y de su extendida), antes de efectivamente cargarlo en el grid. Inmediatamente luego de ejecutado el código asociado al evento Página 66 de 143
Curso GeneXus X Año 2011
Load, se cargará la línea en el grid y se pasará el puntero al siguiente registro de la tabla base, para realizar lo mismo (evento Load, carga de la línea). Este proceso se repetirá hasta cargar todas las líneas del grid. Si un web panel es sin tabla base, GeneXus no puede determinar automáticamente una tabla de la base de datos a recorrer para mostrar la información que se presenta en el form. En este caso en el form solamente parecen variables (y no atributos) y también ocurrirán los eventos Refresh y Load, sólo que el evento Load se ejecutará una única vez, dado que no se estará posicionado en ningún registro de ninguna tabla. Dentro de ese evento habrá que codificar la carga, que podrá requerir acceder a la base de datos (ej: comando for each) o no (supóngase que se desea cargar el grid con información obtenida de recorrer un SDT collection, tras efectuar alguna transformación sobre sus items... o cargar líneas en el grid producto de cálculos). El control de la carga del grid, queda aquí en manos del analista, utilizando el comando Load. Este comando solo es válido dentro del evento de igual nombre. Obsérvese cómo en el caso de grid con tabla base, este comando se torna innecesario. Refresh automático • Válido para Grid con tabla base y sin tabla base.
Estudiemos con más detalle la propiedad Automatic Refresh que se encuentra a nivel del Web Panel puede tomar los siguientes valores: • When variables in conditions change (valor por defecto): luego de provocarse automáticamente el refresh, se dispara el evento Load cargándose el grid según los nuevos valores de las variables. • No: para que el contenido del grid se refresque luego de cambiar los filtros, el usuario debe realizar una acción:
Orden de Disparo de Eventos
Página 67 de 143
Curso GeneXus X Año 2011
Los eventos que se disparan y su orden depende de si se está abriendo el web panel (Get) o si ya estaba abierto y se está efectuando una acción posterior, como presionar un botón (Post). Arriba mostramos con ejemplos el caso general. 1a. vez: Start + Refresh + Load N-ésima vez: Start + Lectura de variables de pantalla + Evento que produjo el Post + Refresh + Load. Este es el caso general... existe una excepción... • Excepción: • Algunos eventos de usuario deben ejecutarse en el Server pero para otros no existe necesidad se ejecutarán solamente en el Cliente (sin ejecutarse todos los otros eventos del server: Start, Refesh y Load). • Evitando roundtrips innecesarios al servidor, y reduciendo la cantidad de datos que viajan de un lado a otro. • Ejemplo: un evento que cambia el estado de un control, no necesita ejecutarse en el server.
• GeneXus tiene la inteligencia para determinarlos. Es transparente para el programador. Internamente GeneXus determina las entradas y salidas de cada evento. Si en sus entradas, se requiere de acciones ejecutadas en el Server, entonces el evento se ejecutará en el Server. Por ejemplo, si entre las entradas de un evento de usuario se encuentra alguna de las salidas del evento Start (del server), entonces el evento de usuario se ejecutará en el Server. Si el código del evento no requiere que se ejecute en el servidor, entonces por performance, se ejecutará en el cliente, como código javascript. De todas maneras el analista GeneXus no deberá preocuparse de estos asuntos, dado que en todo caso será GeneXus quien tendrá la inteligencia de resolver dónde ejecutar el evento.
Múltiples grids – Grids paralelos
Página 68 de 143
Curso GeneXus X Año 2011
Cuando un web panel contiene más de un grid en su form, GeneXus no determina una única tabla base asociada al web panel, sino una tabla base asociada a cada grid. Atributos que participan en la determinación de la tabla base de cada grid: • Los incluidos en el grid (se tienen en cuenta tanto los atributos visibles como los no visibles) • Los referenciados en Order y Conditions locales al grid A diferencia de lo que sucedía para un web panel con un solo grid, en el caso de múltiples grids los atributos de la parte fija del web panel no participan en la determinación de la tabla base de ninguno de ellos, pero deberán pertenecer a la tabla extendida de alguno (para que sea posible inferir sus valores). De no respetarse esto, al especificar al web panel, se mostrará en el listado de navegación resultante, una advertencia informando de esta situación. Los atributos utilizados en los eventos del web panel tampoco participan en la determinación de la tabla base de ninguno de los grids. Los atributos que se incluyan en los eventos fuera de comandos for each, deberán pertenecer a la tabla extendida de alguno de los grids (al igual que los de la parte fija).
Página 69 de 143
Curso GeneXus X Año 2011
Este web panel podría haberse implementado de varias maneras distintas, dando por resultado la misma ejecución. La implementación más natural es la que podemos ver arriba: el primer grid tiene tabla base y el segundo no. Pero podría haberse implementado al revés, con variables en el primer grid y teniendo que realizar la carga de los clientes a mano en el Load, y atributos en el segundo grid, y algunas cosas más para lograr el corte de control, siendo un grid con tabla base. O cualquiera de las otras dos combinaciones (ambos grids con tabla base, o ninguno con tabla base). Lo importante es, una vez elegida la implementación más natural al caso, realizarla correctamente. En cualquiera de los casos, aunque la información a cargar en un par de grids se encuentre relacionada en la base de datos, GeneXus no asumirá ninguna relación entre los datos a la hora de cargar un grid y el otro. Es análogo al caso de un par de for eachs paralelos en el Source de un procedimiento. Como puede verse arriba, al web panel en el que teníamos el grid con los clientes, le hemos agregado la parte de visualización de facturas por fecha que habíamos implementado en web panel aparte. Pero no alcanza con simplemente unir ambos web panels... para poder relacionar las cargas de los grids, deberemos agregar cierta lógica. En nuestro caso deseamos que una vez que se seleccione un cliente del primer grid, las facturas que se carguen en el segundo no sean las de todos los clientes, sino las del seleccionado. Para ello debimos hacer dos cosas: agregar una variable &CustomerId para almacenar el id del cliente seleccionado al presionar ‘Select customer’, y luego agregar un filtro por el valor de esa variable cuando se carga el grid de Invoices. Asimismo tuvimos necesariamente que colocar esa variable en el form para que todo funcione como esperamos... y la razón la encontraremos en el análisis que sigue.
Analicemos lo que sucede cuando se ejecuta este web panel por primera vez. Primero se ejecuta el evento Start, que en nuestro caso oculta la variable &CustomerId. Luego, como puede apreciarse en la imagen, se produce un evento Refresh genérico, luego del cuál se producirán las cargas de todos y cada uno de los grids que se encuentren en el web panel, de izquierda a derecha de arriba a abajo. Por cada uno ocurrirá un evento Refresh propio y el evento Load (N veces si el grid tiene tabla base, 1 sola si no la tiene). Obsérvese que en nuestro caso, el grid de clientes tiene condiciones para cargarse, por las variables de filtro &customerName y &countryName, pero al estar vacías no aplican (dado que ambas cláusulas condicionales tienen when not &var.IsEmpty()) El segundo grid es sin tabla base, pero como ya vimos, el evento Load ejecutaba un for each con cláusulas where, por tanto se cargarán solo aquellas líneas para las que se cumplan sus condiciones. Estas son tres: where InvoiceDate >= &startDate when not &startDate.IsEmpty() where InvoiceDate <= &endDate when not &endDate.IsEmpty() where CustomerId = &customerId when not &customerId.IsEmpty() Obsérvese que en esta primera ejecución &customerId estará vacío, por lo que no se aplicará este filtro y se cargarán todas las facturas por día, de todos los clientes. Página 70 de 143
Curso GeneXus X Año 2011
Luego el usuario selecciona del grid el cliente correspondiente a la segunda línea y presiona el botón ‘Select customer’. Se detecta una acción, y se realiza un post al servidor, quien ejecuta el Start (ocultando la variable), luego lee las variables en pantalla (&customerId por ahora está vacía, pero no sólo son consideradas variables de pantalla las variables definidas, sino, por ejemplo, la información completa de la línea seleccionada por el usuario con el mouse, entre ella, el valor de CustomerId, columna del grid), luego ejecuta el código del evento que produjo el post, en nuestro caso, ‘Select customer’. Aquí la variable &customerId toma el valor del CustomerId de la línea elegida. Luego se produce el Refresh general que dispara el Refresh y Load de cada grid. Por tanto, cada grid se carga ejecutando las conditions. El primero se carga igual que antes, porque ninguna de sus conditions ha variado. El segundo, ahora sí tiene un valor para &customerId, por lo que se mostrarán solamente las facturas del cliente. Uno podría preguntarse por qué se tuvo la necesidad de colocar la variable oculta en el form. Por qué simplemente no podía usársela como variable dentro del programa, sin necesidad de colocarla y hacerla invisible. Con esta segunda ejecución todavía no podemos contestar la pregunta. De hecho, si se analiza, puede verse fácilmente que hubiésemos obtenido el mismo comportamiento si sólo le asignábamos valor a la variable en el ‘Select Customer’ sin colocarla en el form. La razón surgirá claramente con la siguiente ejecución...
Página 71 de 143
Curso GeneXus X Año 2011
Ahora el usuario ya tiene seleccionado el segundo cliente, y lo que desea es filtrar sus facturas por fecha (no quiere visualizarlas todas). Para ello especifica los filtros de fecha, y presiona el botón ‘Search’, que producirá un post al servidor. Entonces se ejecutará el Start con su código, luego se leerán las variables de pantalla: aquí está la razón de haber colocado &customerId en el form. Se leerá entonces el valor de &customerId, que permanecerá incambiado hasta tanto el usuario no vuelva a seleccionar otro cliente del grid y presionar el botón ‘Select customer’... esta variable presente en el form, es la forma de mantener la memoria entre ejecuciones. Recuérdese que cada vez que se hace un post al servidor, es una nueva ejecución del web panel, por lo que las variables comienzan nuevamente vacías. Es por ello que el segundo paso: “Lectura de variables de pantalla” es fundamental. Ahora sí, siguiendo con el ejemplo, se lee de pantalla la variable &customerId invisible, junto con las variables visibles &startDate y &endDate. Y luego, como siempre, se ejecuta la carga (Refresh genérico + Refresh y Load de cada grid). Han cambiado las condiciones del for each que carga el segundo grid, por lo que ahora aparecerán solamente las facturas del segundo cliente, entre las fechas estipuladas por el usuario. Si ahora el usuario quiere cambiar de cliente, para ver sus facturas, lo seleccionará del grid de clientes y presionando ‘Select Customer’, el proceso volverá a empezar como vimos en la 2da. ejecución. ¿Y cómo se vuelve a trabajar con las facturas de todos los clientes y no con las de uno dado? Piense qué pasará si el usuario no selecciona ningún cliente del grid con el mouse, pero presiona ‘Select Customer’. El valor de la variable &CustomerId quedará vacío, pues CustomerId no tendrá valor. Por este motivo, al botón ‘Select Customer’ podríamos haberle llamado ‘Select/Unselect’. Propiedades de los Grids
• Paginado automático: GeneXus realiza un paginado automático si la propiedad Rows tiene un valor distinto de 0.
Los botones que se insertan dependen de la cantidad de registros a mostrar y la cantidad de líneas del grid.
Grid – Ordenamiento automático de las columnas Las columnas pueden ser ordenadas sin necesidad de programar ningún código adicional: clic sobre el título de la columna.
Página 72 de 143
Curso GeneXus X Año 2011
Se ordena la página del grid cargada, por lo que no compite con el Order programado a nivel del grid. Tipos de Grids • Grid estándar: Datos repetitivos en formato fijo (filas y columnas)
• Grid Free Style: Datos repetitivos en formato libre. • Tabla con registros repetitivos • No posee títulos para las columnas • Permite tener más de un tipo de control en una misma celda
Se dispone de dos tipos de grids: • Grid estándar: el que vimos hasta ahora, en transacciones y web panels • Grid Free Style Estos grids agregan potencia al diseño de aplicaciones web, permitiendo al desarrollador mayor libertad a la hora del diseño. El grid Free Style permite al usuario definir el formato de los datos a desplegar de una forma menos estructurada que el grid estándar. El grid Free style es básicamente una tabla a la que se le pueden insertar los atributos/variables, text blocks, imágenes, botones, web components, embedded pages, grids freestyle y/o grids que se van a mostrar posteriormente en la pantalla. En este caso para poder visualizar las propiedades hay que seleccionar la tabla donde se encuentran los atributos/variables. En el ejemplo presentado arriba queremos mostrar alguna información de los países. El atributo CountryFlag se ha incluido en la transacción ‘Country’ para almacenar la foto de la bandera de cada país (es un atributo de tipo Blob). Pero
Página 73 de 143
Curso GeneXus X Año 2011
no queremos mostrar la información como lo haríamos en un grid estándar, con cada elemento de información en una columna distinta del grid. Aquí queremos mostrar la foto y debajo el nombre del país. El comportamiento de las variables dentro de un grid Free Style es análogo al que presentan dentro de un grid estándar, por lo tanto también quedan de ingreso si existe un For each line dentro de algún evento, o si se asocia un evento a cualquier control de la fila. Nuevamente este comportamiento puede modificarse, cambiando la propiedad Read Only. Múltiples grids – Grids anidados Ejemplo: Desplegar todos los países con sus respectivos clientes, y cantidad de clientes.
Este caso de grids anidados es como el de for eachs anidados en el caso de un procedimiento: esto es, aquí sí se relacionan las cargas, y se cargarán por tanto en el Grid2, todos los clientes pertenecientes al país cargado en el Grid1 en cada oportunidad. De hecho, el orden de ejecución de los eventos estará anidado: Refresh (genérico) Grid1.Refresh Grid1.Load carga de un país Grid2.Refresh Grid2.Load carga de cliente del país Grid2.Load carga de cliente del país Grid2.Load carga de cliente del país Grid1.Load carga del siguiente país Grid2.Refresh Grid2.Load carga de cliente del país Grid2.Load carga de cliente del país Grid2.Load carga de cliente del país Grid Free Style
Página 74 de 143
Curso GeneXus X Año 2011
Tipos de Web Panels • Tipos de Web panels • Component • Web Page • Master Page
• Propiedad Type
Los objetos web pueden ser definidos con tres tipos diferentes, configurable en la propiedad Type del objeto. Para un web panel podrá tomar uno de los valores: • Component: (transacción ó web panel, que a partir de aquí podrá ser incluido en otro web object –transacción o web panel) • Web Page (es decir, el objeto será una transacción ó web panel tal como hemos trabajado hasta el momento) • Master Page A continuación introduciremos el Web Panel tipo: “Component” y luego volveremos sobre el “Master Page” pero no lo veremos en profundidad en este curso.
Web Component
Ya teníamos programado un web panel ‘CustomerView’ que mostraba los datos del cliente Un component es un web panel pero que se va a ejecutar dentro de otro. Página 75 de 143
Reutilicémoslo
Curso GeneXus X Año 2011
Ejemplo: Crear un nuevo tab en la instancia de Pattern Work With Countries, mostrando los clientes por cada país. 1) Definir el Web Panel CustomersPerCountry como Component 2) Definir el nuevo tab CustomersPerCountry en la instancia de Pattern Work With Countries
Página 76 de 143
Curso GeneXus X AĂąo 2011
Master Pages El otro tipo de Web Panel que ya vimos es la Master Page, que centraliza layout y comportamiento comĂşn, en un solo objeto y permite reutilizarlo en todo otro objeto sin tener que programar.
PĂĄgina 77 de 143
Curso GeneXus X Año 2011
Tener un look & feel consistente es hoy en día un deber de toda aplicación Web. Crear y mantener cada página de una aplicación Web asegurando la consistencia con el resto del sitio toma gran tiempo de programación. Al crear una base de conocimiento, GeneXus X creará también dos objetos de tipo Master Page: • ApplMasterPage: Para la aplicación. • PromptMasterPage: Para los prompts. Se creará un web panel, ApplMasterPage categorizado como “Master Page” con todo lo que sea el Layout y comportamiento común a todas las páginas del sitio, y en el mismo se dejará un espacio para cargar en cada oportunidad la página que corresponda (el contenido variable del sitio). Corresponde al control especial ContentPlaceholder. Las páginas web que implementan el contenido variable, se implementan como Web Panels o Web Transactions comunes y corrientes (es decir de tipo “Web Page”), y se asocian a la Master Page (a través de la propiedad de igual nombre), de manera que cada vez que se ejecuten, se carguen con ese “contexto”. Abra cualquier objeto GeneXus con form (transacción o web panel) creado en una KB, y vea el valor de la propiedad Master Page. Las Master Pages proveen una forma de centralizar el layout y el comportamiento común en un solo objeto y reutilizarlo en todo otro objeto sin tener que programar. Esto significa que la modificación de alguna parte del layout o del comportamiento común es tan fácil como modificarla en un único objeto y ¡listo!. En una misma base de conocimiento se pueden definir tantas Master Pages como se desee.
Página 78 de 143
Curso GeneXus X Año 2011
Capitulo 6 Fórmulas En este capítulo se introducen los conceptos de fórmulas globales y locales.
6.1. Fórmulas 6.1.1.Objetivos y Definición • Definir fórmulas nos brinda una forma clave de compartir conocimiento y obtener código generado optimizado • Cuando el valor de un atributo o variable puede calcularse a partir de otros atributos, constantes y/o funciones, puede definirse como una fórmula • Contamos con 2 formas de definir fórmulas:
Cuando definimos una fórmula, GeneXus puede combinar la consulta / cálculo asociada a la fórmula con la consulta en la cual la fórmula está presente y así generar sentencias optimizadas.
6.2. Fórmulas Globales • Fórmula Global = atributo al cual el analista GeneXus le asigna un cálculo asociado. • Sólo atributos pueden definirse como fórmulas globales, no variables. • ¿Cómo se realiza esta definición?
• Decimos que:
Significando que: • No se crean como atributos físicos. Página 79 de 143
Curso GeneXus X Año 2011
• Para cada objeto que referencie un atributo fórmula global,GeneXus incluirá en su programa generado el código necesario para realizar el cálculo y desplegarlo en tiempo de ejecución. • Tabla base / Tabla asociada a un atributo fórmula global: • Tabla en la cual el atributo se almacenaría si no fuera fórmula. Los atributos definidos como fórmula global, no se crean como campos físicos en tablas, por lo cual decimos que son atributos virtuales. Sin embargo, decimos que tienen una tabla “asociada” o tabla “base”, para conocer el contexto en el cual se han definido, y contar con ese contexto al momento de disparar el cálculo correspondiente donde sea que se referencien. Clasificación • Horizontales: Una o varias expresiones aritméticas • Aggregate: Sum, Count, Average, Max, Min, Find • Compuestas: Conjunto de expresiones Horizontales y/o Aggregate Estas aplican a GLOBALES y LOCALES, todas permiten que se les incluya condición de disparo. Importante: - Cuando definimos fórmulas en GeneXus no indicamos clasificación. - La clasificación es externa a GeneXus y su finalidad es agrupar fórmulas por: Tipo de cálculo que ofrecen. Atributos que se pueden involucrar en su definición. • Ejemplo de Fórmula Horizontal (Global) : CustomerTotalPurchases – CustomerTotalPayments { CustomerId* CustomerName CustomerTotalPurchases CustomerTotalPayments CustomerBalance → CustomerTotalPurchases – CustomerTotalPayments físicamente a la tabla) } TABLA CUSTOMER CustomerId* CustomerName CustomerTotalPurchases CustomerTotalPayments
(Deja
de
pertenecer
En la definición de un atributo como fórmula horizontal, es posible involucrar atributos pertenecientes a la tabla asociada al atributo que se está definiendo fórmula y a su tabla extendida. En el ejemplo, al definir que el atributo CustomerBalance es fórmula, el mismo dejará de existir como campo físico de la tabla CUSTOMER. Diremos a partir de ese momento CustomerBalance es un atributo virtual y que su tabla “base o asociada” será CUSTOMER, ya que de almacenarse dicho atributo nuevamente (ya sea por definir el analista que deja de sera fórmula o que es fórmula redundante) se crearía como atributo físico en la tabla CUSTOMER. El tipo de cálculo de esta fórmula definida es horizontal, ya que consiste en una expresión aritmética; por lo tanto los atributos que se pueden referenciar en la definición de esta fórmula son los pertenecientes a la tabla CUSTOMER (tanto almacenados como fórmulas) y su tabla extendida. • Ejemplo II de Fórmula Horizontal (Global) : { InvoiceId* CustomerId CustomerName InvoiceDate Detail { InvoiceDetailId* ProductId ProductDescription ProductPrice Página 80 de 143
Curso GeneXus X Año 2011
InvoiceDetailQuantity InvoiceDetailAmount = ProductPrice*InvoiceDetailQuantity if InvoiceDetailQuantity<=100; ProductPrice*InvoiceDetailQuantity*0.9 otherwise; } }
En este ejemplo el atributo InvoiceDetailAmount ha sido definido como fórmula global también. Es decir, utilizando el editor de fórmulas, se ha asociado un cálculo a este atributo y el mismo pasará a ser un atributo virtual. La tabla asociada al atributo InvoiceDetailAmount será INVOICEDETAIL, ya que de almacenarse dicho atributo nuevamente, se crearía en dicha tabla física. Como podemos observar la fórmula definida cae en la clasificación de horizontal, ya que consiste en 2 expresiones aritméticas condicionales. Los atributos involucrados en la definición de la fórmula pertenecen o bien a la tabla INVOICEDETAIL o bien a su tabla extendida.
• Ejemplos de Fórmulas Aggregate (Globales) : Invoice { InvoiceId* CustomerId CustomerName InvoiceDate InvoiceDetails Count(InvoiceDetailQuantity) InvoiceAmount Sum(InvoiceDetailAmount) Detail { InvoiceDetailId* ProductId ProductDescription ProductPrice InvoiceDetailQuantity InvoiceDetailAmount } }
En ambos cálculos solo intervienen los registros que cumplen: INVOICEDETAIL.InvoiceId = INVOICE.InvoiceId En el ejemplo los atributos InvoiceDetails e InvoiceAmount han sido definidos como fórmulas globales, ya que utilizando el editor de fórmulas se ha definido una fórmula para cada uno de estos atributos (pasando ambos a ser atributos virtuales). Dado que las fórmulas definidas son Count y Sum respectivamente, en ambos casos se trata de fórmulas Aggregate. La tabla asociada a ambos atributos fórmula es INVOICE, ya que de almacenarse estos atributos, se crearían en dicha tabla física. Las fórmulas Aggregate no solo tienen una tabla base asociada (como todas las formulas), sino que también involucran una tabla a ser navegada. Si bien resulta intuitivo, la tabla a ser navegada en una fórmula Aggregate, es la tabla que se navegará para realizar el cálculo. GeneXus inferirá cuál es la tabla a ser navegada por una fórmula Aggregate, por los atributos involucrados en la definición de la fórmula. En nuestro ejemplo, la tabla a ser navegada en ambas fórmulas Aggregate es INVOICEDETAIL, ya que tanto en la definición de Sum como Count, hemos referenciado a un único atributo asociado a INVOICEDETAIL. Cuando definimos una fórmula Aggregate, ya tenemos conocimiento de qué tabla pretendemos navegar para efectuar el cálculo. Los atibutos que podremos referenciar en la definición de una fórmula Aggregate deberán pertenecer a la tabla a ser navegada y su tabla extendida + a la tabla asociada al atributo que se está definiendo como fórmula y su tabla extendida. De involucrar en una fórmula Aggregate atributos que no pertenezcan a este contexto mencionado, un error se reportará en el listado de navegación correspondiente. Por último, de haber atributos en común (de igual nombre) en las tablas involucradas en la definición de una fórmula, GeneXus aplicará esa relación (es decir, filtrará automaticamente por igualdad por los atributos de igual nombre). Esto es lo que sucede en estos 2 ejemplos, que GeneXus cuenta y suma las líneas relacionadas a sus cabezales (al sumar y contar, aplica automáticamente el filtro INVOICEDETAIL.InvoiceId = INVOICE.InvoiceId ). Sintaxis completa de Sum, Count, Average: Sum | Count | Average( Expresión, [Condición Explícita, Valor por Defecto]) [if Condición Disparo]; En los 2 ejemplos de fórmulas Sum y Count que hemos visto, solamente hemos definido el parámetro obligatorio, es decir la expresión a ser sumada o contada (que en un caso consistió en un atributo almacenado y en otro caso en un atributo fórmula). También dicha expresión podría haber involucrado constantes y/o funciones. Vimos que GeneXus determina condiciones de filtro implícitas al realizar la suma, cuenta o promedio. También podríamos haber definido condiciones de filtro explícitas y en caso de haberlas, GeneXus tendrá en cuenta a ambas: implícitas + explícitas.
Página 81 de 143
Curso GeneXus X Año 2011
A su vez opcionalmente es posible definir un valor por defecto a ser retornado cuando no se encuentran registros para contar, sumar o promediar. Al igual que todas las fórmulas, estas también permiten incluir condición de disparo. Por último, vale mencionar una excepción y es que en particular en las fórmulas Count, el primer parámetro debe corresponder a un atributo y no a una expresión. Puede referenciarse cualquier atributo de la tabla en la cual se quieran contar registros (que cumplan con condiciones explícitas y/o implícitas).
• Ejemplos de Fórmulas Aggregate (Globales) :
Hemos modificado el diseño de las transacciones para que en lugar de representar que cada producto tiene un único precio, representamos que cada producto tiene una lista de precios de acuerdo a la fecha de cambio de los mismos. Al efectuar este cambio, el atributo ProductPriceListPrice no podrá estar presente en el segundo nivel de la transacción Invoice. ¿Por qué? Porque el nuevo diseño estará representando que un producto ya no tendrá solamente un precio, sino muchos: uno por cada fecha de cambio de precio del mismo. Por lo tanto, para un producto en una línea de una factura, tendremos que “buscar” el precio vigente del mismo, teniendo en cuenta la fecha de la factura. Recordemos que los atributos que se pueden inferir en determinado nivel de una transacción, son los que pertenecen a la tabla extendida de la tabla base asociada al nivel en cuestión. En este ejemplo, la tabla extendida de la tabla INVOICEDETAIL, no incluye a la tabla PRODUCTPRICELIST (en la cual se encuentra el atributo ProductPriceListPrice):
Página 82 de 143
Curso GeneXus X Año 2011
por lo tanto en el nivel Detail de la transacción Invoice (asociado a la tabla INVOICEDETAIL) no es posible inferir al atributo ProductPriceListPrice (almacenado en la tabla PRODUCTPRICELIST).
¿Qué ocurre si dejamos al atributo ProductPriceListPrice en el nivel Detail de la transacción Invoice? Como no se podrá inferir, entonces GeneXus no tendrá otra opción que determinar almacenarlo en la tabla INVOICEDETAIL pese a que ya estará este atributo secundario en otra tabla (PRODUCTPRICELIST). Como el resultado de esto es un modelo de datos no normalizado, se reportará el error al querer reorganizar la base de datos. ¿Y cómo hacemos para desplegar en cada línea de la factura, el precio vigente del producto de la misma? ¿De todos los precios correspondientes al producto de una línea, cuál queremos recuperar? Evidentemente, de todos los precios que tengan fecha menor o igual a la fecha de la factura, queremos aquel que tenga fecha mayor. Procedemos entonces a crear en el nivel Detail de la transacción Invoice, un nuevo atributo de nombre InvoiceDetailProductPrice, al que definiremos como fórmula global y por lo tanto será un atributo virtual. Le asociaremos a dicho atributo la fórmula Max:
GeneXus inferirá la tabla a ser navegada por el último parámetro de la fórmula (el atributo de retorno). Como ya se ha explicado, los atibutos que podremos referenciar en la definición de una fórmula Aggregate deberán pertenecer a la tabla a ser navegada y su tabla extendida + a la tabla asociada al atributo que se está definiendo como fórmula y su tabla extendida. De involucrar atributos que no pertenezcan a este contexto mencionado, un error se reportará en el listado de navegación correspondiente. GeneXus considerará al momento de efectuar la búsqueda, la condición explícita de filtro + las condiciones ímplicitas detactadas. La fórmula Min es totalmente análoga a Max, con la única diferencia de que al encontrar un conjunto de registros que cumplan con las condiciones, se seleccionará aquel registro que tenga valor mínimo para el atributo indicado en el primer parámetro y se retornará el valor de retorno que se haya indicado en el último parámetro. La fórmula Find por su parte, también permite buscar un registro que cumpla con ciertas condiciones, sin embargo de haber más de 1 registro que cumpla con ellas, la fórmula devolverá el atributo de retorno correspondiente al primer registro encontrado (sin maximizar ni minimizar un valor en el conjunto de registros que cumplan con las condiciones). La sintaxis Find es: Find (Expresión de retorno, [Condición explícita], [Valor por defecto]) [if Condición de disparo]
• Ejemplo de Fórmula Compuesta (Global):
Página 83 de 143
Curso GeneXus X Año 2011
6.3. Fórmulas Locales 6.3.1.Generalidades •
Además de definir fórmulas asociadas a atributos (globales, a nivel de KB) también es posible definir fórmulas en el propio código.
•
Llamamos a este caso de uso de las fórmulas: fórmulas inline o fórmulas locales.
•
Es posible definir inline – locales → en source de procedimientos, subrutinas, eventos, etc.
•
Comportamiento depende de si se definen: -
Dentro del for each:
Tabla base de fórmula = tabla base del for each
Es posible incluir en la definición de la fórmula:
•
Atributos de la tabla base de la fórmula + extendida
•
Atributos de la tabla navegada por la fórmula + extendida
•
Variables
Ejemplo:
Otro ejemplo de codificación posible es el que sigue, en el cual la fórmula se define en el propio where del For Each, para filtrar por dicho cálculo: For each order CountryId where Count(CustomerName)>50 … Endfor
-
Fuera del for each
No se está posicionando en ninguna tabla al momento de disparar la fórmula → no hay tabla asociada a la fórmula
Es posible incluir en la definición de la fórmula: •
Atributo de la tabla a ser navegada + tabla extendida
•
Variables Página 84 de 143
Curso GeneXus X Año 2011
En este caso de uso, no hay filtros implicitos:
Consideración importante: El disparo de la fórmula se realiza al componer el grupo donde se encuentra, por lo tanto:
Consideración importante Una fórmula se dispara cuando comienza el grupo que la contiene. Esto significa que si se tiene definida una fórmula con sus parámetros en determinada línea de código, los valores de los parámetros van a ser aquellos que fueron leídos cuando el grupo fue ejecutado en la base de datos. Entonces, si el grupo fue ejecutado en la base de datos, y luego de eso ud. asignó valores diferentes a los parámetros que involucrará en la definición de la fórmula, los valores de los parámetros que se tendrán en cuenta no serán los que usted asignó; por el contrario serán los leídos cuando se ejecutó el grupo que contiene a la fórmula en la base de datos. ¿Y cuándo se ejecutan los grupos? - Cuando comienza un programa. - Cuando comienza un For Each. - Luego de un Endfor. - Cuando comienza una subrutina o un evento. En particular como en este caso estamos explicando fórmulas locales definidas fuera de comandos For Each, los ítems que aplican son 1, 3 y 4. Lo mismo ocurre con las variables. Los valores de las variables que son tenidos en cuenta al momento de disparar las fórmulas, son los valores que las variables tienen asignados cuando el grupo que contiene a la fórmula se ejecuta. De modo que si escribimos el siguiente código: &CustomerId = 1 &total = Sum(InvoiceTotal, CustomerId = &CustomerId)
Página 85 de 143
Curso GeneXus X Año 2011
Capitulo 7 User Controls, SDT y Data Provider 7.1. Tipos de Datos Estructurados. SDT 7.1.1.Introducción • Lenguajes de programación manejan: • Tipos de datos simples (Numeric, Character, etc.) • Tipos de datos compuestos. • Ejemplo de Tipos de datos compuestos (registros o tipos de datos estructurados)
• Se crean como cualquier otro objeto GeneXus. • Editor similar al de estructuras de transacciones. • SDT se compone de: miembros, subestructuras y colecciones:
El editor de tipos de datos estructurados es similar al editor de transacciones. Contiene: Propiedad Name, con el nombre que identifica al miembro, subestructura o colección. Propiedad Type, en la cual se debe seleccionar un tipo de dato simple, un dominio, o un tipo de datos estructurado que ya se haya definido en la KB (propiedad Type solo adquiere valor si se está definiendo un miembro y no una subestructura o colección). Propiedad Is Collection, para indicar si el miembro representa una lista (en seguida veremos un ejemplo). Obsérvese que una subestructura es un miembro compuesto, en lugar de ser uno simple. Es decir, es, en particular, también él, un tipo de datos estructurado. Página 86 de 143
Curso GeneXus X Año 2011
Haciendo botón derecho sobre un miembro de la estructura, se despliega la ventana que se ve a la izquierda, donde se puede insertar otro miembro, o una subestructura. Tip: Si se desea crear un SDT con exactamente la misma estructura que la de una transacción, entonces en lugar de definir uno a uno todos los miembros, subestructuras y colecciones, alcanza con arrastrar (hacer Drag & Drop) el nombre de la transacción desde el Folder View hacia la estructura en edición del SDT. De la misma forma, si se desea que un miembro de la estructura corresponda a un atributo, puede seleccionarse y arrastrarse el atributo desde el Work With Attributes (ventana editable desde opción View del menú de GeneXus) o insertarse con el diálogo Insert/Attribute del menú de GeneXus. • Ejemplo conteniendo colección:
Marcando el check box Is Collection se abrirá una rama de la estructura como puede verse, donde se le pedirán dos nombres: el de la colección en sí, y el de cada ítem de la misma. Como se verá a continuación, cuando se define una colección, junto con el SDT se estará creando implícitamente otro, que corresponderá a los items de la colección. Esto se debe a que de esta forma se podrá luego definir una variable del tipo de datos del ítem, para luego agregarla a la colección.
7.1.2.Utilización • Se utilizan a través de variables. • Los atributos no pueden ser SDT.
A la derecha puede verse el diálogo de propiedades de una variable &country que se está definiendo dentro de algún objeto. El SDT Country definido en la KB tal como se aprecia en la página anterior, da lugar a la creación de dos tipos de datos estructurados: uno correspondiente al propio tipo de datos “country” y otro correspondiente a los ítems de la colección “country.City”. El por qué de este último caso se debe a que uno podría querer definir una variable solo de ese tipo de datos, para luego agregarla con el método Add que ya mencionaremos, a la colección. Página 87 de 143
Curso GeneXus X Año 2011
Obsérvese que la variable &country se ha definido del tipo de datos “country” que aparece en la lista obtenida al hacer clic en el combo box de la propiedad “Data Type” del diálogo de definición de propiedades de la variable. • Para datos no repetitivos, se accede a cada miembro mediante el nombre, como propiedad de la variable.
• Ejemplo: Transacción que registra los recibos efectuados a los clientes y SDT basado en la transacción:
Aquí se presenta un ejemplo con el que continuaremos trabajando en lo que sigue. Agregamos a nuestra realidad una transacción de recibos. Supongamos que una vez al mes, se lanza un proceso de generación de recibos, en el que, tras elegir un período de facturación (usualmente todo el mes anterior) para cada cliente se sumarizan todos los montos de las facturas que se le efectuaron en dicho período, y se le genera un recibo (autonumber). La generación del recibo será automática (la realizará nuestro sistema); ese es un tema que veremos en breve. Por ahora, supongamos que necesitamos un procedimiento que devuelva los datos de un recibo determinado de los generados automáticamente como explicamos recientemente (por ejemplo, el de id. 7). Una opción es acceder mediante un for each a la tabla BILL creada a partir de la transacción de igual nombre, y junto con la regla parm: parm( out: BillDate, out: CustomerId, BillInvoicePeriodEndDate, out: BillAmount);
out:
CustomerName,
out:
BillInvoicePeriodStartDate,
out:
implementar lo pedido. La otra opción, es devolver toda esa información en una sola variable ¡estructurada! parm( out: &bill); cargada como se muestra arriba. Para ello se define un SDT basado en la transacción Bill (los SDTs no pueden tener el mismo nombre que una transacción, razón por la cuál le llamamos BILL_SDT). Para no tener que ingresar uno a uno los miembros del SDT de igual nombre que los atributos de la transacción, alcanza con arrastrar la transacción Bill desde el Folder View, dentro de la estructura del SDT y automáticamente se inicializará como se muestra arriba. • ¿Y si queremos devolver una lista de recibos? • Opción 1: no modificar el SDT y agregar variable &bills colección:
Página 88 de 143
Curso GeneXus X Año 2011
Supongamos que queremos devolver una lista de recibos (por ejemplo, los que se hayan efectuado en un rango de fechas dado).
Hay que pedir nuevo espacio de memoria para la variable &bill, para la siguiente iteración. Como veremos en breve, existe un modo mucho más sencillo, de más alto nivel, DECLARATIVO, de obtener la colección de SDTs cargada, sin tener que preocuparnos de realizar operaciones de bajo nivel, como agregar una variable a la colección y pedir memoria... Utilización de Data Provider
Página 89 de 143
Curso GeneXus X Año 2011
...este modo declarativo, por tanto de alto nivel, de cargar una colección de SDTs se conoce con el nombre de Data Provider. Podemos pensarlo como un procedimiento especializado, que devolverá siempre información estructurada (ya sea simple como colección). Aquí presentamos el ejemplo, que luego ampliaremos cuando entremos de lleno en este tema.
• Opción 2: modificar el SDT para que sea colección y luego trabajar de la misma forma, definiendo las variables:
• Para datos repetitivos, se accede a cada item mediante comando:
Página 90 de 143
Curso GeneXus X Año 2011
La variable &var va tomando los valores de cada posición de la lista. No es posible obtener la posición del ítem durante la recorrida, para esto es necesario definir un variable que actúe como contador. Como puede fácilmente inferirse, este comando es válido para colecciones de cualquier tipo de datos, no solo SDTs.
7.1.3.Propiedades Los nombres de los miembros de una variable SDT se muestran como propiedades:
Las propiedades Count y CurrentItem solo están disponibles para variables SDT Collection.
7.1.4.Operadores y Métodos
Página 91 de 143
Curso GeneXus X Año 2011
Aquí se presentan la mayoría de los métodos con los que cuentan los tipos de datos estructurados. Algunos aplican a variables SDT no colección, se representan con &var, otros a colecciones, se representan con &cVar. Para la lista completa, así como ejemplos, acceder al wiki o al help de la versión.
7.1.5.Ejemplo •
Método ToXml
•
Si se inserta en un form una variable SDT collection:
Página 92 de 143
Curso GeneXus X Año 2011
Puede seleccionarse del SDT los miembros que quieren cargarse como columnas del grid. Obsérvese que en nuestro caso hemos omitido los miembros CustomerId, BillInvoicePeriodStartDate y BillInvoicePeriodEndDate.
7.2. Data Providers. DP 7.2.1.Escenario • Intercambio de información jerárquica entre aplicaciones, o dentro de una misma aplicación.
Supongamos que desde nuestro Billing System, queremos enviar a un sistema de deudores y acreedores, un listado de los recibos correspondientes a cierto período de facturación (es decir, para un período determinado se desea sumarizar para cada cliente el importe total que se le ha facturado y generarle un recibo). Se trata de información jerárquica (enviaremos info de recibos, cada uno de los cuáles tiene determinados datos). El formato más usual de intercambio de información jerárquica suele ser Xml, aunque no sabemos qué deparará el futuro en ese aspecto (por ejemplo se está volviendo común el formato Json).
7.2.2.Procedimientos “Procedurales” • ¿Cómo obtenemos los recibos para un rango de fechas dado?
Página 93 de 143
Curso GeneXus X Año 2011
Teniendo el mismo SDT Bill que definimos cuando estudiamos los tipos de datos estructurados (allí le llamamos Bill_SDT, aquí le llamaremos Bill), estamos utilizando un procedimiento con los siguientes parámetros: parm( in: &start, in: &end, out: &bills ); siendo &start y &end variables de tipo Date que reciben el rango de facturación, y &bills una variable Collection del SDT Bill. Obsérvese que estamos utilizando el Data Selector ActiveCustomers para filtrar por clientes activos y utilizando la fórmula inline sum, en la que únicamente sumamos los totales de aquellas facturas que pertenezcan al cliente de la actual iteración del For each y cuyas fechas se encuentren en el rango dado y tengan el valor True en el atributo Booleano InvoicePendingFlag. Este atributo será cambiado a False una vez que la factura haya sido procesada dentro del proceso de generación de recibos que iniciamos aquí pero que completaremos más adelante, cuando estudiemos las formas de actualizar la información de la base de datos (aquí solamente estamos obteniendo la información de los recibos, pero aún no los registraremos en la base de datos... ). •
Lenguaje de Input
•
Lenguaje de Transformación
•
Lenguaje de Output
Página 94 de 143
Curso GeneXus X Año 2011
Todo procedimiento toma un Input, y mediante alguna transformación, obtiene un Output. Para obtener el Input se tiene cierto lenguaje (en GeneXus si se trata de la base de datos, nombrando los atributos alcanza, como se puede ver en el ejemplo, a la derecha de las asignaciones; así como también se obtiene de los parámetros). Para realizar la Transformación se tiene otro lenguaje (el código que podemos ver en el ejemplo) y luego para expresar el Output otro más. Como puede verse, en el caso de los procedimientos GeneXus, tanto el Input como el Output se encuentran embebidos dentro del propio código de Transformación. Puede decirse entonces que el foco está puesto en la transformación. De esta forma la intención del procedimiento, su salida, queda oscurecida dentro de ese código, que mezcla: • elementos de salida, • con elementos de transformación (en nuestro caso, el comando for each, el método add y el operador new, que implementan el armado de la colección de recibos) • y con elementos de entrada.
7.2.3.Procedimientos “Declarativos” •
Lenguaje de Input
•
Lenguaje de Transformación
•
Lenguaje de Output
Con un Data Provider, el foco está ubicado en el lenguaje de salida: obsérvese que se indica en una estructura jerárquica de qué se compone ese Output. Luego, para cada elemento de la estructura jerárquica, habrá que indicar en el Source del Data Provider, cómo se calcula. Por tanto, alcanza con observar el lado izquierdo para deducir cuál será la estructura resultante. Luego, alcanzará con indicar el formato deseado para esa estructura jerárquica resultante...
Página 95 de 143
Curso GeneXus X Año 2011
Luego, una misma información estructurada, podrá representarse utilizando los diferentes formatos existentes. Esa es la idea del Data Provider. Si en el futuro aparece un nuevo formato de representación de información estructurada, el Data Provider continuará invariable... GeneXus implementará el método de transformación a ese formato, y solo habrá que utilizarlo.
Hasta aquí hicimos un análisis descriptivo. Surge inmediatamente la siguiente pregunta: ¿cómo se representan en GeneXus las estructuras jerárquicas de datos? Por tanto, la salida de un Data Provider será un SDT (o una colección de SDTs). Luego, con esa salida puede realizarse lo que se desee, en particular, convertirla a formato XML, con el método toXml de los SDTs. Veamos cómo declaramos la salida... no será de la forma convencional (como parámetro de out de la regla parm)...
Página 96 de 143
Curso GeneXus X Año 2011
Aquí vemos para el ejemplo que venimos estudiando, que teniendo el SDT definido Bills, collection, que coincide con la estructura que se infiere del Source del Data Provider ‘GetBills’, habrá que declarar ese SDT en la propiedad Output del Data Provider. Pero no es la única posibilidad... veremos otra en la página siguiente, donde cobrará sentido la propiedad Collection.
Si en lugar de tener el SDT collection Bills, tuviéramos el que aparece arriba, Bill, entonces podríamos programar el Source del Data Provider como vemos y luego configurar la propiedad Collection en ‘True’, con lo cuál se abrirá una nueva propiedad, Collection Name, que permitirá dar nombre a la colección. Este Data Provider es equivalente al anterior. Obsérvese que en este caso no es necesario poner la raíz de la jerarquía, Bills, puesto que se infiere de la propiedad ‘Collection Name’. De todas formas, si bien no es requerido, podría programarse el Source exactamente igual al anterior, es decir, con el grupo Bills encabezando la jerarquía. Bills Página 97 de 143
Curso GeneXus X Año 2011
{ Bill using ActiveCustomers() { ... } } Nota: la cláusula using, así como las where que veremos luego, order, etc., podrán especificarse tanto a nivel del grupo Bills, como del grupo Bill, en casos como este. Volveremos sobre este tema más adelante.
7.2.4.Objetivo Retorno de datos estructurados. • Con Procedimiento: hay que armar el SDT con operaciones de bajo nivel. • Con Data Provider: facilidad de escritura y claridad. Es declarativo → independiente de implementación. → para devolver datos estructurados utilizaremos un DP en lugar de un Procedimiento. Los Data Providers atacan eficientemente un tipo de problema: aquel que consiste en retornar datos estructurados. Para ese tipo de problemas contábamos con los procedimientos, pero su desventaja evidente era que necesitábamos implementar la forma de armar esas estructuras con operaciones de bajo nivel, como agregar un ítem a una colección con Add, y pedir memoria (new). Asimismo, si hubiera que hacer cálculos complejos para dar valor a cada miembro de la estructura, quedarían en el código embebidos los elementos de la salida, sin que fuera sencillo visualizar en un golpe de vista la salida resultante del Procedimiento. Todos estos inconvenientes se evitan con un Data Provider. Aquí el foco está puesto en la salida, razón por la cuál con un solo vistazo quedará evidenciada la estructura del Output. Por otro lado, siendo absolutamente declarativo, nos independizamos de la forma en que se implementa realmente la carga del SDT. De eso se encarga GeneXus. Cuanto más declarativa sea una herramienta, más fácil de programar, más adaptable al cambio, más independiente de una implementación particular. GeneXus tiende a permitir declarar lo más posible y cada vez programar ‘proceduralmente’ menos. Lo declarativo traslada el problema de la implementación a la herramienta, y se la quita al programador. Cuanto más inteligente una herramienta, menos necesidad tendrá el programador de resolver el problema: le alcanzará con enunciarlo.
7.2.5.Utilización • Igual que un procedimiento que devuelve información estructurada:
Un Data Provider también puede recibir parámetros vía regla parm, pero a diferencia de un procedimiento donde todos los parámetros pueden ser de entrada/salida, aquí solo podrán ser de entrada. Por otro lado, un procedimiento que devuelve información estructurada lo hace mediante una variable que se carga en el código, y que debe declararse como de salida, en el último parámetro de la regla parm. En un Data Provider, la declaración del tipo de datos de salida se hace mediante las propiedades Output, no en la regla parm. Página 98 de 143
Curso GeneXus X Año 2011
La invocación desde cualquier objeto GeneXus de un Data Provider es idéntica a la invocación de un procedimiento, es decir, con udp. Recuerde que al invocar a un objeto y no especificar el método de invocación, se asume udp. La variable &TheBills deberá estar declarada en el objeto GeneXus en el que se realiza la invocación. Es la variable en la que se devolverá el resultado. Recuerde que si el SDT definido en la KB es el correspondiente a los ítems individuales: Bill, (y no a la colección Bills), entonces la variable &TheBills se definirá como Collection, de tipo de datos Bill. Luego, con la estructura jerárquica devuelta se podrá, por ejemplo, convertir al formato deseado, como XML. Importante: Como veremos, un Data Provider no solo puede retornar un SDT o colección de SDT, sino también otro tipo, el Business Component, que también representa información estructurada. Vea ese tema para completar el conocimiento de Data Providers.
7.2.6.Lenguaje DP • Componentes básicos:
El ejemplo que podemos ver aquí es parecido al que estuvimos trabajando. Hemos agregado el elemento BillQuantity. Tómese unos instantes para pensar cómo deberá ser el SDT BillsInfo, para que matchee con este Source. A la izquierda lo hemos presentado. De todos modos, el Source no tiene por qué escribirse tal cual la estructura del SDT. Cuando en el SDT tenemos un miembro collection de un determinado ítem, podemos omitir del Source el Item como grupo. Ejemplo: con el SDT que tenemos arriba, podríamos haber escrito el Data Provider: Billsnfo { Bills { BillDate = &today CustomerName = CustomerName BillInvoicePeriodStartDate = &start BillInvoicePeriodEndDate = &end BillAmount = sum (InvoiceAmount, ... ) &quantity = &quantity + 1 } BillQuantity = &quantity Página 99 de 143
Curso GeneXus X Año 2011
} con el mismo resultado. GeneXus tiene la inteligencia suficiente como para matchear con el SDT.
7.2.7.Grupos • Agrupan tanto elementos, grupos como variables en un mismo nivel de jerarquía. • Puede ser repetitivo como no serlo (inteligencia para determinarlo).
• Un grupo repetitivo es análogo a un for each: • Determina tabla base (de igual forma que en un for each) • Tiene disponibles las mismas cláusulas que para un for each:
En el ejemplo, el grupo de nombre Bill será repetitivo. ¿Por qué? Para contestar la pregunta, hagamos otra: ¿y si fuera un for each, donde los elementos de la izquierda de las asignaciones corresponden a los distintos elementos de una variable SDT? En este caso la presencia de CustomerName (a la derecha de la segunda asignación) permite afirmar que hay tabla base: CUSTOMER. Por tanto el grupo será repetitivo, iterando sobre la tabla CUSTOMER. En el ejemplo que veníamos trabajando, podíamos tener la cláusula: Bill using ActiveCustomers() que es como tener: For each using ActiveCustomers() por lo que así no estuviera el atributo CustomerName en la segunda asignación, de todos modos sería con tabla base, por la presencia del atributo CustomerStatus en el Data Selector ‘ActiveCustomers’. Obsérvese que el grupo de nombre BillsInfo, en cambio, no será repetitivo, no tiene cláusulas asociadas, y los elementos que contiene están definidos a base de variables y no de atributos: BillQuantity = &quantity Página 100 de 143
Curso GeneXus X Año 2011
&quantity = 0 ¿Y qué pasa con el grupo Bills? Obsérvese que en este caso, es un grupo que sólo contiene otro grupo. El grupo contenido será repetitivo, por lo que Bills será una colección de Bill. Por este motivo, el subgrupo Bill podría omitirse (solamente dejar Bills) y que quede implícito. De este modo, las cláusulas del grupo que permiten definir order, filtros, defined by, se pueden asociar a este grupo. • También pueden repetirse grupos:
Si la condición se hubiese colocado en el grupo Clients, aplicaría a los dos subgrupos Client. Por eso es que se permite que las cláusulas operen a nivel de los grupos que se repiten (items), y no solo a nivel del grupo que es colección de ítems.
7.2.8.Elementos • Un Elemento es un valor atómico en el Output. • Cada Elemento debe ser asignado y su sintaxis es la de las fórmulas.
• Elementos y Atributos usualmente tienen el mismo nombre → notación más compacta:
Página 101 de 143
Curso GeneXus X Año 2011
7.2.9.Variables • Algunas veces es necesario realizar cálculos internos:
7.2.10. Grupos: Opciones avanzadas •
Cláusula Default (~ When none): El grupo solo irá a la salida si el grupo precedente (de igual nombre) no está presente.
•
Cláusulas de Paginado: Count y Skip: Para manejar cuántos registros irán a la salida.
•
Cláusula NoOutput: En un Grupo significa que el grupo no deberá estar presente en la salida, sino solo sus elementos subordinados. Página 102 de 143
Curso GeneXus X Año 2011
•
Cláusula OutputIfDetail: Para grupo cabezal conteniendo grupo con sus líneas: solo se presenta el primero en la salida si tiene líneas.
•
Cláusula Input: Si se necesita trabajar con colecciones (devueltas por Procedimiento o Data Provider) como Input del DP.
•
Count y Skip: Para manejar cuantos registros irán a la salida.
No lo hemos mencionado, pero como intuitivamente podemos pensar, de tener un par de grupos “anidados” (un grupo que, entre otras cosas, contiene a otro), si cada uno debe acceder a la base de datos, entonces las tablas base, así como el criterio de navegación se determinan exactamente igual que en el caso de un par de for eachs anidados. Por ese motivo, en el ejemplo, el grupo Country tiene tabla base COUNTRY; el grupo Customers tiene tabla base CUSTOMER, y se hará un join entre ambas tablas a la hora de recuperar la información para cargar la colección de países. Es decir, por cada registro de COUNTRY se cargará la información de sus atributos CountryId y CountryName en los elementos Id y Name del ítem Country del SDT colección “Countries” que se está cargando, y luego se recorrerá la tabla CUSTOMER filtrando por aquellos registros para los cuales CUSTOMER.CountryId = COUNTRY.CountryId, cargando para cada uno los elementos Id y Name. La presencia de la cláusula OutputIfDetail hace que solamente se presenten en la salida aquellos países que cuenten con clientes asociados. Si un país de la base de datos no tiene cliente alguno, entonces no se presentará como item de la colección de salida, “Countries”.
7.2.11. Cláusula Input •
Hasta aquí asumimos que el Input venía de la BD; pero también se necesitan otro tipo de entradas. Por ejemplo, la salida de un Procedimiento o Data Provider. La forma obvia de trabajo es a través de variables, que una vez asignadas se manejan de forma usual:
&var = Proc.udp( parm1, ..., parmn) &SdtVariable = DataProvider( parm1, ..., parmn ) •
Pero si se necesita trabajar con una colección se necesita la cláusula Input… o
Para trabajar con colecciones.
Página 103 de 143
Curso GeneXus X Año 2011
Página 104 de 143
Curso GeneXus X Año 2011
Capitulo 8 Subtipos En este capitulo verá como definir atributos diferentes pero que son conceptualmente iguales, através de grupos de subtipos.
8.1. Definiciones •
Las relaciones entre atributos GeneXus se establecen a través de sus nombres.
•
Mediante subtipos se puede establecer que dos atributos que se llaman diferente corresponden al mismo concepto.
•
Casos de subtipos: o
A. Múltiples referencias
o
B. Especialización de un nivel (relación 1‐1)
o
C. Subtipos recursivos
8.1.1.Múltiples Referencias •
Atributos conceptualmente iguales que cumplen roles diferentes (ej.: reservas de pasajes).
Realidad a representar/diseñar: En cada reserva hay dos ciudades involucradas, las cuales cumplen roles diferentes. El rol de una de las ciudades es el de ser la “ciudad de partida” (ciudad origen) y el rol de la otra es el de “ciudad de arribo” (ciudad destino). El dominio de ambas ciudades es el mismo, el de la tabla CITY. La forma de representar que tanto el “origen” como el “destino” son ciudades de la tabla CITY, es diseñando la transacción “Reservation” en la forma mencionada inicialmente en la transparencia. Sin embargo, no es posible que en la estructura de una transacción figure el mismo atributo más de una vez, pues no habría manera de identificarlos. SOLUCIÓN: llamar a las dos ciudades de la reserva con diferentes nombres de atributos. Cualquiera de las siguientes opciones es válida. Elegimos la 3era por mayor claridad. Opción 1) ReservationCityFromId Opción 2) CityId
ciudad origen CityId
ciudad destino (mismo nombre que la PK de CITY)
ciudad origen (mismo nombre que la PK de CITY) ReservationCityToId
Opción 3) ReservationCityFromId
ciudad origen ReservationCityToId Página 105 de 143
ciudad destino
ciudad destino
Curso GeneXus X Año 2011
El problema es que al poner por ejemplo ReservationCityFromId en lugar de CityId, GeneXus deja de inferir que ReservationCityFromId corresponde al código de una ciudad de la tabla de CITY. ¿Cómo hacemos para relacionarlos, siendo que tienen diferente nombre de atributo? ver respuesta en próxima hoja … Para estos casos GeneXus provee los SUBTIPOS, que permiten definir que dos atributos que se llaman diferente corresponden al mismo concepto. En nuestro ejemplo, si definimos al atributo ReservationCityFromId como subtipo de CityId, estamos especificando que si bien ReservationCityFromId y CityId son diferentes atributos (de nombres diferentes), corresponden, no obstante, al mismo concepto (una ciudad de la tabla CITY). Al establecer que un atributo es subtipo de otro, estamos estableciendo una dependencia funcional entre ellos. Si ReservationCityFromId es subtipo de CityId, entonces decimos que CityId es el supertipo de ReservationCityFromId. • Los atributos que se encuentran en una relación subtipo-supertipo comparten la misma definición (tipo de datos). • Se realizan los controles de integridad referencial automáticamente. • La tabla extendida que se obtiene con la definición del subtipo, es la misma que se obtendría si se utilizara directamente el supertipo. • Con la definición de los subtipos antes mencionados: o
Se establecen las siguientes relaciones:
o
Se hacen además automáticamente los controles de Integridad Referencial (IR) entre ambas tablas cuando se utilizan sus correspondientes transacciones.
o
Los atributos secundarios de CITY: Pertenecen a la tabla extendida de RESERVATION, pero al existir doble referencia no se pueden utilizar directamente desde RESERVATION (ambigüedad de caminos y con valores de ciudades diferentes). Solución → definir también subtipos para los atributos secundarios de CITY, e incluirlos en c/u de los grupos de subtipos
IMPORTANTE: Notar que este caso de múltiples referencias puede darse tanto: • en la tabla base (*) • como en la tabla extendida (*) es el caso del ejemplo, en el que en la propia tabla (RESERVATION) hay más de una referencia a otra tabla (CITY) y con valores diferentes.. RESUMIENDO: siempre que desde una tabla se accede a otra que está en su tabla extendida por “más de un camino” y con “valores diferentes”, es necesario definir SUBTIPOS, para poder llamarle diferente a los atributos y haciéndose automáticamente todos los controles de integridad referencial. Una vez definidos los grupos de subtipos que sean necesarios, la forma de indicarle a GeneXus cuál de los caminos debe tomar para acceder a la tabla destino, es mencionando los nombres de atributos que correspondan. Ej.: mencionar ReservationCityFromName si lo que se necesita en ese momento es el nombre de la ciudad origen, o mencionar ReservationCityToName si lo que se necesita es el nombre de la ciudad destino.
Página 106 de 143
Curso GeneXus X Año 2011
Con el grupo estamos indicando que los atributos pertenecientes al mismo grupo de subtipos, están relacionados. Por ej., en nuestro ejemplo, GeneXus sabrá que el atributo ReservationCityToName será inferido a través del atributo ReservationCityToId (y no a través del ReservationCityFromId). Esto es por pertenecer ambos al mismo grupo (al de nombre ReservationCityTo). Cuando el usuario digite un valor sobre ReservationCityToId, no solo se va a hacer automáticamente el control de integridad referencial (que exista un ciudad con ese código en la tabla CITY), sino que se va a inferir en ReservationCityToName el nombre correspondiente a ese código de ciudad. IMPORTANTE: Todo grupo de subtipos, debe contener un atributo o conjunto de atributos, cuyos supertipos, juntos, correspondan a la clave primaria de una tabla del modelo. Los demás atributos del grupo deberán ser de tipo “Inferred”, es decir, deberán poder inferirse a través de esa clave. En caso contrario estará mal definido el grupo.
8.1.2.Múltiples Referencias a la tabla extendida • COUNTRY pertenece a la tabla extendida de SALE por caminos diferentes y con códigos de país diferentes.
Página 107 de 143
Curso GeneXus X Año 2011
Si quisiéramos por ejemplo listar las ventas (SALE), y de c/u de ellas mostrar los datos del cliente (nombre, país, etc.) y del vendedor (nombre, país, etc.): • necesitamos un for each con tabla base SALE y acceder a través de su extendida a las tablas CUSTOMER, SELLER y COUNTRY para listar los atributos secundarios del cliente, vendedor y país respectivamente. Problema: Los atributos de nombre CountryId, CountryName y todos los de la tabla extendida de COUNTRY pertenecen a la tabla extendida de SALE por dos caminos diferentes: 1) a través del país del cliente y 2) a través del país del vendedor. Solución: Debemos diferenciarlos, llamarlos con diferente nombre de atributo pero queriendo que se sigan representando todas las relaciones y haciéndose automáticamente todos los controles de integridad referencial.
Una vez definidos los dos grupos de subtipos que se muestran en la figura, y haciendo el cambio correspondiente en la estructura de la transacción Sale, ¡queda resuelta la ambigüedad en el modelo de datos!
Una vez definidos los subtipos, tenemos que recordar usar el nombre de atributo que corresponda a lo que queremos acceder. Por ejemplo, en todos aquellos objetos GeneXus en los cuales queramos acceder al código o al nombre del país del cliente de la venta debemos usar los atributos SaleCustomerCountryId y SaleCustomerCountryName respectivamente.
8.1.3.Especialización de Atributos Ej.: Sistema para una Universidad …
Página 108 de 143
Curso GeneXus X Año 2011
Caso de subtipos “Especialización de atributos”: Cuando se está modelando una categorización. Generalmente es utilizada cuando un objeto del negocio comparte todas las características de otro objeto, pero agrega algunas más. La diferencia puede estar tanto en las propiedades, como en el comportamiento que tendrá. Ejemplo “Sistema para una Universidad”: En este ejemplo, el profesor y el alumno tienen roles y comportamientos claramente diferenciados. Por ejemplo, el profesor tendrá cursos asignados, sueldo, etc. El alumno estará inscripto a un curso, tendrá asignados pagos, asistencia, escolaridad, etc. Estamos frente a un caso en el que los roles y el tratamiento de las entidades de la categorización están claramente diferenciados. Tanto los estudiantes como los docentes comparten información común (ambos tienen un nombre, una dirección, etc) pero también tienen información que difiere, que es propia de c/u de ellos. Para representar esta realidad, se crean las tres transacciones: “Person”, “Teacher” y “Student”. En la transacción “Person” figura la información común. Para representar que tanto los estudiantes como los docentes son personas, se utilizan los subtipos. Al definir que el identificador de “Teacher” es subtipo del identificador de “Person” estamos estableciendo esta relación. Cada vez que se inserte un registro en la tabla TEACHER a través de su transacción, se realizará el chequeo de integridad referencial contra “Person”. Asimismo, cada vez que se intente eliminar un registro de “Person”, se verificará primeramente que no exista ningún registro en la tabla TEACHER (ni en STUDENT) con el mismo valor en la clave primaria.
La transacción “Teacher” tiene asociada una tabla que contendrá físicamente sólo dos atributos: TeacherId y TeacherSalary. Al ser TeacherId identificador de la transacción, será la clave primaria de la tabla asociada. Además, al ser un subtipo de PersonId, será una clave foránea a la tabla PERSON. Por lo tanto, se harán los chequeos de integridad referencial correspondientes.
Página 109 de 143
Curso GeneXus X Año 2011
Los atributos TeacherName y TeacherAddress son subtipos de PersonName y de PersonAddress respectivamente y están agrupados con TeacherId, por lo que serán inferidos de la tabla PERSON, a través de la clave foránea TeacherId (no están almacenados en la tabla TEACHER).
8.1.4.Subtipos Recursivos Ejemplo: Employee‐Manager
Es posible tener una tabla subordinada a sí misma definiendo subtipos. Este tipo de subtipos se utiliza para modelar las relaciones recursivas. Por ejemplo, la relación entre Empleado y Gerente: -
cada empleado tiene un gerente. Un gerente, a su vez, es un empleado (aquí está la recursión).
-
un gerente puede tener varios empleados a su cargo.
Si además la realidad a representar es que “sólo los empleados que no son gerentes tienen un gerente”, entonces, cuando se ingresan los datos hay que realizar los siguientes controles: -
cuando se ingresan los gerentes, hay que permitir dejar en nulo el atributo EmployeeManagerId. Para esto, cambiamos a ‘Yes’ la columna Nulls del atributo EmployeeManagerId, el cual es FK en la tabla EMPLOYEE.
-
que todo empleado que no es gerente, tenga un gerente. Este control lo hacemos con la regla error que se muestra en la figura.
El atributo EmployeeManagerName no queda almacenado en la tabla EMPLOYEE, se infiere luego de ingresar un valor en EmployeeManagerId. Por ser EmployeeManagerId subtipo de EmployeeId, se realizan automáticamente los controles de integridad referencial de la tabla consigo misma. Esto se puede ver en la navegación de la transacción, como se muestra en la siguiente página. Listado de navegación detallado:
Página 110 de 143
Curso GeneXus X Año 2011
8.1.5.Consideraciones •
Cuando se define un subtipo éste "hereda" la definición del supertipo.
•
Al menos uno de los supertipos del grupo (o conjunto de supertipos del grupo) debe(n) corresponder a la PK de una tabla del modelo.
Página 111 de 143
Curso GeneXus X Año 2011
Capitulo 9 Data Selectors 9.1. Data Selectors - DS 9.1.1.Concepto Permite almacenar un conjunto de orders, filtros y un defined by, con el objetivo de reutilizar navegaciones (será referenciado desde distintas consultas y cálculos).
El atributo CustomerStatus está basado en un dominio enumerado, Status, que tiene 3 valores: • Active • On Hold • Closed Los clientes activos serán aquellos que tengan el valor Status.Active.
9.1.2.Definición Crear un objeto del tipo “Data Selector”, darle un nombre y definirlo:
Página 112 de 143
Curso GeneXus X Año 2011
9.1.3.Ejemplo En varios objetos de una KB, se repite la necesidad de consultar los clientes activos.
La forma de referenciar a un Data Selector dependerá desde cuál de los casos se desee invocar. Veremos la sintaxis para cada caso.
9.1.4.Beneficios Ahorro y reutilización de código: Se realiza cierta definición una vez y se reutiliza en varias consultas y cálculos en la KB Facilitan el mantenimiento: Se cambia la definición en un único lugar y el cambio aplica automáticamente a todos los lugares de la KB en los que se utilice. Optimizan el conocimiento: Facilitan el entrenamiento del personal nuevo para un proyecto, gracias al encapsulamiento que proveen. 9.1.5.Usos Dos maneras de uso: • Con clausulas using Página 113 de 143
Curso GeneXus X Año 2011
• Con operador in en clausula where Importante: • Un data selector siempre tiene tabla base • Según la forma de uso, los atributos presentes en la definición del Data Selector intervienen o no en la determinación de la tabla base del for each, grupo, grid o fórmula donde aplique:
For each con cláusula USING
Nota: La performance será la misma si se escriben 2 where en el For Each que si se referencia un Data Selector en el For Each (que tiene definida una condition) + un where en el For Each. GeneXus siempre tratará de optimizar el código generado, ya que el objetivo es, como venimos explicando, centralizar el conocimiento reusable en una sola definición y luego referenciar esa definición en todas las consultas y cálculos de la KB que aplique. Dado que en estos ejemplos presentados, se está referenciando en For Each al Data Selector con cláusula USING, los atributos presentes en la definición del Data Selector intervienen en la determinación de la tabla base del For Each. Por esto, en ambos ejemplos, la tabla base del For each es: CUSTOMER. For each con OPERADOR IN (En cláusulas WHERE)
Página 114 de 143
Curso GeneXus X Año 2011
En este ejemplo: •
La tabla base del Data Selector es: INVOICE
•
El atributo que antecede al operador IN pertenece a la tabla extendida de INVOICE (CustomerId)… y la consulta asociada al Data Selector devolverá una lista de valores correspondientes a dicho atributo (devuelve “una lista de clientes” que tienen facturas en el rango de fechas dado).
•
El For Each tiene su tabla base determinada por los atributosincluidos en el ForEach y sin tener en cuenta a la información almacenada en el Data Selector.
•
El For Each navegará su tabla base y extendida, filtrando los registros que contengan a alguno de los clientes de la lista devuelta por el Data Selector.
En Fórmulas Aggregate Select Es possible utilizar data selector en fórmulas aggregate select tanto globales como locales.
Consideraciones acerca de la sintaxis de las fórmulas Aggregate: Como se detalló en la diapositiva, en la sintaxis de las fórmulas Aggregate la expresión corresponde a la expresión a ser buscada, maximizada, minimizada, sumada o promediada. Puede ser un atributo (almacenado o fórmula) o bien una expresión que involucre atributos, constantes, variables. Sin embargo hay que tener en cuenta que: • Variables solo pueden involucrarse en fórmulas locales. Página 115 de 143
Curso GeneXus X Año 2011
• Para Sum y Average, el resultado deExpresión debe ser un valor numérico. • Únicamente para el caso de Count, no vale una Expresión sino un atributo. Consideraciones •
En los parámetros del Data Selector pueden incluirse tanto variables como atributos. -
•
Incluir atributos implica que se filtre por igualdad de ellos.
Un mismo data selector definido, tendrá cierto comportamiento u otro y participará o no en la determinación de la tabla base de la consulta dependiendo de cómo se haya “Invocado” en el contexto en el que sea usado.
Página 116 de 143
Curso GeneXus X Año 2011
Capitulo 10 Actualización de la Base de Datos En este capítulo se tratará la actualización interactiva y no interactiva de la base de datos (transacciones, Business components y procedimientos).
10.1.Tipos de Actualizaciones 10.1.1. Actualización Interactiva
• Transacciones: • A través del form el usuario ingresa/modifica/elimina los datos. • Ventajas: • Se ejecutan las reglas del negocio (y no se actualiza registro que viole regla Error especificada) • No se actualiza registro que viole integridad referencial.
• Business Components (BC) 10.1.2. Actualización No Interactiva
• Business Components (BC) • Procedures Hasta aquí sólo conocemos una forma de actualizar la base de datos de nuestra aplicación: las transacciones. Como sabemos, una transacción determina la o las tablas requeridas para almacenar su información. A su vez, al ser generada, se convertirá en un programa que implementa la lógica de inserción, eliminación y modificación de datos en esas tablas, en forma totalmente transparente para el programador. También la transacción permite definir todas las reglas de negocio que deberán cumplir los datos asociados. El programa generado se encarga de ejecutarlas. Así, si en una transacción tenemos una o varias reglas Error, que se disparan al satisfacerse determinadas condiciones, esas reglas estarán incluidas en el código generado, y no se permitirá actualizar la base de datos hasta tanto las condiciones que disparan esas reglas dejen de satisfacerse para los datos que se están manejando en esa oportunidad. Asimismo, como vimos, las transacciones aseguran el control de integridad referencial, muy importante para asegurar la consistencia de la base de datos. Pero las transacciones no cubren todas las necesidades en cuanto a la actualización de datos. También se necesitará una forma no interactiva, batch, de realizar actualizaciones sobre tablas. Para ello existen dos alternativas: actualización utilizando lo que se conoce como Business Component, absolutamente relacionado con las transacciones, o utilizar comandos específicos para tal fin dentro de objetos de tipo Procedure. Luego veremos que los Business Components permiten gran flexibilidad, dado que permiten actualizar la base de datos de cualquier forma: tanto interactiva como no interactivamente. En lo que sigue introduciremos los Business Components y luego los comandos de actualización directa dentro de Procedimientos.
10.2.Business Components 10.2.1. Escenario Necesitamos registrar los recibos que efectuamos a nuestros clientes.
Página 117 de 143
Curso GeneXus X Año 2011
Pero este es un caso en el que no nos sirve la transacción, dado que la generación de recibos es una tarea batch: se lanza una vez al mes un proceso que calcula y registra todos los recibos correspondientes a las facturas dentro de determinado período. Ya teníamos declarado el Data Provider ‘GetBills’ que realizaba el proceso de cálculo de recibos deseado, pero no grababa:
Y nos devolvía una colección de tipos de datos estructurados (SDT). Si observamos, la estructura jerárquica de este DP es casi idéntica a la estructura de la transacción Bill... ...¿y si pudiéramos guardar en una estructura los datos y luego grabarlos en la base de datos?
¡&bill no será una variable de tipo SDT, sino Business Component! A partir de una transacción, se puede crear su Business Component (BC).
Página 118 de 143
Curso GeneXus X Año 2011
Se creará automáticamente en la KB un tipo de datos Business Component, que podrá asociarse a variables, y cuyo nombre será el de la transacción. Por defecto la propiedad Business Component estará apagada. Eso significará que la lógica de negocio dada por la transacción, solo se utilizará dentro de la propia transacción, y no se creará un tipo de datos de igual nombre que la transacción, Bill, que permita encapsular la lógica de la transacción para utilizarla desde otros objetos.
En cualquier objeto GeneXus (ej: un procedimiento):
Obsérvese que en este caso la fecha inicial corresponde al día 10 de octubre, mientras la fecha final corresponde al día 9 de octubre... es decir, la fecha inicial será posterior a la fecha final...
Obsérvese que se llenan algunos miembros de la estructura de la variable &bill, Business Component, y luego se ejecuta el método Save (inexistente en SDTs). Esto es equivalente a lo que en forma interactiva ocurriría si el usuario final ingresa esos mismos valores en los atributos correspondientes del form de la transacción, y luego presionara el botón ‘Confirm’. Por tanto, al igual que como ocurre con la transacción, deberán dispararse las reglas y realizarse los controles de integridad referencial. ¿Qué pasará con ese juego de datos si se intenta grabar mediante la transacción? Se impedirá la Página 119 de 143
Curso GeneXus X Año 2011
grabación, debido a que se está cumpliendo la condición de la segunda regla de error (también podría cumplirse la de la primera si la fecha de hoy fuera anterior al 10 de setiembre de 2008). Por otro lado, si no existiera en la tabla CUSTOMER un cliente con identificador 3456, fallará la integridad referencial y así tampoco se dejaría ingresar el registro en la tabla BILL de base de datos.
Antes de ejecutar el método Save del Business Component, el valor de BillId en la estructura de &bill será vacío. Sin embargo, una vez que se ejecute el Save, como el atributo BillId era autonumber en la tabla BILL, se ejecutará la propiedad a nivel de la tabla y el registro se insertará con el número siguiente al último dado. En el caso de BillDate, análogamente, como no se le asigna valor en la estructura de &bill, antes del Save estará vacío, pero cuando éste se ejecute, se disparará la lógica de la transacción, y en particular la regla Default( BillDate, &today ) declarada. Por tanto en el registro insertado en la base de datos, el valor del atributo BillDate corresponderá a la fecha de hoy. Para BillAmount podríamos hacer el mismo análisis. Pero a diferencia del caso anterior, no hay regla que le asigne valor, por lo cuál se insertará el registro con valor vacío en este atributo. De asignarse valor para &bill.CustomerName no será considerado a la hora de la inserción, dado que CustomerName es un atributo inferido, y no tiene regla Update que permita modificarlo. Lo mismo sucedería para atributos definidos a nivel de la estructura de la transacción pero que fueran fórmulas. Es decir, son atributos virtuales en la transacción, por lo que también lo son a nivel del Business Component. Se disparará su valor.
Página 120 de 143
Curso GeneXus X Año 2011
(collection).
Cuando se ejecutan los métodos: Save, Check, Load, Delete se disparan y cargan los mensajes generados automáticamente por GeneXus así como las reglas Msg y Error definidos en la transacción. Se recomienda que siempre se recupere la lista de estos mensajes y se haga un “manejo de errores”. Los mensajes más comunes generados automáticamente por GeneXus son:
Las reglas Msg y Error aceptan en su definición además del parámetro con el mensaje a desplegar, un segundo parámetro que define el Identificador del mensaje. El objetivo es que cuando se ejecute la transacción como Bussiness Component, y se obtenga la lista de mensajes ocurridos luego de ejecutar una acción sobre la base de datos, se tenga de cada mensaje, además del mensaje en sí, su identificador, siendo posible así evaluar el identificador del mensaje para codificar el comportamiento en consecuencia: Msg|Error(<mensaje>, <Id del mensaje>) Ejemplos de <Id del mensaje>: "CustomerNameCannotBeEmpty", etc.
"1",
"2",
"Error1",
"Error2"
o
una
descripción
como
ser
De no especificar un identificador para el mensaje, habrá que preguntar por el texto del mensaje. Nota: Para los mensajes generados automáticamente por GeneXus, el Id es siempre en Inglés (independientemente del idioma seleccionado en el modelo).
10.2.2. Caso de Uso
Página 121 de 143
Curso GeneXus X Año 2011
Aquí completaríamos el proceso de generación de recibos. Observemos primeramente un hecho importante: un data provider no solo puede devolver un SDT simple o colección, sino también un BC simple o colección. Importante: el BC devuelto solo podrá insertarse en la base de datos. Es decir, un Data Provider solo permite llenar su estructura, para luego hacer una operación de INSERT sobre la base de datos. No será posible hacer una actualización o eliminación con un BC cargado vía Data Provider. Obsérvese por otro lado cómo se ha definido la variable &message de tipo de datos Messages.Message, correspondiente a los ítems del SDT predefinido Messages devuelto por el método GetMessages del BC. Una vez que hemos obtenido e ingresado en la tabla BILL todos los recibos correspondientes a la facturación, deberíamos recorrer las facturas procesadas, y modificar el valor de su atributo InvoicePendingFlag, pasándolo a False. Recordemos que el valor de este atributo se utiliza para no procesar dos veces una misma factura. Recordemos que este valor se utiliza en el Data Provider en el cálculo del elemento BillAmount: sum( InvoiceAmount, InvoiceDate >= &start and InvoiceDate <= &end and InvoicePendingFlag ) Dejaremos pendiente esta última parte para cuando estudiemos, unas páginas más adelante, las formas de actualización directa ( dentro de procedimientos exclusivamente ), de los registros de las tablas.
Página 122 de 143
Curso GeneXus X Año 2011
Adelantándonos aquí a presentar el otro tipo de objeto interactivo que estudiaremos un poco más adelante, el Web Panel, mostramos un objeto de este tipo en ejecución. Su función será pedir al usuario final un par de valores, que almacenará en las variables correspondientes de tipo Date (&start y &end) y luego, cuando el usuario presione el botón asociado al Evento Enter del Web Panel, se ejecutará su código. Obsérvese que el código es idéntico al del procedimiento visto antes. Con este ejemplo pretendemos mostrar que mediante un Business Component puede actualizarse la base de datos desde cualquier objeto GeneXus. Si bien el Business Component (BC) utiliza la lógica de la transacción, no considera el Commit. Por lo que hay que asegurarse luego de realizar una operación sobre la base de datos a tavés de un BC, que el commit se realice. Nota: GeneXus reconoce que debe realizar un acceso a la base de datos dentro de un procedimiento cuando se utiliza new, for each para actualizar, delete (operaciones que veremos luego), o el método Load() de BC, así como el Delete luego de un Load. En esos casos, coloca el Commit implícito. En caso contrario, no entiende que hay acceso a la base de datos, y es por eso que cuando se hace: &BC.elemento1 = … &BC.elemento2 = … &BC.Save() no agrega el Commit. No se da cuenta que estamos queriendo hacer un insert (lo trata como si fuera un SDT cualquiera), por lo que hay que escribirlo explícitamente. Si dentro del código del procedimiento hubiera además un new, for each que actualiza, o incluso un Load() o Delete, en cualquiera de esos casos no habría que escribir explícitamente el commit, porque ya estableció una conexión a la BD. Si sólo tenemos en nuestro procedimiento el código anterior, allí sí tendremos que agregar el comando Commit explícitamente.
10.2.3. Resumen • Objetivo: • Reutilizar la lógica del negocio definida en las transacciones. Usar el poder de las transacciones (sin sus forms) desde otros objetos GeneXus: o Procedimientos, Web Panels o ...desde otra Transacción (al igual que una variable podía llenarse interactivamente insertándola en el Form, también un BC) • Beneficios: •
Actualización a la BD garantizando integridad de los datos y ejecución de las reglas del negocio.
•
Reutilización de código (no necesidad de duplicar reglas de negocio)
•
Varios objetos GeneXus pueden actualizar la BD.
10.2.4. Reglas y Eventos • Reglas: son ejecutadas todas las reglas de la transacción excepto (son ignoradas por el especificador): •
• las que incluyen user interface ( Ej: Customer.call(),
•
• las que no aplican: parm, prompt, NoPrompt, Default_mode, etc
• Eventos: todos los eventos de la transacción son ignorados, excepto los eventos Start y After TRN (y si éstos incluyen referencias a objetos con user interface, se ignoran). Nota: cuando decimos ‘se ignora’ nos referimos en el contexto de la actualización utilizando BC, no cuando la actualización se realiza a través de la propia transacción. Si existe una regla que invoca a un objeto que tiene interfaz, es decir, form, esa regla no se incluye en el BC. Existe una forma de especificar que una regla declarada en la transacción no aplique cuando se ejecuta la transacción, sino solo cuando se ejecuta el Business Component asociado: es calificando la regla con [BC]. Ejemplo: [BC] Default( BillDate, &today); Si se quiere calificar de una sola vez un conjunto de reglas: [BC] { regla1; Página 123 de 143
Curso GeneXus X Año 2011
regla2; ... reglan; } Lo mismo vale para eventos. Análogamente, existen calificadores para indicar que una regla solo se ejecute si se está corriendo la transacción con su form web: [WEB].
10.3.Procedimientos (Actualización no Interactiva)
La modificación de datos de la base de datos se realiza en forma implícita: no hay un comando específico de actualización. Para actualizar uno o varios atributos de una tabla se utiliza el comando For each, y dentro del mismo el comando de asignación. Se pueden actualizar varios atributos dentro del mismo For each, pudiendo éstos pertenecer tanto a la propia tabla base como a la tabla extendida. El ejemplo que presentamos completa el que habíamos iniciado respecto al proceso de facturación. Obsérvese que aquí se está recorriendo la tabla INVOICE, filtrando por InvoiceDate, por InvoicePendingFlag, así como por CustomerId (en el listado de navegación se observará que si bien no se ha especificado order, al recibir en el atributo CustomerId, que es foreign key, se ordena por este atributo para optimizar). Dentro del for each, para cada factura que cumple las condiciones, se actualiza el valor del atributo InvoicePendingFlag. Aquí podrían actualizarse no solo ese atributo, sino cualquiera de la propia tabla INVOICE, o de su extendida, con las excepciones que se indican a continuación.
Página 124 de 143
Curso GeneXus X Año 2011
Supongamos que tenemos el siguiente diagrama de Bachman genérico:
Y en el Source de un procedimiento hacemos: For each C = &C E = &E D = &D Endfor Aquí la tabla base del For each será claramente la de clave primaria A y dentro del For each estamos actualizando tanto atributos de la propia tabla base como de la extendida. ¿En qué momento ocurre efectivamente la actualización de los registros involucrados? La actualización no ocurre ni bien se encuentra un comando de asignación dentro del For each, sino luego de que se encuentran todos, para cada instancia de la tabla base, es decir, cuando se llega al Endfor para cada iteración. Como vimos antes, no podemos actualizar dentro del comando For each atributos de la clave primaria. Sin embargo podríamos querer actualizar un atributo que sin ser clave primaria, está definido como clave candidata (mediante un índice unique). Si el atributo es clave candidata, debe controlarse que no se dupliquen sus valores, por lo que de encontrarse duplicado el registro en este sentido, no se permitirá hacer la actualización. Si se desea tomar una acción en caso de que esto ocurra, el comando For each agrega la cláusula when duplicate. Solo tiene sentido si existe alguna clave candidata para ese For each.
Página 125 de 143
Curso GeneXus X Año 2011
Realizar un “blocking” a las operaciones de actualización de la BD significa almacenarlas en memoria y enviarlas en grupo al DBMS. En lugar de interactuar con el DBMS en cada operación de actualización, la interacción tiene lugar solamente cada N operaciones de actualización, donde N es el número que se establece en la cláusula Blocking. No será el caso de nuestro ejemplo, pero ¿qué sucedería si se está haciendo una actualización masiva que incluye algún atributo clave candidata de la tabla, y se encuentran duplicados para algunos de los registros del grupo de 1000 que se está procesando? En ese caso, una vez llenado el buffer con las 1000 actualizaciones, al enviar a la BD el comando UPDATE del grupo, saltará el error de duplicados y se iterará sobre el grupo, realizando un comando UPDATE individual de BD, uno por uno.
Para eliminar datos se utiliza el comando Delete dentro del comando For each. El comando Delete elimina el registro en el que se está posicionado en un momento dado. Es por ello que no puede aparecer “suelto” dentro del Source. Debe colocarse dentro de un comando For each, cuya tabla base sea la tabla de la que se quieren eliminar registros. Solo se eliminan los registros de la tabla base, no de la extendida. Si deseamos eliminar todas las facturas anteriores a una fecha dada, podemos programar un procedimiento: For each where InvoiceDate <=&date For each defined by InvoiceDetailQuantity DELETE //se eliminan las líneas Endfor DELETE //luego de eliminar las líneas se elimina el cabezal Endfor Para mayor eficiencia en la eliminación, dependiendo del número de registros de la base de datos, convendrá agregar cláusula Blocking con un factor de bloqueo, N, adecuado. Por ejemplo, si se agrega “Blocking 1000” la eliminación física no se realizará en cada iteración, sino que cada 1000 veces que se llegue al Endfor, se eliminarán el grupo de 1000 registros en un único acceso a la Base de Datos (en lugar de 1000).
Página 126 de 143
Curso GeneXus X Año 2011
Supongamos que queremos implementar un procedimiento que haga lo siguiente: para el producto cuyo código es recibido por parámetro, dé de alta un nuevo precio (también recibido por parámetro) en su lista de precios, para la fecha correspondiente al día en que se ejecuta el procedimiento. Este debe crear un nuevo registro en la tabla PRODUCTPRICELIST, que está compuesta por los atributos ProductId, ProductPriceListDate y ProductPriceListPrice, siendo su clave primaria una compuesta, conformada por ProductId y ProductPriceListDate. Para ello se utiliza el comando new que escribimos arriba. Observemos que dentro del mismo aparecen comandos de asignación, donde se le da valor a los atributos de la tabla en la que se quiere insertar el registro. Cada vez que GeneXus encuentra un new, debe determinar la tabla en la que se realizará la inserción (tabla base del new). Es determinada a partir de los atributos que aparecen dentro del comando new, del lado izquierdo en una asignación. El único control que realiza, es el de duplicados. En nuestro caso, si existiera un registro con los valores de ProductId y ProductPriceListDate, no se realizará la inserción (pero en cambio si lo que no existiera fuera un producto en la tabla PRODUCT con el ProductId, allí sí se insertará, pues no controla integridad referencial). Si se programa la cláusula when duplicate, se actualizará el registro encontrado. Observemos que para realizar la actualización del atributo, la asignación debe estar dentro de un comando For each. Si no colocamos el For each no se realizará la actualización del precio para ese registro, es decir, es como si no se hubiera incluido cláusula when duplicate. Si se quisiera insertar un registro para el cual existiera clave candidata duplicada, también se controlará y no se realizará la inserción. En caso de existir cláusula when duplicate, se ejecutará.
Página 127 de 143
Curso GeneXus X Año 2011
El caso más común será tener el comando new dentro de un for each, dado que en general se quieren insertar registros en base a cálculos efectuados en función de otros. El mismo proceso de generación de recibos que habíamos resuelto anteriormente utilizando Data Provider y Business Component, podríamos realizarlo con un procedimiento que utilice el comando new de inserción. ¿Cuál alternativa elegiría usted? Recuerde que con los comandos de actualización dentro de procedimientos, el único control que se realiza es el de duplicados. Si las inserciones son muchas, así como vimos para el caso de las actualizaciones, disponemos de la cláusula blocking para ir guardando en un buffer y luego hacer la inserción de todos esos registros a la vez. En nuestro caso no fallará la inserción, debido a que la clave primaria es autonumber, y que no tenemos claves candidatas (vía índices unique) en la tabla de recibos (BILL). Pero si no fuera el caso, como las inserciones se van realizando en el buffer hasta llegar a la 1000, no es posible hasta completar el buffer saber si alguna operación fallará. Cuando se ejecuta el INSERT masivo de base de datos, allí sí puede fallar alguna inserción. En ese caso, se itera en los elementos del buffer, ejecutando el INSERT simple, uno por uno. Si existe cláusula When duplicate en el New, entonces por los registros que fallen, se ejecutará la cláusula.
En la sintaxis presentada, bloque_asignaciones1 es un bloque de código compuesto mayormente por sucesivos comandos de asignación (aquí se asigna valor a los atributos de la tabla en la que se insertará el registro, aunque también pueden asignarse valores a variables, así como anidarse otro comando new). La cláusula Defined By opcional, se incorpora a los mismos efectos que lo hacía para el comando For each: ayudar a determinar la tabla base.
Página 128 de 143
Curso GeneXus X Año 2011
La tabla base del new se obtiene de los atributos del Defined By y los que aparezcan del lado izquierdo de asignaciones dentro del bloque_asignaciones1. Aquí no se utiliza la tabla extendida. En el caso de que el comando new esté dentro de una cláusula repetitiva (ej. For each), es posible reducir el número de accesos a la base de datos usando la cláusula blocking. El comando new realiza un control de duplicados, de manera tal que no se permitirá insertar un registro que ya exista en la tabla. La cláusula when duplicate del comando permite programar la acción en caso de que el registro ya exista en la tabla base (tanto por clave primaria como por clave candidata). Normalmente, de ocurrir lo anterior, se quiere actualizar algunos de los atributos de dicho registro. Para ello, en bloque_asignaciones2 se realizan tales asignaciones, pero como lo que se hace es actualizar un registro (y no insertar uno nuevo), estas asignaciones aparecen rodeadas de “For each – Endfor”, pues como hemos visto, las actualizaciones solo pueden realizarse dentro de un For each. De no especificarse cláusula when duplicate para un new, si el registro que quiere insertarse se encuentra duplicado no se realizará acción alguna y la ejecución continuará en el comando siguiente. Es decir, como no puede insertar el registro porque ya existe uno, no hace nada y sigue adelante, con el próximo comando.
En los procedimientos el único control de integridad que se realiza automáticamente es el control de duplicados. El control de integridad referencial queda a cargo del programador, lo que no ocurre en las transacciones (ergo, en un Business Component). Queda claro de la enumeración mostrada arriba que los Business Components ofrecen todas las garantías y constituirán la forma de actualización privilegiada. Cabe preguntarse entonces: ¿cuándo actualizar utilizando estos comandos? La respuesta es: cuando la performance sea un problema. Un ejemplo discutible es el que utilizamos para cambiar el valor del atributo InvoicePendingFlag a False. No había ninguna regla que lo implicara, no involucraba integridad referencial, ni duplicados y podía involucrar millones de registros. ¿Cuál hubiese sido la alternativa? Prender la propiedad Business Component de la transacción Invoice, y en el procedimiento MarkInvoiceAsNotPending definir variable &invoice con ese tipo de datos y luego: for each where InvoiceDate >= &startDate where InvoiceDate <= &endDate where InvoicePendingFlag &invoice.Load ( InvoiceId ) &invoice.InvoicePendingFlag = False &invoice.Save() Endfor Pero esta solución será más ineficiente que la actualización directa utilizando “blocking factor”, sabiendo que se deberán actualizar millones de facturas.
Página 129 de 143
Curso GeneXus X Año 2011
Capitulo 11 KnowledgeManager, KB Information y Full Text Search En este capitulo veremos como importar/exportar objetos a nuestra base de conocimiento. Además como obtener información estadística sobre la base. Además realizaremos búsquedas dentro del IDE utilizando el mecanismo Full Text Search.
11.1. Knowledge Manager 11.1.1. Definición En el Knowledge Manager ahora la distribución se llama Exportación y la consolidación Importación (Menú principal/Knowledge Manager):
11.1.2. Exportación Por defecto se exporta toda la KB, aunque podemos seleccionar los objetos que queremos exportar con Add.
El diálogo de selección de objetos incluye filtros como en versiones anteriores, pero permite agrupar y filtrar por categoría:
Página 130 de 143
Curso GeneXus X Año 2011
Para mostrar los objetos referenciados por los seleccionados previamente, presionamos el botón References
Podemos también modificar las opciones de la exportación, mediante la ventana de propiedades, presionando el link Opciones:
Página 131 de 143
Curso GeneXus X Año 2011
11.1.3. Importación Por defecto se importan todos los objetos, aunque el diálogo nos permite seleccionar cada objeto:
Podemos también modificar las opciones de la importación, mediante la ventana de propiedades, presionando el link Opciones:
Página 132 de 143
Curso GeneXus X Año 2011
11.2. Información de la Knowledge Manager GeneXus X provee información estadística de la Knowledge Base. Para verla: View/Knowldge Base Information (CTRL+MAY+I)
La información disponible es la siguiente: Información de la Knowledge Base • Gráfico de torta con cantidad de Objetos GeneXus por tipo • Cantidad de tablas, atributos e índices de la Base de Datos • Lista de Objetos no referenciados • Lista de Objetos menos referenciados • Lista de Objetos más referenciados Página 133 de 143
Curso GeneXus X Año 2011
11.3. Full Text Search en la Knowledge Base 11.3.1. Características Generales •
•
Mecanismo de búsqueda que permite encontrar rápidamente cualquier cosa que necesitemos buscar dentro de una KB. Una búsqueda puede efectuarse: A través de una palabra o frase. A través de valores específicos en las propiedades de los objetos (ejemplo: Object type=Transaction) El resultado de una búsqueda puede ser salvado como una Categoría.
11.3.2. Cómo desplegar la ventana de búsqueda (Search window) • •
A través de la opción View\Other Tool Windows\Search A través de la caja de texto Search de la menu bar
Es posible disparar una búsqueda a partir de la especificación de valores en las propiedades de los objetos de una KB.
11.3.3. Ejemplo Búsqueda de los objetos de tipo Transacción
Página 134 de 143
Curso GeneXus X Año 2011
11.3.4. Index Monitor Permite al usuario monitorear el estado de un proceso de búsqueda (Full Text Search), ya que indexar un gran volumen de información puede llevar mucho tiempo. Para acceder a esta vista View \ Other Tool Windows \ Indexer Monitor
Situaciones a considerar: •
Cuando se selecciona el botón Pause, se detiene el proceso de indexado para el usuario y KB actualmente en uso. El proceso permanecerá en pausa hasta que se presione el botón Resume (aún cuando se cierre y abra nuevamente la base de conocimiento). Si otro usuario abre la KB, entonces el estado del proceso puede ser diferente.
•
El proceso de indexado es automáticamente pausado en actualizaciones masivas ( por ejemplo, en un proceso de importación).
Se retoma luego también en forma automática cuando finaliza el proceso que provocó la pausa.
Página 135 de 143
Curso GeneXus X Año 2011
Capitulo 12 Objetos Externos Creación de objetos externos. Consumo de web service.
12.1. Generalidades
12.2. Creación
Página 136 de 143
Curso GeneXus X Año 2011
12.3. Consumo de un Web Services en Genexus X
Página 137 de 143
Curso GeneXus X Año 2011
Página 138 de 143
Curso GeneXus X Año 2011
Página 139 de 143
Curso GeneXus X Año 2011
Página 140 de 143
Curso GeneXus X Año 2011
Capitulo 13 GX Extesions 13.1. Generalidades
La mayoría de las funcionalidades de GeneXus, están actualmente implementadas como extensiones que pueden ser integradas a un IDE genérico.
13.2. Instalación desde Genexus Start Page
13.3. Instalación desde Genexus Extensions Manager
Página 141 de 143
Curso GeneXus X Año 2011
13.4. Instalación Manual
Cualquier extensión se puede descargar e instalar en forma manual. La ejecución de GeneXus con la opción / install, provoca que se busque y se instale cualquier extensión que se encuentre en la carpeta Packages (bajo el directorio de instalación de GeneXus). De esta forma, la próxima vez que se inicie GeneXus se podrá encontrar la nueva extensión.
13.5. ¿Cómo crear instalaciones Propias?
Las extensiones que actualmente implementan muchas de las funcionalidades de GeneXus son construidas de la misma forma que el propio usuario podría hacerlo. Con GeneXux X se incluye un SDK con el cual se pueden desarrollar extensiones al propio IDE.
13.6. Ejemplo
Página 142 de 143
Curso GeneXus X Año 2011
Página 143 de 143