Subido por Christian Rafael Huamaní Castro

El Arte de Programar SAP NetWeaver

Anuncio
Para mi esposa Milly y
mi hija Tammy Luz.
Sobre el Autor…
Alvaro Tejada Galindo se inició en el mundo de la
programación a los 20 años. Sus primeros lenguajes
fueron C++ y Visual Basic 6.0 lo cuales aprendió de
manera autodidacta gracias a libros, foros, tutoriales y
códigos fuente.
A lo largo de los años, fue agregando nuevos lenguajes a su
colección, tales como Java, QBasic, Pascal, Delphi, HTML y
JavaScript.
Fue cuando tenía 24 años, que conoció el mundo del SAP, puesto
que entró a hacer sus prácticas en TSNet Global, en donde trabajó
por espacio de 2 años, con grandes proyectos en empresas como
Telefónica del Perú, Carsa, Minsur, Alicorp y Funsur.
Luego de dejar TSNet Global (Consultora Peruana), se dio un
descanso para continuar estudiando y dedicarse al aprendizaje de
PHP. Luego de esto, trabajó durante 2 años en la Consultora
ActualiSap (Consultora Chilena), en donde además de dos exitosos
proyectos de implementación en Suez Enegy Perú y el Aeropuerto
Jorge Chávez, dictó un curso interno de IDoc’s en Santiago de Chile.
Tuvo un breve paso por Servisoft (Consultora Peruana) en el
proyecto de E. Wong. Con Stefanini IT Solutions trabajó para
Alicorp, Exsa, Votorantim Metais y el Banco de Crédito del Perú.
2
Su experiencia en SAP y ABAP es de 5 años a la fecha, en los
cuales siempre ha buscado obtener lo mejor del sistema.
Adicionalmente, es uno de los Bloggers o Columnistas del SAP
Developer Network o SDN (http://sdn.sap.com) en donde escribe
artículos sobre ABAP, HR, Integración de PHP con SAP e
Integración de Ruby con SAP.
Además, es Moderador en los foros del SDN y Mentor de SAP para
América Latina y el resto del mundo.
Pueden leer sus artículos en la siguiente dirección:
http://tinyurl.com/jnlfd
http://atejada.blogspot.com
3
Indice
Conociendo el entorno SAP NetWeaver
Introducción
Ingresando al sistema
Conociendo las transacciones más importantes
El Menú de SAP NetWeaver
Diccionario de Datos
Introducción
Elementos del Diccionario de Datos
Creando una tabla
Creando un dominio
Creando un elemento de datos
Creando una vista de actualización
Creando una ayuda de búsqueda
Creando una estructura
Creando una Vista
Programación en ABAP
Introducción
Estructura de un programa ABAP
Declaración de variables y tablas internas
Selección de datos
Lectura de datos en tablas internas
Operadores de comparación
Operaciones en tablas internas
Copiar tablas internas
Ordenar tablas internas
Estructuras de Control
Trabajando con Cadenas de Texto
Variables de Sistema
Modularización de programas
Depuración de programas
Programas de ejemplo
SapScript
Introducción
Creando un formulario
Crear una página principal
7
7
8
12
21
23
23
23
26
37
38
50
60
65
66
70
70
71
78
83
86
89
98
104
108
110
111
119
121
129
138
159
159
159
161
4
Crear ventana en página
Crear párrafo por defecto
Creando un programa de impresión
Diseñando el formulario
Ejecutando el formulario
Debugger en SAPScript
SmartForms
Introducción
Creando un estilo
Creando un formulario
Creando un programa de impresión
Ejecutando el formulario
Crear una tabla
Screen Painter y Menu Painter
Introducción
Screen Painter
Controles del Screen Painter
Ejemplo de Screen Painter
Menu Painter
Agregando componentes
Programación avanzada en Dynpros
MatchCodes dinámicos
Eliminar registros en un Table Control
Escritura/Lectura en un Table Control
Trabajando con subScreens
Utilizando listas desplegables
Leyendo datos de un Dynpro
Módulos de Función y BAPIS
Introducción Módulos de Función
Creando nuestra primera función
Llamando funciones desde un programa
Introducción BAPIS
ALV (ABAP List Viewer)
Introducción
Creando un ALV
Agregando una cabecera al reporte
Eventos ALV
Pintemos con colores
Barra de menú en ALV
ABAP Orientado a Objetos
163
166
168
175
182
185
187
187
187
191
196
200
202
210
210
210
213
215
228
235
240
240
246
249
259
272
278
280
280
280
288
290
292
292
292
301
308
313
320
328
5
Introducción
¿Qué es la Orientación a Objetos?
Conceptos básicos de POO
Como programar en ABAP Objects
Componentes Orientados a Objetos
Crear un ALV Grid OO
Agregar validaciones y eventos
Crear un ALV Tree OO
Agregar validaciones y eventos
Crear un ALV Object Model
Agregar validaciones y eventos
Cargar Imágenes en Dynpros
Leer PDF’s
Comprimir (zip) archivos
Crear un Control de Texto
WebDynpro
Introducción
Creando nuestro primer WebDynpro
BSP
Introducción
Creando nuestro primer BSP
ABAP y XML
Scripting in a Box
SAPLink
Integración PHP-NetWeaver
Introducción
Instalando el SAPRFC
Comunicándonos con NetWeaver
Integración Ruby-NetWeaver
Introducción
Instalando el SAP:Rfc
Comunicándonos con NetWeaver
Donde conseguir el SAP NetWeaver Sneak Preview
Bibliografía y agradecimientos
Enlaces Web
328
328
329
335
351
351
365
379
399
409
416
421
430
438
451
458
458
458
489
489
489
501
518
521
525
525
526
527
539
539
540
540
546
547
549
6
Conociendo el entorno SAP NetWeaver
Introducción
NetWeaver es la evolución del SAP R/3 que es un ERP (Enterprise
Resource Planning – Planificador de Recursos Empresariales).
¿Porque llamamos a NetWeaver una evolución del R/3? Pues porque
NetWeaver incorpora todos los aspectos de la programación orientada
a objetos, así como una fuerte integración web.
Al decir que se incorporan todos los aspectos de la programación
orientada a objetos, nos referimos a que el ABAP (Advanced Business
Application Programming) ha evolucionado también, proveyendo
herramientas de desarrollo que aumentan la productividad.
A lo largo de este libro, vamos a trabajar con SAP NetWeaver 7.0
Trial Version al que llamaremos NSP (NetWeaver Sneak Preview),
que no es más que una versión reducida del NetWeaver que nos
permite tomar ventaja de todos los componentes de desarrollo del
sistema.
Para poder tener un mejor aprendizaje de los conceptos que se
explican en el libro, vamos a crear dos tablas de base de datos muy
sencillas y las mismas serán utilizadas para todos los ejemplos.
7
Ingresando al Sistema
Para poder ingresar a NSP, deberemos contar con un usuario y
password, proporcionados por el administrador del sistema.
En nuestro caso, tenemos 2 usuarios que comparten un mismo
password.
•
SAP* Æ Super Usuario. Con este usuario podemos crear
nuevos usuarios en NSP.
•
BCUSER Æ Usuario Desarrollador. Con este usuario
podemos programar en NSP.
•
DDIC Æ Usuario de Diccionario de Datos. Con este usuario,
podemos acceder a los datos almacenados dentro del NSP.
En esta pantalla podemos ver el SAP Logon, que es donde se
almacenan las entradas a los diferentes servidores de NSP.
En su caso, ustedes solamente van a tener uno, así que los marcan
(NSP Local) y presionamos Acceder al Sistema.
8
9
En esta ventana es donde ingresaremos nuestro usuario y password. En
la caja de texto de Language (Idioma), solamente podremos ingresar
EN Æ Inglés o DE Æ Alemán. El inglés es el idioma por defecto.
Luego de haber ingresado al sistema, veremos la pantalla principal del
NSP.
Esta es la pantalla de inicio, en donde podremos acceder a las
transacciones que nos ofrece NSP. Las transacciones son códigos
generalmente de 4 dígitos que sirven como accesos directos a los
programas que se ejecutan internamente en el NSP.
Para acceder, tenemos dos opciones, buscarlas en el menú del lado
izquierdo.
10
O escribir directamente la transacción a la cual queremos dirigirnos.
11
Conociendo las transacciones más importantes
1.- SE38 (Editor ABAP)
Este es el entorno de programación del NSP.
Aquí podemos crear nuestro programas o includes (Programas no
ejecutables que se incluyen dentro de programas ejecutables).
12
2.- SE11 (Diccionario ABAP)
En esta transacción podremos crear, visualizar o modificar Tablas,
Vistas, Dominios, Estructuras y Ayudas para Búsqueda.
13
3.- SE16 (Browser de Datos)
Es donde visualizamos los datos incluidos en Tablas o Vistas.
14
4.- SE71 (Form Painter)
Nos permite crear formularios de Impresión.
15
Podemos definir páginas, ventanas, tipos de párrafo, márgenes,
tabuladores, insertar imágenes.
Por lo general se utilizan para generar Cartas de Pago a Bancos,
Facturas, Cheques, Certificados.
5.- SmartForms (Form Painter Avanzado)
Nos permite crear formularios de Impresión. El SmartForms es la
nueva versión del SapScript. Se puede utilizar cualquiera de los dos,
aunque depende de cada desarrollador.
16
6.- SE51 (Screen Painter)
Nos permite diseñar pantallas para crear programas interactivos.
17
7.- SE41 (Menu Painter)
18
8.- SE37 (Function Builder)
Nos permite crear funciones para utilizar en nuestros programas, así
como modificar o visualizar funciones ya creadas.
19
20
El menú de SAP NetWeaver
En todas las transacciones, contamos con una barra de menú, que nos
permite interactuar con las aplicaciones de NetWeaver. Veamos
cuales son:
Æ Equivale a hacer clic en la tecla Enter.
Æ Aquí es donde se escribe la transacción
a la cual queremos acceder. Si escribimos /n antes de la transacción,
accederemos a ella en la misma ventana. Si escribimos /o antes de la
transacción, accederemos a ella en una nueva ventana. Ej: /nSE38 ó
/oSE16.
Æ Grabar.
Æ Retroceder una pantalla.
Æ Salir del programa o transacción.
Æ Cancelar el programa o transacción.
Æ Imprimir.
Æ Buscar.
Æ Buscar más.
21
Æ Se habilitan en Tablas y sirven para avanzar o
retroceder registros.
Æ Abre una nueva ventana o sesión del NetWeaver.
Æ Crea un acceso directo en el escritorio.
Æ Ayuda.
Æ Configuraciones GUI.
22
Diccionario de Datos
Introducción
El Diccionario de Datos en NetWeaver, es el repositorio en el cual se
almacenan todas las tablas, elementos de datos, dominios, estructuras,
ayudas de búsqueda.
Vamos a dar un breve repaso de todos los conceptos que intervienen
en el diccionario de datos, así como la manera de crear cada uno de sus
diferentes componentes.
Cabe destacar, que en NetWeaver, todo es manejado por tablas, es
decir, todos los programas, funciones, includes y elementos del
diccionario son almacenados en tablas. Por lo tanto, NetWeaver
cuenta con 63,348 tablas standard...Y eso que hablamos de la versión
Sneak Preview, la versión real del NetWeaver debe tener por lo
menos el doble o triple de tablas.
Elementos del Diccionario de Datos
1.- Tablas
Las tablas se dividen en 3 tipos básicos:
•
Tablas Transparentes (Transparent Tables):
Posee una relación de uno a uno con una tabla de la Base de
Datos. Es decir, cada tabla transparente definida en el
Diccionario de Datos, existe físicamente en la base de datos. Es
el tipo más común de tabla y es el más utilizado por los
desarrolladores ABAP.
23
•
Tablas Reunidas (Pooled Tables):
Posee una relación de muchos a uno con una tabla de la Base
de Datos. Es decir, por una tabla que existe físicamente en la
base de datos, existen muchas tablas en el Diccionario de
Datos. Muchas tablas Pool, se encuentran almacenadas
físicamente en la Base de Datos en tablas llamadas Pool
Tables. Este tipo de tablas, son definidas por SAP.
•
Tablas Racimo (Cluster Tables):
Una tabla racimo, es similar a una Pool Table. Poseen una
relación de muchos a uno con una tabla de la Base de Datos.
Muchas tablas racimo son almacenadas físicamente en la
Base de Datos en tablas llamadas Table Cluster. Este tipo de
tablas son definidas por SAP y su uso se limita a tablas que
son accedidas constantemente, como las tablas del sistema.
2.- Componentes de una tabla
Las tablas están compuestas por campos, y cada campo debe de estar
asignado a un Elemento de Datos o a un Tipo Predefinido. Los
Elementos de Datos, contienen los nombres del campo, así como
también almacenan los valores de la ayuda en línea.
Una definición de Elementos de Datos, requiere a su vez de un
Dominio. Los Dominios, contienen las características técnicas de un
campo, tales como su longitud y su tipo de dato.
24
Tipos de Datos para Dominios
Tanto los Elementos de Datos como los Dominios, son reutilizables.
Lo que significa que pueden estar definidos en más de una tabla, sin
que esto genere algún tipo de conflicto.
Creación de Objetos del Diccionario
Para acceder al Diccionario de Datos, deberemos ingresar a la
transacción SE11.
25
En esta transacción podremos visualizar, modificar, eliminar o crear
los siguientes elementos:
•
Tablas Transparentes
•
Vistas
•
Estructuras
•
Dominios
•
Elementos de Datos
•
Ayudas para búsqueda
1.- Creando una tabla
Para propósitos del libro, vamos a crear 2 tablas, llamadas
ZLENGUAJES_PROG y ZENTORNOS_PROG. Con estas
26
tablas, vamos a trabajar todos los ejemplos del libro, así que es muy
importante que las creen para poder seguir los ejemplos con mayor
facilidad.
Como se habrán dado cuenta, ambas tablas comienzan con el prefijo
“Z”, puesto que es la única restricción que nos da NetWeaver al
momento de crear cualquier elemento o componente.
Para crear nuestra primera tabla, hacemos lo siguiente:
•
Escribimos el nombre de la tabla que queremos crear.
•
Presionamos el botón Create.
En este momento, se nos presenta una ventana, en la cual,
deberemos ingresar una descripción para la tabla, además de una
clase de entrega y definir si la tabla puede ser mantenida desde la
transacción SE16 o por algún programa externo.
27
Clase de Entrega
Casi en un 99% de las veces, se utiliza la clase de entrega A, así que
es la que vamos a utilizar nosotros. En todo caso, la única que
podríamos utilizar además de esta, son los tipo C y L.
También debemos escoger el tipo de Mantenimiento que se le va a
dar a la tabla. En nuestro caso, escogeremos la opción
Display/Maintenance Allowed para poder generar una Vista de
Actualización más adelante.
Cuando grabemos, nos encontraremos con una ventana muy común
en NetWeaver. Esta ventana, nos pide asociar nuestra tabla a un
Package (Paquete), que nos es más que una tabla donde se
organizan los desarrollos por tipos. Tenemos dos opciones, o
28
utilizamos el paquete $TMP que es local y por lo tanto no
transportable (Es decir, no puede salir del ambiente DEV de
desarrollo), o podemos crear nuestro propio Package en donde
almacenaremos todos los componentes que creemos en el libro.
Obviamente, vamos a crear nuestro propio Package, así que
debemos hacer lo siguiente:
•
Abrimos una nueva ventana o sesión con /oSE80 (Object
Navigator). Y escogemos la opción Package de la lista.
•
Con un clic derecho, abrimos un menú emergente y
escogemos Create Æ Package.
29
•
Llenamos los campos y presionamos el botón Save.
•
NetWeaver nos solicita que ingresemos una Orden de
Transporte
para
almacenar
nuestro
nuevo
Package,
presionamos el botón Create Request.
30
•
Ingresamos una descripción y grabamos.
•
Una vez creada y asignada la Orden de Transporte,
presionamos el botón Continue o presionamos Enter.
31
Atributos del Package ZARTE_PROGRAMAR
•
Regresamos a la sesión donde teníamos la tabla y hacemos
un clic en el botón del parámetro Package.
32
•
Podemos ingresar el nombre del Package, o solicitar que se
muestren todos los disponibles, presionando el botón Start
Search o presionando la tecla Enter.
•
En nuestro caso, lo mejor es escribir Z* para que nos
muestre solamente los paquetes creados por nosotros o por el
sistema (Definidos para los usuarios).
•
Escogemos nuestro Package con un doble clic para que
quede asignado a nuestra tabla.
33
•
Presionamos el botón Save. Y nos va a aparecer la ventana
solicitando una Orden de Transporte. Como creamos una
orden al momento de crear el Package, entonces la misma
orden aparecerá por defecto. Presionamos el botón Continue
o la tecla Enter.
Ahora, podemos continuar con la creación de nuestra tabla.
Debemos ir a la pestaña Fields (Campos), para poder agregar los
campos necesarios para nuestra tabla.
34
El primer campo que vamos a utilizar es el MANDT, que
identifica al ambiente en el cual estamos trabajando (Y que es un
campo llave). El segundo campo, se llamará Id, y será el encargado
de identificar a cada uno de los registros (También es un campo
llave).
Como se darán cuenta, en el gráfico no he llenado el campo
Data Element (Elemento de Datos), para el campo Id. Esto es
porque vamos a utilizar un Predefined Type (Tipo Predefinido).
•
Hacemos clic en el botón Predefined Type. Y como
podemos ver, la interfaz de la pantalla cambia un poco.
35
•
Queremos que el tipo de dato sea CHAR y tenga una
longitud de 3, además, agregamos una pequeña descripción
del campo.
•
El siguiente campo también necesita un tipo predefinido, así
que lo llamamos Nombre y lo definimos como un CHAR de
longitud 15.
•
El siguiente campo se llamará Entorno. Este estará
relacionado
con
una
tabla
que
llamaremos
ZENTORNOS_PROG, así que abrimos otro modo para
poder
crearla,
antes
de
continuar
con
la
tabla
ZLENGUAJES_PROG.
•
Al igual que en la tabla ZLENGUAJES_PROG, los 2
primeros campos serán Mandt y Id.
36
•
El tercer campo, se llamará Nombre, y no tendrá asociado un
Tipo Predefinido, sino que contará con un Elemento de
Datos y un Dominio. Para esto, abrimos una nueva ventana
en la SE11 y nos posicionamos en Domain (Dominio).
2.- Creando un Dominio
A
este
Dominio,
lo
llamaremos
ZD_ENT_NAME.
Llenamos los campos como se muestra en la imagen.
37
Lo grabamos y lo activamos utilizando el botón Activate
(Activar)
o presionando Crtl + F3.
3.- Creando un Elemento de Datos
•
Una vez creado el Dominio, pasamos a crear nuestro Data
Element (Elemento de Datos). En la misma transacción, nos
posicionamos en Data Type (Tipo de Dato).
Lo llamaremos ZE_ENT_NAME. Se dará cuenta, de que al
momento de presionar el botón Create, aparece una ventana
preguntándonos por el Tipo de Dato que queremos crear. Lo
dejamos como Data Element y presionamos Enter.
Llenamos los datos como se muestra en la figura, utilizando
el Dominio que creamos.
38
Ahora, debemos pasar a la pestaña Field Label (Etiqueta de
Campo), que no es más que la descripción del Elemento de
Datos. La llenamos como se muestra en la figura. Grabamos
y activamos.
•
Regresamos a la ventana donde estábamos creando la tabla
ZENTORNO_PROG.
•
Como estábamos ingresando Tipos Predefinidos, debemos
presionar el botón Data Element
. E
ingresar el nombre de nuestro Elemento de Datos.
•
Grabamos y ahora debemos llenar los datos de Technical
Settings (Caraterísticas Técnicas) Æ Goto Æ Technical
Settings o presionar Crtl + Shift + F9. Y luego debemos
39
llenar el Enhacement Category (Categoría de Ampliación)
Æ Extras Æ Enhacement Category.
El campo Data Class (Clase de Datos) especifica el área
física en la cual se va a crear la tabla. Para nosotros, el valor
por defecto siempre será APPL0.
El campo Size Category (Categoría de Tamaño), determina
la cantidad de espacio que se reservará inicialmente para la
tabla, en nuestro caso, nuestra tabla no contendrá mucho
datos, así que 0 es la opción a tomar.
Grabamos
y
retrocedemos
para
poder
acceder
al
Enhacement Category.
40
Esto sirve para definir si la tabla puede ser modificada con
campos adicionales. En nuestro caso, le decimos que no,
puesto que son tablas que hemos creado como ejemplo para
el libro.
Grabamos y activamos.
•
Regresamos a nuestra tabla ZLENGUAJES_PROG y
presionamos el botón Data Element, para poder ingresar
nuestro Elemento de Datos para el campo Entorno.
•
Nos posicionamos sobre el campo Entorno y presionamos el
botón Foreign Keys (Llaves Foráneas)
. Esto nos
mostrará una ventana, que veremos a continuación.
41
En el campo Check Table (Tabla de Verificación), escribimos
el nombre de nuestra tabla ZENTORNOS_PROG. Y
presionamos Enter.
El sistema nos propone crear una asignación entre las tablas,
presionamos Yes (Sí) o presionamos Enter.
42
Recibimos este mensaje, porque la llave completa de la tabla
ZENTORNOS_PROG
no
existe
en
la
tabla
ZLENGUAJES_PROG.
Dejamos la ventana, como se muestra en la figura.
•
El ultimo campo, se llama CONEX_SAP y determina si el
lenguaje posee algún tipo de conexión con SAP. Para esto,
43
vamos a crear nuevamente un Dominio (ZD_CONEX_SAP)
y un Elemento de Datos (ZE_CONEX_SAP).
Como ven, es simplemente un CHAR de 1. Ahora, pasamos a
la pestaña Value Range (Rango de Valores). Y llenamos
solamente dos valores.
44
•
Grabamos, activamos y creamos nuestro Elemento de Datos.
Llenamos la pestaña Field Label (Etiqueta de Campo),
grabamos y activamos.
•
De vuelta en la tabla ZLENGUAJES_PROG, agregamos el
campo CONEX_SAP con su respectivo elemento de datos.
•
Llenamos la Características Técnicas, la Categoría de
Ampliación, grabamos y activamos.
45
Ahora que tenemos nuestras dos tablas listas, es hora de agregar
algunos datos. Nos vamos a la transacción SE16 (Browser de
Datos). Colocamos el nombre de nuestra tabla de entornos, y
presionamos el botón Create Entries (Crear Entradas)
o
presionamos F5.
Ingresamos algunos cuantos valores. Y grabamos con el botón Save
(Guardar)
o presionamos Crtl. + S.
46
Una vez que hemos terminado de insertar registros, retrocedemos
presionando el botón Back (Atrás)
o presionando el botón F3.
Para poder ver los registro que hemos creado, podemos presionar el
botón Table Contents (Contenido de Tabla)
o presionar Enter.
En esta ventana, podemos hacer un filtro ya sea por Id o por
Entorno. En nuestro caso, queremos ver todos los valores, así que
dejamos esos campos en blanco. Presionamos el botón Execute
(Ejecutar)
o presionamos F8.
47
Tenemos 3 registros grabados en la base de datos. Ahora, es el turno
de la tabla ZLENGUAJES_PROG.
Seguimos el mismo procedimiento.
Cuando se posicionen en el campo Entorno, se darán cuenta de algo
interesante. Aparece un pequeño botón al final del campo, lo cual
nos indica que existen valores de los cuales podemos escoger. Para
esto debemos hacer clic en ese botón o presionar F4.
48
Esos
son
los
registros
que
ingresamos
en
la
tabla
ZENTORNOS_PROG y que ahora podemos insertar en nuestra
tabla ZLENGUAJES_PROG. Lo mismo sucede con el campo
CONEX_SAP.
Cabe
destacar
que
los
valores
que
están
en
la
tabla
ZENTORNOS_PROG, son los únicos valores válidos, es decir, si
ingresamos cualquier otro valor, el sistema nos mostrará un mensaje
de error.
49
Ingresamos algunos datos y estamos listos.
Seguramente, les parecerá que ingresar los datos así, es un poco
tedioso...No se preocupen, que ahora vamos a crear una vista de
actualización.
3.- Creando una Vista de Actualización
Para crear nuestra vista de actualización, debemos regresar a la
transacción SE11 y modificar la tabla ZLENGUAJES_PROG.
En el menú, vamos a Utilities (Utilidades) Æ Table Maintenance
Generator (Generador de Mantenimiento de Tabla).
Llenamos la ventana como se muestra a continuación.
50
Presionamos el botón Find Scr. Number(s) (Buscar Número(s) de
Pantalla)
o presionamos Shift + F7.
En esta ventana, siempre escogemos la primera opción Propose
Screen Number(s) (Proponer número(s) de Ventana).
51
Finalmente presionamos el botón Create (Crear)
o presionamos
F6. Grabamos y activamos. Ahora, debemos ir a la transacción
SM30.
Y presionar el botón Maintain.
Se nos muestra una pantalla más amigable para el ingreso de datos,
pero como se darán cuenta, los dos primeros campos aparecen como
52
“+”. Esto es porque al ser Tipos Predefinidos, no poseen un texto
descriptivo. Esto lo podemos solucionar fácilmente regresando a la
transacción SE11 y al Generador de Mantenimiento de Tabla.
Debemos hacer clic tanto en el Overview Screen (Ventana de Vista
general) como en el Single Screen (Ventana sencilla).
Veamos primero en el Overview Screen.
53
Seguramente esta pantalla los asusta un poco, pero no se preocupen,
que por el momento no vamos a hacer nada con esto, puesto que es
código generado automáticamente por el NetWeaver.
Debemos hacer clic en el botón Layout (Disposición)
.
La pantalla del Screen Painter es la que nos interesa, sobre todos las
cabeceras que tienen un “+”.
54
Debemos hacer un clic en el botón Display <-> Change (Mostrar <> Cambiar)
o presionar F1.
Ahora, nos colocamos sobre la primer columna y en la ventana que
dice Text (Texto), escribimos lo siguiente
Y en la segunda columna:
Grabamos, activamos y retrocedemos dos veces hasta regresar a la
ventana del Generador de Mantenimiento de Tabla.
Hacemos doble clic en Single Screen.
55
Y repetimos la operación, modificando los símbolos “+”. Grabamos,
activamos y regresamos nuevamente.
Una vez hecho esto, nos vamos a la transacción SM30 y veremos
que los símbolos “+” han sido reemplazados por los textos correctos.
Ahora, para hacer las cosas más interesantes y poder trabajar mejor
los ejemplos del libro, regresamos a la transacción SE11 para crear
una nueva y última tabla con las siguientes características.
56
La tabla se llamará ZPROGRAMAS y contendrá algunos
programas
que
hemos
hecho
utilizando
los
lenguajes
de
programación que hemos creado.
En otra ventana, creamos un dominio para el código del lenguaje de
programación, llamado ZD_ID_LENGUAJE.
Ahora,
creamos
un
Elemento
de
Datos
llamado
ZE_ID_LENGUAJE.
57
Regresamos a la tabla ZPROGRAMAS y tendremos la siguiente
estructura:
Para que esto funcione correctamente y podamos hacer una
asociación
entre
ZLENGUAJES_PROG,
las
tablas
debemos
ZPROGRAMAS
modificar
la
y
tabla
58
ZLENGUAJES_PROG incluyendo el Elemento de Datos que
creamos:
Luego de haber grabado y activado, regresamos a ZPROGRAMAS
y nos posicionamos en el campo Id y presionamos el botón Foreign
Keys
.
59
Grabamos, actualizamos las Características Técnicas y la Categoría
de Amplicación y activamos la tabla.
Como solamente hemos asignado el campo Id a nuestra tabla, al
momento de querer elegir un lenguaje de programación, solamente
vamos a ver el código, lo cual no nos va a ayudar de mucho, así que
hora de crear una ayuda de búsqueda.
4.- Creando una Ayuda de Búsqueda
Para esto, en una nueva ventana, vamos a la transacción SE11. Y
escogemos la opción Seach Help (Ayuda de búsqueda).
Elegimos la primera opción.
60
Como pueden ver, el campo Nombre tiene asignamos un elemento
de datos, así que nuevamente, creamos un Dominio y un Elemento
de Datos como se muestra a continuación.
61
Grabamos y activamos nuestra ayuda de búsqueda y la probamos
con presionando el botón Test (Prueba)
o presionando F8.
62
En esta ventana, podemos filtrar por Id o por Nombre del lenguaje,
en este caso, presionamos Enter porque queremos ver todos los
registros disponibles.
Nuestra ayuda de búsqueda está terminada, así que regresamos a la
tabla ZPROGRAMAS a la pestaña Entry Help/Check (Entrada de
Ayuda/Verificación).
Nos posicionamos en el campo Id, y presionamos el botón Search
Help
.
63
Asignamos la ayuda de búsqueda que creamos.
Grabamos y activamos.
En la transacción SE16 agregamos algunos cuantos registros.
64
Como podemos ver, al hacer F4 en el campo Id, podremos ver tanto
el código como el nombre del Lenguaje.
Finalmente, nuestra tabla contendrá los siguientes registros.
Con esto terminamos y podemos crear una estructura, que no es otra
caso que una tabla que solamente contiene una cabecera, es decir, no
puede almacenar registros. Esto nos va a ser útil al momento de
desarrollar nuestros programas, puesto que vamos a poder contar con
la estructura sin utilizar memoria adicional de la base de datos.
5.- Creando una Estructura
En la transacción SE11, en el campo Data type, creamos nuestra
estructura llamada ZSLENGUAJES_PROG.
65
, se nos
Cuando presionamos Create (Crear)
muestra una ventana en donde debemos elegir Structure
(Estructura).
Utilizamos
los
mismos
componentes
que
en
la
tabla
ZLENGUAJES_PROG, aunque quitamos el campo MANDT.
Grabamos y activamos. Nos va a pedir, el Enhacement Category
(Categoría de ampliación), lo agregamos para poder activar.
6.- Creando una Vista
Dentro de la transacción SE11, creamos nuestra vista en el campo
VIEW (Vista). Llamada ZVLENGUAJES_PROG.
66
En la ventana que aparece, elegimos Database view (Vista de Base
de Datos). Los demás tipos no los vamos a ver en este libro, puesto
que el Database view es el más utilizado.
Primero, debemos de llenar los campos que vamos a utilizar para
relacionar las tablas que vamos a utilizar en la vista, en este caso,
ZPROGRAMAS y ZLENGUAJES_PROG.
67
En la pestaña View Flds (Campos de la Vista). Definimos los
campos que queremos que se muestren en la vista.
Grabamos y activamos. Los mensajes de advertencia, podemos
obviarlos.
Una vez que la Vista está activa, podemos comprobar los valores
presionando el botón Contents (Contenidos)
o presionando Ctrl.
+ Shift + F10.
Se darán cuenta de que el sistema no envía a la transacción SE16.
Ejecutamos y vemos los datos generados por la Vista.
68
Con esto, terminamos el capítulo dedicado a Diccionario de Datos.
Ahora, ya pueden crear sus propias tablas, elementos de datos,
dominios o vistas.
69
Programación en ABAP
Introducción
ABAP (Advances Business Application Programming), es el
lenguaje de programación propietario de SAP AG, con el cual se
desarrollan aplicaciones que son integradas al NetWeaver. Cabe de
destacar que muchos de los componentes de NetWeaver han sido
desarrollados utilizado ABAP, lo cual nos permite hacer
modificaciones que otro tipo de sistemas serían imposibles.
El ABAP, viene a ser una especie de nieto del COBOL (Common
Object Business Oriented Language), que era muy utilizado para el
desarrollo de aplicaciones empresariales.
En cuanto a la sintaxis de lenguajes, podemos tomarlo como un
híbrido entre COBOL, PASCAL y SQL Server.
Hasta la versión 45B, el ABAP, era un lenguaje procedural, aunque
con el tiempo se el agregaron funcionalidades para convertirlo en un
lenguaje orientado a objetos, por lo cual al momento de programar,
se pueden mezclar ambas tecnologías sin mayores problemas.
El NetWeaver actualmente está en la versión 7.0.0, lo cual significa
que nos permite trabajar con ABAP Objects de manera muy
completa, aunque como es de suponerse, en versiones posteriores de
NetWeaver, se adicionarán algunos componentes extras.
En el presente capítulo, vamos a revisar los principales componentes
del ABAP, así como la estructura de los programas que se crean con
el.
70
Estructura de un programa en ABAP
Para crear un programa en ABAP, debemos ingresar a la transacción
SE38, y especificar el nombre del programa que queremos crear.
Recodemos que debemos utilizar la letra Z antes del nombre del
programa. Esto es porque SAP, reserva el nombre Z para los
programas que son creados por los clientes y no por los mismos
trabajadores de SAP.
Cuando presionamos el botón Create (Crear), el sistema nos
mostrará la siguiente ventana, en donde debemos escoger el Type
(Tipo de programa) y el Status (Estado) asociado.
71
En Type (Tipo) siempre escogemos Executable program
(Programa ejecutable) y en Status (Estado), elegimos SAP
Standard Production Program (Programa Standard SAP para
Productivo).
Al presionar el botón Save
cual
queremos
asignar
, nos va a pedir el paquete al
el
desarrollo,
elegimos
ZARTE_PROGRAMAR. Y cuando nos pida la orden de
transporte, elegimos la creamos puesto que se nos muestra por
defecto (Siempre y cuando no hayamos creado otras ordenes).
El sistema, nos envía al Editor ABAP, que es donde vamos a poder
crear nuestros programas. Debemos tener claro, que existen ciertos
comentarios, que debemos colocar en todos nuestros programas,
para poder definir algunos bloques importantes.
72
En el espacio de comentario, debemos incluir por ejemplo, quien
está creando el programa y cuando.
La primera y única línea que nos muestra el editor es REPORT y el
nombre de nuestro programa. Esto indica que se trata de un
programa ejecutable.
Para empezar, vamos a hacer un programa muy simple, que
simplemente solicite al usuario un texto y lo imprima en pantalla,
luego de esto, veremos la sintaxis de ABAP y podremos hacer
programas más complejos.
73
*&----------------------------------------------------*
*& Report
ZDUMMY_PRIMER_PROGRAMA
*
*&----------------------------------------------------*
*& Creado por: Alvaro "Blag" Tejada Galindo.
*
*& Fecha creación: 14 de Noviembre del 2007
*
*&----------------------------------------------------*
REPORT
ZDUMMY_PRIMER_PROGRAMA.
*=====================================================*
* SELECTION-SCREEN
*
*=====================================================*
SELECTION-SCREEN BEGIN OF BLOCK PRUEBA
WITH FRAME TITLE TEXT-T01.
PARAMETERS:
TEXTO(30) TYPE C.
SELECTION-SCREEN END OF BLOCK PRUEBA.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
WRITE: TEXTO.
El SELECTION-SCREEN BEGIN OF BLOCK, nos permite
definir un espacio en donde van a ir los parámetros de entrada de
nuestro programa. PRUEBA, es el nombre que le estamos
asignando al bloque de parámetros.
WITH FRAME TITLE, significa que el área de los parámetros
de selección va a estar rodeados por un marco y TITLE, significa
que va a contar con un título definido por nosotros, en este caso
74
TEXT-T01. TEXT-T01, lo podemos separar en dos partes TEXT,
que nos indica que es un texto del sistema y T01, es el nombre de
dicho texto. Para poder modificarlo, simplemente deberemos hacer
doble clic en el texto. Si no lo hemos creado, nos aparecerá la
siguiente ventana:
Simplemente, ingresamos el texto, grabamos y activamos. Dentro
del bloque que hemos definido para los parámetros, podemos utilizar
2 tipos de parámetros:
• PARAMETERS ÆSon parámetros simples que aceptan
solamente un valor.
• SELECT-OPTIONS ÆSon parámetros compuestos que
aceptan un rango de valores.
75
El parámetro que hemos utilizado en este programa, es un CHAR de
30 caracteres.
TEXTO(30) TYPE C.
Si activamos y ejecutamos el reporte (Presionando la tecla F8),
veremos el parámetro de entrada que definimos.
Ahora, si queremos cambiar el texto que muestra nuestro parámetro,
deberemos de ingresar al siguiente menú.
GotoÆText ElementsÆSelection Texts.
76
Aquí, deberemos de ingresar el texto que queremos que tenga
nuestro parámetro, lo grabamos, lo activamos y listo.
Ahora, volvamos a ejecutar el reporte.
77
El START-OF-SELECTION, nos indica que va a comenzar la
ejecución de nuestro programa, es aquí donde colocamos toda la
lógica.
WRITE: TEXTO, significa que vamos a escribir en la pantalla, el
valor que hemos ingresado en el parámetro de entrada TEXTO.
Ese fue nuestro primer y más simple programa en ABAP.
Declaración de Variables y Tablas Internas
Para poder declarar variables, utilizamos la sentencia DATA, que lo
único que hace es separar un espacio en memoria.
DATA: TEXTO TYPE C.
78
Aquí estamos diciendo que vamos a crear una variable llamada
TEXTO, y que va a ser de tipo C (Caracter). Además, podemos
especificar su tamaño.
DATA: TEXTO(30) TYPE C.
Entre los tipos de datos que nos ofrece el ABAP, tenemos:
C
Character
1
Space
N
Numeric String
1
’00…0’
D
Date
8
‘000000000’
6
‘000000’
1
X’00’
(YYYYMMDD)
T
Time
(HHMMSS)
X
Byte
(Hexadecimal)
I
Integer
4
0
P
Packed Integer
8
0
F
Floating point
8
‘0.0’
number
STRING
String
Variable
Empty string
XSTRING
Byte Sequence
Variable
Empty X
String
Adicionalmente, podemos utilizar campos de tablas para poder hacer
la declaración de variables.
79
DATA: V_CARRID TYPE SPFLI-CARRID.
En este caso, estamos declarando una variable que va a ser
exactamente igual que el campo CARRID de la tabla SPFLI.
Por lo tanto, V_CARRID es un CHAR de 3 caracteres.
Ahora veamos las tablas internas, que son uno de los elementos más
valiosos del ABAP.
Las tablas internas, son tablas temporales que existen solamente en
el ámbito del programa que las creó y permiten almacenar
información para luego poder manipularla sin tener que acceder
múltiples veces a la base de datos.
En versiones anteriores, podíamos utilizar la siguiente sintaxis:
DATA: BEGIN OF TABLA OCCURS 0,
…
END OF TABLA.
Con la introducción de NetWeaver, esto no es posible, así que de
ahora en adelante, vamos a utilizar y aprender solamente las nuevas
sintaxis que se nos ofrecen gracias a la creación del ABAP Objects.
TYPES: BEGIN OF TY_TABLA,
…
END OF TY_TABLA.
DATA: T_TABLA TYPE STANDARD TABLE
OF TY_TABLA.
80
Primero, debemos crear un TYPE, es decir, un tipo de tabla interna
y luego, utilizando el DATA, creamos una tabla interna que haga
referencia a nuestro tipo de tabla.
Para los que ya conocen ABAP, se darán cuenta de que no hemos
creado la tabla con una cabecera. Es decir, no utilizamos ni
OCCURS 0, ni tampoco WITH HEADER LINE. Esto es porque,
en ABAP Objects, está prohibido utilizar cabeceras o workareas.
Para los que no conocen ABAP, en versiones anteriores, podiamos
crear tablas internas con líneas de cabecera, lo cual facilitaba la
lectura de datos, pero que al mismo tiempo ocasionaba problemas de
performance. Es por eso, que SAP decició eliminar la cabeceras
completamente.
Además de crear tablas internas, de la manera que hemos visto,
podemos también incluir estructuras completas de Base de Datos.
Esto podemos hacerlo de dos maneras, dependiendo de si queremos
o no incluir campos adicionales.
DATA: T_SPFLI TYPE STANDARD TABLE
OF SPFLI.
TYPES: BEGIN OF TY_SPFLI.
INCLUDE STRUCTURE SPFLI.
TYPES: TEST TYPE STRING.
TYPES: END OF TY_SPFLI.
DATA: T_SPFLI TYPE STANDARD TABLE
OF TY_SPFLI.
81
Claro, si queremos crear una tabla interna que tenga datos propios,
lo hacemos de la siguiente forma.
TYPES: BEGIN OF TY_TEST,
NOMBRE(30) TYPE C,
EDAD TYPE I,
END OF TY_TEST.
DATA: TEST TYPE STANDARD TABLE
OF TY_TEST.
Seguramente se habrán dado cuenta y sobre todo se preguntarán,
porque tenemos que utilizar el TYPE STANDARD TABLE, muy
simple, porque tenemos disponibles más tipos de tablas.
TYPES: BEGIN OF TY_TEST,
ID(3) TYPE C,
NOMBRE TYPE STRING,
EDAD TYPE I,
END OF TY_TEST.
DATA: TEST TYPE STANDARD TABLE OF TY_TEST.
DATA: TEST_H TYPE HASHED TABLE OF TY_TEST
WITH UNIQUE KEY ID.
DATA: TEST_S TYPE SORTED TABLE OF TY_TEST
WITH UNIQUE KEY ID
Como pueden ver, tenemos 3 tablas internas.
82
TEST Æ Tablas Standard. Puede ser accedida mediante un índice o
mediante campos.
TEST_H Æ Tabla de tipo hashed. De rápido acceso, pero no puede
ser accedida mediante un índice.
TEST_S Æ Sorted table. De rápido acceso, siempre está ordenada,
no puede ser accedida mediante un índice.
En realidad, el uso de tablas Hashed o Sorted, depende del nivel de
nivel de datos o de la complejidad del programa, en lo personal, solo
he utilizado este tipo de tablas algunas cuantas veces en toda mi
carrera.
Selección de Datos
Al igual que en SQL, podemos utilizar la clásica sentencia
SELECT, para poder seleccionar datos. Aunque en el caso de
ABAP, tenemos mayor flexibilidad para poder almacenar los datos,
ya sea en Variable o en Tablas internas.
En Variables:
DATA: NOMBRE TYPE ZPROGRAMAS-NOM_PROG.
SELECT SINGLE NOM_PROG
INTO NOMBRE
FROM ZPROGRAMAS
WHERE ID_PROG EQ '001'.
83
Declaramos una variable llamada NOMBRE del tipo del campo
NOM_PROG de la tabla ZPROGRAMAS. Hacemos un SELECT
SINGLE para obtener un registro cuyo campo ID_PROG sea igual
a 001.
En Tablas internas:
TYPES: BEGIN OF TY_PROGRAMAS,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS.
SELECT NOM_PROG
INTO TABLE T_PROGRAMAS
FROM ZPROGRAMAS.
En esta caso, creamos un TYPE, luego una tabla interna y
finalmente leemos todas las instancias del campo NOM_PROG
dentro de nuestra tabla interna.
Claro, también podemos utilizar INNER JOINS para hacer nuestras
consultas.
TYPES: BEGIN OF TY_PROGRAMAS,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
ENTORNO TYPE ZLENGUAJES_PROG-ENTORNO,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
84
DATA: T_PROGRAMAS TYPE STANDARD TABLE OF TY_PROGRAMAS.
SELECT NOMBRE ENTORNO NOM_PROG
INTO TABLE T_PROGRAMAS
FROM ( ZLENGUAJES_PROG INNER JOIN ZPROGRAMAS
ON ZLENGUAJES_PROG~ID = ZPROGRAMAS~ID ).
Ahora, supongamos que tenemos un campo más en nuestra tabla
interna, pero no queremos seleccionarlo, entonces, el SELECT va a
estar incompleto y los registros pueden guardarse donde no les
corresponde. Esto lo podemos solucionar utilizando un INTO
CORRESPONDING FIELDS, que lo que hace es almacenar los
registros en los campos correspondientes, aunque claro, esto afecta
el performance de nuestros programas, así que lo mejor es evitarlos.
TYPES: BEGIN OF TY_PROGRAMAS,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
ENTORNO TYPE ZLENGUAJES_PROG-ENTORNO,
CONEX_SAP TYPE ZLENGUAJES_PROG-CONEX_SAP,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
DATA: T_PROGRAMAS TYPE STANDARD TABLE OF TY_PROGRAMAS.
SELECT NOMBRE ENTORNO NOM_PROG
INTO CORRESPONDING FIELDS OF TABLE T_PROGRAMAS
FROM ( ZLENGUAJES_PROG INNER JOIN ZPROGRAMAS
ON ZLENGUAJES_PROG~ID = ZPROGRAMAS~ID ).
85
Lectura de datos de Tablas Internas
Una vez que tenemos datos en nuestras tablas internas, debemos de
leerlas para poder hacer algo con ellas. Para eso, contamos con dos
opciones.
Aunque, antes de eso, debemos conocer un elemento muy
importante, sin el cual no podríamos hacer mucho en ABAP.
Estamos hablando de los Field-Symbols. Para los que han
programado alguna vez en C++, los Fields-Symbols, son muy
parecidos a los punteros, es decir, almacenas la dirección en
memoria de una variable. Por lo general, los utilizamos para crear
cabeceras de tablas internas.
FIELD-SYMBOLS: <FS_TABLA> LIKE LINE
OF T_TABLA.
Con esto, creamos una referencia a la tabla T_TABLA, la cual
contiene únicamente una línea de cabecera, con lo cual ganamos
mucho performance al hacer lecturas de tablas internas.
• LOOP AT
TYPES: BEGIN OF TY_PROGRAMAS,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
ENTORNO TYPE ZLENGUAJES_PROG-ENTORNO,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
86
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS.
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS.
SELECT NOMBRE ENTORNO NOM_PROG
INTO TABLE T_PROGRAMAS
FROM ( ZLENGUAJES_PROG INNER JOIN ZPROGRAMAS
ON ZLENGUAJES_PROG~ID = ZPROGRAMAS~ID ).
LOOP AT T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>.
WRITE:/ <FS_PROGRAMAS>-NOM_PROG.
ENDLOOP.
Al hacer un LOOP AT, lo que hacemos es leer cada uno de
los registros almacenados en nuestra tabla interna, y al asignar
cada uno de estos registros a nuestro Field-Symbol, lo que
estamos haciendo es pasar simplemente la cabecera de ese
registro, por lo cual la lectura de la tablas es mucho más veloz.
Finalmente, utilizando un WRITE imprimimos el contenido
del campo NOM_PROG. El símbolo / nos sirve para dejar un
espacio hacia abajo luego de haber impreso el valor
(Equivales a hacer un ENTER).
87
• READ TABLE
TYPES: BEGIN OF TY_PROGRAMAS,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
ENTORNO TYPE ZLENGUAJES_PROG-ENTORNO,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS.
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS.
SELECT NOMBRE ENTORNO NOM_PROG
INTO TABLE T_PROGRAMAS
FROM ( ZLENGUAJES_PROG INNER JOIN ZPROGRAMAS
ON ZLENGUAJES_PROG~ID = ZPROGRAMAS~ID ).
READ TABLE T_PROGRAMAS INDEX 1
ASSIGNING <FS_PROGRAMAS>.
WRITE:/ <FS_PROGRAMAS>-NOM_PROG.
READ TABLE T_PROGRAMAS
WITH KEY NOMBRE = 'PHP'
ASSIGNING <FS_PROGRAMAS>.
WRITE:/ <FS_PROGRAMAS>-NOM_PROG.
En este caso, al utilizar un READ TABLE, leemos un solo
registro de nuestra tabla, como podemos ver, podemos utilizar
88
un Indice o también un Campo para leer el contenido y
asignarlo a nuestro Field-Symbol.
Operadores de Comparación
Un proceso muy común, es el comparar valores entre variables o
tablas internas, para esto, contamos con los siguientes comandos.
=, EQ
Igual a
<>, NE
Distinto a
>, GT
Mayor que
<, LT
Menor que
>=, GE
Mayor igual
<=, LE
Menor igual
Ambos tipos de comandos son equivalentes, por lo tanto es lo
mismo decir:
IF NOMBRE EQ ‘PHP’.
WRITE:/ ‘Viva PHP!’.
ENDIF.
Que decir:
IF NOMBRE == ‘PHP’.
WRITE:/ ‘Viva PHP!’.
89
ENDIF.
Para poder afianzar los conocimientos adquiridos hasta el momento,
vamos a crear una pequeña aplicación.
REPORT ZDUMMY_PRIMER_PROGRAMA
NO STANDARD PAGE HEADING.
*=====================================================*
* DECLARACION DE TABLES
*
*=====================================================*
TABLES: ZPROGRAMAS.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_PROGRAMAS,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
ENTORNO TYPE ZLENGUAJES_PROG-ENTORNO,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS.
*=====================================================*
* FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
90
OF T_PROGRAMAS.
*=====================================================*
* SELECTION-SCREEN
*
*=====================================================*
SELECTION-SCREEN BEGIN OF BLOCK PRG
WITH FRAME TITLE TEXT-T01.
SELECT-OPTIONS:
S_ID FOR ZPROGRAMAS-ID.
SELECTION-SCREEN END OF BLOCK PRG.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
SELECT NOMBRE ENTORNO NOM_PROG
INTO TABLE T_PROGRAMAS
FROM ( ZLENGUAJES_PROG INNER JOIN ZPROGRAMAS
ON ZLENGUAJES_PROG~ID = ZPROGRAMAS~ID )
WHERE ZPROGRAMAS~ID IN S_ID.
WRITE:/1 'Lenguaje',17 'Entorno',33 'Programa'.
WRITE:/ SY-ULINE(45).
LOOP AT T_PROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
WRITE:/ <FS_PROGRAMAS>-NOMBRE,<FS_PROGRAMAS>-ENTORNO,
<FS_PROGRAMAS>-NOM_PROG.
ENDLOOP.
91
Analicemos un poco el programa antes de ejecutarlo y ver el
resultado.
REPORT ZDUMMY_PRIMER_PROGRAMA
NO STANDARD PAGE HEADING.
REPORT indica que estamos creando y ejecutando un programa.
ZDUMMY_PRIMER_PROGRAMA es el nombre de nuestro
programa.
NO STANDARD PAGE HEADING, indica que no queremos que
el título del programa se muestre en el output del reporte.
TABLES: ZPROGRAMAS.
TABLES indica que vamos a utilizar una tabla para hacer referencia
a un campo en el SELECTION-SCREEN.
TYPES: BEGIN OF TY_PROGRAMAS,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
ENTORNO TYPE ZLENGUAJES_PROG-ENTORNO,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
TYPES indica que vamos a crear un tipo de tabla definido por
nosotros.
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS.
92
DATA indica que vamos a crear una variable o una tabla interna.
T_PROGRAMAS es el nombre de nuestra tabla interna.
TYPE STANDARD TABLE indica que la tabla es de tipo
STANDARD.
OF indica a que tipo de dato va a hacer referencia nuestra tabla
interna.
TY_PROGRAMAS es el nombre del tipo de tabla que creamos y al
cual va a hacer referencia nuestra tabla interna.
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS.
FIELD-SYMBOLS crea un field-symbol.
<FS_PROGRAMAS> es el nombre de nuestro field-symbol.
LIKE LINE OF indica que va a representar una línea de cabecera
de una tabla interna.
T_PROGRAMAS es la tabla interna de la cual el field-symbol va a
representar la cabecera.
SELECTION-SCREEN BEGIN OF BLOCK PRG
WITH FRAME TITLE TEXT-T01.
SELECT-OPTIONS:
S_ID FOR ZPROGRAMAS-ID.
SELECTION-SCREEN END OF BLOCK PRG.
SELECTION-SCREEN BEGIN OF BLOCK indica el inicio de
un bloque de parámetros.
PRG es el nombre del bloque de parámetros.
93
WITH FRAME indica que nuestro bloque de parámetro debe tener
un marco (más que nada un tema de visualización).
TITLE TEXT indica que el bloque de parámetros debe tener un
título.
T01 contiene el título.
SELECT-OPTIONS indica que es un parámetros con rango de
valores.
S_ID es el nombre del SELECT-OPTION.
FOR indica que hace referencia a un campo de Base de Datos.
ZPROGRAMAS-ID es el nombre de la Base de Datos y el campo
respectivamente.
SELECTION-SCREEN END OF indica el fin del bloque de
parámetros.
START-OF-SELECTION.
START-OF-SELECTION indica el inicio de nuestro programa.
SELECT NOMBRE ENTORNO NOM_PROG
INTO TABLE T_PROGRAMAS
FROM ( ZLENGUAJES_PROG INNER JOIN ZPROGRAMAS
ON ZLENGUAJES_PROG~ID = ZPROGRAMAS~ID )
WHERE ZPROGRAMAS~ID IN S_ID.
SELECT indica que queremos seleccionar datos.
NOMBRE ENTORNO NOM_PROG son los campos que
queremos seleccionar.
94
INTO TABLE indica en que tabla interna queremos guardar los
datos.
T_PROGRAMAS es la tabla donde vamos a guardar los datos.
FROM indica de donde queremos obtener los datos.
ZLENGUAJES_PROG INNER JOIN ZPROGRAMAS indica
que queremos realizar un INNER JOIN entre estas dos tablas
ON indica el parámetro de igualdad de campos del INNER JOIN.
ZLENGUAJES~ID = ZPROGRAMAS~ID indica que el campo
ID de ambas tablas va a utilizarse como campo de igualdad.
WHERE indica el parámetro de restricción del SELECT.
ZPROGRAMAS~ID es el campo por el cual queremos hacer el
filtro.
IN indica que el campo del filtro debe de estar dentro de los valores
del SELECT-OPTION.
S_ID es el SELECT-OPTION contra el cual vamos a validar el
campo ZPROGRAMAS~ID.
WRITE:/1 'Lenguaje',17 'Entorno',33 'Programa'.
WRITE:/ SY-ULINE(45).
WRITE indica que queremos escribir algo en la pantalla.
/ indica que luego de escribir en la pantalla queremos hacer un salto
de líneas.
1, 17 y 33, indican las posiciones X en las cuales queremos escribir.
‘Lenguaje’, ‘Entorno’ y ‘Programa’ son los texto que queremos
escribir.
95
SY-ULINE(45) es una variable del sistema que nos permite dibujar
una línea. El 45 entre paréntesis indica la longitud de la línea.
LOOP AT T_PROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
WRITE:/ <FS_PROGRAMAS>-NOMBRE,<FS_PROGRAMAS>-ENTORNO,
<FS_PROGRAMAS>-NOM_PROG.
ENDLOOP.
LOOP AT indica que vamos a recorrer todos los registros de una
tabla interna.
T_PROGRAMAS es la tabla interna de la cual vamos a leer los
registros.
ASSIGNING <FS_PROGRAMAS> indica que vamos a asignar el
registro leído a un Field-Symbol.
<FS_PROGRAMAS> es el nombre del Field-Symbol.
<FS_PROGRAMA>-NOMBRE es el nombre del campo que
queremos escribir en la pantalla.
ENDLOOP indica el fín del LOOP.
Ahora que ya hemos revisado todo el programa línea por línea,
podemos ejecutarlo.
96
Si queremos que nuestro programa se vea un poco más colorido,
podemos agregar un par de líneas.
FORMAT COLOR 5.
WRITE:/1 'Lenguaje',17 'Entorno',33 'Programa'.
FORMAT COLOR OFF.
WRITE:/ SY-ULINE(45).
97
FORMAT COLOR indica que queremos pintar el fondo de un
color.
5 representa al color verde.
FORMAT COLOR OFF indica que ya no queremos seguir
pintando el fondo de un color.
El reporte quedaría así:
Operaciones en tablas internas
Cuando se trabaja con tablas internas, muchas veces se necesita
agregar, modificar o eliminar registros (Ya sea porque no nos sirven
o porque están duplicados). Veamos como manejar esto:
•
Agregando registros
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
98
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS.
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS.
APPEND INITIAL LINE TO
T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>.
<FS_PROGRAMAS>-ID_PROG = '006'.
<FS_PROGRAMAS>-NOM_PROG = 'MP3 Player'.
READ TABLE T_PROGRAMAS INDEX 1
ASSIGNING <FS_PROGRAMAS>.
WRITE:/ <FS_PROGRAMAS>-ID_PROG.
WRITE:/ <FS_PROGRAMAS>-NOM_PROG.
Como vemos, creamos una tabla interna. Pero como esta no
tiene cabecera, entonces debemos asignarle una utilizando
APPEND
INITIAL
LINE
TO
y
asignándola
a
<FS_PROGRAMAS>.
Luego, agregamos los valores a la tabla y al momento de
leerla con el índice 1, podemos imprimir los nuevos valores.
99
•
Modificando registros
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS.
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS.
APPEND INITIAL LINE TO
T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>.
<FS_PROGRAMAS>-ID_PROG = '006'.
<FS_PROGRAMAS>-NOM_PROG = 'MP3 Player'.
READ TABLE T_PROGRAMAS INDEX 1
ASSIGNING <FS_PROGRAMAS>.
WRITE:/ <FS_PROGRAMAS>-ID_PROG.
WRITE:/ <FS_PROGRAMAS>-NOM_PROG.
READ TABLE T_PROGRAMAS INDEX 1
ASSIGNING <FS_PROGRAMAS>.
SKIP 1.
WRITE:/ 'Modificamos el registro'.
SKIP 1.
100
<FS_PROGRAMAS>-NOM_PROG = 'Web Browser'.
READ TABLE T_PROGRAMAS INDEX 1
ASSIGNING <FS_PROGRAMAS>.
WRITE:/ <FS_PROGRAMAS>-ID_PROG.
WRITE:/ <FS_PROGRAMAS>-NOM_PROG.
Tomando el ejemplo anterior, leemos el primer registro y lo
asignamos a <FS_PROGRAMAS>. Simplemente escribimos
el nuevo valor para que se modifique automáticamente.
Podrán ver que además, estamos utilizando la sentencia SKIP,
esta sentencia nos permite realizar saltos de línea. En esta caso
solamente 1.
•
Eliminado registros
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS.
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS.
APPEND INITIAL LINE TO
T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>.
101
<FS_PROGRAMAS>-ID_PROG = '006'.
<FS_PROGRAMAS>-NOM_PROG = 'MP3 Player'.
APPEND INITIAL LINE TO
T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>.
<FS_PROGRAMAS>-ID_PROG = '007'.
<FS_PROGRAMAS>-NOM_PROG = 'Web Browser'.
DELETE T_PROGRAMAS INDEX 1.
DELETE T_PROGRAMAS WHERE ID_PROG EQ '006'.
Asignamos dos registros a nuestra tabla interna, y como
podemos ver, podemos eliminarlos utilizando ya sea un
índice o uno de los campos como parámetro de búsqueda.
Ahora, supongamos que tenemos registros repetidos y
queremos eliminarlos sin preocuparnos por el índice (Puesto
que eliminado por campo, eliminaríamos todos los registros).
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS.
102
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS.
APPEND INITIAL LINE TO
T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>.
<FS_PROGRAMAS>-ID_PROG = '006'.
<FS_PROGRAMAS>-NOM_PROG = 'MP3 Player'.
APPEND INITIAL LINE TO
T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>.
<FS_PROGRAMAS>-ID_PROG = '006'.
<FS_PROGRAMAS>-NOM_PROG = 'MP3 Player'.
DELETE ADJACENT DUPLICATES FROM T_PROGRAMAS.
Como podemos ver, tenemos dos veces el mismo registro,
por
lo
tanto
utilizamos
DELETE
ADJACENT
DUPLICATES para dejar solamente uno de los dos
registros.
Claro, quizás se podría dar el caso de que solamente el
campo ID_PROG esté repetido, más no el campo
NOM_PROG. Entonces debemos hacer algo más.
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
103
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS.
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS.
APPEND INITIAL LINE TO
T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>.
<FS_PROGRAMAS>-ID_PROG = '006'.
<FS_PROGRAMAS>-NOM_PROG = 'MP3 Player'.
APPEND INITIAL LINE TO
T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>.
<FS_PROGRAMAS>-ID_PROG = '006'.
<FS_PROGRAMAS>-NOM_PROG = 'Web Browser'.
DELETE ADJACENT DUPLICATES FROM T_PROGRAMAS
COMPARING ID_PROG.
Simplemente debemos agregar un COMPARING, con lo
cual solamente se toma en cuenta el campo ID_PROG para
hacer la validación de registros repetidos.
Copiar tablas internas
Algunas veces, necesitamos copiar el contenido de una tabla interna
a otra. En este caso, dos opciones, las tablas tienen la misma
estructura o tienen una estructura distinta.
104
• Copiar tablas con la misma estructura
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS,
T_PROGRAMAS_AUX TYPE STANDARD TABLE
OF TY_PROGRAMAS.
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS.
APPEND INITIAL LINE TO
T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>.
<FS_PROGRAMAS>-ID_PROG = '006'.
<FS_PROGRAMAS>-NOM_PROG = 'MP3 Player'.
T_PROGRAMAS_AUX[] = T_PROGRAMAS[].
En
este
caso,
T_PROGRAMAS
tenemos
y
las
dos
tablas
T_PROGRAMAS_AUX,
internas
ambas
haciendo referencia a TY_PROGRAMAS, por lo tanto,
poseen exactamente la misma estructura. Por ello, podemos
utilizar el [] para copiar los datos de una tabla a otra.
105
• Copiar tablas con diferente estructura
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
TYPES: BEGIN OF TY_PROGRAMAS_AUX,
ID TYPE ZPROGRAMAS-ID,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS_AUX.
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS,
T_PROGRAMAS_AUX TYPE STANDARD TABLE
OF TY_PROGRAMAS_AUX.
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS,
<FS_PROGRAMAS_AUX> LIKE LINE
OF T_PROGRAMAS_AUX.
APPEND INITIAL LINE TO
T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>.
<FS_PROGRAMAS>-ID_PROG = '006'.
<FS_PROGRAMAS>-NOM_PROG = 'MP3 Player'.
LOOP AT T_PROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
APPEND INITIAL LINE TO T_PROGRAMAS_AUX
106
ASSIGNING <FS_PROGRAMAS_AUX>.
MOVE <FS_PROGRAMAS>-ID_PROG TO
<FS_PROGRAMAS_AUX>-ID_PROG.
MOVE <FS_PROGRAMAS>-NOM_PROG TO
<FS_PROGRAMAS_AUX>-NOM_PROG.
ENDLOOP.
Creamos un tipo de tabla llamado TY_PROGRAMAS_AUX
al cual le agregamos el campo ID, con lo cual hacemos que
ambas tablas internas sean distintas, por lo cual no podemos
seguir utilizando el []. En vez de eso, debemos hacer un
LOOP y asignar los valores de la tabla T_PROGRAMAS a
la tabla T_PROGRAMAS_AUX. Eso está bien para algunos
campos, pero si tenemos por decir 20 campos...Entonces
debemos utilizar una forma alternativa.
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
TYPES: BEGIN OF TY_PROGRAMAS_AUX,
ID TYPE ZPROGRAMAS-ID,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS_AUX.
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS,
T_PROGRAMAS_AUX TYPE STANDARD TABLE
107
OF TY_PROGRAMAS_AUX.
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS,
<FS_PROGRAMAS_AUX> LIKE LINE
OF T_PROGRAMAS_AUX.
APPEND INITIAL LINE TO
T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>.
<FS_PROGRAMAS>-ID_PROG = '006'.
<FS_PROGRAMAS>-NOM_PROG = 'MP3 Player'.
LOOP AT T_PROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
APPEND INITIAL LINE TO T_PROGRAMAS_AUX
ASSIGNING <FS_PROGRAMAS_AUX>.
MOVE-CORRESPONDING <FS_PROGRAMAS> TO
<FS_PROGRAMAS_AUX>.
ENDLOOP.
Cuando utilizamos el MOVE-CORRESPONDING, lo que
hacemos es que el ABAP se encargue de mover todos los
campos
de
la
tabla
T_PROGRAMAS
a
la
tabla
T_PROGRAMAS_AUX.
Ordenar tablas internas
Esto es bastante simple, ya se solamente tenemos una forma de
hacerlo.
108
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS.
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS.
APPEND INITIAL LINE TO
T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>.
<FS_PROGRAMAS>-ID_PROG = '006'.
<FS_PROGRAMAS>-NOM_PROG = 'MP3 Player'.
<FS_PROGRAMAS>-ID_PROG = '005'.
<FS_PROGRAMAS>-NOM_PROG = 'Web Browser'.
SORT T_PROGRAMAS BY ID_PROG ASCENDING.
SORT T_PROGRAMAS BY ID_PROG DESCENDING.
Utilizamos la sentencia SORT para ordenar una tabla interna,
podemos incluir BY para indicar por cual o cuales campos
debería ordenarse y además podemos indicar si la ordenación
es Ascending (Ascendente) o Descending (Descendente).
109
Estructuras de Control
Como vimos, podemos utilizar un LOOP para recorrer todos los
registros de una tabla interna. ¿Pero que pasa si lo que necesitamos
es recorrer los posibles valores de una variable?
Para estos casos contamos con WHILE-ENDWHILE.
DATA: VAR TYPE I.
WHILE VAR LT 10.
WRITE:/ VAR.
VAR = VAR + 1.
ENDWHILE.
Esto significa que mientras la variable VAR sea menor o igual a 10,
imprimimos el valor. Aumentamos el valor de VAR de uno en uno,
por cada vuelta del WHILE.
También tenemos presente el DO-ENDO.
DATA: VAR TYPE I.
DO.
IF VAR LT 10.
WRITE:/ VAR.
VAR = VAR + 1.
ELSE.
EXIT.
ENDIF.
ENDDO.
110
El DO-ENDDO es un bucle repetitivo que avanza mientras no le
digamos que tiene que salir. Utilizando un IF preguntamos si el
valor de la variable es menor o igual a 10. En el caso afirmativo
imprimimos y aumentamos en uno. Cuando VAR es mayor a 10,
salimos del DO-ENDDO utilizando un EXIT.
Aunque, también podríamos haberlo escrito así:
DATA: VAR TYPE I.
DO 10 TIMES.
WRITE:/ VAR.
VAR = VAR + 1.
ENDDO.
En esta caso, le decimos al DO-ENDO que solo realice el bucle 10
veces. Imprimimos el valor de la variable VAR y la aumentamos en
uno.
Adicionalmente tenemos los comandos CONTINUE y EXIT, que
sirven para continuar en la siguiente iteración de la estructura de la
estructura de control o para salir de la estructura de control
completamente.
Trabajando con Cadenas de Texto
Si bien el ABAP no está diseñado para trabajar con cadenas de
texto, o por lo menos no tanto como otros lenguajes (Como por
111
ejemplo PERL), de todos modos nos brinda alguna poderosas
herramientas como estas:
•
TRANSLATE
Convierte una cadena de texto a Mayúsculas o Minúsculas.
DATA: VAR TYPE STRING.
VAR = 'El Arte de Programar'.
TRANSLATE VAR TO UPPER CASE.
WRITE:/ VAR.
TRANSLATE VAR TO LOWER CASE.
WRITE:/ VAR.
Con TO UPPER CASE convertimos a Mayúsculas y con
TO LOWER CASE convertimos a Minúsculas.
•
CONCATENATE
Concatena dos o más cadenas de texto. Es decir, une cadenas
en una cadena más grande.
DATA: VAR TYPE STRING,
VAR_AUX TYPE STRING,
VAR_TEXT TYPE STRING.
112
VAR = 'El Arte de Programar'.
VAR_AUX = 'SAP NETWEAVER'.
CONCATENATE VAR VAR_AUX
INTO VAR_TEXT.
WRITE:/ VAR_TEXT.
Con el CONCATENATE decimos que los valores de las
variables VAR y VAR_AUX se guarden en la variable
VAR_TEXT.
Aunque claro, si ejecutamos el programa nos daremos cuenta
de que no hay ningún espacio entre las dos palabras, esto lo
arreglamos fácilmente.
DATA: VAR TYPE STRING,
VAR_AUX TYPE STRING,
VAR_TEXT TYPE STRING.
VAR = 'El Arte de Programar'.
VAR_AUX = 'SAP NETWEAVER'.
CONCATENATE VAR VAR_AUX
INTO VAR_TEXT
SEPARATED BY SPACE.
WRITE:/ VAR_TEXT.
Simplemente agregamos un SEPARATED BY al final del
CONCATENATE, en este caso al utilizar SPACE le
113
estamos diciendo que separe el texto con un espacio en
blanco.
•
SPLIT
Divide una cadena en subcadenas, dependiendo de un carácter
aguja.
DATA: VAR TYPE STRING,
VAR_AUX TYPE STRING,
VAR_TEXT TYPE STRING.
VAR_TEXT = 'SAP NETWEAVER'.
SPLIT VAR_TEXT AT SPACE
INTO VAR VAR_TEXT.
WRITE:/ VAR.
WRITE:/ VAR_TEXT.
Utilizamos el SPLIT para dividir la cadena utilizando como
aguja un espacio y pasamos los valores a las variables VAR y
VAR_TEXT.
TYPES: BEGIN OF TY_CADENAS,
VAR(30) TYPE C,
END OF TY_CADENAS.
DATA: T_CADENAS TYPE STANDARD TABLE
OF TY_CADENAS.
114
DATA: VAR_TEXT TYPE STRING.
VAR_TEXT = 'SAP NETWEAVER'.
SPLIT VAR_TEXT AT SPACE
INTO TABLE T_CADENAS.
Creamos un tipo de tabla con un campo llamado VAR de tipo
C y longitud 30.
Creamos una tabla interna con referencia a nuestro tipo de
tabla. Declaramos una variable de tipo STRING y utilizando
el SPLIT mandamos las subcadenas a la tabla interna.
•
SHIFT
Utilizado con la sentencia DELETING, permite eliminar los
espacios o caracteres al inicio o al final de una cadena de
texto.
DATA: VAR_TEXT TYPE STRING.
VAR_TEXT = '
SAP NETWEAVER
'.
SHIFT VAR_TEXT LEFT DELETING LEADING SPACE.
SHIFT VAR_TEXT RIGHT DELETING TRAILING SPACE.
La variable VAR_TEXT tiene cinco espacios al inicio y
cinco espacios al final del texto.
Utilizando el SHIFT LEFT DELETING LEADING
eliminamos los espacios en blanco del inicio de la cadena.
115
Utilizando el SHIFT RIGHT DELETING TRAILING
eliminamos los espacios en blanco del final de la cadena.
•
CONDENSE
Elimina los espacios en blanco, como lo hace el SHIFT, pero
toma los espacios del final y del inicio al mismo tiempo.
DATA: VAR_TEXT TYPE STRING.
VAR_TEXT = '
SAP NETWEAVER
'.
CONDENSE VAR_TEXT.
CONDENSE VAR_TEXT NO-GAPS.
Con el CONDESE eliminamos los espacios del inicio y del
final de la cadena.
Con el CONDENSE eliminamos todos los espacios de la
cadena.
•
REPLACE
Reemplaza una cadena por otra.
DATA: VAR_TEXT TYPE STRING.
VAR_TEXT = 'SAP NETWEAVER PROGRAMMING'.
REPLACE SPACE WITH '/'
INTO VAR_TEXT.
116
VAR_TEXT = 'SAP/NETWEAVER/PROGRAMMING'.
REPLACE ALL OCCURRENCES OF
'/' IN VAR_TEXT WITH SPACE .
Con REPLACE decimos que reemplace el espacio en blanco
con el caracter “/”. Aunque solamente va a buscar y
reemplazar la primera ocurrencia del espacio.
En el segundo caso, queremos que busque todas las
ocurrencia de “/” y la reemplace con un espacio.
Si hubiéramos dicho que busque las ocurrencia del espacio y
las reemplace por un “/”, nuestro programa había fallado
miserablemente. Porque? Realmente no lo sé...Tuvimos una
emocionante discusión en el SDN (SAP Developer Network)
acerca de esto, pero lamentablemente no pudimos hallar una
respuesta adecuada.
•
FIND
Busca una subcadena en una cadena.
DATA: RESULT_TAB TYPE MATCH_RESULT_TAB.
DATA: VAR_TEXT TYPE STRING.
FIELD-SYMBOLS: <FS_RESULT> LIKE LINE
OF RESULT_TAB.
VAR_TEXT = 'SAP NETWEAVER PROGRAMMING'.
117
FIND 'NETWEAVER' IN VAR_TEXT
RESULTS RESULT_TAB.
IF SY-SUBRC EQ 0.
READ TABLE RESULT_TAB INDEX 1
ASSIGNING <FS_RESULT>.
WRITE:/ <FS_RESULT>-OFFSET.
ENDIF.
Creamos una tabla interna llamada RESULT_TAB de tipo
MATCH_RESULT_TAB (Que es un tipo predefinido en
ABAP). Cuando utilizamos el FIND para hacer la búsqueda
de la subcadena, debemos asignar el resultado a la tabla
interna RESULT_TAB y de ella leer la posición de la
subcadena dentro de la cadena.
Si quisiéramos encontrar todas la ocurrencias de la cadena,
deberíamos hacer lo siguiente:
DATA: RESULT_TAB TYPE MATCH_RESULT_TAB.
DATA: VAR_TEXT TYPE STRING.
FIELD-SYMBOLS: <FS_RESULT> LIKE LINE
OF RESULT_TAB.
VAR_TEXT = 'SAP NETWEAVER SAP PROGRAMMING'.
FIND ALL OCCURRENCES OF 'SAP'
IN VAR_TEXT
RESULTS RESULT_TAB.
IF SY-SUBRC EQ 0.
118
LOOP AT RESULT_TAB
ASSIGNING <FS_RESULT>.
WRITE:/ <FS_RESULT>-OFFSET.
ENDLOOP.
ENDIF.
Incluyendo el ALL OCCURRENCES OF podemos
conocer todas las ocurrencias de una subcadena dentro de
una cadena.
•
STRLEN
Obtiene la longitud de una cadena.
DATA: VAR_TEXT TYPE STRING,
LONG TYPE I.
VAR_TEXT = 'SAP NETWEAVER SAP PROGRAMMING'.
LONG = STRLEN( VAR_TEXT ).
Utilizando STRLEN podemos conocer la longitud en
caracteres de una cadena de texto.
Variables del Sistema
ABAP cuenta con variables internas del sistema que nos ayudan a
conocer información importante acerca de nuestro programa o del
119
sistema. Veamos cuales son las más importantes. (Todas pueden ser
encontradas en la tabla SYST).
•
SY-SUBRC
Retorna un valor que determina el estado de las operaciones
en ABAP.
SY-SUBRC = 0 Æ Ejecutado con éxito.
SY-SUBRC = 4 Æ Error. No ha podido ejecutarse.
•
SY-PAGNO
En un reporte de tipo listado, determina el número de página
en la cual nos encontramos.
•
SY-TABIX
Dentro de un LOOP, nos indica el número de “vuelta” o
iteración o el índice del registro que estamos leyendo.
•
SY-LANGU
Idioma de trabajo actual.
•
SY-BACTH
Nos indica si el programa se está ejecutando en fondo o en
modo directo.
•
SY-MANDT
120
Nos indica el número de mandante en el cual estamos
trabajando.
•
SY-TCODE
El nombre de la transacción con la cual estamos trabajando.
•
SY-UCOMM
Nombre del código de función lanzado por un Dynpro o por
un menú.
•
SY-DATUM
Fecha actual del sistema.
•
SY-UZEIT
Hora actual del sistema.
•
SY-REPID
Nombre del programa que estamos creando o ejecutando.
•
SY-UNAME
Nombre del usuario logeado en el sistema.
Modularización de Programas
Para que nuestros programas sean más fáciles de mantener y de
programas, debemos utilizar funciones para poder modularizarlos.
121
Las funciones tienen el siguiente formato:
FORM XXX USING YYY
CHANGING YYY
TABLES YYY.
…
ENDFORM.
Y se llaman así:
PERFORM XXX USING YYY
CHANGING YYY
TABLE YYY.
Para entender mejor a que nos referimos, vamos a hacer un ejemplo.
REPORT ZDUMMY_PRIMER_PROGRAMA
NO STANDARD PAGE HEADING.
*=====================================================*
* DECLARACION DE TABLES
*
*=====================================================*
TABLES: ZPROGRAMAS.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_PROGRAMAS,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
122
ENTORNO TYPE ZLENGUAJES_PROG-ENTORNO,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS.
*=====================================================*
* FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS.
*=====================================================*
* SELECTION-SCREEN
*
*=====================================================*
SELECTION-SCREEN BEGIN OF BLOCK PRG
WITH FRAME TITLE TEXT-T01.
SELECT-OPTIONS:
S_ID FOR ZPROGRAMAS-ID.
SELECTION-SCREEN END OF BLOCK PRG.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM OBTENER_DATOS.
PERFORM MOSTRAR_REPORTE.
123
*&----------------------------------------------------*
*&
Form
obtener_datos
*
*&----------------------------------------------------*
FORM OBTENER_DATOS.
SELECT NOMBRE ENTORNO NOM_PROG
INTO TABLE T_PROGRAMAS
FROM ( ZLENGUAJES_PROG INNER JOIN ZPROGRAMAS
ON ZLENGUAJES_PROG~ID = ZPROGRAMAS~ID )
WHERE ZPROGRAMAS~ID IN S_ID.
ENDFORM.
"obtener_datos
*&----------------------------------------------------*
*&
Form
MOSTRAR_REPORTE
*
*&----------------------------------------------------*
FORM MOSTRAR_REPORTE.
WRITE:/1 'Lenguaje',17 'Entorno',33 'Programa'.
WRITE:/ SY-ULINE(45).
LOOP AT T_PROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
WRITE:/ <FS_PROGRAMAS>-NOMBRE,
<FS_PROGRAMAS>-ENTORNO,
<FS_PROGRAMAS>-NOM_PROG.
ENDLOOP.
ENDFORM.
"MOSTRAR_REPORTE
Se darán cuenta de que este es el mismo ejemplo que ya habíamos
desarrollado, aunque ahora, lo hemos modularizado.
124
START-OF-SELECTION.
PERFORM OBTENER_DATOS.
PERFORM MOSTRAR_REPORTE.
Luego del START-OF-SELECTION, llamamos a nuestras dos
funciones OBTENER_DATOS y MOSTRAR_REPORTE.
FORM OBTENER_DATOS.
SELECT NOMBRE ENTORNO NOM_PROG
INTO TABLE T_PROGRAMAS
FROM ( ZLENGUAJES_PROG INNER JOIN ZPROGRAMAS
ON ZLENGUAJES_PROG~ID = ZPROGRAMAS~ID )
WHERE ZPROGRAMAS~ID IN S_ID.
ENDFORM.
"obtener_datos
En este caso, las funciones o FORM no hacen más que encapsular
las funcionalidades que habíamos desarrollado anteriormente.
Aunque de todos modos, nos ayuda a tener un código más ordenado.
Ahora, veamos un ejemplo que utilice las características de los
FORMS.
REPORT ZDUMMY_PRIMER_PROGRAMA
NO STANDARD PAGE HEADING.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: VAR TYPE STRING.
*=====================================================*
* START-OF-SELECTION
*
125
*=====================================================*
START-OF-SELECTION.
PERFORM INICIALIZAR.
PERFORM CONVERTIR USING 'U'
CHANGING VAR.
WRITE:/ VAR.
PERFORM CONVERTIR USING 'L'
CHANGING VAR.
WRITE:/ VAR.
*&----------------------------------------------------*
*&
Form
INICIALIZAR
*
*&----------------------------------------------------*
FORM INICIALIZAR.
VAR = 'El Arte de Programar NETWEAVER'.
ENDFORM.
"INICIALIZAR
*&----------------------------------------------------*
*&
Form
CONVERTIR
*
*&----------------------------------------------------*
FORM CONVERTIR USING P_TIPO
CHANGING P_VAR.
CASE P_TIPO.
WHEN 'U'.
TRANSLATE P_VAR TO UPPER CASE.
WHEN 'L'.
TRANSLATE P_VAR TO LOWER CASE.
ENDCASE.
ENDFORM.
"CONVERTIR
126
En este caso, tenemos dos FORMS, uno llamado INICIALIZAR
donde simplemente asignamos un valor a la variable VAR y otro
llamado CONVERTIR el cual recibe un valor de texto (Ya sea U o
L) y cambia el valor de la variable VAR. Dentro del FORM
CONVERTIR, utilizamos un CASE-ENDCASE para poder
determinar el valor del parámetro P_TIPO. En el caso de ser U
(Upper), entonces hacemos un TRANSLATE TO UPPER CASE y
cambiamos el valor de P_VAR. En caso contrario, si es L (Lower),
hacemos un TRANSLATE TO LOWER CASE y cambiamos el
valor de P_VAR. Luego de llamar a cada PERFORM, imprimimos
el valor de P_VAR.
REPORT ZDUMMY_PRIMER_PROGRAMA
NO STANDARD PAGE HEADING.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS.
*=====================================================*
127
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM INICIALIZAR.
PERFORM IMPRIMIR_REPORTE TABLES T_PROGRAMAS.
*&----------------------------------------------------*
*&
Form
inicializar
*
*&----------------------------------------------------*
FORM INICIALIZAR.
APPEND INITIAL LINE
TO T_PROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
<FS_PROGRAMAS>-ID_PROG = '001'.
<FS_PROGRAMAS>-NOM_PROG = 'TETRIS'.
APPEND INITIAL LINE
TO T_PROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
<FS_PROGRAMAS>-ID_PROG = '002'.
<FS_PROGRAMAS>-NOM_PROG = 'POKEMON'.
ENDFORM.
" inicializar
*&----------------------------------------------------*
*&
Form
IMPRIMIR_REPORTE
*
*&----------------------------------------------------*
FORM IMPRIMIR_REPORTE TABLES T_TABLA.
LOOP AT T_TABLA
128
ASSIGNING <FS_PROGRAMAS>.
WRITE:/ <FS_PROGRAMAS>-ID_PROG.
WRITE: <FS_PROGRAMAS>-NOM_PROG.
ENDLOOP.
ENDFORM.
"IMPRIMIR_REPORTE
En esta caso, nuestro FORM recibe un parámetros TABLES, es
decir una tabla interna. Por lo tanto, podemos hacerle un LOOP,
asignarla a un Field-Symbol e imprimir sus valores.
Depuración de Programas
El termino “bug” en programación, se refiere a un error o a un
evento no planeado dentro de la ejecución de un programa.
Utilizando las herramientas que nos brinda NetWeaver podemos
hacer un DEBUG (Eliminar bichos) a cualquier programa para
analizar su funcionamiento interno.
Tenemos 3 formas de iniciar un DEBUG, utilizando la palabra
reservada BREAK-POINT en nuestro código fuente, utilizar un
BREAK-POINT lógico o simplemente escribir /H en la barra de
menú. Veamos más a fondo las 3 formas.
Pero antes de comenzar, es mejor que hagamos un pequeño ajuste en
las propiedades del NetWeaver, puesto que el nuevo Debugger
(Que no vamos a revisar en este libro), es bastante complejo y
consume muchos recursos del sistema. Para esto, seguimos la ruta
Utilities Æ Settings Æ ABAP Editor Æ Debugging Æ Classic
Debugger. El Debugger clásico basta y sobra para que podamos
revisar y corregir nuestros programas.
129
Como mencioné arriba, la primera forma de activar el Debugger es
utilizar la palabra clave BREAK-POINT.
REPORT ZDUMMY_PRIMER_PROGRAMA
NO STANDARD PAGE HEADING.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
130
END OF TY_PROGRAMAS.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
SELECT ID_PROG NOM_PROG
INTO TABLE T_PROGRAMAS
FROM ZPROGRAMAS.
BREAK-POINT.
LOOP AT T_PROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
WRITE:/ <FS_PROGRAMAS>-ID_PROG,
<FS_PROGRAMAS>-NOM_PROG.
ENDLOOP.
Luego de hacer el SELECT, colocamos un BREAK-POINT, con lo
cual hacemos que el programa se detenga.
131
Cuando ejecutamos el programa, entraremos al Debugger.
Tenemos disponible el código fuente para poder ejecutarlo línea por
línea, por bloques o continuar con la ejecución.
132
Pero lo más importante es la parte del final, donde dice Field names
(Nombre de campos), puesto que ahí podemos colocar una variable
o una tabla interna para ver su contenido. Solo necesitamos escribir
su nombre o hacer doble clic sobre su nombre en la pantalla de
código.
Una vez que tenemos el nombre escrito, basta con que hagamos
doble clic para poder ver su contenido.
Además, por si esto fuera poco, contamos con botones para
manipular el contenido de los registros.
Con Change (Cambiamos el valor del registro).
Con Insert (Agregamos una nueva línea).
133
Con Append (Copiamos un registro).
Con Delete (Eliminamos un registro).
En el caso de las variables, podemos tomar como ejemplo
<FS_PROGRAMA>-ID_PROG.
Para cambiar el valor debemos sobrescribirlo y presionar el botón
Change Field Content (Cambiar contenido de campo)
.
Para avanzar entre las líneas del código, contamos con los siguientes
botones.
Single Step (F5) Æ Ejecuta una línea de código.
Execute (F6) Æ Ejecuta un bloque de código.
Return (F7) Æ Sale de un bloque de código.
Run (to Cursor) (F8) Æ Continua con la ejecución del programa
hasta encontrar otro Break-Point.
134
Si no queremos que nuestro programa se pare cada vez que lo
ejecutemos. Debemos reemplazar el Break-Point por un BreakPoint lógico.
Nos posicionamos en la línea donde queremos que pare el programa.
En el menú seleccionamos Utilities Æ Breakpoints Æ Set/Delete.
O si no presionamos Ctrl. + Shift + F12 o presionamos el botón
. Esto funciona de la misma manera que el BREAK-POINT con
la diferencia de que si reiniciamos el programa, el BREAK-POINT
desaparece. Este tipo de BREAK-POINT es muy útil cuando
tenemos que hacer un DEBUG en un programa que no podemos
modificar.
135
Felizmente, tenemos una forma de guardar los break-points lógicos
para un programa. Este es un truco que aprendí hace un par de años
en el SDN ( http://sdn.sap.com ).
Cuando estamos en el Debugger, nos vamos al menú, y
seleccionamos Debugging Æ Sessions.
136
En esta pantalla podemos guardar un grupo de Break-Points
lógicos, los cuales se almacenan por defecto durante un mes.
Podemos cargarlos la próxima vez que ejecutemos el programa,
podemos extender su estadía por una semana más o podemos
eliminarlos.
Finalmente, la última manera para hace DEBUG a un programa, es
escribir /H en la barra de menú y presionar Enter.
Luego de eso, al ejecutar, entraremos al Debugger, aunque al del
programa que ejecuta los programas...Con un poco de paciencia
llegaremos a nuestro propio programa.
137
Programas de ejemplo
Decimal a Binario
REPORT ZDUMMY_PRIMER_PROGRAMA
NO STANDARD PAGE HEADING.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: SUMA_TEXT(50) TYPE C,
SUMA TYPE I,
EXPONENTE TYPE I,
FLAG TYPE C.
*=====================================================*
* SELECTION-SCREEN
*
*=====================================================*
SELECTION-SCREEN BEGIN OF BLOCK DEC_TO_BIN WITH FRAME.
PARAMETERS:
P_NUMERO TYPE I.
SELECTION-SCREEN END OF BLOCK DEC_TO_BIN.
138
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM INICIALIZAR.
PERFORM CALCULAR_BINARIO USING P_NUMERO
CHANGING SUMA_TEXT.
IF FLAG EQ SPACE.
PERFORM IMPRIMIR.
ENDIF.
*&----------------------------------------------------*
*&
Form
INICIALIZAR
*
*&----------------------------------------------------*
FORM INICIALIZAR.
SUMA = 0.
EXPONENTE = 1.
ENDFORM.
" INICIALIZAR
*&----------------------------------------------------*
*&
Form
CALCULAR_BINARIO
*
*&----------------------------------------------------*
FORM CALCULAR_BINARIO USING P_NUM
CHANGING P_SUM.
DATA: DIGITO TYPE I,
NUMERO TYPE I.
NUMERO = P_NUM.
139
CATCH SYSTEM-EXCEPTIONS ARITHMETIC_ERRORS = 5.
WHILE NUMERO GT 0.
DIGITO = NUMERO MOD 2.
NUMERO = NUMERO DIV 2.
SUMA = SUMA + DIGITO * EXPONENTE.
EXPONENTE = EXPONENTE * 10.
ENDWHILE.
P_SUM = SUMA.
CONDENSE P_SUM NO-GAPS.
ENDCATCH.
IF SY-SUBRC = 5.
WRITE / 'Error de cálculo'.
FLAG = 'X'.
ENDIF.
ENDFORM.
" CALCULAR_BINARIO
*&----------------------------------------------------*
*&
Form
IMPRIMIR
*
*&----------------------------------------------------*
FORM IMPRIMIR.
WRITE:/ 'El número binario de',25 P_NUMERO.
WRITE:/ 'es:',5 SUMA_TEXT.
ENDFORM.
" IMPRIMIR
Este programa convierte un número decimal a binario, e incluye un
elemento muy interesante, el CATCH.
140
CATCH SYSTEM-EXCEPTIONS ARITHMETIC_ERRORS = 5.
WHILE NUMERO GT 0.
DIGITO = NUMERO MOD 2.
NUMERO = NUMERO DIV 2.
SUMA = SUMA + DIGITO * EXPONENTE.
EXPONENTE = EXPONENTE * 10.
ENDWHILE.
P_SUM = SUMA.
CONDENSE P_SUM NO-GAPS.
ENDCATCH.
Como podemos ver, el código que convierte el decimal a un binario
está encerrado entre las sentencias CATCH-ENDCATCH. En este
caso, lo que queremos es que el ABAP capture cualquier error de
tipo aritmético, por lo tanto utilizamos ARITHMETIC_ERRORS.
Si hay algún error el valor 5 se pasa a la variable del sistema SYSUBRC, por lo tanto:
IF SY-SUBRC = 5.
WRITE / 'Error de cálculo'.
FLAG = 'X'.
ENDIF.
Si SY-SUBRC es igual a 5, mostramos un mensaje de error. Y
además utilizamos una variable FLAG a la cual se le pasa el valor
X. Antes de imprimir, comprobamos el valor de esta variable para
no imprimir si es que ha habido errores.
141
IF FLAG EQ SPACE.
PERFORM IMPRIMIR.
ENDIF.
Colores en ABAP
Este programa muestra las posibles combinaciones de colores que
pueden generarse en ABAP.
REPORT ZDUMMY_PRIMER_PROGRAMA
NO STANDARD PAGE HEADING.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: I TYPE I,
142
COL(15) TYPE C.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM IMPRIMIR.
*&----------------------------------------------------*
*&
Form
IMPRIMIR
*
*&----------------------------------------------------*
FORM IMPRIMIR.
WRITE:/9 'INTESIFIED ON', 27 'INTENSIFIED OFF',
48 'INVERSE'.
SKIP 2.
WHILE I < 8.
CASE I.
WHEN 0. COL = 'COL_BACKGROUND '.
WHEN 1. COL = 'COL_HEADING '.
WHEN 2. COL = 'COL_NORMAL '.
WHEN 3. COL = 'COL_TOTAL '.
WHEN 4. COL = 'COL_KEY '.
WHEN 5. COL = 'COL_POSITIVE '.
WHEN 6. COL = 'COL_NEGATIVE '.
WHEN 7. COL = 'COL_GROUP '.
ENDCASE.
FORMAT INTENSIFIED COLOR = I.
WRITE: /(4) I, AT 7 SY-VLINE,
COL, SY-VLINE,
143
COL INTENSIFIED OFF, SY-VLINE,
COL INVERSE.
I = I + 1.
ENDWHILE.
ENDFORM.
" IMPRIMIR
Lenguajes y Programas
REPORT ZDUMMY_PRIMER_PROGRAMA
NO STANDARD PAGE HEADING.
*=====================================================*
* DECLARACION DE INCLUDES
*
*=====================================================*
INCLUDE <ICON>.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
144
TYPES: BEGIN OF TY_PROGRAMAS,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
ENTORNO TYPE ZLENGUAJES_PROG-ENTORNO,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
END OF TY_PROGRAMAS.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE
OF T_PROGRAMAS.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: ICONO TYPE STRING.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM SELECCIONAR_DATOS.
PERFORM IMPRIMIR_DATOS.
*&----------------------------------------------------*
*&
Form
SELECCIONAR_DATOS
*
*&----------------------------------------------------*
145
FORM SELECCIONAR_DATOS.
SELECT NOMBRE ENTORNO NOM_PROG
INTO TABLE T_PROGRAMAS
FROM ( ZLENGUAJES_PROG INNER JOIN ZPROGRAMAS
ON ZLENGUAJES_PROG~ID = ZPROGRAMAS~ID ).
ENDFORM.
" SELECCIONAR_DATOS
*&----------------------------------------------------*
*&
Form
IMPRIMIR_DATOS
*
*&----------------------------------------------------*
FORM IMPRIMIR_DATOS.
FORMAT COLOR 5.
WRITE:/1 'Lenguaje',15 'Entorno', 25 'Programa',
40 'Icono'.
FORMAT COLOR OFF.
SKIP 1.
LOOP AT T_PROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
WRITE:/1 <FS_PROGRAMAS>-NOMBRE,
15 <FS_PROGRAMAS>-ENTORNO,
25 <FS_PROGRAMAS>-NOM_PROG.
CASE <FS_PROGRAMAS>-ENTORNO.
WHEN 'WEB'.
ICONO = ICON_WD_WEB_PROJECT.
WHEN 'SCRIPT'.
ICONO = ICON_HISTORY.
WHEN 'DESKTOP'.
ICONO = ICON_FOLDER.
146
ENDCASE.
WRITE: 40 ICONO.
ENDLOOP.
ENDFORM.
" IMPRIMIR_DATOS
Este programa es muy parecido al que hicimos alguna páginas antes,
con la particularidad, de estamos mostrando un icono que representa
a cada tipo de lenguaje. Para esto, debemos incluir un subprograma
llamado <ICON> con la palabra reservada INCLUDE. Gracias a
este include, podemos llamar a cualquier icono de la tabla ICON.
LOOP AT T_PROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
WRITE:/1 <FS_PROGRAMAS>-NOMBRE,
15 <FS_PROGRAMAS>-ENTORNO,
25 <FS_PROGRAMAS>-NOM_PROG.
CASE <FS_PROGRAMAS>-ENTORNO.
WHEN 'WEB'.
ICONO = ICON_WD_WEB_PROJECT.
WHEN 'SCRIPT'.
ICONO = ICON_HISTORY.
WHEN 'DESKTOP'.
ICONO = ICON_FOLDER.
ENDCASE.
WRITE: 40 ICONO.
ENDLOOP.
Declaramos una variable de tipo STRING llamada ICONO, a la
cual vamos a asignarle las constantes de iconos que obtenemos de la
tabla ICON (Campo Name). Simplemente utilizando un CASE-
147
ENDCASE sabemos que valores asignar a la variable ICON para
luego simplemente imprimirlo.
Manejo de Cadenas de Texto
En este programa vamos a hacer algunas funciones que no son parte
de la sintaxis de ABAP (Aunque pueden utilizarse con Objetos
como veremos más adelante).
REPORT ZDUMMY_PRIMER_PROGRAMA
NO STANDARD PAGE HEADING.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: X_LINES TYPE STRING.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
148
DATA: T_TABLE TYPE STANDARD TABLE OF X_LINES.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_TABLE> LIKE LINE OF T_TABLE.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: TEXTO(30) TYPE C,
AUX_TEXT(30) TYPE C,
V_LEN TYPE I,
V_LONG TYPE I.
*=====================================================*
* SELECTION-SCREEN
*
*=====================================================*
SELECTION-SCREEN BEGIN OF BLOCK DEC_TO_BIN WITH FRAME.
PARAMETERS:
P_TEXTO(20) TYPE C.
SELECTION-SCREEN END OF BLOCK DEC_TO_BIN.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM INICIALIZAR.
PERFORM FILL_RIGHT_CHARACTERS USING '*'
CHANGING TEXTO.
PERFORM IMPRIMIR USING TEXTO.
149
PERFORM INICIALIZAR.
PERFORM CAPITALIZE_LETTERS CHANGING TEXTO.
PERFORM IMPRIMIR USING TEXTO.
PERFORM INICIALIZAR.
PERFORM SPLIT_LONG_TEXT TABLES T_TABLE
USING TEXTO.
PERFORM IMPRIMIR_TABLA TABLES T_TABLE.
PERFORM INICIALIZAR.
PERFORM REVERSE_STRING USING TEXTO.
PERFORM IMPRIMIR USING TEXTO.
*&----------------------------------------------------*
*&
Form
INICIALIZAR
*
*&----------------------------------------------------*
FORM INICIALIZAR.
TEXTO = P_TEXTO.
SKIP 1.
ENDFORM.
"INICIALIZAR
*&----------------------------------------------------*
*&
Form
IMPRIMIR_TABLA
*
*&----------------------------------------------------*
FORM IMPRIMIR_TABLA TABLES T_TAB.
LOOP AT T_TAB
ASSIGNING <FS_TABLE>.
WRITE:/ <FS_TABLE>.
150
ENDLOOP.
ENDFORM.
"INICIALIZAR
*&----------------------------------------------------*
*&
Form
imprimir
*
*&----------------------------------------------------*
FORM IMPRIMIR USING L_TEXTO.
WRITE:/ TEXTO.
ENDFORM.
"imprimir
*&----------------------------------------------------*
*&
Form
FILL_RIGHT_CHARACTERS
*
*&----------------------------------------------------*
*
Llena una cadena con caracteres a la derecha. *
*-----------------------------------------------------*
FORM FILL_RIGHT_CHARACTERS USING L_CHAR
CHANGING L_TEXTO.
V_LEN = STRLEN( L_TEXTO ).
DESCRIBE FIELD L_TEXTO LENGTH V_LONG
IN CHARACTER MODE.
V_LEN = V_LONG - V_LEN.
DO V_LEN TIMES.
CONCATENATE L_TEXTO L_CHAR INTO L_TEXTO.
ENDDO.
ENDFORM.
"FILL_RIGHT_CHARACTERS
151
*&----------------------------------------------------*
*&
Form
CAPITALIZE_LETTERS
*
*&----------------------------------------------------*
*
Capitaliza un texto dado.
*
*-----------------------------------------------------*
FORM CAPITALIZE_LETTERS CHANGING L_TEXTO.
TRANSLATE L_TEXTO TO LOWER CASE.
SPLIT L_TEXTO AT SPACE INTO TABLE T_TABLE.
CLEAR L_TEXTO.
LOOP AT T_TABLE
ASSIGNING <FS_TABLE>.
V_LONG = STRLEN( <FS_TABLE> ).
V_LONG = V_LONG - 1.
AUX_TEXT = <FS_TABLE>+0(1).
TRANSLATE AUX_TEXT TO UPPER CASE.
CONCATENATE AUX_TEXT <FS_TABLE>+1(V_LONG)
INTO AUX_TEXT.
CONCATENATE L_TEXTO AUX_TEXT INTO L_TEXTO
SEPARATED BY SPACE.
SHIFT L_TEXTO LEFT DELETING LEADING SPACE.
ENDLOOP.
ENDFORM.
"CAPITALIZE_LETTERS
152
*&----------------------------------------------------*
*&
Form
SPLIT_LONG_TEXT
*
*&----------------------------------------------------*
*
Corta un texto largo en varios registros de
*
*
una tabla
*
*-----------------------------------------------------*
FORM SPLIT_LONG_TEXT TABLES T_TAB
USING L_TEXTO.
CALL FUNCTION 'RSDG_WORD_WRAP'
EXPORTING
TEXTLINE
= L_TEXTO
DELIMITER
= SPACE
TABLES
OUT_LINES
= T_TAB
EXCEPTIONS
OUTPUTLEN_TOO_LARGE = 1
OTHERS
= 2.
DESCRIBE TABLE T_TAB LINES V_LEN.
DELETE T_TAB INDEX V_LEN.
ENDFORM.
"SPLIT_LONG_TEXT
153
*&----------------------------------------------------*
*&
Form
REVERSE_STRING
*
*&----------------------------------------------------*
*
Invierte una cadena
*
*-----------------------------------------------------*
FORM REVERSE_STRING CHANGING L_TEXTO.
CALL FUNCTION 'STRING_REVERSE'
EXPORTING
STRING
= L_TEXTO
LANG
= SY-LANGU
IMPORTING
RSTRING
= L_TEXTO
EXCEPTIONS
TOO_SMALL = 1
OTHERS
= 2.
ENDFORM.
"REVERSE_STRING
En este programa tenemos varios FORMS y cada uno realiza una
tarea en particular. Veamos cada uno de ellos.
FORM FILL_RIGHT_CHARACTERS USING L_CHAR
CHANGING L_TEXTO.
V_LEN = STRLEN( L_TEXTO ).
DESCRIBE FIELD L_TEXTO LENGTH V_LONG
IN CHARACTER MODE.
154
V_LEN = V_LONG - V_LEN.
DO V_LEN TIMES.
CONCATENATE L_TEXTO L_CHAR INTO L_TEXTO.
ENDDO.
ENDFORM.
"FILL_RIGHT_CHARACTERS
Utilizamos STRLEN para determinar la longitud total del texto
(Incluyendo espacios en blanco). Y luego utilizamos DESCRIBE
FIELD LENGTH IN CHARACTER MODE para determinar la
longitud pero sin contar los espacios en blanco.
En la variable V_LEN, restamos la longitud de V_LONG menos
V_LEN, es decir, la longitud con espacios menos la longitud sin
espacios.
Utizamos un DO para ejecutar un bucle un número V_LEN de veces
y concatenamos el texto con el carácter de relleno, en este caso el
‘*’.
FORM CAPITALIZE_LETTERS CHANGING L_TEXTO.
TRANSLATE L_TEXTO TO LOWER CASE.
SPLIT L_TEXTO AT SPACE INTO TABLE T_TABLE.
CLEAR L_TEXTO.
LOOP AT T_TABLE
ASSIGNING <FS_TABLE>.
V_LONG = STRLEN( <FS_TABLE> ).
155
V_LONG = V_LONG - 1.
AUX_TEXT = <FS_TABLE>+0(1).
TRANSLATE AUX_TEXT TO UPPER CASE.
CONCATENATE AUX_TEXT <FS_TABLE>+1(V_LONG)
INTO AUX_TEXT.
CONCATENATE L_TEXTO AUX_TEXT INTO L_TEXTO
SEPARATED BY SPACE.
SHIFT L_TEXTO LEFT DELETING LEADING SPACE.
ENDLOOP.
ENDFORM.
"CAPITALIZE_LETTERS
Primero, convertimos la cadena a Minúsculas utilizando el
TRANSLATE TO LOWER CASE.
Luego con un SPLIT AT SPACE INTO TABLE, enviamos las
subcadenas a una tabla interna, para poder modificarlos uno por uno.
Con STRLEN determinamos la longitud del Field-Symbol y le
restamos uno. Leemos el primer carácter utilizando +0(1) que
significa, leer un caracter empezando desde la primera posición.
Utilizando TRANSLATE TO UPPER CASE, convertimos a
Mayúsculas el caracter que hemos leído.
Concatenamos el primer caracter mas los demás caracteres de la
cadena.
Finalmente concatenamos todos las subcadenas en una cadena más
grande con los letras ya capitalizadas.
FORM SPLIT_LONG_TEXT TABLES T_TAB
USING L_TEXTO.
156
CALL FUNCTION 'RSDG_WORD_WRAP'
EXPORTING
TEXTLINE
= L_TEXTO
DELIMITER
= SPACE
TABLES
OUT_LINES
= T_TAB
EXCEPTIONS
OUTPUTLEN_TOO_LARGE = 1
OTHERS
= 2.
DESCRIBE TABLE T_TAB LINES V_LEN.
DELETE T_TAB INDEX V_LEN.
ENDFORM.
"SPLIT_LONG_TEXT
Utilizamos la función RSDG_WORD_WRAP para partir una
cadena en subcadenas y enviarlas a una tabla. Podríamos haber
utilizado un SPLIT INTO TABLE, pero quería que vieran como se
utilizan los módulos de funciones. Al final, utilizando un
DESCRIBE TABLE LINES para poder determinar cuantas líneas
tiene nuestra tabla interna. El módulo de funciones que utilizamos
tiende a tomar los espacios en blanco y agregarlos como longitud
total de la cadena, es por eso que sabiendo cuantas líneas tienen la
tabla interna, podemos eliminar la última sin tener problemas.
FORM REVERSE_STRING CHANGING L_TEXTO.
CALL FUNCTION 'STRING_REVERSE'
EXPORTING
STRING
= L_TEXTO
157
LANG
= SY-LANGU
IMPORTING
RSTRING
= L_TEXTO
EXCEPTIONS
TOO_SMALL = 1
OTHERS
= 2.
ENDFORM.
"REVERSE_STRING
Utilizamos el módulo de funciones STRING_REVERSE para
invertir el orden de la cadena.
158
SapScript
Introducción
Uno de los puntos fuertes que tiene el ABAP, es la posibilidad de
crear formularios, que pueden ser utilizados por ejemplo, para
Certificados de Trabajo, Notificaciones de Empresa, Facturas, etc.
Su creación no es complicada y permite hacer varias cosas
interesantes. Como por ejemplo, incluir logos o imprimir códigos de
barras, aunque lo último depende más que nada de la impresora o de
software adicional para poder mostrarlos correctamente.
Creando un formulario
Para poder crear formularios, debemos ingresar a la transacción
SE71 (Form Painter).
159
En el campo formulario, ingresamos el nombre de nuestro
formulario y en el campo idioma, el idioma en el cual queremos
crearlo (Bastante simple no?). En realidad, el idioma se utiliza para
poder crear de una manera más sencilla las traducciones, puesto que
cuando uno ingresa a NetWeaver, puede hacerlo con muchos
idiomas predefinidos (Bueno, no esta versión, puesto que solamente
permite Alemán e Inglés...La versión completa del NetWeaver
incluye entre muchos otros, Español, Francés, Italiano...).
Presionamos el botón Create (Crear)
para poder
crear nuestro formulario.
160
En esta ventana, deberemos ingresar un texto que describa
brevemente al formulario. Luego de esto, deberemos ingresar a la
sección de Basic Settings (Parametrizaciones Básicas) con el
siguiente botón
.
161
En esta ventana, se nos exige especificar una Página Inicial y un
Párrafo por defecto. Como este formulario es nuevo, ninguno de
estos existe, por lo cual deberemos crearlos.
1.- Crear una página inicial
Ingresamos a la opción Pages (Páginas), presionando el siguiente
botón
.
Como vemos, no existe ninguna página. Para poder crearla,
necesitamos crear un nuevo elemento. Esto podemos hacerlo de 3
162
formas. Con un clic derecho, con el menú de la transacción o
presionando SHIFT + F6.
Menú de clic derecho
Menú de transacción
Cuando creamos un nuevo elemento, el sistema nos mostrará la
siguiente ventana:
163
En Page (Página) ingresaremos el nombre de la página que
queremos crear y en Description (Description), una descripción de
la página. Por convención, simpre la página principal será llamada
MAIN.
Luego de haber creado el elemento, podremos verlo como se
muestra a continuación.
2.- Crear una ventana en una página
Después de haber creado nuestra página, deberemos asignarle una
ventana, que es el espacio en el cual vamos a trabajar para poder
crear nuestros formularios. Una página puede tener muchas
ventanas. Y un formulario puede tener muchas páginas.
Para poder crear una ventana, deberemos hacer clic en el botón Page
Windows (Ventanas de Página)
.
164
Y seguir un procedimiento similar al de la creación de la página.
Es decir, deberemos de crear un nuevo elemento.
Cuando creamos el nuevo elemento, se nos muestra la siguiente
ventana.
En esta ventana, se nos muestra una ventana por defecto, creada
cuando creamos la página. Si queremos crear ventanas adicionales,
165
y
deberemos hacer clic en el botón Windows (Ventanas)
crear un nuevo elemento.
Para poder elegir la ventana, simplemente deberemos de hacer un
doble clic sobre ella.
Por cada Ventana Página que creemos, deberemos llenar sus
atributos en la siguiente ventana (Que está al final de la pantalla).
Por ejemplo:
Para poder definir las propiedades de las ventanas, tenemos
disponibles los siguientes sistemas de medición.
166
3.- Crear párrafo por defecto
Para crear un nuevo párrafo, deberemos hacer clic en el botón
.
Paragraph Formats (Formatos de Párrafo)
Se nos mostrará la siguiente ventana:
Aquí deberemos asignarle un nombre al nuevo párrafo (Por
convención utilizamos ST), así como el tipo de letra (Con el botón
Font
), los tabuladores (Con el botón Tabs
),
su
asignación
(Con
el
botón
Outline
), etc.
167
Una vez que hemos creado la página y el párrafo por defecto,
deberemos regresar a la configuración de cabecera para poder
finalizar con las parametrizaciones básicas. Para esto, simplemente
hacemos un clic en el botón Header (Cabecera)
F5.
Y
luego
en
el
botón
o presionamos
parametrizaciones
básicas
. Es importante indicar
cual es la página principal.
168
Una vez que hemos terminado, debemos grabar el formulario y
activarlo mediante el menú.
Creando un programa de impresión
Para que un SAPScript pueda mostrar información, esta debe
provenir de un programa. Este programa llama al formulario y le
pasa toda la información necesaria. Es por eso, que vamos a crear un
programa bastante sencillo para utilizar como ejemplo.
REPORT ZDUMMY_PRIMER_PROGRAMA
NO STANDARD PAGE HEADING.
*=====================================================*
* DECLARACION DE TABLAS
*
*=====================================================*
TABLES: ZPROGRAMAS,ITCPO,TOA_DARA,ITCPP.
169
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_ZPROGRAMAS TYPE STANDARD TABLE OF
ZPROGRAMAS.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE OF
T_ZPROGRAMAS.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: V_FORM(14) TYPE C,
V_SCRIPT.
*=====================================================*
* SELECTION-SCREEN
*
*=====================================================*
SELECTION-SCREEN BEGIN OF BLOCK APP WITH FRAME.
SELECT-OPTIONS:
SID_PROG FOR ZPROGRAMAS-ID_PROG.
SELECTION-SCREEN END OF BLOCK APP.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM INICIALIZAR.
170
*=====================================================*
* END-OF-SELECTION
*
*=====================================================*
END-OF-SELECTION.
PERFORM ABRIR_SAPSCRIPT.
*&----------------------------------------------------*
*&
Form
INICIALIZAR
*
*&----------------------------------------------------*
FORM INICIALIZAR.
V_FORM = 'ZDUMMY_FORM'.
SELECT *
INTO TABLE T_ZPROGRAMAS
FROM ZPROGRAMAS
WHERE ID_PROG IN SID_PROG.
ENDFORM.
"INICIALIZAR
*&----------------------------------------------------*
*&
Form
ABRIR_SAPSCRIPT
*
*&----------------------------------------------------*
FORM ABRIR_SAPSCRIPT.
ITCPO-TDIMMED = '*'.
ITCPO-TDDELETE = '*'.
ITCPO-TDLIFETIME = '7'.
ITCPO-TDPREVIEW = 'X'.
IF V_SCRIPT EQ SPACE.
CALL FUNCTION 'OPEN_FORM'
EXPORTING
171
FORM
= V_FORM
LANGUAGE
= 'S'
OPTIONS
= ITCPO
ARCHIVE_INDEX = TOA_DARA
DEVICE
= 'PRINTER'
DIALOG
= 'X'
EXCEPTIONS
CANCELED
= 01.
IF SY-SUBRC NE 0.
EXIT.
ENDIF.
V_SCRIPT = 'X'.
ENDIF.
CALL FUNCTION 'START_FORM'
EXPORTING
FORM
= V_FORM
LANGUAGE = 'S'.
LOOP AT T_ZPROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
CALL FUNCTION 'WRITE_FORM'
EXPORTING
ELEMENT = 'MAIN'
WINDOW
= 'MAIN'
EXCEPTIONS
OTHERS
= 01.
ENDLOOP.
CALL FUNCTION 'END_FORM'
IMPORTING
RESULT = ITCPP.
172
IF V_SCRIPT NE SPACE.
CALL FUNCTION 'CLOSE_FORM'.
ENDIF.
ENDFORM.
"ABRIR_SAPSCRIPT
Analicemos un poco el programa.
TABLES: ZPROGRAMAS,ITCPO,TOA_DARA,ITCPP.
Las tablas ITCPO, TOA_DARA y ITCPP son necesarias para poder
ejecutar el SAPScript.
DATA: V_FORM(14) TYPE C,
V_SCRIPT.
La variable V_FORM contendrá el nombre del formulario, mientras
que la variable V_SCRIPT nos sirve para determinar si el formulario
está activo o no.
ITCPO-TDIMMED = '*'.
ITCPO-TDDELETE = '*'.
ITCPO-TDLIFETIME = '7'.
ITCPO-TDPREVIEW = 'X'.
•
Parámetro impresión, salida inmediata.
•
Parámetro impresión, borrar tras salida.
•
Parámetro impresión, tiempo de permanencia en SPOOL.
173
•
Visualización de impresión.
IF V_SCRIPT EQ SPACE.
CALL FUNCTION 'OPEN_FORM'
EXPORTING
FORM
= V_FORM
LANGUAGE
= 'S'
OPTIONS
= ITCPO
ARCHIVE_INDEX = TOA_DARA
DEVICE
= 'PRINTER'
DIALOG
= 'X'
EXCEPTIONS
CANCELED
= 01.
IF SY-SUBRC NE 0.
EXIT.
ENDIF.
V_SCRIPT = 'X'.
ENDIF.
Si la variable V_SCRIPT está vacía, entonces debemos abrir el
formulario, para
esto
utilizamos el módulo de funciones
OPEN_FORM.
CALL FUNCTION 'START_FORM'
EXPORTING
FORM
= V_FORM
LANGUAGE = 'S'.
Iniciamos la ejecución del formulario.
174
LOOP AT T_ZPROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
CALL FUNCTION 'WRITE_FORM'
EXPORTING
ELEMENT = 'ITEM'
WINDOW
= 'MAIN'
EXCEPTIONS
OTHERS
= 01.
ENDLOOP.
Hacemos un LOOP a la tabla interna T_ZPROGRAMAS y
asignamos los valores al Field-Symbol <FS_PROGRAMAS>. Por
cada vuelta del LOOP, llamamos al módulo de funciones
WRITE_FORM que lo que hace es llamar a la ventana de nuestro
formulario.
CALL FUNCTION 'END_FORM'
IMPORTING
RESULT = ITCPP.
IF V_SCRIPT NE SPACE.
CALL FUNCTION 'CLOSE_FORM'.
ENDIF.
Con el módulo de funciones END_FORM indicamos que hemos
terminado de utilizar la ventana. Si la variable V_SCRIPT no está
vacía, cerramos el formulario con el módulo de funciones
CLOSE_FORM.
175
Una vez creado el programa, podemos regresar a nuestro formulario
para poder comenzar a llenar los datos que queremos mostrar cuando
sea ejecutado.
Diseñando el formulario
Además de la forma que ya vimos para crear nuestro formulario,
contamos con un editor gráfico. Para poder acceder, deberemos
ingresar a la transacción SE71 (Form Painter).
Debemos ingresar al menú Settings (Opciones) y presionar Form
Painter.
Simplemente, debemos marcar el check Graphical Form Painter
(Form Painter gráfico).
176
Ingresamos a nuestro formulario y presionamos el botón Layout
(Diposición)
.
En este editor gráfico, podemos especificar el tamaño y la
disposición de las distintas ventanas que creemos en nuestro
formulario.
Para volver al modo de edición por defecto, simplemente debemos
desmarcar el checkbox de Form Painter gráfico.
Para poder asignar el código a la ventana, deberemos presionar el
botón Text (Texto)
.
177
En esta ventana, podremos escribir el código para llamar a los
campos que definimos en nuestro programa (Es decir, a los campos
de la tabla interna) y mostrarlos al momento de imprimir el
formulario.
Como podemos ver, podemos llamar al tipo de párrafo por defecto
de dos maneras, dejando el menú por defecto, o llamandolo
explicítamente como ST Párrafo por defecto.
En la parte de código, veremos que hemos escrito texto y el nombre
de los campos del field-symbol dentro de ampersands (&). Si los
178
ampersand, no podríamos llamar a las variables de nuestro programa
dentro del formulario.
Supongamos que queremos que los textos se muestren en negrita y
los valores como un texto normal.
Primero que nada, debemos grabar nuestro código y retroceder con
el botón BACK (Retroceder)
o presionar F3.
Luego hacemos clic en el botón Paragraph Formats (Formatos de
Párrafo)
.
Una vez ahí, hacemos clic en Character Formats (Formatos de
Caracter)
.
Ahora, de vuelta en nuestra ventana, haremos lo siguiente.
Seleccionamos el texto que queremos en negrita, y seleccionamos el
formato de caracter que creamos.
179
Para terminar, vamos a crear una caja para que se muestre el nombre
del programa. Para esto, debemos cambiar de editor, puesto que
debemos crear la caja utilizando código.
Y de paso eliminamos cualquier código adicional que haya
agreagado el SAPScript. (Yo recomiendo siempre utilizar este
editor).
180
Creamos una caja que empieza en la posición 0, que tenga 50
milimetros de ancho y 5 de alto, y que el borde sea de 10 TW.
Se habrán dado cuenta, de que además agregamos la línea
/E
ITEM
Puesto que ITEM va a ser nuestro ELEMENTO dentro del
ventana. Una ventana se puede llamar una sola vez, pero un
elemento, varias veces.
Grabamos, activamos y ejecutamos nuestro programa.
Al momento de ejecutar el programa, se nos muestra una ventana de
impresión, puesto que al tratarse de un formulario, debemos
imprimirlo para poder visualizarlo. Elegimos LP01 como impresora
por defecto y presionamos Print Preview (Vista previa de
181
impresión). Puesto que no queremos imprimirlo, sino simplemente
comprobar que está bien hecho el formulario.
Los datos son correctos, pero los textos están muy juntos los unos de
los otros, además la caja de texto simpre está en el mismo lugar,
veamos como resolvemos esto.
En el editor, agregamos YPOS &POS& MM, es decir indicamos
cual va a ser la posición Y de la caja. En este caso, va a ser un valor
variales indicado por &POS&.
182
Además, agregamos una línea en blanco.
Para que esto funcione correctamente, debemo también agregar
algunas cosas en el programa.
DATA: V_FORM(14) TYPE C,
V_SCRIPT,
POS TYPE STRING.
Agregamos la variable POS de tipo STRING.
V_FORM = 'ZDUMMY_FORM'.
POS = 0.
Inicializamos la variable POS en 0.
LOOP AT T_ZPROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
CALL FUNCTION 'WRITE_FORM'
EXPORTING
ELEMENT = 'ITEM'
WINDOW
= 'MAIN'
EXCEPTIONS
OTHERS
= 01.
POS = POS + 13.
ENDLOOP.
En cada iteración del LOOP, luego de haber impreso el contenido
del elemento, aumentamos el valor de la variable POS en 13.
Ahora, ya podemos imprimir nuevamente el formulario.
183
Para terminar, podríamos crear una tabulación, con lo cual el texto
ID Programa quedaría más centrado en la caja.
.
Nos vamos a Paragraph Formats
Seleccionamos nuestro párrafo por defecto y presionamos el botón
Tabs (Tabuladores)
.
184
Creamos una tabulación de 5 MM con alineación a la izquierda.
Regresamos al Layout y cambiamos el formato del editor. Para
agregar un tabulador, escribimos ,, antes del texto.
Grabamos, activamos y ejecutamos el programa.
185
Debugger en SAPScript
Hay que veces que necesitamos hacer un Debug a un SAPScript. Esto es
bastante sencillo, aunque no mucha gente lo sepa, puesto que no es una
opción que esté muy a la vista, y claro...No es de las mejores herramientas
de SAP.
Para activar el Debugger, ingresamos al transacción SE71 (Form Painter).
Y elegir Utilities Æ Activate Debugger (Utilidades Æ Activar Debugger).
Ahora, al ejecutar el programa, veremos la siguiente ventana.
186
Simplemente presionamos OK
para poder continuar con el
Debugger.
Por lo general se utiliza el botón Single Step (Paso sencillo), puesto
que nos permite recorrer el formulario línea por línea (En el caso de
los textos caracter por caracter).
Con esto, podemos ver cual es el contenido de las variables.
187
SmartForms
Introducción
Los SmartForms, son la nueva generación de formularios (Ok...No
tan nuevas si consideramos a los Adobe Forms, que no se incluyen
en este libro) que reemplazan a los para algunos obsoletos
formularios SAPScript.
En realidad es cuestión de gustos...Yo siempre he sido un fanático
acerrimo del SAPScript.
Creando un estilo
Para
crear
un
estilo
debemos
ingresar
a
la
transacción
SMARTFORM y seleccionar el radio button Style (Estilo).
188
Lo nombramos ZTEST_ESTILO y presionamos el botón Create
(Crear)
.
En esta ventana, vamos a crear nuestro párrafo por defecto, tal como
lo hicimos en el SAPScript.
Debemos crear un nuevo nodo en la opción Paragraph Format
(Formato de Párrafo).
189
Llamemos ST a este nuevo nodo.
Y llenamos su descripción.
Tipo de Letra, tamaño e inclusive color (Claro...para impresoras a
colores, que seamos realistas, es lo que menos abunda en las
empresas).
190
Cuando hemos terminado, hacemos doble clic en Header Data
(Datos de Cabecera).
Y asignamos nuestro párrafo por defecto.
191
Cuando terminamos, grabamos, activamos y salimos a la pantalla
inicial.
Creando un Formulario
Creamos
nuestro
primer
SmartForm
llamado
ZPRIMER_SMARTFORM.
En la pestaña Output Options (Opciones de Salida), debemos
asignar el estilo que creamos.
192
Al igual que en SAPScript, necesitamos una página y una ventana.
Como vemos %PAGE1 está creada por defecto, así que podemos
dejarla así, o asignarle un nuevo nombre. Lo mismo ocurre con la
ventana MAIN.
Para poder escribir en esta ventana, debemos crear un texto de la
siguiente manera.
Clic derecho en MAIN, Create Æ Text (Crear Æ Texto).
193
Cuando creamos el texto, veremos una pantalla como esta.
Llenamos el texto tal como hicimos con el SAPScript.
194
Para poder utilizar las variables dentro del SmartForms, debemos
definir algunas variables globales, para esto nos vamos a Form
Interface (Interface de Formulario).
En la pestaña Tables (Tablas), asignamos una tabla interna.
Ahora nos vamos a Global Definitions (Definiciones Globales) y
luego a la pestaña Global Data (Datos Globales). Aquí creamos una
estructura intermedia que recoge los datos del SmartForms y los
almacena para poder mostrarlos en el formulario.
195
Debemos crear un LOOP para poder asignar los valores de la tabla
de parámetro a la tabla intermedia.
Una vez creado, debemos mover nuestro texto, dentro del LOOP.
196
En el LOOP debemos asignar los datos de la tabla hacia la tabla
intermedia.
Creando un programa de impresión
Ahora, debemos crear el programa que va a llamar a nuestro
SmartForms.
REPORT ZDUMMY_PRIMER_PROGRAMA
NO STANDARD PAGE HEADING.
*=====================================================*
* DECLARACION DE TABLAS
*
*=====================================================*
TABLES: ZPROGRAMAS.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_ZPROGRAMAS TYPE STANDARD TABLE OF
ZPROGRAMAS.
197
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: MODULO_FUNCION TYPE RS38L_FNAM.
*=====================================================*
* SELECTION-SCREEN
*
*=====================================================*
SELECTION-SCREEN BEGIN OF BLOCK APP WITH FRAME.
SELECT-OPTIONS:
SID_PROG FOR ZPROGRAMAS-ID_PROG.
SELECTION-SCREEN END OF BLOCK APP.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM INICIALIZAR.
*=====================================================*
* END-OF-SELECTION
*
*=====================================================*
END-OF-SELECTION.
PERFORM ABRIR_SMARTFORMS.
198
*&----------------------------------------------------*
*&
Form
INICIALIZAR
*
*&----------------------------------------------------*
FORM INICIALIZAR.
SELECT *
INTO TABLE T_ZPROGRAMAS
FROM ZPROGRAMAS
WHERE ID_PROG IN SID_PROG.
ENDFORM.
"INICIALIZAR
*&----------------------------------------------------*
*&
Form
ABRIR_SAPSCRIPT
*
*&----------------------------------------------------*
FORM ABRIR_SMARTFORMS.
CALL FUNCTION 'SSF_FUNCTION_MODULE_NAME'
EXPORTING
FORMNAME
= 'ZPRIMER_SMARTFORM'
IMPORTING
FM_NAME
= MODULO_FUNCION
EXCEPTIONS
NO_FORM
= 1
NO_FUNCTION_MODULE = 2
OTHERS
= 3.
CALL FUNCTION MODULO_FUNCION
TABLES
T_PROGRAMAS
= T_ZPROGRAMAS
EXCEPTIONS
199
FORMATTING_ERROR = 1
INTERNAL_ERROR
= 2
SEND_ERROR
= 3
USER_CANCELED
= 4
OTHERS
= 5.
ENDFORM.
"ABRIR_SAPSCRIPT
Revisemos el programa.
DATA: MODULO_FUNCION TYPE RS38L_FNAM.
Declaramos una variable llamada MODULO_FUNCION del tipo
RS38L_FNAM.
Porque
hacemos
esto?
Muy
simple,
los
SmartForms se generan como módulos de funciones, por lo tanto,
necesitamos una variable que tenga el mismo tipo que los Módulos
de Funciones.
CALL FUNCTION 'SSF_FUNCTION_MODULE_NAME'
EXPORTING
FORMNAME
= 'ZPRIMER_SMARTFORM'
IMPORTING
FM_NAME
= MODULO_FUNCION
EXCEPTIONS
NO_FORM
= 1
NO_FUNCTION_MODULE = 2
OTHERS
= 3.
200
Utilizamos
el
módulo
de
funciones
SSF_FUNCTION_MODULE_NAME para poder obtener el
nombre del módulo de funciones que generó el SmartForms.
CALL FUNCTION MODULO_FUNCION
TABLES
T_PROGRAMAS
= T_ZPROGRAMAS
EXCEPTIONS
FORMATTING_ERROR = 1
INTERNAL_ERROR
= 2
SEND_ERROR
= 3
USER_CANCELED
= 4
OTHERS
= 5.
Con la variable MODULO_FUNCION, hacemos una llamada
dinámica al SmartForms y pasamos los parámetros necesarios, en
este caso la tabla interna T_ZPROGRAMAS.
Grabamos, activamos y ejecutamos el programa.
Ejecutando el formulario
Al igual que en el SAPScript, se nos mostrará una pantalla de
parámetros de impresión.
201
Tenemos nuestra lista, impresa en color azul. Si les parece que está
un poco abajo, es cuestión simplemente de ajustar las propiedades
del texto.
202
Crear una tabla
Si bien el formulario se ve bien, debemos aprovechar una de las
opciones
más
interesantes
que
nos
brindan
los
SmartForms...Utilizar una tabla para mostrar la información. (Si
imaginan hacer algo así como multiples box’s en SAPScript?).
Para esto, vamos a modificar un poco nuestro programa.
Eliminamos el LOOP que habíamos creado y creamos una nueva
tabla.
203
En la pestaña DATA (Datos) debemos hacer lo mismo que
habíamos hecho en el LOOP, asignar la tabla de parámetro a nuestra
tabla intermedia.
En la pestaña Table (Tabla), debemos hacer clic en el botón Details
(Detalles)
, para poder asignar un nombre de
línea. Lo cual nos servirá para definir los componentes de la tabla.
204
Para crear la cabecera de nuestra tabla, nos posicionamos en Header
y con un clic derecho escogemos Create (Crear) Æ Table Line
(Línea de Tabla).
Escogemos el tipo de línea que hemos creado.
205
Como veremos, solamente tenemos un %CELL1 es decir, una sola
celda, pero nosotros necesitamos dos, así que vamos al nodo
principal %TABLE1 a la pestaña Table y dentro del Formulario
gráfico,
dibujamos
una
línea
vertical
(Con
esto
creamos
automáticamente un %CELL2).
Como tenemos reservados los espacios para dos campos, debemos
agregar en cada uno un Texto para poder mostrar el título de cada
campo.
206
Seguimos el mismo procedimiento para Main Area (Area
Principal), es decir, creamos una Línea de Tabla, asignamos los
textos
y
pasamos
la
variables
&T_AUX_PROGRAMAS-
ID_PROG& y &T_AUX_PROGRAMAS-NOM_PROG& tal
como hicimos con el SAPScript.
Con esto, estamos listos para ejecutar el programa.
207
Si queremos que se note que hemos utilizado una tabla, hacemos lo
siguiente.
En el nodo principal de la tabla, hacemos clic sobre el botón Draw
Lines and Columns (Dibujar líneas y columnas)
, para
deshabilitarlo.
Seleccionamos los campos de nuestra línea de tabla (Utilizando la
tecla Control).
Presionamos el botón Select Pattern (Seleccionar Patrón)
.
208
El botón Display Framed Patterns (Mostrar patrones con marco)
.
Y seleccionamos el tipo que se muestra en la figura.
209
Cuando ejecutamos el formulario, lo veremos de la siguiente
manera.
210
Screen Painter y Menu Painter
Introducción
En ABAP, además de los reportes, podemos crear pantallas o
dynpros, en los cuales podemos incluir diversos elementos tales
como cajas de textos, radio buttons o tablas. Estos son muy útiles
cuando queremos crear programas con mayor capacidad de
procesamiento gráfico.
Una adición importante y necesaria a los Dynpros, son los menús,
creados con el Menu Painter.
Screen Painter
Para poder crear Dynpros o pantallas en ABAP, deberemos ingresar
a la transacción SE51 (Screen Painter).
211
En el campo Program (Programa) ingresaremos el nombre del
programa en el cual se va a utilizar el dynpro, y en el campos Screen
Number (Número de Pantalla), establecemos el número de la
pantalla, ya que podemos tener muchas pantallas relacionadas entre
si. O inclusive, llamar a pantallas distintas dependiendo de una
condición dada.
Como en el caso de los programas, deberemos hacer clic en el botón
Create (Crear)
. Es importante que el programa
exista, puesto que simplemente vamos a asociarle una pantalla, no
vamos a crear un programa nuevo.
Aquí deberemos ingresar una descripción breve, que explique para
que va a ser utilizada la pantalla.
212
En Screen Type (Tipo pantalla), debemos especificar que tipo de
Dynpro queremos crear, por lo general utilizamos Normal.
En el caso de Next Screen (Siguiente pantalla), lo llenaremos
solamente si es que luego de esta pantalla vamos a llamar a otra. En
este caso, lo dejamos con el valor por defecto.
Los demás parámetros podemos dejarlos como están.
Para poder comenzar a diseñar nuestra pantalla, debemos hacer clic
en el botón Layout (Disposición)
.
213
Controles del Screen Painter
Æ Cursor que sirve para seleccionar y mover los controles
dentro del dynpro.
Æ Campo de texto, sirve para crear labels o etiquetas
descriptivas.
Æ Campo de Entrada/Salida, nos sirve tanto para recoger
como para obtener datos.
Æ Casilla de Selección o Checkbox.
Æ Botón de Selección o RadioButton.
Æ Pulsador o Botón.
Æ Control de fichas o Control de Tabuladores.
Æ Wizard de control de Fichas o Control de tabuladores.
Permite crear este control mediante un ayudante paso a paso.
Æ Box. Permite crear un contenedor para los controles.
Æ Área SubScreen. Permite colocar un Dynpro de tipo
SubScreen dentro de otro Dynpro de tipo Normal.
Æ Table control. Llamado también Tabla o Grilla.
Æ Wizard de Table Control o Grilla. Permite crear este
control mediante un ayudante paso a paso.
214
Æ Custom Control o Control Personalizado. Nos permite
crear elementos en tiempos de ejecución. Muy utilizados para
programación en ABAP Objects.
Æ Icono Status. Permite establecer el status de una pantalla,
es decir, si la ejecución de la pantalla fue exitosa o errónea.
Para poder crear cualquiera de estos controles, bastará con
seleccionarlos y arrástrarlos hacia el espacio de nuestro dynpro.
215
Ejemplo de Screen Painter
Para este ejemplo, vamos a hacer algo bastante sencillo. Vamos a
crear una caja de texto, en donde especificaremos el valor que
queremos utilizar como filtro y en una tabla o grilla, mostraremos
los campos seleccionados en base a ese filtro.
Primero, creamos un control Text Field (Campo de Texto), al cual
llamaremos TEXT_FILTRO y que tendrá como texto “Filtro”.
Luego, vamos a crear un control Input/Output Field (Campo de
Entrada/Salida), al cual vamos a asociarlo con un campo de la base
de datos. Por lo tanto utilizaremos el nombre ZPROGRAMAS-ID.
Una vez ingresado el nombre, se nos mostrará el siguiente mensaje
(Esto es porque estamos utilizando el nombre de una tabla y un
campo, por lo cual, obtenemos sus características y existe una
relación directa con la Base de Datos).
216
Deberemos hacer clic en Yes (Sí) para que nuestra caja de texto
tome la referencia del campo ID de la tabla ZPROGRAMAS.
Para poder acceder a las propiedades de este control, simplemente
debemos hacer un doble clic.
217
218
Ahora, debemos crear nuestra tabla con el control Table Control
.
Nuestra tabla, se llamará TABLA.
Para poder crear los campos de nuestra tabla, deberemos utilizar un
control Text Field y otro control Input/Output Field, para cada
campo que deseemos mostrar.
219
Primero debemos crear el control Input/Output Field y luego el
Text Field. Ambos referenciando a un campo de una tabla. En este
caso, el primero T_ZPROGRAMAS-ID_PROG y el segundo
T_ZPROGRAMAS-NOM_PROG.
En este caso, vamos a asociar los campos de nuestra tabla a una tabla
interna, que vamos a crear en nuestro programa, llamada
T_ZPROGRAMAS. Esta tabla vamos a definirla en el programa
ZDUMMY_PRIMER_PROGRAMA que en este caso, es el
programa control para el Dynpro.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_ZPROGRAMAS TYPE STANDARD TABLE OF
ZPROGRAMAS.
Luego de haber creado nuestros controles en el Dynpro, debemos
crear la lógica de proceso para nuestra pantalla. Para esto, debemos
hacer clic en el botón Flow Logic (Flujo lógico)
.
220
Debemos descomentar ambos módulos y hacer doble clic en cada
uno de ellos para que se autogeneren en el código fuente de nuestro
programa de control y así poder utilizarlos.
Para poder llenar la tabla con los datos obtenidos en base al filtro,
deberemos incluir el siguiente código marcado en rojo.
Con esto, simplemente leemos el contenido de la tabla interna
T_ZPROGRAMAS y lo asignamos a cada campo de nuestra tabla.
Dentro del MODULE STATUS_0100, debemos ingresar el
siguiente código (Para esto, simplemente hacemos doble clic en el
texto STATUS_0100).
221
En esta ventana, nos avisan que el módulo STATUS_0100 no
existe. Por lo tanto, debemos crearlo aceptando el mensaje.
Cuando el NetWeaver va a crear un nuevo módulo o función, nos
pregunta si queremos crearlo en el programa fuente, o queremos
crear un INCLUDE (Es decir, un programa adicional que será
llamado por el programa principal). En este caso, vamos a crearlo en
el programa principal.
222
*&----------------------------------------------------*
*&
Module
STATUS_0100
OUTPUT
*
*&----------------------------------------------------*
MODULE STATUS_0100 OUTPUT.
DATA: LINEAS TYPE I,
ID TYPE ZPROGRAMAS-ID.
ID = ZPROGRAMAS-ID.
SELECT *
INTO TABLE T_ZPROGRAMAS
FROM ZPROGRAMAS
WHERE ID EQ ID.
DESCRIBE TABLE T_ZPROGRAMAS LINES LINEAS.
TABLA-LINES = LINEAS.
*
SET PF-STATUS 'xxxxxxxx'.
*
SET TITLEBAR 'xxx'.
ENDMODULE.
" STATUS_0100
OUTPUT
Se darán cuenta de que los SET están comentados...Eso es porque
vamos a verlos más adelante.
Ahora, podemos agregar el código necesario a nuestro programa.
223
REPORT ZDUMMY_PRIMER_PROGRAMA
NO STANDARD PAGE HEADING.
*=====================================================*
* DECLARACION DE TABLAS
*
*=====================================================*
TABLES: ZPROGRAMAS.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_ZPROGRAMAS TYPE STANDARD TABLE OF
ZPROGRAMAS WITH HEADER LINE.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
CONTROLS TABLA TYPE TABLEVIEW USING SCREEN '100'.
CALL SCREEN '100'.
*&----------------------------------------------------*
*&
Module
STATUS_0100
OUTPUT
*
*&----------------------------------------------------*
MODULE STATUS_0100 OUTPUT.
DATA: LINEAS TYPE I,
ID TYPE ZPROGRAMAS-ID.
ID = ZPROGRAMAS-ID.
224
IF ID NE SPACE.
SELECT *
INTO TABLE T_ZPROGRAMAS
FROM ZPROGRAMAS
WHERE ID EQ ID.
DESCRIBE TABLE T_ZPROGRAMAS LINES LINEAS.
TABLA-LINES = LINEAS.
ENDIF.
*
SET PF-STATUS 'xxxxxxxx'.
*
SET TITLEBAR 'xxx'.
ENDMODULE.
" STATUS_0100
OUTPUT
*&----------------------------------------------------*
*&
Module
USER_COMMAND_0100
INPUT
*
*&----------------------------------------------------*
MODULE USER_COMMAND_0100 INPUT.
ENDMODULE.
" USER_COMMAND_0100
INPUT
Revisemos el código.
START-OF-SELECTION.
CONTROLS TABLA TYPE TABLEVIEW USING SCREEN '100'.
CALL SCREEN '100'.
Debemos crear un control llamado TABLA de tipo TABLEVIEW
(Vista de Tabla) utilizando la pantalla 100. En otras palabras,
225
debemos declarar por código, que hemos creado una tabla en la
pantalla 100.
Finalmente, llamamos a la pantalla 100.
MODULE STATUS_0100 OUTPUT.
DATA: LINEAS TYPE I,
ID TYPE ZPROGRAMAS-ID.
ID = ZPROGRAMAS-ID.
IF ID NE SPACE.
SELECT *
INTO TABLE T_ZPROGRAMAS
FROM ZPROGRAMAS
WHERE ID EQ ID.
DESCRIBE TABLE T_ZPROGRAMAS LINES LINEAS.
TABLA-LINES = LINEAS.
ENDIF.
*
SET PF-STATUS 'xxxxxxxx'.
*
SET TITLEBAR 'xxx'.
ENDMODULE.
" STATUS_0100
OUTPUT
Creamos dos variables, LINEAS de tipo I e ID
de tipo
ZPROGRAMAS-ID.
226
Al pasar el valor de ZPROGRAMAS-ID a la variable ID, significa
que recogemos el valor de la caja de texto del dynpro y lo
guardamos en una variable auxiliar.
Si la variable ID no está vacía (Es decir, hemos ingresado un valor
en la caja de texto), entonces podemos continuar.
Seleccionamos todos los campos de la tabla ZPROGRAMAS
siempre y cuando, el ID sea igual a nuestra variable ID.
Obtenemos la cantidad de líneas que hay en la tabla interna,
utilizando el comando DESCRIBE TABLE.
Finalmente asignamos la variable LINEAS (Que contiene la
cantidad de líneas de la tabla interna) al campo LINES de nuestra
tabla TABLA, es decir, le decimos cuantas líneas se deberían de
mostrar en la tabla.
Para poder ejecutar el programa, debemos activar tanto el Dynpro
como el programa.
227
Si ingresamos el código de un lenguage, veremos como funciona el
programa.
Si se fijan bien, se darán cuenta de que todos los botones en la barra
de transacciones están deshabilitados.
Esto se debe a que no hemos asociado un menú a nuestra pantalla.
Por lo tanto debemos crear uno en la transacción SE41.
228
Menu Painter
Lo primero que debemos hacer, es crear un Status, que en este caso,
va a ser el 100. Para ello, como siempre, deberemos hacer clic en el
botón Create (Crear)
.
En la ventana que nos muestra el NetWeaver, debemos ingresar un
texto breve o descripción que nos indique para que se utilizará este
menú. En cuanto al Status Type (Tipo de Status), dejemos el que
viene por defecto, es decir Online Status (Status en línea).
229
Una vez que aceptemos, se nos mostrará la siguiente pantalla.
En la gran mayoría de los casos, solo nos va a importar esta sección.
Debemos hacer clic en este botón
para poder expandirlo.
230
Nuevamente, para este ejemplo, solo nos interesa esta sección.
Aquí debemos establecer que botones queremos habilitar y cuales
son sus nombre de función.
Ahora, debemos grabar y activar. Con esto, nuestro menú está listo
para utilizarse.
Para poder agregarlo a nuestro programa, simplemente deberemos
incluir la siguiente línea (O más bien, descomentarla).
SET PF-STATUS '100'.
231
El SET PF-STATUS le dice a nuestro Dynpro, que queremos
utilizar un menú creado por nosotros, y ‘100’ es el número de dicho
menú.
Con esto parecería ser suficiente...Pero no...Aún debemos asignar las
funciones que va a cumplir cada uno de los botones de nuestro
menú, para ello, debemos incluir el siguiente código.
MODULE USER_COMMAND_0100 INPUT.
DATA: OK_CODE TYPE SY-UCOMM.
OK_CODE = SY-UCOMM.
CASE OK_CODE.
WHEN 'BACK' OR 'EXIT' OR 'CANCEL'.
SET SCREEN 0.
LEAVE SCREEN.
CLEAR SY-UCOMM.
ENDCASE.
ENDMODULE.
" USER_COMMAND_0100
INPUT
En este caso, los tres botones hacen lo mismo, así que no hay mucho
problema.
La variable del sistema SY-UCOMM, determina que valor tiene el
botón que ha sido presionado.
Aunque en este caso no es necesario, cuando tengamos más botones,
deberíamos incluir el OK_CODE dentro de nuestro Dynpro, así
que vamos a la transacción SE51.
232
Escogemos Element list (Lista de elementos) y presionamos el
botón Change (Cambiar)
.
Al final de la lista, agregamos el OK_CODE.
233
Grabamos, activamos y ejecutamos el programa.
Se darán cuenta de que el título de nuestro programa es SAP...Y este
es un título por defecto, así que vamos a crear uno más interesante.
Primero, debemos descomentar la siguiente línea.
SET TITLEBAR '100'.
Y luego, hacemos un doble clic.
234
Aceptamos y continuamos.
Agregamos un título, aceptamos...Y ahora viene una pequeña crítica
a SAP...Si queremos activar el título, debemos hacer una de dos
cosas...Ingresar a la SE41 y activar o sino, recompilar el código
fuente y activarlos juntos. Para mí, el título debería activarse cuando
aceptamos esta ventana...en fín...
235
Agregando componentes al ejemplo
Ahora que ya sabemos crear un Dynpro sencillo, vamos a mejorarlo
un poco, agregando un botón. Este botón se llamará PROCESAR y
tendrá el mismo texto.
Una vez creado,
podemos hacer doble clic para modificar sus
propiedades.
Como podemos ver, los atributos del pulsador poseen dos campos
adicionales, que son Icon Name (Nombre de Icono) y Tooltip
(Información adicional). Para seleccionar el icono, simplemente
hacemos clic en el botón Choose Icon (Escoger Icono)
.
236
Esta ventana nos muestra todos los iconos disponibles. El Tooltip,
no es más que el mensaje que aparece cuando situamos el mouse
sobre el componente.
El valor del FctCode (Function Code – Código de Función), es
sumamente importante, puesto que nos va a ayudar a activar el
evento relacionado al botón PROCESAR.
Como FctCode vamos a utilizar el mismo nombre del botón.
Dentro del programa, hacemos unos pequeños cambios.
237
MODULE USER_COMMAND_0100 INPUT.
DATA: OK_CODE TYPE SY-UCOMM.
OK_CODE = SY-UCOMM.
CASE OK_CODE.
WHEN 'BACK' OR 'EXIT' OR 'CANCEL'.
SET SCREEN 0.
LEAVE SCREEN.
CLEAR SY-UCOMM.
WHEN 'PROCESAR'.
PERFORM PROCESAR_DATOS.
ENDCASE.
ENDMODULE.
" USER_COMMAND_0100
INPUT
Agregamos WHEN ‘PROCESAR’ lo cual significa que vamos a
ejecutar una acción cuando se presione el botón. La acción que
vamos a ejecutar es el FORM PROCESAR_DATOS.
FORM PROCESAR_DATOS.
DATA: LINEAS TYPE I,
ID TYPE ZPROGRAMAS-ID.
ID = ZPROGRAMAS-ID.
IF ID NE SPACE.
SELECT *
INTO TABLE T_ZPROGRAMAS
FROM ZPROGRAMAS
238
WHERE ID EQ ID.
DESCRIBE TABLE T_ZPROGRAMAS LINES LINEAS.
TABLA-LINES = LINEAS.
ENDIF.
ENDFORM.
" PROCESAR_DATOS
Se darán cuenta de que el código es el mismo que estaba dentro del
MODULE STATUS_0100 OUTPUT. Con lo cual el módulo
quedaría así.
MODULE STATUS_0100 OUTPUT.
SET PF-STATUS '100'.
SET TITLEBAR '100'.
ENDMODULE.
" STATUS_0100
OUTPUT
Si ejecutamos el programa (Ahora tenemos que presionar el botón
para que se llene la tabla).
239
Si han sido curiosos u observadores, se habrán dado cuenta de que
los campos de la tabla pueden modificarse, por lo tanto podríamos
cambiar los valores que obtenemos de la tabla.
Corregir esto es muy sencillo, simplemente regresamos a la
transacción SE51 y vamos a las propiedades de los campos que
conforman la tabla.
Por defecto tenemos esto.
Por lo tanto, debemos desmarcar el campo Input Field (Campo de
entrada), para que los valores de la tabla no puedan ser modificados.
Si ejecutamos de nuevo el programa, veremos que los campos están
protegidos de malas acciones.
240
Programación Avanzada en Dynpros
Ahora que ya hemos trabajado un poco con Dynpros, y conocemos
su funcionamiento, es hora de que hagamos cosas un poco más
interesantes.
1.- MatchCodes dinámicos
¿Qué es un MatchCode? Pues simplemente es, la pequeña ventana
con valores que aparecen luego de que hacemos clic en un campo o
presionamos F4.
Los MatchCodes dinámicos, se pueden crear de dos maneras. A
partir de una ayuda de búsqueda (En este caso, el MatchCode nos
ayuda a realizar una delimitación automática de valores) o utilizando
una tabla interna.
Como nuestro parámetro FILTRO ya posee un MatchCode, vamos
a asignarle uno nuevo.
En el Flow Logic debemos agregar el siguiente código.
PROCESS ON VALUE-REQUEST.
FIELD ZPROGRAMAS-ID MODULE MATCH_CODE_ID.
Lo que hace el POV (Process on value-request / Proceso en
requerimiento de valor) es llamar a un módulo cuando presionamos
F4 en un campo. Gracias a esto, podemos definir nosotros mismos la
ayuda de búsqueda.
241
Lo primero que debemos hacer es crear el módulo, que como ya
sabemos se hace haciendo doble clic sobre el nombre del módulo.
De vuelta en nuestro programa, debemos crear un TYPE que defina
nuestro MatchCode.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_MATCHCODE_ID,
ID TYPE ZPROGRAMAS-ID,
END OF TY_MATCHCODE_ID.
Un TYPE no es más que un modo genérico de crear una tabla
interna, algo así como una plantilla.
Luego, debemos crear una tabla interna que haga referencia al tipo
que hemos creado.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_ZPROGRAMAS TYPE STANDARD TABLE OF
ZPROGRAMAS WITH HEADER LINE,
T_MATCHCODE_ID TYPE STANDARD TABLE OF
TY_MATCHCODE_ID WITH HEADER LINE.
Es importante que lo creemos con cabecera (Aunque esto sea romper
las reglas…
Además, debemos crear algunas variables necesarias para la función
que genera los MatchCodes.
242
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: DYNPROFIELD TYPE HELP_INFO-DYNPROFLD,
PROGNAME TYPE SY-REPID,
DYNNUM TYPE SY-DYNNR.
Adicionalmente, necesitamos una tabla interna para la función que
crea los MatchCodes dinámicos.
RETURN_TAB TYPE STANDARD TABLE OF DDSHRETVAL
WITH HEADER LINE.
Con
esto,
ya
estamos
MatchCode…Claro
hacer...Si
el
listos
para
programar
nuestro
ustedes se preguntarán...¿Qué vamos a
campo
ID
ya
tiene
un
MatchCode?
Muy
simple...Nuestro MatchCode va a mostrar solamente el ID y ya no
el ID y el Nombre...Claro debería de ser al revés, pero el ejemplo
sirve para ilustrar lo que podemos hacer.
Antes de continuar, como vamos a llamar a un Módulo de
Funciones, podemos utilizar un poco de ayuda.
Presionamos el botón Pattern (Patrón)
y veremos una
pantalla muy interesante.
Esta pantalla completa el código por nosotros utilizando una
plantilla, en donde los parámetros no obligatorios están comentados.
Ingresamos F4IF_INT_TABLE_VALUE_REQUEST en el campo
CALL FUNCION (Llamar función) y aceptamos.
243
MODULE MATCH_CODE_ID INPUT.
SELECT ID
INTO TABLE T_MATCHCODE_ID
FROM ZPROGRAMAS.
DYNPROFIELD = 'ZPROGRAMAS-ID'.
DYNNUM = '100'.
PROGNAME = SY-REPID.
CLEAR RETURN_TAB.
REFRESH RETURN_TAB.
CALL FUNCTION 'F4IF_INT_TABLE_VALUE_REQUEST'
EXPORTING
RETFIELD
= 'ID'
DYNPPROG
= PROGNAME
244
DYNPNR
= DYNNUM
DYNPROFIELD
= DYNPROFIELD
VALUE_ORG
= 'S'
TABLES
VALUE_TAB
= T_MATCHCODE_ID
RETURN_TAB
= RETURN_TAB
EXCEPTIONS
PARAMETER_ERROR = 1
NO_VALUES_FOUND = 2
OTHERS
= 3.
IF RETURN_TAB-FIELDVAL NE SPACE.
ZPROGRAMAS-ID = RETURN_TAB-FIELDVAL.
ENDIF.
ENDMODULE.
" MATCH_CODE_ID
INPUT
Revisemos un poco el código...
SELECT ID
INTO TABLE T_MATCHCODE_ID
FROM ZPROGRAMAS.
Seleccionamos todos los ID’s de la tabla ZPROGRAMAS.
DYNPROFIELD = 'ZPROGRAMAS-ID'.
DYNNUM = '100'.
PROGNAME = SY-REPID.
245
El campo DYNPROFIELD almacena a que campo se va a aplicar el
MatchCode, el campo DYNNUM almacena cual es el número del
Dynpro y el campo PROGNAME almanacena cual es el nombre
del programa.
CLEAR RETURN_TAB.
REFRESH RETURN_TAB.
Limpiamos la cabecera y el contenido de la tabla RETURN_TAB.
CALL FUNCTION 'F4IF_INT_TABLE_VALUE_REQUEST'
EXPORTING
RETFIELD
= 'ID'
DYNPPROG
= PROGNAME
DYNPNR
= DYNNUM
DYNPROFIELD
= DYNPROFIELD
VALUE_ORG
= 'S'
TABLES
VALUE_TAB
= T_MATCHCODE_ID
RETURN_TAB
= RETURN_TAB
EXCEPTIONS
PARAMETER_ERROR = 1
NO_VALUES_FOUND = 2
OTHERS
= 3.
IF RETURN_TAB-FIELDVAL NE SPACE.
ZPROGRAMAS-ID = RETURN_TAB-FIELDVAL.
ENDIF.
246
El módulo de funciones recibe las variables con la información del
Dynpro y la tabla conteniendo los valores de los ID’s. Nos muestra
una ventana con los valores disponibles y el que hayamos elegido es
guardado en la tabla RETURN_TAB.
Si hemos escogido algún valor, entonces lo mostramos en el campo
ZPROGRAMAS-ID.
Listo, tenemos un MatchCode dinámico.
2.- Eliminar registros en un table control
Ahora, supongamos que queremos tener la posibilidad de elegir un
registro y eliminarlo.
Esto
es
bastante
sencillo,
y supone
hacer
unas
cuantas
modificaciones en los atributos de la tabla. (Además de agregar
algunos registros más en nuestra tabla ZPROGRAMAS).
247
Debemos marcar el check w/SelColumn (con Selección de
columna) y asignarle un nombre, que en este caso, va a ser “FILA”.
La variable FILA nos a indicar cuando hayamos seleccionado un
registro. Además, vamos a agregar un botón llamado ELIMINAR.
248
Como vemos, el botón tiene un FctCode llamado “ELIMINA” y un
Icono llamado ICON_DELETE, además de un Tooltip con el texto
“Eliminar”.
249
Tenemos que ingresar al Flow Logic para crear un módulo asociado
el botón ELIMINAR.
PROCESS AFTER INPUT.
MODULE USER_COMMAND_0100.
LOOP AT T_ZPROGRAMAS.
MODULE ELIMINAR_FILA.
ENDLOOP.
Creamos el módulo dentro de nuestro programa.
MODULE ELIMINAR_FILA INPUT.
DATA: FILA TYPE C.
IF SY-UCOMM EQ 'ELIMINA'.
IF FILA EQ 'X'.
DELETE T_ZPROGRAMAS INDEX TABLA-CURRENT_LINE.
ENDIF.
ENDIF.
ENDMODULE.
" ELIMINAR_FILA
INPUT
Declaramos una variable llamada FILA de tipo C, recordemos que
esta variable va a ser equivalente a la del Dynpro. Si hemos
presionado el botón ELIMINAR, entonces continuamos, y
automáticamente, el sistema va a recorrer todas las filas de nuestra
tabla hasta encontrar la que esté seleccionada, en ese momento el
250
valor de FILA va a ser ‘X’. Borramos este valor de nuestra tabla
interna y como consecuencia, se borra también de la tabla.
Claro, esto no lo borra de la Base de Datos, aunque sería bastante
sencillo hacer que lo haga, simplemente necesitaríamos seleccionar
la llave primaria de la tabla y hacer un DELETE TABLE.
3.- Escritura/Lectura en un table control
Puede ser que en algún momento, necesitemos modificar algún
registro de nuestro table control, por lo tanto, deberíamos poder
tener la posibilidad de Escritura y Lectura.
Debemos modificar los atributos de la tabla para que sea de
Escritura/Lectura. Recuerden que quitamos este valor para otro de
los ejemplos.
Ahora, debemos crear un nuevo módulo en nuestro Screen Painter.
PROCESS AFTER INPUT.
MODULE USER_COMMAND_0100.
LOOP AT T_ZPROGRAMAS.
MODULE ELIMINAR_FILA.
251
MODULE ACTUALIZA_TABLA.
ENDLOOP.
Dentro del programa, deberemos colocar el siguiente código al
módulo.
MODULE ACTUALIZA_TABLA INPUT.
MODIFY T_ZPROGRAMAS INDEX TABLA-CURRENT_LINE.
ENDMODULE.
" ACTUALIZA_TABLA
INPUT
Con esto, modificaremos el contenido del registro del table control
que hemos modificado. La variable CURRENT_LINE nos indica
que registro es el que estamos modificando.
Ahora, lo que vamos a hacer es agregar un botón que nos permita
cambiar entre el modo visualización y el modo modificación en el
programa.
252
Para esto, vamos a agregar un pulsador en el menú (Menú Painter –
SE41).
En la primera casilla escribimos CHANGESTAT y presionamos
ENTER.
En este caso, podemos definir un Static Text (Texto Estático) o un
Dynamic Text (Texto Dinámico).
Empecemos creando uno estático.
253
En esta ventana, definimos el código de función asociado al
pulsador, el texto asociado a la función, el icono asociado y
finalmente el texto informativo.
254
Debemos asignar una tecla de función a cada pulsador que creemos.
En este caso, vamos a elegir el F2.
Finalmente, deberemos hacer un clic al costado del Icono para una
última ventana de propiedades.
En esta nueva ventana, debemos escoger el texto que acompañará al
icono que hemos seleccionado. En el caso de nos especificar ningún
texto, el sistema tomará el texto por defecto del pulsador.
Cuando grabamos y activamos, queda así.
255
Le toca ahora al pulsador con texto dinámico, veamos como crearlo.
Debemos seguir los pasos anteriores hasta que lleguemos a esta
pantalla (Claro, debemos escoger Dynamic Text esta vez).
En este caso, Field name (Nombre Campo) es una variable que
deberemos crear en nuestro programa. Asignaremos cualquier tecla
de función y continuaremos. Finalmente tendremos esto.
De vuelta en el programa, tendremos que crear una variable y
asignarle valores para nuestro texto dinámico.
DATA: DYNPROFIELD TYPE HELP_INFO-DYNPROFLD,
PROGNAME TYPE SY-REPID,
DYNNUM TYPE SY-DYNNR,
TEXTO_DINAMICO TYPE SMP_DYNTXT.
256
Y debemos también asignarle un texto inicial.
START-OF-SELECTION.
TEXTO_DINAMICO = 'Este es un texto dinámico'.
CONTROLS TABLA TYPE TABLEVIEW USING SCREEN '100'.
CALL SCREEN '100'.
En el Screen Painter debemos cambiar los atributos de los
componentes de la tabla. Nuevamente deben de estar como campos
de salida.
Esto es para que los componentes de la tabla estén protegidos por
defecto.
En el programa, deberemos hacer lo siguiente. Creamos una tabla de
tipo COLS que pertenece a TABLA y contiene los atributos de
todos los registros que componen la TABLA.
START-OF-SELECTION.
TEXTO_DINAMICO = 'Este es un texto dinámico'.
CONTROLS TABLA TYPE TABLEVIEW USING SCREEN '100'.
DATA: COLS LIKE LINE OF TABLA-COLS.
CALL SCREEN '100'.
Declaramos la tabla COLS como una línea de la tabla TABLACOLS, esto es porque TABLA-COLS es una Deep Structure
257
(Estructura profunda), es decir, es una tabla dentro de otra tabla.
Además, solo necesitamos una cabecera y no toda la tabla.
Agregamos el evento generado por el pulsador CHANGESTAT.
MODULE USER_COMMAND_0100 INPUT.
DATA: OK_CODE TYPE SY-UCOMM.
OK_CODE = SY-UCOMM.
CASE OK_CODE.
WHEN 'BACK' OR 'EXIT' OR 'CANCEL'.
SET SCREEN 0.
LEAVE SCREEN.
CLEAR SY-UCOMM.
WHEN 'PROCESAR'.
PERFORM PROCESAR_DATOS.
WHEN 'CHANGESTAT'.
PERFORM CAMBIAR_ESTADO.
ENDCASE.
ENDMODULE.
" USER_COMMAND_0100
INPUT
Para terminar, creamos el FORM CAMBIAR_ESTADO.
FORM CAMBIAR_ESTADO.
LOOP AT TABLA-COLS INTO COLS.
IF COLS-SCREEN-INPUT = '0'.
COLS-SCREEN-INPUT = '1'.
TEXTO_DINAMICO = 'Modificar'.
ELSEIF COLS-SCREEN-INPUT = '1'.
258
COLS-SCREEN-INPUT = '0'.
TEXTO_DINAMICO = 'Visualizar'.
ENDIF.
MODIFY TABLA-COLS FROM COLS.
ENDLOOP.
ENDFORM.
" CAMBIAR_ESTADO
Hacemos un LOOP a la tabla TABLA-COLS y guardamos cada
línea o registro dentro de nuestra tabla COLS.
Verificamos el estado del campo INPUT, si es cero, entonces
estamos visualizando y si es uno estamos modificando. Cambiamos
los estados, los textos del botón dinámico y finalmente modificamos
la tabla para que los cambios queden registrados.
259
Trabajando con SubScreens
260
Algunas veces necesitamos incluir pantallas completas de otras
pantallas, sobre todo cuando tenemos que utilizar Tabs o Pestañas.
Vamos a olvidarnos de nuestro programa anterior, y vamos a crear
uno nuevo utilizando pestañas y un SubScreen.
Vamos a crear una nueva pantalla y un nuevo programa. Y por
supuesto nuestro TabStrip Control llamado “TAB”.
Asignamos un nombre a cada TAB (Pestaña) del TabStrip Control.
Es muy importante que definamos el Tipo de Función que este caso
es P (Local Gui Function – Función GUI Local), puesto que sin esto,
los TABS no van a funcionar.
261
Creamos un SubScreen, y lo asignamos al control TAB. La única
propiedad que tenemos que definir para el SubScreen es el nombre.
Dentro de los SubScreens no podemos colocar ningún control,
puesto que son contenedores que admiten únicamente pantalla de
tipo SubScreen.
Así que debemos crear dos pantallas más, la 101 y 102, pero esta vez
de tipo SubScreen.
262
Luego de haber creado las dos SubScreens, este será el Flow Logic
para la pantalla principal.
PROCESS BEFORE OUTPUT.
MODULE STATUS_0100.
CALL SUBSCREEN: SUBSCREEN_1 INCLUDING
SY-REPID '0101'.
CALL SUBSCREEN: SUBSCREEN_2 INCLUDING
SY-REPID '0102'.
PROCESS AFTER INPUT.
MODULE USER_COMMAND_0100.
CALL SUBSCREEN: SUBSCREEN_1,SUBSCREEN_2.
Con el CALL SUBSCREEN llamamos a cada una de las pantallas
SubScreen que creamos. Con el INCLUDING referenciamos al
programa control y al número de la pantalla.
263
Como podemos ver, en el PAI también utilizamos el CALL
SUBSCREEN para llamar a las SubScreens.
Como mencioné lineas arriba, los componentes tendremos que
colocarlos dentro de cada SubScreen, por lo cual vamos a comenzar
con el número 0101.
Vamos a crear un Box o Caja con los siguientes atributos.
Dentro del Box (Caja), vamos a crear un campo de texto para el
código del programa. Si queremos que esté asociado con una tabla
de la Base de Datos, tendremos que utilizar el nombre TABLACAMPO
como
nombre.
En
este
caso,
el
nombre
será
ZPROGRAMAS-ID.
Vamos a agregar también un botón llamado PROCESAR y que va a
tener como código de función la palabra PROCESS.
Además, vamos a tener dos campos de Entrada/Salida.
•
ZPROGRAMAS-NOM_PROG.
•
ZLENGUAJES_PROG-NOMBRE.
264
Finalmente, tendremos un botón llamado MODIFICAR
con el
código de función MODIFY. Este botón nos va a servir, puesto que
una vez obtenidos los datos del programa, podremos modificarlos y
mostrarlos en el segundo SubScreen. En el cual no podrán ser
modificados.
Cabe destacar que aunque tengamos pensado utilizar dos
SubScreens, los nombres de los componentes no pueden repetirse,
es decir, no podemos crear
el
campo
ZPROGRAMAS-
NOM_PROG en ambas pantallas...Lo mejor sería crear una
estructura para cada una de ellas y problema resuelto. Pero aquí
simplemente vamos a utilizar variables para el segundo SubScreen.
265
Por lo tanto, vamos a tener V_NOM_PROG y V_NOMBRE.
En el programa, deberemos declarar la tablas ZPROGRAMAS y
ZLENGUAJES_PROG, crear las variables correspondientes y
crear el componente TAB_CONTROL.
START-OF-SELECTION.
CONTROLS TAB_CONTROL TYPE TABSTRIP.
TAB_CONTROL-ACTIVETAB = 'TAB_1'.
CALL SCREEN '100'.
La sentencia TAB_CONTROL-ACTIVETAB, indica que pestaña
del TAB va a estar activa cuando ejecutemos el programa.
Para el botón PROCESAR vamos a crear el siguiente form.
FORM OBTENER_DATOS.
SELECT SINGLE NOMBRE NOM_PROG
INTO (ZLENGUAJES_PROG-NOMBRE,ZPROGRAMAS-NOM_PROG)
FROM ( ZPROGRAMAS INNER JOIN ZLENGUAJES_PROG
ON ZPROGRAMAS~ID EQ ZLENGUAJES_PROG~ID )
WHERE ZPROGRAMAS~ID EQ ID.
ENDFORM.
"OBTENER_DATOS
266
Hacemos un INNER JOIN entre las dos tablas, y obtenemos los
valores NOMBRE y NOM_PROG, los cuales son guardados en los
Campos de Entrada/Salida del Dynpro.
No debemos olvidarnos del USER-COMMAND.
MODULE USER_COMMAND_0100 INPUT.
DATA: OK_CODE TYPE SY-UCOMM.
OK_CODE = SY-UCOMM.
CLEAR SY-UCOMM.
CASE OK_CODE.
WHEN 'PROCESS'.
PERFORM OBTENER_DATOS.
ENDCASE.
ENDMODULE.
" USER_COMMAND_0100
INPUT
267
Cuando ejecutamos el programa, nos vamos a dar cuenta de algo
bastante peculiar...Cuando ingresamos el primer código, todo va
bien, pero con el segundo, los datos se quedan pegados. Esto es
porque estamos trabajando en un SubScreen y los datos no se
actualizan directamente. Por suerte, y como debe ser, existe una
función que actualiza los Dynpros.
Para esto, debemos crear los siguientes TYPES y Tablas Internas.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TAB,
FIELDNAME TYPE DYNPREAD-FIELDNAME,
STEPL TYPE DYNPREAD-STEPL,
FIELDVALUE TYPE DYNPREAD-FIELDVALUE,
268
FIELDINP TYPE DYNPREAD-FIELDINP,
END OF TAB.
TYPES: BEGIN OF DYNP,
DYNUMB TYPE D020S-DNUM,
END OF DYNP.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: MY_TAB TYPE STANDARD TABLE OF
TAB,
MY_DYNP TYPE STANDARD TABLE OF
DYNP.
Nuestro FORM para actualizar el Dynpro será el siguiente (Aunque
primero debemos crear dos Field-Symbols.
FIELD-SYMBOLS: <FS_MY_DYNP> LIKE LINE OF MY_DYNP,
<FS_MY_TAB> LIKE LINE OF MY_TAB.
FORM ACTUALIZAR_DYNPRO TABLES MY_TAB MY_DYNP.
LOOP AT MY_DYNP
ASSIGNING <FS_MY_DYNP>.
CALL FUNCTION 'DYNP_UPDATE_FIELDS'
EXPORTING
DYNAME
= SY-CPROG
DYNUMB
= <FS_MY_DYNP>-DYNUMB
REQUEST
= 'A'
TABLES
269
DYNPFIELDS = MY_TAB.
ENDLOOP.
REFRESH: MY_TAB, MY_DYNP.
ENDFORM.
"ACTUALIZAR_DYNPRO
El módulo de funciones DYNP_UPDATE_FIELD lo que hace es
actualizar cada campo recibido en MY_TAB de acuerdo a la
pantalla especificada en <FS_MY_DYNP>-DYNUMB.
Gracias a esto, podemos mejorar la rutina OBTENER_DATOS.
FORM OBTENER_DATOS.
SELECT SINGLE NOMBRE NOM_PROG
INTO (ZLENGUAJES_PROG-NOMBRE,ZPROGRAMAS-NOM_PROG)
FROM ( ZPROGRAMAS INNER JOIN ZLENGUAJES_PROG
ON ZPROGRAMAS~ID EQ ZLENGUAJES_PROG~ID )
WHERE ZPROGRAMAS~ID EQ ID.
IF SY-SUBRC EQ 0.
APPEND INITIAL LINE TO MY_TAB
ASSIGNING <FS_MY_TAB>.
<FS_MY_TAB>-FIELDNAME = 'ZPROGRAMAS-ID'.
<FS_MY_TAB>-FIELDVALUE = ZPROGRAMAS-ID.
APPEND INITIAL LINE TO MY_TAB
ASSIGNING <FS_MY_TAB>.
<FS_MY_TAB>-FIELDNAME = 'ZPROGRAMAS-NOM_PROG'.
<FS_MY_TAB>-FIELDVALUE = ZPROGRAMAS-NOM_PROG.
APPEND INITIAL LINE TO MY_TAB
270
ASSIGNING <FS_MY_TAB>.
<FS_MY_TAB>-FIELDNAME = 'ZLENGUAJES_PROG-NOMBRE'.
<FS_MY_TAB>-FIELDVALUE = ZLENGUAJES_PROG-NOMBRE.
APPEND INITIAL LINE TO MY_DYNP
ASSIGNING <FS_MY_DYNP>.
<FS_MY_DYNP>-DYNUMB = '0101'.
PERFORM ACTUALIZAR_DYNPRO TABLES MY_TAB MY_DYNP.
ENDIF.
ENDFORM.
"OBTENER_DATOS
Primero, si el SELECT es exitoso, agregamos los campos del
Dynpro a nuestra tabla MY_TAB, además agregamos el número del
Dynpro a nuestra tabla MY_DYNP.
Finalmente llamamos
al FORM ACTUALIZAR_DYNPRO
pasando las tablas como parámetros.
Con esto listo, podemos continuar con el botón MODIFICAR.
MODULE USER_COMMAND_0100 INPUT.
DATA: OK_CODE TYPE SY-UCOMM.
OK_CODE = SY-UCOMM.
CLEAR SY-UCOMM.
CASE OK_CODE.
WHEN 'PROCESS'.
PERFORM OBTENER_DATOS.
271
WHEN 'MODIFY'.
PERFORM TRASPASAR_VALORES.
ENDCASE.
ENDMODULE.
" USER_COMMAND_0100
INPUT
Creamos el nuevo FORM. (Aunque antes creamos un par de
variables que están relacionadas con los campos del segundo
SubScreen).
DATA: V_NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
V_NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE.
FORM TRASPASAR_VALORES.
CLEAR: V_NOM_PROG,V_NOMBRE.
MOVE ZPROGRAMAS-NOM_PROG TO V_NOM_PROG.
MOVE ZLENGUAJES_PROG-NOMBRE TO V_NOMBRE.
ENDFORM.
" TRASPASAR_VALORES
272
273
Utilizando Listas Deplegables
Lo que vamos a hacer ahora, es obtener los datos de la tabla
ZPROGRAMAS, pero esta vez, utilizando un lista desplegable que
nos muestre todos los códigos de programas.
Debemos crear un campo de Entrada/Salido asociado al campo
ZPROGRAMAS-ID, pero debemos asignarle un atributo especial,
que es el de Listbox (Lista desplegable).
En el Flow Logic, crearemos un módulo que llene la lista con datos.
274
PROCESS BEFORE OUTPUT.
MODULE STATUS_0100.
MODULE LLENAR_LISTA_DESPLEGABLE.
PROCESS AFTER INPUT.
MODULE USER_COMMAND_0100.
Como era de esperarse, debemos crear algunas variables, tablas
internas, un Type e inclusive un Type-Pool.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPE-POOLS: VRM.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: VALUES TYPE VRM_VALUES,
FC_VALUES LIKE VALUES WITH HEADER LINE.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: ID TYPE VRM_ID.
Como ven, nuevamente necesitamos una tabla con cabecera...Esto es
porque el componente Listbox sigue siendo parte del ABAP
antiguo.
275
Este es el código que utilizaremos para llenar con datos la lista, ya se
que se ve un poco redundante, pero la estructura de la función que
genera la lista así lo requiere.
MODULE LLENAR_LISTA_DESPLEGABLE OUTPUT.
IF ID EQ SPACE.
ID = 'ZPROGRAMAS-ID'.
SELECT ID NOMBRE
INTO (FC_VALUES-KEY,FC_VALUES-TEXT)
FROM ZLENGUAJES_PROG.
APPEND FC_VALUES.
ENDSELECT.
LOOP AT FC_VALUES.
APPEND FC_VALUES TO VALUES.
ENDLOOP.
CALL FUNCTION 'VRM_SET_VALUES'
EXPORTING
ID
= ID
VALUES
=
VALUES
EXCEPTIONS
ID_ILLEGAL_NAME
= 1
OTHERS
= 2.
ENDIF.
ENDMODULE.
" LLENAR_LISTA_DESPLEGABLE
OUTPUT
276
Aunque no es nada recomendable utilizarlo, en este caso
necesitamos el SELECT-ENDSELECT (El GOTO del ABAP).
Necesitamos que los valores queden almacenados en la tabla
VALUES que es la que vamos a pasar al módulo de funciones
VRM_SET_VALUES, que es el que finalmente crea la lista
despegable.
Si ejecutamos el programa, veremos que la lista está llena, pero no
hace nada, puesto que si seleccionamos un lenguaje de
programación, no nos muestra los datos, además, nos muestra el
nombre del lenguaje en un espacio muy reducido, así que
deberíamos ampliar un poco su tamaño, antes de poder continuar.
Así que vamos a hacer que cuando seleccionemos el lenguaje, se
muestren los datos, sin necesidad de un botón auxiliar.
Para esto, simplemente agregamos un código de función al
componente.
277
Dentro del programa, agregamos una llamada al código de función.
MODULE USER_COMMAND_0100 INPUT.
DATA: OK_CODE TYPE SY-UCOMM.
OK_CODE = SY-UCOMM.
CLEAR SY-UCOMM.
CASE OK_CODE.
WHEN 'LISTA'.
PERFORM OBTENER_DATOS.
ENDCASE.
ENDMODULE.
" USER_COMMAND_0100
INPUT
Con esto listo, podemos llamar al FORM OBTENER_DATOS.
FORM OBTENER_DATOS.
SELECT SINGLE ID_PROG NOM_PROG
INTO (ZPROGRAMAS-ID_PROG,ZPROGRAMAS-NOM_PROG)
FROM ZPROGRAMAS
WHERE ID EQ ZPROGRAMAS-ID.
IF SY-SUBRC NE 0.
CLEAR: ZPROGRAMAS-ID_PROG,ZPROGRAMAS-NOM_PROG.
ENDIF.
ENDFORM.
" OBTENER_DATOS
278
Ahora, cuando elegimos un valor de la lista desplegable, podemos
obtener los datos asociados.
279
Leyendo datos de un Dynpro
Algunas veces, necesitamos leer el valor de un componente del
Dynpro, pero nos damos cuenta de que este no está activo hasta que
presionamos la tecla ENTER o activamos algún evento.
Obviamente no podemos obligar al usuario a presionar ENTER o
activar un evento, por eso, existe una función muy útil para leer los
valores de un Dynpro.
Necesitaremos crear dos tablas internas y una variable.
DATA: DYNPRO_VALUES TYPE TABLE OF DYNPREAD
WITH HEADER LINE,
FIELD_VALUE LIKE LINE OF DYNPRO_VALUES.
DATA: DYNUM TYPE D020S-DNUM.
Y creamos un módulo para leer los valores del Dynpro.
PROCESS AFTER INPUT.
MODULE USER_COMMAND_0100.
MODULE READ_DYNP_VALUES.
MODULE READ_DYNP_VALUES INPUT.
FREE FIELD_VALUE.
FREE DYNPRO_VALUES.
FIELD_VALUE-FIELDNAME = 'ZPROGRAMAS-ID'.
APPEND FIELD_VALUE TO DYNPRO_VALUES.
280
DYNUMB = '0100'.
CALL FUNCTION 'DYNP_VALUES_READ'
EXPORTING
DYNAME
= SY-CPROG
DYNUMB
= DYNUMB
TABLES
DYNPFIELDS = DYNPRO_VALUES.
READ TABLE DYNPRO_VALUES WITH KEY
FIELDNAME = 'ZPROGRAMAS-ID'.
IF SY-SUBRC EQ 0.
ZPROGRAMAS-ID = DYNPRO_VALUES-FIELDVALUE.
ENDIF.
ENDMODULE.
" READ_DYNP_VALUES
INPUT
Como podemos ver, tenemos que pasar el número del Dynpro, y el
valor que queremos leer. Si la función puede leerlo, entonces lo
asignamos a nuestro campo o a donde lo necesitemos.
281
Módulos de Función y BAPIS
Introducción Módulos de Función
Un módulo de funciones, es un programa capaz de recibir
parámetros de entrada y producir un resultado. Se diferencia de una
función normal, en que puede poseer una estructura bastante
compleja y debe ser diseñado en su propio entorno, fuera del
programa que lo utiliza.
Existen dos tipos de módulos de funciones, los Standard y los RFC
(Remote Function Call – Llamada de Procedimiento Remoto), que
sirven para la comunicación entre sistemas externos y el
NetWeaver.
Los módulos de función son creados en la transacción SE37.
Creando nuestra primera función
Como siempre, la mejor explicación es un ejemplo, así que vamos a
crear uno bastante sencillo.
En
la
transacción
SE37,
crearemos
nuestra
ZDUMMY_FUNCTION. Para esto, utilizaremos el grupo de
funciones que creamos en la sección Creando una vista de
Actualización.
Para empezar, definamos que va a hacer nuestra función. Por
ejemplo, calcular la edad de una persona en años, meses, días. Para
282
esto, necesitaremos como parámetro de entrada la fecha de
nacimiento del usuario y como salida podemos mostrar una tabla
que contenga los campos años, meses y días. Por lo tanto,
necesitaremos crear una estructura que contenga dichos campos.
Vamos a llamarla ZEDAD_STR.
Cuando creemos la función, hay ciertos campos que debemos ir
llenando.
En este caso, solo vamos a utilizar un parámetro de entrada, llamado
FECHA_NAC de tipo SY-DATUM.
283
Como podemos ver, hay dos CheckBox, Optional (Opcional), que
indica si el campo es obligatorio o no y Pass Value (Pasar valor)
que indica si el valor es pasado por Valor o por Referencia. En este
caso, dejamos ambos como están, es decir, mantenemos los valores
por defecto.
En la pestaña Changing (Cambiando), vamos a crear nuestro
parámetro de salida, que hará referencia a la estructura que hemos
creado.
Para todos los parámetros existen un botón muy importante llamado
Long text (Texto largo)
. Este botón, nos permite crear un
texto de ayuda que indica que hace o que necesita el parámetro para
funcionar correctamente. Basta con hacer clic en el botón Create
(Crear) para ingresar al editor. Escribimos un pequeño texto
descriptivo, grabamos, activamos y salimos.
284
Luego de esto, podemos darnos cuenta de que el botón ha cambiado,
indicándonos que el texto ha sido asignado correctamente.
Finalmente, podemos pasar a la pestaña de Source Code (Código
Fuente) en donde vamos a crear el código necesario para que nuestra
función...funcione.
Claro, no va a ser el cálculo más exacto, pero a modo de ejemplo nos
va a ser útil.
Antes de comenzar con el código, es importante saber que podemos
asignar excepciones, que son errores que puede encontrar la función,
por ejemplo, si la fecha de nacimiento ingresada como parámetro es
mayor que la fecha actual, deberíamos mostrar un mensaje de error.
Esto lo hacemos ingresando a la pestaña Exceptions (Excepciones).
FUNCTION ZDUMMY_FUNCTION.
*"----------------------------------------------------*"*"Local Interface:
*"
*"
*"
IMPORTING
REFERENCE(FECHA_NAC) TYPE
SY-DATUM
CHANGING
285
*"
*"
REFERENCE(TABLA_EDAD) TYPE
ZEDAD_STR
EXCEPTIONS
*"
EDAD_FUERA_RANGO
*"----------------------------------------------------DATA: ZYEARS(4) TYPE C,
ZMONTHS(2) TYPE C,
ZDAYS(2) TYPE C.
IF FECHA_NAC GT SY-DATUM.
RAISE EDAD_FUERA_RANGO.
ENDIF.
ZYEARS = FECHA_NAC+0(4).
ZMONTHS = FECHA_NAC+4(2).
ZDAYS = FECHA_NAC+6(2).
ZYEARS = SY-DATUM+0(4) - ZYEARS.
IF SY-DATUM+4(2) LT ZMONTHS.
ZYEARS = ZYEARS - 1.
ZMONTHS = SY-DATUM+4(2) + 1.
ENDIF.
IF SY-DATUM+6(2) LT ZDAYS.
ZDAYS = 30 - ZDAYS.
ZMONTHS = ZMONTHS - 1.
ZDAYS = ZDAYS + SY-DATUM+6(2).
ENDIF.
TABLA_EDAD-ZYEARS = ZYEARS.
TABLA_EDAD-ZMONTHS = ZMONTHS.
TABLA_EDAD-ZDAYS = ZDAYS.
286
ENDFUNCTION.
Revisemos un poco el código.
DATA: ZYEARS(4) TYPE C,
ZMONTHS(2) TYPE C,
ZDAYS(2) TYPE C,
ZMONTHS_AUX TYPE C.
Declaramos 3 variables, una para guardar el año, otra el mes y la
última los días. Además una variable auxiliar para los meses.
IF FECHA_NAC GT SY-DATUM.
RAISE EDAD_FUERA_RANGO.
ENDIF.
Si la fecha de nacimiento ingresada, es menor a la fecha actual del
sistema, lanzamos un mensaje de error.
ZYEARS = FECHA_NAC+0(4).
ZMONTHS = FECHA_NAC+4(2).
ZDAYS = FECHA_NAC+6(2).
ZYEARS = SY-DATUM+0(4) - ZYEARS.
En SAP, las fechas se almacenan en un orden inverso, por lo tanto,
al ingresar nosotros 22.11.1977 se almacena como 19771122.
Entonces, para poder obtener los años, meses y días en variables,
utilizamos posiciones de inicio y cantidad de caracteres, es decir, al
287
tener nostros FECHA_NAC+0(4) estamos diciendo, “Toma 4
caracteres, comenzando desde el carácter 0”. En otras palabras 1977.
Restamos el año actual, menos el año del parámetro.
IF SY-DATUM+4(2) LT ZMONTHS.
ZYEARS = ZYEARS - 1.
ZMONTHS_AUX = 12 - ZMONTHS.
ZMONTHS = SY-DATUM+4(2) + ZMONTHS.
ENDIF.
Si el mes actual es menor que el mes del parámetro, entonces
debemos restar un año. Restamos la cantidad de meses a 12, para
saber cuantos hay de diferencia, finalmente, sumamos esos meses de
diferencia.
IF SY-DATUM+6(2) LT ZDAYS.
ZDAYS = 30 - ZDAYS.
ZMONTHS = ZMONTHS - 1.
ZDAYS = ZDAYS + SY-DATUM+6(2).
ENDIF.
Si los días actuales son menores que los días del parámetro, entonces
a 30 le restamos la cantidad de días. Restamos 1 a meses y sumamos
los días actuales más los días que obtuvimos al hacer la resta de 30
menos los días del parámetro.
Para probar nuestra función, simplemente debemos ejecutarla
presionando la tecla F8 o el botón Test/Exectute (Probar/Ejecutar)
.
288
Ingresamos una fecha de nacimiento y lo ejecutamos, ya sea con el
botón Execute (Ejecutar) o presionando F8
. Además podríamos
hacer un debug si presionaramos el botón Debugging o las teclas
Ctrl + F7
.
Para ver más claramente el resultado, debemos hacer clic en Details
View/Edit (Visualizar/Editar Detalles)
.
289
30 Años, 01 mes y 09 días.
Si queremos ver el resultado con un mayor detalle, deberemos
presionar el botón Single Entry (Entrada Individual) o presionar los
botones Shift + F7
.
Ahora que ya vimos que nuestra función hace lo que tiene que hacer,
es hora de llamarla desde un programa.
Llamando funciones desde un programa
Dentro de nuestro programa, la manera más sencilla de llamar a una
función es con el botón Pattern (Patrón)
tal y como vimos
en una sección anterior.
REPORT ZDUMMY_PRIMER_PROGRAMA
NO STANDARD PAGE HEADING.
290
*=====================================================*
* DECLARACION DE TABLAS
*
*=====================================================*
DATA: TABLA_EDAD TYPE ZEDAD_STR.
*=====================================================*
* Selection screen
*
*=====================================================*
SELECTION-SCREEN BEGIN OF BLOCK DATA WITH FRAME.
PARAMETERS:
P_FECHA TYPE SY-DATUM.
SELECTION-SCREEN END OF BLOCK DATA.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
CALL FUNCTION 'ZDUMMY_FUNCTION'
EXPORTING
FECHA_NAC
= P_FECHA
CHANGING
TABLA_EDAD
= TABLA_EDAD
EXCEPTIONS
EDAD_FUERA_RANGO = 1
OTHERS
= 2.
IF SY-SUBRC <> 0.
WRITE: 'La edad ingresada está fuera del rango'.
ELSE.
WRITE: 'Su edad actual es',
TABLA_EDAD-ZYEARS, 'Años',
291
TABLA_EDAD-ZMONTHS, 'Meses',
TABLA_EDAD-ZDAYS, 'Días'.
ENDIF.
El código es bastante sencillo y no necesita mayor explicación.
Llamamos a la función, obtenemos los datos y los mostramos en
pantalla.
Introducción BAPIS
Una BAPI (Business Application Programming Interface – Interface
de Programación de Aplicaciones de Negocio), no es en el fondo
más que un módulo de funciones con características de RFC.
Aunque las BAPI’s son utilizadas para realizar tareas específicas,
como por ejemplo, crear pedidos, realizar contabilizaciones, cargar
datos maestros, etc.
292
Las BAPI’s son estables, puesto que cuentan con muchos
mecanismos de control y aseguramiento. Además, encapsulan
operaciones complejas en una simple interfaz. Por eso, las BAPI’s
cuentan con su propia transacción llamada justamente BAPI.
293
ALV (ABAP List Viewer)
Introducción
En ABAP es muy común crear reportes que muestren información
de varias tablas. Estos reportes, por lo general son bastante sencillos
y estáticos puesto que simplemente muestran información.
ABAP, nos ofrece los ALV’s que son Visores de Listas ABAP.
Estas listas son interactivas, y nos permiten ocultar o visualizar
columnas, ordenar datos, mostrar subtotales, promedios y muchas
cosas más.
Creando un ALV
Para crear un ALV, necesitamos una función muy importante
llamada REUSE_ALV_GRID_DISPLAY, que es la que finalmente
crea todo el diseño visual y la gran mayoría de las funcionalidades
standard que nos ofrecen estos reportes dinámicos.
Otro punto importante, es el uso de un TYPE-POOLS, llamado
SLIS. Este TYPE-POOLS, contiene todas las definiciones de
variables y tablas internas que necesitamos para poder trabajar, así
que veamos como sería el esqueleto del programa.
Lo primero que hay que hacer, es obtener los datos tal como lo
haríamos en un programa normal, puesto que una vez que tengamos
lista la tabla interna, será muy sencillo generar el ALV.
294
*=====================================================*
* DECLARACION DE TABLAS
*
*=====================================================*
TABLES: ZPROGRAMAS.
*=====================================================*
* DECLARACION DE TYPE-POOLS
*
*=====================================================*
TYPE-POOLS: SLIS.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
ENTORNO TYPE ZLENGUAJES_PROG-ENTORNO,
CONEX_SAP TYPE ZLENGUAJES_PROG-CONEX_SAP,
END OF TY_PROGRAMAS.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE OF
TY_PROGRAMAS.
*=====================================================*
* Selection screen
*
*=====================================================*
SELECTION-SCREEN BEGIN OF BLOCK DATA WITH FRAME.
SELECT-OPTIONS:
295
S_IDPROG FOR ZPROGRAMAS-ID_PROG.
SELECTION-SCREEN END OF BLOCK DATA.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM OBTENER_DATOS.
*&----------------------------------------------------*
*&
Form
OBTENER_DATOS
*
*&----------------------------------------------------*
FORM OBTENER_DATOS.
SELECT ID_PROG NOM_PROG NOMBRE ENTORNO CONEX_SAP
INTO TABLE T_PROGRAMAS
FROM ( ZPROGRAMAS INNER JOIN ZLENGUAJES_PROG
ON ZPROGRAMAS~ID EQ ZLENGUAJES_PROG~ID )
WHERE ID_PROG IN S_IDPROG.
ENDFORM.
" OBTENER_DATOS
Vamos a necesitar crear algunas tablas internas para poder llenar las
estructuras del ALV, así como algunas variables para manejar su
formato de visualización.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE OF
TY_PROGRAMAS,
I_FIELDCAT TYPE SLIS_T_FIELDCAT_ALV,
296
I_SORT_ALV TYPE SLIS_T_SORTINFO_ALV,
GS_LAYOUT TYPE SLIS_LAYOUT_ALV.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: G_PROGRAM TYPE SY-REPID,
G_TITULO TYPE SY-TITLE,
G_REPID TYPE SY-REPID,
GS_SORT TYPE SLIS_T_SORTINFO_ALV.
Vamos a agregar un nuevo evento, que se dispara cuando se ha
terminado la selección de datos.
*=====================================================*
* END-OF-SELECTION
*
*=====================================================*
END-OF-SELECTION.
En este evento, vamos a agregar una serie de funciones que se van a
encargar de formar el ALV.
*=====================================================*
* END-OF-SELECTION
*
*=====================================================*
END-OF-SELECTION.
PERFORM INIT_LAYOUT.
PERFORM FORMATEAR_DATOS_ALV_DET USING I_FIELDCAT[].
PERFORM BUILD_SORT.
PERFORM F_GENERAR_LISTA_ALV.
297
Vamos a crear y analizar cada una de estas funciones.
FORM INIT_LAYOUT.
GS_LAYOUT-ZEBRA = 'X'.
ENDFORM.
" INIT_LAYOUT
Indicamos que queremos que el reporte se muestre con colores
intercalados por registro, es decir, el primero blanco, el segundo gris,
el tercero blanco, etc.
FORM BUILD_SORT.
APPEND INITIAL LINE TO GS_SORT
ASSIGNING <FS_SORT>.
<FS_SORT>-SPOS = 1.
<FS_SORT>-FIELDNAME = 'ID_PROG'.
<FS_SORT>-UP = 'X'.
ENDFORM.
" BUILD_SORT
Primero, indicamos el número de la columna por la cual vamos a
ordenar la lista, segundo, indicamos el nombre del campo
relacionado a dicha columna,
finalmente indicamos que la
ordenación va a ser ascendente.
FORM FORMATEAR_DATOS_ALV_DET USING
T_FIELDCAT TYPE SLIS_T_FIELDCAT_ALV.
298
DATA: L_FIELDCAT TYPE SLIS_FIELDCAT_ALV.
CLEAR L_FIELDCAT.
L_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
L_FIELDCAT-FIELDNAME = 'ID_PROG'.
L_FIELDCAT-SELTEXT_L = 'Id'.
L_FIELDCAT-COL_POS = 1.
L_FIELDCAT-OUTPUTLEN = 5.
APPEND L_FIELDCAT TO T_FIELDCAT.
CLEAR L_FIELDCAT.
L_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
L_FIELDCAT-FIELDNAME = 'NOM_PROG'.
L_FIELDCAT-SELTEXT_L = 'Nombre Programa'.
L_FIELDCAT-COL_POS = 2.
L_FIELDCAT-OUTPUTLEN = 15.
APPEND L_FIELDCAT TO T_FIELDCAT.
CLEAR L_FIELDCAT.
L_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
L_FIELDCAT-FIELDNAME = 'NOMBRE'.
L_FIELDCAT-SELTEXT_L = 'Nombre Lenguaje'.
L_FIELDCAT-COL_POS = 3.
L_FIELDCAT-OUTPUTLEN = 15.
APPEND L_FIELDCAT TO T_FIELDCAT.
CLEAR L_FIELDCAT.
L_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
L_FIELDCAT-FIELDNAME = 'ENTORNO'.
L_FIELDCAT-SELTEXT_L = 'Entorno'.
L_FIELDCAT-COL_POS = 4.
L_FIELDCAT-OUTPUTLEN = 10.
299
APPEND L_FIELDCAT TO T_FIELDCAT.
CLEAR L_FIELDCAT.
L_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
L_FIELDCAT-FIELDNAME = 'CONEX_SAP'.
L_FIELDCAT-SELTEXT_L = 'Conexión con SAP'.
L_FIELDCAT-COL_POS = 5.
L_FIELDCAT-OUTPUTLEN = 15.
APPEND L_FIELDCAT TO T_FIELDCAT.
ENDFORM.
"FORMATEAR_DATOS_ALV_DET
La tabla L_FIELDCAT se llena con todos los campos que
queremos mostrar en nuestro ALV, esta es apendada a la tabla
T_FIELDCAT, que luego es usada para llenar la tabla
I_FIELDCAT que se pasa finalmente al ALV.
TABNAME es el nombre de la tabla que contiene la estructura del
ALV.
FIELDNAME es el nombre del campo que queremos mostrar.
SELTEXT_L es el texto que se va a mostrar en el campo.
COL_POS es la posición que va a tener el campo dentro del ALV.
OUTPUTLEN es la cantidad de caracteres de salida.
Este FORM es el más importante, puesto que indica que campos
deben leerse, de que tamaño deben de ser mostrados, en que
posición, y que texto va a describirlos en la cabecera. En este
ejemplo hemos utilizado pocos campos, y hasta donde mi
experiencia personal ha llegado, el ALV soporta hasta 65 campos
sin ningún problema.
Con todos los ingredientes listos, solo nos falta llamar al ALV.
300
FORM F_GENERAR_LISTA_ALV.
G_PROGRAM = SY-REPID.
G_TITULO = 'Lista de Programas'.
CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'
EXPORTING
I_BUFFER_ACTIVE
= ' '
I_CALLBACK_PROGRAM = G_PROGRAM
I_GRID_TITLE
= G_TITULO
IS_LAYOUT
= GS_LAYOUT
IT_FIELDCAT
= I_FIELDCAT
IT_SORT
= GS_SORT[]
TABLES
T_OUTTAB
= T_PROGRAMAS
EXCEPTIONS
PROGRAM_ERROR
= 1
OTHERS
= 2.
IF SY-SUBRC <> 0.
EXIT.
ENDIF.
ENDFORM.
" F_GENERAR_LISTA_ALV
G_PROGRAM es el programa al cual está enlazado el ALV. (Esta
variable se asigna al parámetro I_CALLBACK_PROGRAM.
G_TITULO es el título que va a llevar el listado ALV. (Esta
variable se asigna al parámetro I_GRID_TITLE).
301
GS_LAYOUT almacena el formato del listado. (Esta tabla se asigna
al parámetro IS_LAYOUT).
I_FIELDCAT almacena los campos que conforman el ALV. (Esta
tabla se asigna al parámetro IT_FIELDCAT).
GS_SORT almacena el orden del ALV. (Esta tabla se asigna al
parámetro IT_SORT).
T_PROGRAMAS es la tabla que almacena todos los registros
obtenidos en el INNER JOIN. (Esta tabla se asigna al parámetro
T_OUTTAB).
Ahora, podemos ejecutar el programa.
302
Ahora que ya vimos como es un ALV, investigemos un poco.
Supongamos que no queremos que sea de tipo ZEBRA, así que
cambiamos el comando.
FORM INIT_LAYOUT.
GS_LAYOUT-ZEBRA = ' '.
ENDFORM.
" INIT_LAYOUT
Como podrán ver, se nota bastante la diferencia.
Agregando una Cabecera al Reporte
Supongamos que queremos agregar una cabecera que muestre
información en el ALV, y con esto no me refiero a una línea como
en el caso anterior, sino a varias que muestren mayor información.
Tenemos que agregar dos tabla nuevas que almacenen los título que
vamos a mostrar en la cabecera.
303
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE OF
TY_PROGRAMAS,
I_FIELDCAT TYPE SLIS_T_FIELDCAT_ALV,
I_SORT_ALV TYPE SLIS_T_SORTINFO_ALV,
GS_LAYOUT TYPE SLIS_LAYOUT_ALV,
GT_LIST_TOP_OF_PAGE TYPE SLIS_T_LISTHEADER,
I_EVENTS TYPE SLIS_T_EVENT.
GS_LIST_TOP_OF_PAGE almacena los título de la cabecera,
mientras que I_EVENT controla los eventos del ALV.
Además necesitamos dos nuevas funciones.
*=====================================================*
* END-OF-SELECTION
*
*=====================================================*
END-OF-SELECTION.
PERFORM INIT_LAYOUT.
PERFORM FORMATEAR_DATOS_ALV_DET USING I_FIELDCAT[].
PERFORM F_FORMATO_PAGE CHANGING GT_LIST_TOP_OF_PAGE.
PERFORM F_FORMATEAR_EVENTOS_ALV USING I_EVENTS[].
PERFORM BUILD_SORT.
PERFORM F_GENERAR_LISTA_ALV.
F_FORMATO_PAGE nos permite definir el texto de la cabecera.
FORM F_FORMATO_PAGE CHANGING GT_TOP_OF_PAGE
TYPE SLIS_T_LISTHEADER.
304
DATA: GS_LINE TYPE SLIS_LISTHEADER.
CLEAR GS_LINE.
GS_LINE-TYP = 'H'.
CONCATENATE 'FECHA:' SY-DATUM
INTO GS_LINE-INFO
SEPARATED BY SPACE.
APPEND GS_LINE TO GT_TOP_OF_PAGE.
CLEAR GS_LINE.
GS_LINE-TYP = 'H'.
CONCATENATE 'HORA:' SY-UZEIT
INTO GS_LINE-INFO
SEPARATED BY SPACE.
APPEND GS_LINE TO GT_TOP_OF_PAGE.
ENDFORM.
"F_FORMATO_PAGE
GS_LINE-TYP es el tipo de cabecera, H equivale a Header
(Cabecera).
GS_LINE-INFO es el texto que se va a mostrar.
GS_LINE se adiciona a la tabla GT_TOP_OF_PAGE.
Mientras que el segundo, nos permite definir el evento que requiere
el ALV para poder activar la cabecera.
FORM F_FORMATEAR_EVENTOS_ALV USING P_EVENTS
TYPE SLIS_T_EVENT.
DATA: L_EVENTS TYPE SLIS_ALV_EVENT.
305
CLEAR L_EVENTS.
L_EVENTS-NAME = 'TOP_OF_PAGE'.
L_EVENTS-FORM = 'TOP_OF_PAGE'.
APPEND L_EVENTS TO P_EVENTS.
ENDFORM.
"F_FORMATEAR_EVENTOS_ALV
Declaramos una variable llamada L_EVENTS que controla los
eventos. En este caso, queremos que el evento sea TOP_OF_PAGE
es decir, la cabecera del ALV.
Para terminar, un último FORM que asigna la cabecera al ALV.
FORM TOP_OF_PAGE.
CALL FUNCTION 'REUSE_ALV_COMMENTARY_WRITE'
EXPORTING
IT_LIST_COMMENTARY = GT_LIST_TOP_OF_PAGE.
ENDFORM.
"TOP_OF_PAGE
La función REUSE_ALV_COMMENTARY_WRITE es la coloca
el título en el ALV.
Y como utilizamos una función que crea el ALV, debemos indicarle
que evento tiene que ser activado.
FORM F_GENERAR_LISTA_ALV.
G_PROGRAM = G_TITULO = SY-REPID.
306
G_TITULO = 'Lista de Programas'.
CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'
EXPORTING
I_BUFFER_ACTIVE
= ' '
I_CALLBACK_PROGRAM = G_PROGRAM
I_GRID_TITLE
= G_TITULO
IS_LAYOUT
= GS_LAYOUT
IT_FIELDCAT
= I_FIELDCAT
IT_SORT
= GS_SORT[]
IT_EVENTS
= I_EVENTS
TABLES
T_OUTTAB
= T_PROGRAMAS
EXCEPTIONS
PROGRAM_ERROR
= 1
OTHERS
= 2.
IF SY-SUBRC <> 0.
EXIT.
ENDIF.
ENDFORM.
" F_GENERAR_LISTA_ALV
Debemos agregar el parámetro IT_EVENTS que recibe la tabla
I_EVENTS, que contiene los detalles del evento.
Ahora, cuando lo ejecutamos, podremos ver esto.
307
Claro que la fecha y lo hora se ven bastante mal, esto es porque se
muestran en el formato interno de SAP. Podemos corregirlos de una
manera muy sencilla.
Simplemente debemos de crear dos variables de tipo texto, obtener
los componentes de la fecha y la hora, y concatenarlos utilizando
separadores tales como “/” y “.”, para luego asignarlos a nuestra
tabla de cabecera.
FORM F_FORMATO_PAGE CHANGING GT_TOP_OF_PAGE
TYPE SLIS_T_LISTHEADER.
DATA: GS_LINE TYPE SLIS_LISTHEADER,
FECHA(10) TYPE C,
HORA(10) TYPE C.
CONCATENATE SY-DATUM+6(2) SY-DATUM+4(2) SY-DATUM+0(4)
INTO FECHA SEPARATED BY '/'.
CONCATENATE SY-UZEIT+0(2) SY-UZEIT+2(2) SY-UZEIT+4(2)
308
INTO HORA SEPARATED BY ':'.
CLEAR GS_LINE.
GS_LINE-TYP = 'H'.
CONCATENATE 'FECHA:' FECHA
INTO GS_LINE-INFO
SEPARATED BY SPACE.
APPEND GS_LINE TO GT_TOP_OF_PAGE.
CLEAR GS_LINE.
GS_LINE-TYP = 'H'.
CONCATENATE 'HORA:' HORA
INTO GS_LINE-INFO
SEPARATED BY SPACE.
APPEND GS_LINE TO GT_TOP_OF_PAGE.
ENDFORM.
"F_FORMATO_PAGE
El nuevo ALV se vería así.
309
Eventos ALV Grid
Una de las grandes ventajas de los ALV’s es que nos permiten
realizar varias tareas relacionadas a eventos, como por ejemplo,
hacer clic en una fila.
Y este evento “clic” es muy importante, puesto que nos permite
interactuar con el registro que hemos seleccionado.
Primero que nada, vamos a definir el evento “clic” y después un par
de ejemplos de su aplicación.
De nuevo, debemos agregar una nueva variable para manejar este
evento.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: G_PROGRAM TYPE SY-REPID,
G_TITULO TYPE SY-TITLE,
G_REPID TYPE SY-REPID,
GS_SORT TYPE SLIS_T_SORTINFO_ALV,
G_USER_COMMAND TYPE SLIS_FORMNAME
VALUE 'USER_COMMAND'.
La variable G_USER_COMMAND tiene el valor por defecto
USER_COMMAND, que es el nombre del evento que va a
manejar.
También, debemos asignar un código de función.
310
FORM INIT_LAYOUT.
GS_LAYOUT-ZEBRA = ' '.
GS_LAYOUT-F2CODE = 'FUNCION'.
ENDFORM.
" INIT_LAYOUT
GS_LAYOUT-F2CODE guarda el nombre que le asignamos al
código de función que en este caso es “FUNCION”.
Debemos crear una nueva función para poder establecer cual va a ser
la acción ha tomar por el ALV cuando el usuario haga doble clic
sobre un registro.
Antes de empezar, debemos declarar un nuevo Field-Symbol.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_SORT> LIKE LINE OF GS_SORT,
<FS_PROGRAMAS> LIKE LINE OF T_PROGRAMAS.
FORM USER_COMMAND USING R_UCOMM LIKE SY-UCOMM
RS_SELFIELD TYPE SLIS_SELFIELD.
DATA: MENSAJE TYPE SHKONTEXT-MELDUNG.
CHECK NOT RS_SELFIELD-TABNAME IS INITIAL.
CASE R_UCOMM.
WHEN 'FUNCION'.
READ TABLE T_PROGRAMAS INDEX RS_SELFIELD-TABINDEX
311
ASSIGNING <FS_PROGRAMAS>.
CONCATENATE <FS_PROGRAMAS>-ID_PROG
<FS_PROGRAMAS>-NOM_PROG
<FS_PROGRAMAS>-NOMBRE
INTO MENSAJE SEPARATED BY SPACE.
CALL FUNCTION 'MESSAGE_TEXT_DISPLAY_WITH_PARA'
EXPORTING
TEXT = MENSAJE.
ENDCASE.
ENDFORM.
"USER_COMMAND
Declaramos una variable MENSAJE la cual va a contener el texto
que queremos mostrar al hacer doble clic en una línea.
Revisamos que la tabla RS_SELFIELD-TABNAME no esté vacía,
es decir, que hayamos seleccionado un registro.
Cuando el código de función que hemos definido, es decir
“FUNCION”, podemos continuar. Leemos la tabla interna con el
índice del registro seleccionado. Concatenamos algunos campos de
la tabla en nuestra variable MENSAJE y llamamos a la función
MESSAGE_TEXT_DISPLAY_WITH_PARA que muestra un
mensaje en la pantalla.
Como era de esperarse, debemos primero asignar un nuevo valor a
nuestra función creadora de ALV’s.
312
FORM F_GENERAR_LISTA_ALV.
G_PROGRAM = G_TITULO = SY-REPID.
G_TITULO = 'Lista de Programas'.
CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'
EXPORTING
I_BUFFER_ACTIVE
= ' '
I_CALLBACK_PROGRAM
= G_PROGRAM
I_GRID_TITLE
= G_TITULO
IS_LAYOUT
= GS_LAYOUT
I_SAVE
= 'A'
IT_FIELDCAT
= I_FIELDCAT
IT_SORT
= GS_SORT[]
IT_EVENTS
= I_EVENTS
I_CALLBACK_USER_COMMAND = G_USER_COMMAND
TABLES
T_OUTTAB
= T_PROGRAMAS
EXCEPTIONS
PROGRAM_ERROR
= 1
OTHERS
= 2.
IF SY-SUBRC <> 0.
EXIT.
ENDIF.
ENDFORM.
" F_GENERAR_LISTA_ALV
Agregamos el parámetro I_CALLBACK_USER_COMMAND que
permite hacer que funcionen los eventos, a este parámetro le
pasamos la variable G_USER_COMMAND.
313
El parámetro I_SAVE = ‘A’ nos permite manejar las variantes.
Cuando ejecutamos el programa, bastará con hacer un doble clic en
cualquier registro para ejecutar el evento.
Si queremos hacer un solo clic, podemos entonces mejorar un poco
el reporte. Hacemos lo siguiente.
FORM INIT_LAYOUT.
GS_LAYOUT-ZEBRA = ' '.
GS_LAYOUT-F2CODE = 'FUNCION'.
GS_LAYOUT-KEY_HOTSPOT = 'X'.
ENDFORM.
" INIT_LAYOUT
GS_LAYOUT-KEY_HOTSPOT indica que los campos pueden ser
del tipo enlace.
CLEAR L_FIELDCAT.
L_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
314
L_FIELDCAT-FIELDNAME = 'ID_PROG'.
L_FIELDCAT-SELTEXT_L = 'Id'.
L_FIELDCAT-KEY
= 'X'.
L_FIELDCAT-COL_POS = 1.
L_FIELDCAT-OUTPUTLEN = 5.
APPEND L_FIELDCAT TO T_FIELDCAT.
El L_FIELDCAT-KEY indica que este campo va a ser de tipo
enlace.
Como pueden ver, los valores del campo ID_PROG, están
subrayados, lo cual indica que son campos claves para la activación
con un clic por parte del usuario.
Pintemos con colores
Aunque esto no es muy común, puede ser que alguna vez tengamos
que pagar un ALV de colores, es decir, pintar un registro, una fila,
una celda.
Primero, lo que vamos a hacer es pintar un registro de algún color.
Para esto, necesitamos modificar un poco nuestra tabla interna.
315
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
ENTORNO TYPE ZLENGUAJES_PROG-ENTORNO,
CONEX_SAP TYPE ZLENGUAJES_PROG-CONEX_SAP,
LINE_COLOR(4) TYPE C,
END OF TY_PROGRAMAS.
Agregamos un campo llamado LINE_COLOR que determina el
color que queremos utilizar.
Luego de obtener los datos, debemos determinar que línea debe
pintarse.
FORM OBTENER_DATOS.
SELECT ID_PROG NOM_PROG NOMBRE ENTORNO CONEX_SAP
INTO TABLE T_PROGRAMAS
FROM ( ZPROGRAMAS INNER JOIN ZLENGUAJES_PROG
ON ZPROGRAMAS~ID EQ ZLENGUAJES_PROG~ID )
WHERE ID_PROG IN S_IDPROG.
LOOP AT T_PROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
IF <FS_PROGRAMAS>-NOMBRE EQ 'RUBY'.
<FS_PROGRAMAS>-LINE_COLOR = 'C510'.
MODIFY T_PROGRAMAS FROM <FS_PROGRAMAS>.
ENDIF.
316
ENDLOOP.
ENDFORM.
" OBTENER_DATOS
Si el registro tiene el valor “RUBY” en el campo NOMBRE,
entonces pintamos la línea de verde utilizando el código C510.
Finalmente, debemos indicar que queremos pintar una línea.
FORM INIT_LAYOUT.
GS_LAYOUT-ZEBRA = ' '.
GS_LAYOUT-F2CODE = 'FUNCION'.
GS_LAYOUT-KEY_HOTSPOT = 'X'.
GS_LAYOUT-INFO_FIELDNAME = 'LINE_COLOR'.
ENDFORM.
" INIT_LAYOUT
Como ven, es bastante sencillo, aunque no necesariamente útil, pero
ya me tocado hacerlo en algún proyecto, así que espero les sirva
también a ustedes.
317
Ahora que ya estamos felices con nuestro colorido ALV, podemos
pasar a algo un poco más complicado, pero simple de todos modos.
Vamos a colorear celdas individuales en un ALV.
Primero, tenemos que crear una tabla interna auxiliar y modificar un
poco la tabla que teníamos, esto porque al menos en el NetWeaver
no se pueden hacer SELECT’s a una tabla interna con DEEP
STRUCTURE, es decir, una tabla interna que tiene como campo a
otra tabla interna.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_PROGRAMAS_AUX,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
ENTORNO TYPE ZLENGUAJES_PROG-ENTORNO,
CONEX_SAP TYPE ZLENGUAJES_PROG-CONEX_SAP,
END OF TY_PROGRAMAS_AUX.
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
ENTORNO TYPE ZLENGUAJES_PROG-ENTORNO,
CONEX_SAP TYPE ZLENGUAJES_PROG-CONEX_SAP,
COLOR_CELL TYPE LVC_T_SCOL,
END OF TY_PROGRAMAS.
318
Debemos crear un nuevo TYPES, y modificar el original agregando
el campo COLOR_CELL que es en realidad, una tabla interna
dentro de nuestra tabla interna. WA_COLOR y IT_COLOR nos
sirven para almacenar algunos parámetros adicionales necesarios
para el color de la celda.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE OF
TY_PROGRAMAS,
T_PROGRAMAS_AUX TYPE STANDARD TABLE OF
TY_PROGRAMAS_AUX,
I_FIELDCAT TYPE SLIS_T_FIELDCAT_ALV,
I_SORT_ALV TYPE SLIS_T_SORTINFO_ALV,
GS_LAYOUT TYPE SLIS_LAYOUT_ALV,
GT_LIST_TOP_OF_PAGE TYPE SLIS_T_LISTHEADER,
I_EVENTS TYPE SLIS_T_EVENT,
WA_COLOR TYPE LVC_S_SCOL,
IT_COLOR TYPE TABLE OF LVC_S_SCOL.
Creamos una tabla interna basada en nuestro nuevo TYPE. Y
agregamos un nuevo Field-Symbol.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_SORT> LIKE LINE OF GS_SORT,
<FS_PROGRAMAS> LIKE LINE OF
319
T_PROGRAMAS,
<FS_PROGRAMAS_AUX> LIKE LINE OF
T_PROGRAMAS_AUX.
Nuestro código va a tener que cambiar un poco. Primero, debemos
hacer el SELECT a nuestra tabla interna auxiliar, luego, pasar todos
los campos a nuestra tabla interna original para recién poder
determinar cuales son los campos que tienen que estar dibujados de
algún color.
No es dificil, pero definitivamente quita más tiempo.
FORM OBTENER_DATOS.
SELECT ID_PROG NOM_PROG NOMBRE ENTORNO CONEX_SAP
INTO TABLE T_PROGRAMAS_AUX
FROM ( ZPROGRAMAS INNER JOIN ZLENGUAJES_PROG
ON ZPROGRAMAS~ID EQ ZLENGUAJES_PROG~ID )
WHERE ID_PROG IN S_IDPROG.
LOOP AT T_PROGRAMAS_AUX
ASSIGNING <FS_PROGRAMAS_AUX>.
REFRESH IT_COLOR.
MOVE 'NOMBRE' TO WA_COLOR-FNAME.
MOVE '6' TO WA_COLOR-COLOR-COL.
APPEND WA_COLOR TO IT_COLOR.
APPEND INITIAL LINE TO
T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>.
MOVE-CORRESPONDING <FS_PROGRAMAS_AUX>
TO <FS_PROGRAMAS>.
IF <FS_PROGRAMAS_AUX>-NOMBRE EQ 'RUBY'.
320
<FS_PROGRAMAS>-COLOR_CELL[] = IT_COLOR[].
ENDIF.
ENDLOOP.
ENDFORM.
" OBTENER_DATOS
Debemos asignar el nombre del campo que se va a colorear a la tabla
WA_COLOR-FNAME.
Asignamos el color que queremos para la celda (En este caso, 6 es
rojo) a la tabla WA_COLOR-COLOR-COL (Como ven,
WA_COLOR tiene una tabla DEEP llamada COLOR).
Apendamos WA_COLOR a la tabla IT_COLOR.
Asignamos los registros de la tabla auxiliar a la tabla original y
verificamos si el campo NOMBRE es igual a “RUBY”, en ese caso,
asignamos
la tabla
IT_COLOR al
DEEP STRUCTURE
COLOR_CELL de nuestra tabla original.
Finalmente, el INIT_LAYOUT también cambia, puesto que como
estamos utilizando una tabla interna para contener los colores,
debemos utilizar los atributos del Layout, indicándoles que ahora
nos toca manejar campos y no registros.
FORM INIT_LAYOUT.
GS_LAYOUT-ZEBRA = ' '.
GS_LAYOUT-F2CODE = 'FUNCION'.
GS_LAYOUT-KEY_HOTSPOT = 'X'.
GS_LAYOUT-COLTAB_FIELDNAME = 'COLOR_CELL'.
321
ENDFORM.
" INIT_LAYOUT
El campo COLTAB_FIELDNAME indica el tipo de evento que se
va a aplicar a la columna de la lista, en este caso “COLOR_CELL”,
es decir, pintar una celda.
El ALV quedaría así.
Ahora que ya sabemos como jugar con los eventos y con algunas
propiedades del ALV, sería bueno que también conocieramos un
poco de su barra de menús, que además de ser Standard, nos brinda
toda la funcionalidad que podamos necesitar, por lo cual, muy rara
vez tendremos que agregar botones adicionales.
Barra de Menús del ALV
Entre las opciones standard que nos ofrece el ALV, tenemos las
siguientes.
322
A Æ Permite mostrar una línea del reporte
B Æ Orden Ascendente
C Æ Orden Descendente
323
D Æ Permite crear filtros adicionales
E Æ Presentación Preliminar de Impresión
324
F Æ Abre Microsoft Excel
G Æ Abre Microsoft Word
H Æ Fichero local
325
I Æ Destinatario de Correo
326
J Æ Función Gráfica
K Æ Modificar Disposición
327
L Æ Seleccionar Disposición
M Æ Grabar disposición
328
N Æ Información
329
ABAP Orientado a Objetos
Introducción
Al igual que todos los productos de SAP, el ABAP ha evolucionado
para mejor.
A partir de la versión 4.6D, ABAP cuenta con una extensión del
lenguage, lo cual permite hacerse dueños de muchos de los puntos
importantes que conforman el paradigma de la orientación a objetos
u OOP (Object Oriented Programming).
Al decir que es una extensión, nos estamos refiriendo, a que el
ABAP no ha sido reemplazado, sino que por el contrario, ha ganado
funcionalidades que le permite continuar siendo el lenguaje de
programación más poderoso para el desarrollo de aplicaciones de
ERP’s (Enterprise Resource Planning).
¿Qué es la Orientación a Objetos?
La Programación Orientada a Objetos, es una serie de reglas,
sentencias y liniamientos que se deben seguir para poder moldear
procesos del mundo real. Es decir, con la POO nosotros podemos de
una manera más clara y sencilla, representar situaciones y/o
procesos.
330
Existen muchos puntos que debemos conocer primero y que son
análogos en casi todos los lenguajes de programación orientados a
objetos, y por supuesto, ABAP Objects no es la excepción.
Una vez que conozcamos estos conceptos, podremos ahondar en el
ABAP Objects, con ejemplos sencillos que ayuden a adquirir una
base sólida para programar con este nuevo modelo de negocio.
Cabe destacar, que si bien en un escenario ideal, toda la
programación debería ser orientada a objetos, esto no ocurre en los
escenarios reales. Muchas veces, se utiliza una combinación de
ambas fuerzas. Por lo cual, no es imperativo desarrollar aplicaciones
100% OO...Sino tal vez un 60-40% sería suficiente (Dependiendo
del caso, claro está).
Conceptos básicos de la POO
Los conceptos más importantes de la POO son.
•
Clase: Una clase, es simplemente un pedazo de código que
define objetos y que define sus propiedades y métodos. Las
clases se componen de dos capas, una pública y otra privada
(Que puede ser PRIVATE o PROTECTED). La capa pública
puede ser accedida por cualquier usuario. La capa privada,
solo puede ser accedida por los objetos creados a partir de la
misma clase. En la capa privada, se guardan las propiedades
de los objetos para poder encapsularlos, y que puedan ser
accesados a través de métodos públicos. Las clases pueden
ser definidas localmente en un programa o de modo global a
331
través de la transacción SE24 (Class Builder). Las clases
están compuestas por diferentes secciones:
o Atributos Æ Son los datos internos dentro de una
clase. Pueden ser definidos como cualquier tipo de
dato ABAP. Existen dos tipos de atributos, de
instancia y estáticos. Para utilizar atributos de
instancia, es necesario crear un objeto. Los atributos
estáticos no requieren de un objeto para poder ser
utilizados.
o Métodos Æ Son los procedimientos de la clase.
Pueden acceder a todos los atributos de una clase y
por lo tanto modificarlos. Son muy parecidos a los
módulos de función.
De igual manera, existen métodos de instancia (Pueden
acceder a todos los atributos de la clase) y métodos estáticos
(Solamente pueden acceder a los atributos estáticos de la
clase).
Las clases poseen dos fases importantes. La primera es la de
Definición (Es donde se declaran los atributos y los métodos)
y la segunda es la de Implementación (En donde se define cual
va a ser la funcionalidad de cada método).
Un pequeño ejemplo de una clase.
332
*-----------------------------------------------------*
* CLASS C_MATH DEFINITION
*
*-----------------------------------------------------*
CLASS C_MATH DEFINITION.
*DEFINICIÓN DE LA CLASE
PUBLIC SECTION.
*SECCIÓN PÚBLICA DE LA CLASE
METHODS:
*MÉTODOS
FACTORIAL.
PRIVATE SECTION.
*SECCIÓN PRIVADA DE LA CLASE
ENDCLASS.
"C_MATH DEFINITION
*-----------------------------------------------------*
* CLASS C_MATH IMPLEMENTATION
*
*-----------------------------------------------------*
CLASS C_MATH IMPLEMENTATION.
*IMPLEMENTACIÓN DE LA CLASE
METHOD FACTORIAL.
*DEFINICIÓN DE UN MÉTODO
ENDMETHOD.
ENDCLASS.
"FACTORIAL
"C_MATH IMPLEMENTATION
Las clases pueden utilizar los llamados Constructores, que lo único
que hacen, es inicializar un objeto con valores en el mismo momento
que está siendo creado.
Esto es útil cuando necesitamos que el objeto tenga un valor por
defecto, si es que el programa no se lo asigna. Es decir, si un usuario
no asigna un valor a la hora de ejecutar el programa, nuestro objeto
siempre tendrá un valor gracias al constructor.
333
*-----------------------------------------------------*
* CLASS C_MATH DEFINITION
*
*-----------------------------------------------------*
CLASS C_MATH DEFINITION.
*DEFINICIÓN DE LA CLASE
PUBLIC SECTION.
*SECCIÓN PÚBLICA DE LA CLASE
METHODS:
*MÉTODOS
CONSTRUCTOR
IMPORTING NAME TYPE STRING,
FACTORIAL.
PRIVATE SECTION.
*SECCIÓN PRIVADA DE LA CLASE
ENDCLASS.
"C_MATH DEFINITION
*-----------------------------------------------------*
* CLASS C_MATH IMPLEMENTATION
*
*-----------------------------------------------------*
CLASS C_MATH IMPLEMENTATION.
*IMPLEMENTACIÓN DE LA CLASE
METHOD CONSTRUCTOR.
WRITE:/ NAME.
*ENVIAMOS EL PARÁMETRO AL CONSTRUCTOR DE LA CLASE
ENDMETHOD.
"CONSTRUCTOR
METHOD FACTORIAL.
*DEFINICIÓN DE UN MÉTODO
ENDMETHOD.
ENDCLASS.
"FACTORIAL
"C_MATH IMPLEMENTATION
Las clases pueden ser definidas utilizando Herencias, que no es más
que crear una clase a partir de otra existente. Esto nos sirve para
334
agregar funcionalidades que queremos que se encuentren separadas.
Es decir, nosotros podemos crear una clase que obtenga el número
de factura de un pedido, y podemos crear otra clase que herede de
nuestra clase factura, pero que además, nos de la posibilidad de
obtener la cuenta contrato de dicha factura. Así, tenemos dos clases,
una se creó heredando características de la primera, pero le agregó
una funcionalidad adicional, haciéndolas distintas e independientes.
La clase que brinda su funcionalidad, es llamada una Super Clase y
la que ha heredado dichas funcionalidades, es llamada una Sub
Clase.
Una Sub Clase se define de la siguiente manera.
*-----------------------------------------------------*
* CLASS C_SUPERMATH DEFINITION
*
*-----------------------------------------------------*
CLASS C_SUPERMATH DEFINITION.
ENDCLASS.
"C_SUPERMATH DEFINITION
*Definición de la clase
*-----------------------------------------------------*
* CLASS C_MATH DEFINITION
*
*-----------------------------------------------------*
CLASS C_MATH DEFINITION
INHERITING FROM C_SUPERMATH .
*Hereda de C_SUPERMATH
PUBLIC SECTION.
*Sección Pública de la clase
METHODS:
*Métodos
FACTORIAL.
335
PRIVATE SECTION.
*SECCIÓN PRIVADA DE LA CLASE
ENDCLASS.
"C_MATH
DEFINITION
*-----------------------------------------------------*
* CLASS C_MATH IMPLEMENTATION
*
*-----------------------------------------------------*
CLASS C_MATH IMPLEMENTATION.
*IMPLEMENTACIÓN DE LA CLASE
METHOD FACTORIAL.
*DEFINICIÓN DE UN MÉTODO
ENDMETHOD.
ENDCLASS.
•
"FACTORIAL
"C_MATH IMPLEMENTATION
Objeto: Los objetos son instancias de una clase. Es decir,
son copias que poseen la misma funcionalidad de la clase que
los creó. No existe un límite acerca de la cantidad de objetos
que se pueden crear a partir de una clase, por lo cual, no hay
preocuparse. Cada objeto creado a partir de una clase, se
comporta como un elemento completamente independiente
de los demás.
Para instanciar un objeto, lo hacemos de la siguiente manera.
*Definimos un tipo de dato que se
*referiencia a la clase C_MATH
DATA DESCR_REF TYPE REF TO C_MATH.
*Creamos e instanciamos un objeto
*con referencia al tipo que creamos.
CREATE OBJECT DESCR_REF.
336
Como programar en ABAP Objects
Hasta ahora, los ejemplos que hemos desarrollado, fueron escritos
utilizando el ABAP tradicional, lo cual no está mal, pero lo mejor es
utilizar las nuevas tecnologías, por lo tanto, vamos a ver un ejemplo
reescritos en ABAP Objects. Tanto en código como con el
Generador de Clases (SE24).
Factorial
REPORT ZDUMMY_PRIMER_PROGRAMA.
SELECTION-SCREEN BEGIN OF SCREEN 101 AS WINDOW.
PARAMETERS NUMBER TYPE I.
SELECTION-SCREEN END OF SCREEN 101.
*-----------------------------------------------------*
*
CLASS FACTORIAL DEFINITION
*-----------------------------------------------------*
CLASS C_FACTORIAL DEFINITION.
PUBLIC SECTION.
CLASS-METHODS: MAIN.
METHODS:
SET_FACT IMPORTING NUM TYPE I,
GET_RESULT RETURNING VALUE(FACT) TYPE I.
PRIVATE SECTION.
DATA FACT TYPE I.
ENDCLASS.
"FACTORIAL DEFINITION
337
*-----------------------------------------------------*
*
CLASS FACTORIAL IMPLEMENTATION
*-----------------------------------------------------*
CLASS C_FACTORIAL IMPLEMENTATION.
METHOD MAIN.
CALL SELECTION-SCREEN '101' STARTING AT 10 10.
IF SY-SUBRC NE 0.
EXIT.
ENDIF.
ENDMETHOD.
"MAIN
METHOD SET_FACT.
FACT = 1.
IF NUM GT 0.
DO NUM TIMES.
FACT = FACT * SY-INDEX.
ENDDO.
ENDIF.
ENDMETHOD.
"SET_FACT
METHOD GET_RESULT.
WRITE: 'El Factorial es: ', ME->FACT.
ENDMETHOD.
ENDCLASS.
"GET_RESULT
"FACTORIAL IMPLEMENTATION
START-OF-SELECTION.
C_FACTORIAL=>MAIN( ).
DATA MY_OBJ TYPE REF TO C_FACTORIAL.
CREATE OBJECT MY_OBJ.
CALL METHOD
MY_OBJ->SET_FACT( EXPORTING NUM = NUMBER ).
CALL METHOD
MY_OBJ->GET_RESULT.
338
Este código se puede ver un poco complicado, extraño e innecesario,
pero creanme que con el tiempo uno se acostumbra y la verdad es
que es lo mejor programar así.
Revisemos un poco el código antes de ejecutarlo.
SELECTION-SCREEN BEGIN OF SCREEN 101 AS WINDOW.
PARAMETERS NUMBER TYPE I.
SELECTION-SCREEN END OF SCREEN 101.
Declaramos un SELECTION-SCREEN pero utilizando un tipo
ventana, esto para poder llamarlo desde nuestra clase.
*-----------------------------------------------------*
*
CLASS FACTORIAL DEFINITION
*
*-----------------------------------------------------*
CLASS C_FACTORIAL DEFINITION.
PUBLIC SECTION.
CLASS-METHODS: MAIN.
METHODS:
SET_FACT IMPORTING NUM TYPE I,
GET_RESULT RETURNING VALUE(FACT) TYPE I.
PRIVATE SECTION.
DATA FACT TYPE I.
ENDCLASS.
"FACTORIAL DEFINITION
Creamos nuestra clase C_FACTORIAL. En la sección pública,
definimos un CLASS-METHOD, es decir, un método de instacia.
Además, declaramos dos métodos, SET_FACT que recibe un
339
parámetro NUM y GET_RESULT que retorna el valor final del
programa.
En la sección privada, declaramos una variable llamada FACT, que
es la que va a tener el resultado del factorial y es la variable que va a
imprimir GET_RESULT.
*-----------------------------------------------------*
*
CLASS FACTORIAL IMPLEMENTATION
*
*-----------------------------------------------------*
CLASS C_FACTORIAL IMPLEMENTATION.
METHOD MAIN.
CALL SELECTION-SCREEN '101' STARTING AT 10 10.
IF SY-SUBRC NE 0.
EXIT.
ENDIF.
ENDMETHOD.
"MAIN
METHOD SET_FACT.
FACT = 1.
IF NUM GT 0.
DO NUM TIMES.
FACT = FACT * SY-INDEX.
ENDDO.
ENDIF.
ENDMETHOD.
"SET_FACT
METHOD GET_RESULT.
WRITE: 'El Factorial es: ', ME->FACT.
ENDMETHOD.
ENDCLASS.
"GET_RESULT
"FACTORIAL IMPLEMENTATION
Implementamos nuestra clase, agregando el código a los métodos.
340
En el método MAIN, llamamos a nuestra pantalla para poder
capturar el valor ingresado por el usuario.
En el método SET_FACT realizamos el algoritmo para determinar
el factorial.
En el método GET_RESULT imprimimos el valor de la variable
FACT. Como se darán cuenta, utilizamos ME->FACT para llamar
a la variable, esto es porque este método no recibe parámetros, por lo
cual necesita obtener el valor de la misma clase, pero como la
variable está en la sección privada, entonces necesitamos acceder a
ella definiendo el nombre de la clase y la variable, con lo cual
quedaría así C_FACTORIAL->FACT. Felizmente, y para no
escribir tanto, podemos escribirlo así ME->FACT, donde ME
equivale a C_FACTORIAL.
START-OF-SELECTION.
C_FACTORIAL=>MAIN( ).
DATA MY_OBJ TYPE REF TO C_FACTORIAL.
CREATE OBJECT MY_OBJ.
CALL METHOD
MY_OBJ->SET_FACT( EXPORTING NUM = NUMBER ).
CALL METHOD
MY_OBJ->GET_RESULT.
Llamamos al método estático MAIN, declaramos un objeto de la
clase C_FACTORIAL, y lo creamos.
Llamamos al método SET_FACT pasando el valor del parámetro
obtenido en la ventana del SELECTION-SCREEN.
Llamamos al método GET_RESULT para mostrar el resultado.
341
Por cuestiones de tamaño de almacenamiento de la variable, los
números pueden ir desde el 1 hasta el 12, así que veamos que pasa si
ingresamos 13.
342
En este caso, se ha producido un error, puesto que el tamaño de la
variable que utilizamos no fue lo suficientemente grande como para
poder almacenar el valor del factorial. La imagen que vemos, es la
de un ShortDump (Es decir, un error grave en la programación).
Esta ventana, nos indica porque se ha producido el error, cuando, en
donde y hasta como podríamos resolverlo.
En esta caso, vamos a utilizar una pequeña artimaña para que
nuestro programa siga funcionando a pesar de este error, para ello,
vamos a utilizar un comando llamado CATCH SYSTEMEXCEPTION. El cual funciona como un TRY-CATCH en Java o
.NET, y lo que hace es simplemente intentar ejecutar una sentencia
y si no puede, nos envía un error personalizado, que permite que el
programa siga funcionando a pesar del error. Para esto, debemos
hacer unas cuantas modificaciones al programa.
CLASS C_FACTORIAL DEFINITION.
PUBLIC SECTION.
CLASS-METHODS: MAIN.
METHODS:
SET_FACT IMPORTING NUM TYPE I,
GET_RESULT RETURNING VALUE(FACT) TYPE I.
PRIVATE SECTION.
DATA: FACT TYPE I,
FLAG TYPE C.
ENDCLASS.
"FACTORIAL DEFINITION
Declaramos una variable llamada FLAG que nos va a servir al
momento de mostrar el mensaje de resultado.
343
METHOD SET_FACT.
CLEAR FLAG.
FACT = 1.
IF NUM GT 0.
DO NUM TIMES.
CATCH SYSTEM-EXCEPTIONS ARITHMETIC_ERRORS = 5.
FACT = FACT * SY-INDEX.
ENDCATCH.
IF SY-SUBRC EQ 5.
FACT = 0.
FLAG = 'X'.
EXIT.
ENDIF.
ENDDO.
ENDIF.
ENDMETHOD.
"SET_FACT
Dentro del método SET_FACT, limpiamos nuestra variable FLAG
y utilizamos un CATCH SYSTEM-EXCEPTIONS, en este caso el
ARITHMETIC_ERRORS, que significa que cualquier error de
tipo Overflow (valor más grande que el tamaño de variable), va a
lanzar un SY-SUBRC igual a 5 en vez de una pantalla de
ShortDump. En el caso de que el SY-SUBRC sea igual a 5,
entonces limpiamos la variable FACT y llenamos con “X” la
variable FLAG. Obviamente, salimos del método utilizando la
sentencia EXIT.
344
METHOD GET_RESULT.
IF FLAG EQ SPACE.
WRITE: 'El Factorial es: ', ME->FACT.
ELSE.
WRITE: 'Error, ingrese valores del 1 al 12'.
ENDIF.
ENDMETHOD.
"GET_RESULT
En el método GET_RESULT, preguntamos si la variable FLAG
está vacía o tiene el valor “X”. Si está vacía, imprimimos el valor del
factorial, si tiene el valor “X”, imprimimos un mensaje de
advertencia.
Ahora, veamos el mismo ejemplo pero utilizando el Generador de
Clases (SE24), gracias a lo cual podríamos obtener el Factorial de
un número desde cualquier programa.
345
346
Primero, debemos ir a la pestaña Attributes (Atributos) para
declarar nuestras variables.
Ambas variables tiene un Level (Nivel) 0 Instance Attribute
(Atributo de Instacia), un Visibility (Visibilidad) 0 Private
(Privado).
Luego, nos dirigimos a la pestaña Methods (Métodos).
Declaramos dos métodos, SET_FACT y GET_RESULT, ambos
con Nivel 0 Atributo de Instacia y Visibilidad 2 Public (Pública).
Para asignar los parámetros, debemos presionar el botón Parameters
(Parámetros)
.
347
SET_FACT tiene el parámetro NUM de tipo IMPORTING.
GET_RESULT tiene el parámetro FACT de tipo RETURNING.
Para poder asignarles código a los métodos, debemos posicionarnos
sobre cada uno de ellos y presionar el botón Code
.
348
Escribimos los códigos para cada método.
method SET_FACT.
CLEAR FLAG.
FACT = 1.
IF NUM GT 0.
DO NUM TIMES.
CATCH SYSTEM-EXCEPTIONS ARITHMETIC_ERRORS = 5.
FACT = FACT * SY-INDEX.
ENDCATCH.
IF SY-SUBRC EQ 5.
FACT = 0.
FLAG = 'X'.
EXIT.
ENDIF.
ENDDO.
ENDIF.
endmethod.
method GET_RESULT.
IF FLAG EQ SPACE.
WRITE: 'El Factorial es: ', ME->FACT.
ELSE.
WRITE: 'Error, ingrese valores del 1 al 12'.
ENDIF.
endmethod.
Con esto, solo nos queda grabar, activar. La clase está lista para ser
utilizada, pero claro, lo mejor es probarlo antes. Para esto,
presionamos el botón Test o presionamos F8
.
349
Como sabemos que SET_FACT es el método que recibe el valor
ingreado por el usario, lo ejecutamos presionando el botón Execute
Method (Ejecutar Método)
.
Ingresamos un valor de prueba y presionamos el botón Execute
(Ejecutar) o presionamos F8
.
De vuelta en la pantalla principal, ejecutamos el método
GET_RESULT.
350
A pesar de que el valor de FACT aparece como 0, simplemente
debemos retroceder para ver el resultado.
Listo, ahora, ya podemos utilizar nuestra nueva clase en un
programa.
REPORT ZDUMMY_PRIMER_PROGRAMA.
SELECTION-SCREEN BEGIN OF SCREEN 101 AS WINDOW.
PARAMETERS NUMBER TYPE I.
SELECTION-SCREEN END OF SCREEN 101.
351
START-OF-SELECTION.
CALL SELECTION-SCREEN '101' STARTING AT 10 10.
IF SY-SUBRC NE 0.
EXIT.
ENDIF.
DATA MY_OBJ TYPE REF TO ZFACTORIAL.
CREATE OBJECT MY_OBJ.
CALL METHOD
MY_OBJ->SET_FACT( EXPORTING NUM = NUMBER ).
CALL METHOD
MY_OBJ->GET_RESULT.
Ejecutamos y veremos que el resultado es el mismo, aunque el
código es mucho más corto y reutilizable.
352
Componentes Orientados a Objetos
Crear un ALV Grid OO
Ya vimos como crear un ALV, pero ahora veremos como se hace
con ABAP Objects.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
ENTORNO TYPE ZLENGUAJES_PROG-ENTORNO,
CONEX_SAP TYPE ZLENGUAJES_PROG-CONEX_SAP,
END OF TY_PROGRAMAS.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS,
GS_LAYOUT TYPE LVC_S_LAYO,
GT_FIELDCAT TYPE LVC_T_FCAT,
GT_SORT TYPE LVC_T_SORT,
GS_VARIANT TYPE DISVARIANT.
353
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: MYCONTAINER TYPE SCRFNAME VALUE 'CUSTOM_ALV',
CUSTOM_CONTAINER TYPE REF TO
CL_GUI_CUSTOM_CONTAINER,
GRID1 TYPE REF TO CL_GUI_ALV_GRID,
X_SAVE.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM CARGAR_DATOS.
PERFORM FILL_LAYOUT.
PERFORM FILL_CATALOG.
PERFORM LLAMAR_ALV.
CALL SCREEN 0100.
*&----------------------------------------------------*
*&
Form
cargar_datos
*
*&----------------------------------------------------*
FORM CARGAR_DATOS.
SELECT ID_PROG NOM_PROG NOMBRE ENTORNO CONEX_SAP
INTO TABLE T_PROGRAMAS
FROM ( ZPROGRAMAS INNER JOIN ZLENGUAJES_PROG
ON ZPROGRAMAS~ID EQ ZLENGUAJES_PROG~ID ).
ENDFORM.
" cargar_datos
354
*&----------------------------------------------------*
*&
Form
fill_layout
*
*&----------------------------------------------------*
FORM FILL_LAYOUT.
GS_LAYOUT-SEL_MODE = 'A'.
ENDFORM.
" fill_layout
*&----------------------------------------------------*
*&
Form
fill_catalog
*
*&----------------------------------------------------*
FORM FILL_CATALOG.
DATA: GS_FIELDCAT TYPE LVC_S_FCAT.
CLEAR GS_FIELDCAT.
GS_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
GS_FIELDCAT-FIELDNAME = 'ID_PROG'.
GS_FIELDCAT-REPTEXT = 'Id'.
GS_FIELDCAT-COL_POS = 1.
GS_FIELDCAT-OUTPUTLEN = 5.
APPEND GS_FIELDCAT TO GT_FIELDCAT.
CLEAR GS_FIELDCAT.
GS_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
GS_FIELDCAT-FIELDNAME = 'NOM_PROG'.
GS_FIELDCAT-REPTEXT = 'Nombre Programa'.
GS_FIELDCAT-COL_POS = 2.
GS_FIELDCAT-OUTPUTLEN = 15.
APPEND GS_FIELDCAT TO GT_FIELDCAT.
355
CLEAR GS_FIELDCAT.
GS_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
GS_FIELDCAT-FIELDNAME = 'NOMBRE'.
GS_FIELDCAT-REPTEXT = 'Nombre Lenguaje'.
GS_FIELDCAT-COL_POS = 3.
GS_FIELDCAT-OUTPUTLEN = 15.
APPEND GS_FIELDCAT TO GT_FIELDCAT.
CLEAR GS_FIELDCAT.
GS_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
GS_FIELDCAT-FIELDNAME = 'ENTORNO'.
GS_FIELDCAT-REPTEXT = 'Entorno'.
GS_FIELDCAT-COL_POS = 4.
GS_FIELDCAT-OUTPUTLEN = 10.
APPEND GS_FIELDCAT TO GT_FIELDCAT.
CLEAR GS_FIELDCAT.
GS_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
GS_FIELDCAT-FIELDNAME = 'CONEX_SAP'.
GS_FIELDCAT-REPTEXT = 'Conexión con SAP'.
GS_FIELDCAT-COL_POS = 5.
GS_FIELDCAT-OUTPUTLEN = 15.
APPEND GS_FIELDCAT TO GT_FIELDCAT.
ENDFORM.
" fill_catalog
*&----------------------------------------------------*
*&
Form
llamar_alv
*
*&----------------------------------------------------*
FORM LLAMAR_ALV.
IF CUSTOM_CONTAINER IS INITIAL.
356
CREATE OBJECT CUSTOM_CONTAINER
EXPORTING
CONTAINER_NAME
= MYCONTAINER
EXCEPTIONS
CNTL_ERROR
= 1
CNTL_SYSTEM_ERROR
= 2
CREATE_ERROR
= 3
LIFETIME_ERROR
= 4
LIFETIME_DYNPRO_DYNPRO_LINK = 5.
ENDIF.
CREATE OBJECT GRID1
EXPORTING
I_PARENT = CUSTOM_CONTAINER.
CALL METHOD GRID1->SET_TABLE_FOR_FIRST_DISPLAY
EXPORTING
IS_VARIANT
= GS_VARIANT
I_SAVE
= X_SAVE
I_DEFAULT
= 'X'
IS_LAYOUT
= GS_LAYOUT
CHANGING
IT_FIELDCATALOG = GT_FIELDCAT
IT_SORT
= GT_SORT[]
IT_OUTTAB
= T_PROGRAMAS[].
CALL METHOD GRID1->SET_READY_FOR_INPUT
EXPORTING
I_READY_FOR_INPUT = 0.
ENDFORM.
" llamar_alv
357
*&----------------------------------------------------*
*&
Module
STATUS_0100
OUTPUT
*
*&----------------------------------------------------*
MODULE STATUS_0100 OUTPUT.
SET PF-STATUS '100'.
SET TITLEBAR '100'.
ENDMODULE.
" STATUS_0100
OUTPUT
*&----------------------------------------------------*
*&
Module
USER_COMMAND_0100
INPUT
*
*&----------------------------------------------------*
MODULE USER_COMMAND_0100 INPUT.
DATA: OK_CODE TYPE SY-UCOMM.
OK_CODE = SY-UCOMM.
CASE OK_CODE.
WHEN 'BACK' OR 'EXIT' OR 'CANCEL'.
SET SCREEN 0.
LEAVE SCREEN.
CLEAR SY-UCOMM.
ENDCASE.
ENDMODULE.
" USER_COMMAND_0100
INPUT
El código no deja de ser un poco extenso, así que vamos a revisarlo
por partes. Aunque antes de continuar, hay un detalle muy
importante que debemos tener en cuenta. Debemos crear un Dynpro
asociado a esta pantalla, en donde el único componente será un
358
Custom Control (Control Personalizado) al cual llamaremos
CUSTOM_ALV.
Continuemos con el código.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
ENTORNO TYPE ZLENGUAJES_PROG-ENTORNO,
CONEX_SAP TYPE ZLENGUAJES_PROG-CONEX_SAP,
END OF TY_PROGRAMAS.
Declaramos un TYPE incluyendo los campos que queremos mostrar
en el ALV.
359
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS,
GS_LAYOUT TYPE LVC_S_LAYO,
GT_FIELDCAT TYPE LVC_T_FCAT,
GT_SORT TYPE LVC_T_SORT,
GS_VARIANT TYPE DISVARIANT.
Declaramos algunas tablas internas, T_PROGRAMAS donde van a
estar los registros para el ALV, GS_LAYOUT donde vamos a
especificar como se visualiza el ALV, GT_FIELDCAT donde va a
estar el catálogo de campos, GT_SORT donde se indican los
criterios de ordenación y GS_VARIANT que indica el manejo de
variantes.
Si bien hay tablas que no vamos a utilizar, debemos declararlas y
llamarlas de todos modos, porque el ALV así nos lo exige.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: MYCONTAINER TYPE SCRFNAME VALUE 'CUSTOM_ALV',
CUSTOM_CONTAINER TYPE REF TO
CL_GUI_CUSTOM_CONTAINER,
GRID1 TYPE REF TO CL_GUI_ALV_GRID,
X_SAVE.
MYCONTAINER es un tipo de variable que guarda el nombre de
nuestro CUSTOM_CONTROL, CUSTOM_CONTAINER hace
360
referencia al contenedor del ALV, GRID1 es un objeto de la clase
CL_GUI_ALV_GRID y X_SAVE es una variable utilizada para
determinar si se graban o no las variantes y de que modo.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM CARGAR_DATOS.
PERFORM FILL_LAYOUT.
PERFORM FILL_CATALOG.
PERFORM LLAMAR_ALV.
CALL SCREEN 0100.
Tenemos varios PERFORM’s, el primero carga los datos, el
segundo determina el formato del ALV, el tercero llena el catálogo y
el cuarto llama al ALV. Como creamos un Dynpro, debemos
llamarlo.
*&----------------------------------------------------*
*&
Form
cargar_datos
*
*&----------------------------------------------------*
FORM CARGAR_DATOS.
SELECT ID_PROG NOM_PROG NOMBRE ENTORNO CONEX_SAP
INTO TABLE T_PROGRAMAS
FROM ( ZPROGRAMAS INNER JOIN ZLENGUAJES_PROG
ON ZPROGRAMAS~ID EQ ZLENGUAJES_PROG~ID ).
ENDFORM.
" cargar_datos
361
Hacemos un INNER JOIN para seleccionar los datos de la tablas.
*&----------------------------------------------------*
*&
Form
fill_layout
*
*&----------------------------------------------------*
FORM FILL_LAYOUT.
GS_LAYOUT-SEL_MODE = 'A'.
ENDFORM.
" fill_layout
Establecemos el modo se selección.
*&----------------------------------------------------*
*&
Form
fill_catalog
*
*&----------------------------------------------------*
FORM FILL_CATALOG.
DATA: GS_FIELDCAT TYPE LVC_S_FCAT.
CLEAR GS_FIELDCAT.
GS_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
GS_FIELDCAT-FIELDNAME = 'ID_PROG'.
GS_FIELDCAT-REPTEXT = 'Id'.
GS_FIELDCAT-COL_POS = 1.
GS_FIELDCAT-OUTPUTLEN = 5.
APPEND GS_FIELDCAT TO GT_FIELDCAT.
CLEAR GS_FIELDCAT.
GS_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
GS_FIELDCAT-FIELDNAME = 'NOM_PROG'.
362
GS_FIELDCAT-REPTEXT = 'Nombre Programa'.
GS_FIELDCAT-COL_POS = 2.
GS_FIELDCAT-OUTPUTLEN = 15.
APPEND GS_FIELDCAT TO GT_FIELDCAT.
CLEAR GS_FIELDCAT.
GS_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
GS_FIELDCAT-FIELDNAME = 'NOMBRE'.
GS_FIELDCAT-REPTEXT = 'Nombre Lenguaje'.
GS_FIELDCAT-COL_POS = 3.
GS_FIELDCAT-OUTPUTLEN = 15.
APPEND GS_FIELDCAT TO GT_FIELDCAT.
CLEAR GS_FIELDCAT.
GS_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
GS_FIELDCAT-FIELDNAME = 'ENTORNO'.
GS_FIELDCAT-REPTEXT = 'Entorno'.
GS_FIELDCAT-COL_POS = 4.
GS_FIELDCAT-OUTPUTLEN = 10.
APPEND GS_FIELDCAT TO GT_FIELDCAT.
CLEAR GS_FIELDCAT.
GS_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
GS_FIELDCAT-FIELDNAME = 'CONEX_SAP'.
GS_FIELDCAT-REPTEXT = 'Conexión con SAP'.
GS_FIELDCAT-COL_POS = 5.
GS_FIELDCAT-OUTPUTLEN = 15.
APPEND GS_FIELDCAT TO GT_FIELDCAT.
ENDFORM.
" fill_catalog
Llenamos el catálogo del ALV.
363
*&----------------------------------------------------*
*&
Form
llamar_alv
*
*&----------------------------------------------------*
FORM LLAMAR_ALV.
IF CUSTOM_CONTAINER IS INITIAL.
CREATE OBJECT CUSTOM_CONTAINER
EXPORTING
CONTAINER_NAME
= MYCONTAINER
EXCEPTIONS
CNTL_ERROR
= 1
CNTL_SYSTEM_ERROR
= 2
CREATE_ERROR
= 3
LIFETIME_ERROR
= 4
LIFETIME_DYNPRO_DYNPRO_LINK = 5.
ENDIF.
CREATE OBJECT GRID1
EXPORTING
I_PARENT = CUSTOM_CONTAINER.
CALL METHOD GRID1->SET_TABLE_FOR_FIRST_DISPLAY
EXPORTING
IS_VARIANT
= GS_VARIANT
I_SAVE
= X_SAVE
I_DEFAULT
= 'X'
IS_LAYOUT
= GS_LAYOUT
CHANGING
IT_FIELDCATALOG = GT_FIELDCAT
IT_SORT
= GT_SORT[]
IT_OUTTAB
= T_PROGRAMAS[].
364
CALL METHOD GRID1->SET_READY_FOR_INPUT
EXPORTING
I_READY_FOR_INPUT = 0.
ENDFORM.
" llamar_alv
Creamos el objeto CUSTOM_CONTAINER apuntando a nuestro
CUSTOM_CONTROL (Cuyo nombre está almacenado en la
variable MYCONTAINER).
Creamos el objeto GRID1, especificando que se debe mostrar dentro
del contenedor CUSTOM_CONTAINER.
Llamamos al
método
SET_TABLE_FOR_FIRST_DISPLAY
pasándole los parámetros correspondientes.
Llamamos al método SET_READY_FOR_INPUT pasando 0 como
parámetro, lo cual significa que el ALV no podrá ser modificado.
*&----------------------------------------------------*
*&
Module
STATUS_0100
OUTPUT
*
*&----------------------------------------------------*
MODULE STATUS_0100 OUTPUT.
SET PF-STATUS '100'.
SET TITLEBAR '100'.
ENDMODULE.
" STATUS_0100
OUTPUT
Establecemos el menú y el título de la aplicación.
365
*&----------------------------------------------------*
*&
Module
USER_COMMAND_0100
INPUT
*
*&----------------------------------------------------*
MODULE USER_COMMAND_0100 INPUT.
DATA: OK_CODE TYPE SY-UCOMM.
OK_CODE = SY-UCOMM.
CASE OK_CODE.
WHEN 'BACK' OR 'EXIT' OR 'CANCEL'.
SET SCREEN 0.
LEAVE SCREEN.
CLEAR SY-UCOMM.
ENDCASE.
ENDMODULE.
" USER_COMMAND_0100
INPUT
Definimos las acciones de los códigos de función.
Como ven, el ALV está listo para salir a producción.
366
Agregar validaciones y eventos
Si bien el programa funciona, no es muy útil que digamos puesto
que no hace...nada...Así que vamos a agregar algunas cosas
adicionales...
Primero, nos vamos al Menu Painter (SE41) y agregamos un nuevo
botón.
Debemos llamar a la clase LCL_EVENT_RECIEVER, pero al
mismo tiempo debemos modificar su estructura (Solamente dentro
de nuestro programa, claro está...)
CLASS LCL_EVENT_RECEIVER DEFINITION DEFERRED.
Agregamos un nuevo campos a nuestro TYPE TY_PROGRAMAS
y creamos un nuevo TYPE llamado TY_NOMBRE.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZPROGRAMAS-ID_PROG,
NOM_PROG TYPE ZPROGRAMAS-NOM_PROG,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
367
ENTORNO TYPE ZLENGUAJES_PROG-ENTORNO,
CONEX_SAP TYPE ZLENGUAJES_PROG-CONEX_SAP,
ID TYPE ZPROGRAMAS-ID,
END OF TY_PROGRAMAS.
TYPES: BEGIN OF TY_NOMBRE,
ID TYPE ZLENGUAJES_PROG-ID,
NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE,
END OF TY_NOMBRE.
Agregamos algunas tablas internas.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE
OF TY_PROGRAMAS,
T_NOMBRE TYPE STANDARD TABLE
OF TY_NOMBRE,
LT_F4 TYPE LVC_T_F4 WITH HEADER LINE,
RETURN_TAB TYPE STANDARD TABLE OF
DDSHRETVAL WITH HEADER LINE,
T_STABLE TYPE STANDARD TABLE OF
LVC_S_STBL WITH HEADER LINE,
GS_LAYOUT TYPE LVC_S_LAYO,
GT_FIELDCAT TYPE LVC_T_FCAT,
GT_SORT TYPE LVC_T_SORT,
GS_VARIANT TYPE DISVARIANT.
T_NOMBRE nos sirve para crear una ayuda de búsqueda
personalizada.
368
LT_F4 nos sirve para determinar que campos van a tener asignada
una ayuda de búsqueda.
RETURN_TAB es la tabla que nos devuelve el valor seleccionado
por la función para crear ayudas de búsqueda.
T_STABLE
nos permite que el ALV mantenga su posición al
momento de actualizar los datos.
Agregamos algunas variables.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: MYCONTAINER TYPE SCRFNAME VALUE 'CUSTOM_ALV',
CUSTOM_CONTAINER TYPE REF TO
CL_GUI_CUSTOM_CONTAINER,
GRID1 TYPE REF TO CL_GUI_ALV_GRID,
EVENT_RECEIVER TYPE REF TO LCL_EVENT_RECEIVER,
X_SAVE,
W_ERROR TYPE C,
L_VALID(1) TYPE C.
EVENT_RECIEVER nos permite crear una referencia para llamar
eventos en el ALV.
W_ERROR nos indica si hay algún error en la modificación de
datos.
L_VALID indica si la operación es válida o no.
369
Agregamos algunos Field-Symbols.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_NOMBRE> LIKE LINE OF T_NOMBRE,
<FS_PROGRAMAS> LIKE LINE OF
T_PROGRAMAS.
Redefinimos la clase LCL_EVENT_RECIEVER.
*-----------------------------------------------------*
*
CLASS LCL_EVENT_RECEIVER DEFINITION
*
*-----------------------------------------------------*
CLASS LCL_EVENT_RECEIVER DEFINITION.
PUBLIC SECTION.
METHODS: HANDLE_DATA_CHANGED
FOR EVENT DATA_CHANGED OF CL_GUI_ALV_GRID
IMPORTING ER_DATA_CHANGED,
HANDLE_F4_HELP
FOR EVENT ONF4 OF CL_GUI_ALV_GRID
IMPORTING E_FIELDNAME ES_ROW_NO ER_EVENT_DATA.
ENDCLASS.
"LCL_EVENT_RECEIVER DEFINITION
Definimos dos métodos, HANDLE_DATA_CHANGED (Que
valida
si
algún
campo
ha
cambiado
su
valor)
y
HANDLE_F4_HELP que sirve para asignar las ayudas de
búsqueda dinámicas.
370
*-----------------------------------------------------*
*
CLASS lcl_event_receiver IMPLEMENTATION
*
*-----------------------------------------------------*
CLASS LCL_EVENT_RECEIVER IMPLEMENTATION.
METHOD HANDLE_DATA_CHANGED.
PERFORM DATA_CHANGED USING ER_DATA_CHANGED.
ENDMETHOD.
"HANDLE_DATA_CHANGED
METHOD HANDLE_F4_HELP.
PERFORM HANDLE_ONF4 USING E_FIELDNAME ES_ROW_NO.
ER_EVENT_DATA->M_EVENT_HANDLED = 'X'.
ENDMETHOD.
"HANDLE_F4_HELP
ENDCLASS.
"LCL_EVENT_RECEIVER IMPLEMENTATION
Dentro del método HANDLE_DATA_CHANGED llamamos al
FORM DATA_CHANGED.
Dentro del método HANDLE_F4_HELP llamamos al FORM
HANDEL_ONF4.
ER_EVENT_DATA->M_EVENT_HANDLED
=
‘X’,
especificamos que queremos activar el evento.
*&----------------------------------------------------*
*&
Form
cargar_datos
*
*&----------------------------------------------------*
FORM CARGAR_DATOS.
SELECT ID_PROG NOM_PROG NOMBRE ENTORNO CONEX_SAP
INTO TABLE T_PROGRAMAS
FROM ( ZPROGRAMAS INNER JOIN ZLENGUAJES_PROG
ON ZPROGRAMAS~ID EQ ZLENGUAJES_PROG~ID ).
371
SELECT ID NOMBRE
INTO TABLE T_NOMBRE
FROM ZLENGUAJES_PROG.
ENDFORM.
" cargar_datos
Agregamos un nuevo select, esta vez a ZLENGUAJES_PROG
para poder obtener los valores necesarios para nuestra ayuda de
búsqueda dinámica.
CLEAR GS_FIELDCAT.
GS_FIELDCAT-TABNAME = 'T_PROGRAMAS'.
GS_FIELDCAT-FIELDNAME = 'NOMBRE'.
GS_FIELDCAT-EDIT
= 'X'.
GS_FIELDCAT-F4AVAILABL = 'X'.
GS_FIELDCAT-REPTEXT = 'Nombre Lenguaje'.
GS_FIELDCAT-COL_POS = 3.
GS_FIELDCAT-OUTPUTLEN = 15.
APPEND GS_FIELDCAT TO GT_FIELDCAT.
Dentro del catálogo, solamente vamos a modificar un campo, por lo
que agregamos EDIT para que pueda modificarse y F4AVAILABL
para que acepte la ayuda de búsqueda dinámica.
*&----------------------------------------------------*
*&
Form
llamar_alv
*
*&----------------------------------------------------*
FORM LLAMAR_ALV.
372
IF CUSTOM_CONTAINER IS INITIAL.
CREATE OBJECT CUSTOM_CONTAINER
EXPORTING
CONTAINER_NAME
= MYCONTAINER
EXCEPTIONS
CNTL_ERROR
= 1
CNTL_SYSTEM_ERROR
= 2
CREATE_ERROR
= 3
LIFETIME_ERROR
= 4
LIFETIME_DYNPRO_DYNPRO_LINK = 5.
ENDIF.
CREATE OBJECT GRID1
EXPORTING
I_PARENT = CUSTOM_CONTAINER.
CREATE OBJECT EVENT_RECEIVER.
LT_F4-FIELDNAME = 'NOMBRE'.
LT_F4-REGISTER = 'X' .
LT_F4-GETBEFORE = 'X' .
LT_F4-CHNGEAFTER = 'X' .
APPEND LT_F4.
SET HANDLER EVENT_RECEIVER>HANDLE_DATA_CHANGED FOR GRID1.
SET HANDLER EVENT_RECEIVER->HANDLE_F4_HELP FOR GRID1.
CALL METHOD GRID1->REGISTER_F4_FOR_FIELDS
EXPORTING
IT_F4 = LT_F4[].
373
IF SY-BATCH IS INITIAL.
CALL METHOD GRID1->REGISTER_EDIT_EVENT
EXPORTING
I_EVENT_ID = CL_GUI_ALV_GRID=>MC_EVT_MODIFIED.
ENDIF.
CALL METHOD GRID1->SET_TABLE_FOR_FIRST_DISPLAY
EXPORTING
IS_VARIANT
= GS_VARIANT
I_SAVE
= X_SAVE
I_DEFAULT
= 'X'
IS_LAYOUT
= GS_LAYOUT
CHANGING
IT_FIELDCATALOG = GT_FIELDCAT
IT_SORT
= GT_SORT[]
IT_OUTTAB
= T_PROGRAMAS[].
CALL METHOD GRID1->SET_READY_FOR_INPUT
EXPORTING
I_READY_FOR_INPUT = 1.
ENDFORM.
" llamar_alv
Creamos el objeto EVENT_RECIEVER para poder asignar los
eventos al ALV.
Llenamos la tabla LT_F4, con el campo que tendrá la ayuda de
búsqueda dinámica.
Establecemos los eventos utilizando el comando SET HANDLER.
374
La variable SY-BATCH nos indica si estamos ejecutando el
programa en fondo o en modo directo, si es en modo directo,
entonces registramos el evento de modificación.
*&----------------------------------------------------*
*&
Module
USER_COMMAND_0100
INPUT
*
*&----------------------------------------------------*
MODULE USER_COMMAND_0100 INPUT.
DATA: OK_CODE TYPE SY-UCOMM.
OK_CODE = SY-UCOMM.
CLEAR SY-UCOMM.
CASE OK_CODE.
WHEN 'BACK' OR 'EXIT' OR 'CANCEL'.
SET SCREEN 0.
LEAVE SCREEN.
WHEN 'SAVE'.
CALL METHOD GRID1->CHECK_CHANGED_DATA
IMPORTING
E_VALID = L_VALID.
IF L_VALID EQ 'X'.
PERFORM GRABAR_DATOS.
ENDIF.
ENDCASE.
ENDMODULE.
" USER_COMMAND_0100
INPUT
Agregamos el código de función SAVE. Validamos que los registros
del ALV hayan cambiado de valor y llamamos al FORM
GRABAR_DATOS para guardar los cambios en la Base de Datos.
375
*&----------------------------------------------------*
*&
Form
data_changed
*
*&----------------------------------------------------*
FORM DATA_CHANGED USING RR_DATA_CHANGED
TYPE REF TO
CL_ALV_CHANGED_DATA_PROTOCOL.
DATA: W_NEW,
LS_MOD_CELLS TYPE LVC_S_MODI,
LS_CELLS TYPE LVC_S_MODI.
DATA: L_NOMBRE TYPE ZLENGUAJES_PROG-NOMBRE.
LOOP AT RR_DATA_CHANGED->MT_GOOD_CELLS
INTO LS_MOD_CELLS.
CASE LS_MOD_CELLS-FIELDNAME.
WHEN 'NOMBRE'.
CALL METHOD RR_DATA_CHANGED->GET_CELL_VALUE
EXPORTING
I_ROW_ID
= LS_MOD_CELLS-ROW_ID
I_FIELDNAME = LS_MOD_CELLS-FIELDNAME
IMPORTING
E_VALUE
= L_NOMBRE.
IF L_NOMBRE IS INITIAL.
CALL METHOD
RR_DATA_CHANGED->ADD_PROTOCOL_ENTRY
EXPORTING
I_MSGID
= '0K'
I_MSGNO
= '000'
I_MSGTY
= 'E'
I_MSGV1
= 'Seleccione algún nombre'
I_FIELDNAME = LS_MOD_CELLS-FIELDNAME
376
I_ROW_ID
= LS_MOD_CELLS-ROW_ID.
W_ERROR = 'X'.
ELSE.
READ TABLE T_NOMBRE WITH KEY
NOMBRE = L_NOMBRE
ASSIGNING <FS_NOMBRE>.
IF SY-SUBRC NE 0.
CALL METHOD
RR_DATA_CHANGED->ADD_PROTOCOL_ENTRY
EXPORTING
I_MSGID
= '0K'
I_MSGNO
= '000'
I_MSGTY
= 'E'
I_MSGV1
= 'Nombre ingresado no existe'
I_FIELDNAME = LS_MOD_CELLS-FIELDNAME
I_ROW_ID
= LS_MOD_CELLS-ROW_ID.
W_ERROR = 'X'.
ELSE.
CALL METHOD RR_DATA_CHANGED->MODIFY_CELL
EXPORTING
I_ROW_ID
= LS_MOD_CELLS-ROW_ID
I_FIELDNAME = 'ID'
I_VALUE
= <FS_NOMBRE>-ID.
ENDIF.
ENDIF.
ENDCASE.
ENDLOOP.
ENDFORM.
Hacemos
"data_changed
un
LOOP
a
RR_DATA_CHANGED-
>MT_GOOD_CELLS, es decir a los campos que han cambiado de
377
valor. Llamamos al método GET_CELL_VALUE para validar el
nuevo contenido del campo. Si está vacío, mostramos un mensaje de
error con el método ADD_PROTOCOL_ENTRY, en caso
contrario leemos la tabla interna T_NOMBRE para validar que el
valor exista. Si no hay más problemas, llamamos al método
MODIFY_CELL para modificar el campo ID (Puesto que nosotros
hemos modificado el campo NOMBRE, pero necesitamos que a su
vez se modifique también el campo ID).
*&----------------------------------------------------*
*&
Form
handle_onf4
*
*&----------------------------------------------------*
FORM HANDLE_ONF4 USING P_E_FIELDNAME
P_ES_ROW_NO STRUCTURE
LVC_S_ROID.
CASE P_E_FIELDNAME.
WHEN 'NOMBRE'.
CALL FUNCTION 'F4IF_INT_TABLE_VALUE_REQUEST'
EXPORTING
RETFIELD
= 'NOMBRE'
VALUE_ORG
= 'S'
TABLES
VALUE_TAB
= T_NOMBRE
RETURN_TAB
= RETURN_TAB
EXCEPTIONS
PARAMETER_ERROR = 1
NO_VALUES_FOUND = 2
OTHERS
= 3.
378
IF NOT RETURN_TAB[] IS INITIAL.
READ TABLE RETURN_TAB INDEX 1.
READ TABLE T_PROGRAMAS INDEX P_ES_ROW_NO-ROW_ID
ASSIGNING <FS_PROGRAMAS>.
READ TABLE T_NOMBRE WITH KEY
NOMBRE = RETURN_TAB-FIELDVAL
ASSIGNING <FS_NOMBRE>.
<FS_PROGRAMAS>-NOMBRE = RETURN_TAB-FIELDVAL.
<FS_PROGRAMAS>-ID = <FS_NOMBRE>-ID.
CALL METHOD GRID1->REFRESH_TABLE_DISPLAY
EXPORTING
IS_STABLE = T_STABLE.
ENDIF.
ENDCASE.
ENDFORM.
"handle_onf4
Llamamos al método F4IF_INT_TABLE_VALUE_REQUEST
para poder mostrar la ayuda de búsqueda dinámica. Llamamos al
método REFRESH_TABLE_DISPLAY para refrescar el contenido
del ALV.
*&----------------------------------------------------*
*&
Form
grabar_datos
*
*&----------------------------------------------------*
FORM GRABAR_DATOS.
LOOP AT T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>
WHERE ID NE SPACE.
UPDATE ZPROGRAMAS SET ID = <FS_PROGRAMAS>-ID
WHERE ID_PROG EQ <FS_PROGRAMAS>-ID_PROG.
379
IF SY-SUBRC EQ 0.
COMMIT WORK.
ELSE.
ROLLBACK WORK.
ENDIF.
ENDLOOP.
ENDFORM.
" GRABAR_DATOS
Recorremos todos los registros de nuestra tabla interna, que tengan
un valor en el campo ID, luego utilizamos un UPDATE para
actualizar los valores de la Base de Datos.
Grabamos, activamos y ejecutamos.
380
Crear un ALV Tree OO
Un ALV Tree es un tipo de reporte jerárquico, en donde un nodo
principal agrupa subnodos u hojas.
Este tipo de reporte no es muy utilizado, pero de todos modos es
muy intersante y vale la pena revisarlo.
Este es el código.
REPORT ZDUMMY_PRIMER_PROGRAMA.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_PROGRAMAS,
LENGUAJE TYPE ZVLENGUAJES_PROG-LENGUAJE,
NOM_PROG TYPE ZVLENGUAJES_PROG-NOM_PROG,
ENTORNO TYPE ZVLENGUAJES_PROG-ENTORNO,
END OF TY_PROGRAMAS.
TYPES: BEGIN OF TY_HEADER,
LENGUAJE TYPE ZVLENGUAJES_PROG-LENGUAJE,
END OF TY_HEADER.
381
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE OF
TY_PROGRAMAS,
T_TREE TYPE STANDARD TABLE OF
TY_PROGRAMAS WITH HEADER LINE,
T_HEADER TYPE STANDARD TABLE OF
TY_HEADER,
GT_FIELDCAT_TREE TYPE LVC_T_FCAT.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: OK_CODE TYPE SY-UCOMM,
G_ALV_TREE TYPE REF TO CL_GUI_ALV_TREE,
G_CUSTOM_CONTAINER TYPE REF TO
CL_GUI_CUSTOM_CONTAINER,
L_HIERARCHY_HEADER TYPE TREEV_HHDR.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE OF
T_PROGRAMAS,
<FS_HEADER> LIKE LINE OF
T_HEADER,
<FS_TREE> LIKE LINE OF
T_PROGRAMAS.
382
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM CARGAR_DATOS.
PERFORM INIT_TREE.
CALL SCREEN 0100.
*&----------------------------------------------------*
*&
Form
CARGAR_DATOS
*
*&----------------------------------------------------*
FORM CARGAR_DATOS.
SELECT LENGUAJE NOM_PROG ENTORNO
INTO TABLE T_PROGRAMAS
FROM ZVLENGUAJES_PROG.
LOOP AT T_PROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
APPEND INITIAL LINE TO
T_HEADER ASSIGNING <FS_HEADER>.
<FS_HEADER>-LENGUAJE = <FS_PROGRAMAS>-LENGUAJE.
ENDLOOP.
SORT T_HEADER.
DELETE ADJACENT DUPLICATES FROM T_HEADER.
ENDFORM.
" CARGAR_DATOS
383
*&----------------------------------------------------*
*&
Module
STATUS_0100
OUTPUT
*
*&----------------------------------------------------*
MODULE STATUS_0100 OUTPUT.
SET PF-STATUS '100'.
SET TITLEBAR '100'.
ENDMODULE.
" STATUS_0100
OUTPUT
*&----------------------------------------------------*
*&
Module
USER_COMMAND_0100
INPUT
*
*&----------------------------------------------------*
MODULE USER_COMMAND_0100 INPUT.
OK_CODE = SY-UCOMM.
CLEAR SY-UCOMM.
CASE OK_CODE.
WHEN 'BACK' OR 'STOP' OR 'CANCEL'.
SET SCREEN 0.
LEAVE SCREEN.
ENDCASE.
ENDMODULE.
" USER_COMMAND_0100
INPUT
384
*&----------------------------------------------------*
*&
Form
INIT_TREE
*
*&----------------------------------------------------*
FORM INIT_TREE.
DATA: L_TREE_CONTAINER_NAME(30) TYPE C.
L_TREE_CONTAINER_NAME = 'CUSTOM_ALV'.
CREATE OBJECT G_CUSTOM_CONTAINER
EXPORTING
CONTAINER_NAME
=
L_TREE_CONTAINER_NAME
EXCEPTIONS
CNTL_ERROR
= 1
CNTL_SYSTEM_ERROR
= 2
CREATE_ERROR
= 3
LIFETIME_ERROR
= 4
LIFETIME_DYNPRO_DYNPRO_LINK = 5.
CREATE OBJECT G_ALV_TREE
EXPORTING
PARENT
= G_CUSTOM_CONTAINER
NODE_SELECTION_MODE
=
CL_GUI_COLUMN_TREE=>NODE_SEL_MODE_SINGLE
ITEM_SELECTION
= ''
NO_HTML_HEADER
= 'X'
NO_TOOLBAR
= ''
EXCEPTIONS
CNTL_ERROR
= 1
CNTL_SYSTEM_ERROR
= 2
CREATE_ERROR
= 3
385
LIFETIME_ERROR
= 4
ILLEGAL_NODE_SELECTION_MODE = 5
FAILED
= 6
ILLEGAL_COLUMN_NAME
= 7.
PERFORM FILL_CATALOG_TREE.
PERFORM BUILD_HIERARCHY_HEADER CHANGING
L_HIERARCHY_HEADER.
CALL METHOD G_ALV_TREE->SET_TABLE_FOR_FIRST_DISPLAY
EXPORTING
IS_HIERARCHY_HEADER = L_HIERARCHY_HEADER
CHANGING
IT_OUTTAB
= T_TREE[]
IT_FIELDCATALOG
= GT_FIELDCAT_TREE.
PERFORM CREATE_HIERARCHY.
ENDFORM.
" INIT_TREE
*&----------------------------------------------------*
*&
Form
fill_catalog_tree
*
*&----------------------------------------------------*
FORM FILL_CATALOG_TREE.
DATA: GS_FIELDCAT TYPE LVC_S_FCAT.
CLEAR GS_FIELDCAT.
GS_FIELDCAT-COL_POS
= 1.
GS_FIELDCAT-FIELDNAME = 'NOM_PROG'.
GS_FIELDCAT-SCRTEXT_S = 'Nom. Programa'.
GS_FIELDCAT-TABNAME
= 'T_PROGRAMAS'.
GS_FIELDCAT-OUTPUTLEN = 20.
386
APPEND GS_FIELDCAT TO GT_FIELDCAT_TREE.
CLEAR GS_FIELDCAT.
GS_FIELDCAT-COL_POS
= 2.
GS_FIELDCAT-FIELDNAME = 'ENTORNO'.
GS_FIELDCAT-SCRTEXT_S = 'Entorno'.
GS_FIELDCAT-TABNAME
= 'T_PROGRAMAS'.
GS_FIELDCAT-OUTPUTLEN = 15.
APPEND GS_FIELDCAT TO GT_FIELDCAT_TREE.
ENDFORM.
"fill_catalog_tree
*&----------------------------------------------------*
*
FORM build_hierarchy_header
*
*&----------------------------------------------------*
FORM BUILD_HIERARCHY_HEADER CHANGING
P_HIERARCHY_HEADER
TYPE TREEV_HHDR.
CLEAR P_HIERARCHY_HEADER.
P_HIERARCHY_HEADER-HEADING = 'Código'(300).
P_HIERARCHY_HEADER-WIDTH = 60.
P_HIERARCHY_HEADER-WIDTH_PIX = ' '.
ENDFORM.
"BUILD_HIERARCHY_HEADER
*&----------------------------------------------------*
*&
Form
create_hierarchy
*
*&----------------------------------------------------*
FORM CREATE_HIERARCHY.
DATA: L_ROOT_KEY TYPE LVC_NKEY,
L_NEXT_KEY TYPE LVC_NKEY,
387
L_LAST_KEY TYPE LVC_NKEY,
HEADER TYPE STRING,
W_MENGE_TEXT(13) TYPE C.
CLEAR L_ROOT_KEY.
LOOP AT T_HEADER ASSIGNING <FS_HEADER>.
HEADER = <FS_HEADER>-LENGUAJE.
CLEAR L_ROOT_KEY.
CLEAR L_NEXT_KEY.
PERFORM ADD_NODE USING HEADER L_ROOT_KEY
CHANGING L_NEXT_KEY.
LOOP AT T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>
WHERE LENGUAJE EQ <FS_HEADER>-LENGUAJE.
MOVE-CORRESPONDING <FS_PROGRAMAS> TO T_TREE.
PERFORM ADD_LEAF USING T_TREE L_NEXT_KEY
CHANGING L_LAST_KEY.
ENDLOOP.
ENDLOOP.
CALL METHOD G_ALV_TREE->FRONTEND_UPDATE.
ENDFORM.
"CREATE_HIERARCHY
*&----------------------------------------------------*
*
FORM ADD_NODE
*
*&----------------------------------------------------*
FORM ADD_NODE USING L_NAME L_ROOT_KEY
CHANGING L_NEXT_KEY.
388
DATA: L_NODE_TEXT TYPE LVC_VALUE,
LS_TREE TYPE TY_PROGRAMAS.
L_NODE_TEXT =
L_NAME.
CALL METHOD G_ALV_TREE->ADD_NODE
EXPORTING
I_RELAT_NODE_KEY = L_ROOT_KEY
I_RELATIONSHIP
=
CL_GUI_COLUMN_TREE=>RELAT_LAST_CHILD
I_NODE_TEXT
= L_NODE_TEXT
IS_OUTTAB_LINE
= LS_TREE
IMPORTING
E_NEW_NODE_KEY
= L_NEXT_KEY.
ENDFORM.
"ADD_NODE
*&----------------------------------------------------*
*
FORM ADD_LEAF
*
*&----------------------------------------------------*
FORM ADD_LEAF USING L_TREE TYPE TY_PROGRAMAS
L_NEXT_KEY
CHANGING L_LAST_KEY.
CALL METHOD G_ALV_TREE->ADD_NODE
EXPORTING
I_RELAT_NODE_KEY = L_NEXT_KEY
I_RELATIONSHIP
=
CL_GUI_COLUMN_TREE=>RELAT_LAST_CHILD
IS_OUTTAB_LINE
= T_TREE
IMPORTING
E_NEW_NODE_KEY
= L_LAST_KEY.
389
ENDFORM.
"ADD_LEAF
Revisemos el código paso a paso.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_PROGRAMAS,
LENGUAJE TYPE ZVLENGUAJES_PROG-LENGUAJE,
NOM_PROG TYPE ZVLENGUAJES_PROG-NOM_PROG,
ENTORNO TYPE ZVLENGUAJES_PROG-ENTORNO,
END OF TY_PROGRAMAS.
TYPES: BEGIN OF TY_HEADER,
LENGUAJE TYPE ZVLENGUAJES_PROG-LENGUAJE,
END OF TY_HEADER.
Declaramos dos TYPE’s, uno para crear la tabla que guardará los
datos del ALV y otro para crear la tabla que guardará los datos de la
cabecera del programa.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE OF
TY_PROGRAMAS,
T_TREE TYPE STANDARD TABLE OF
TY_PROGRAMAS WITH HEADER LINE,
T_HEADER TYPE STANDARD TABLE OF
390
TY_HEADER,
GT_FIELDCAT_TREE TYPE LVC_T_FCAT.
Declaramos algunas tablas internas, T_PROGRAMAS que guarda
los datos del ALV, T_TREE que es una tabla intermedia para
mostrar los datos del ALV, T_HEADER es la cabecera del ALV,
GT_FIELDCAT_TREE es el catálogo del ALV.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: OK_CODE TYPE SY-UCOMM,
G_ALV_TREE TYPE REF TO CL_GUI_ALV_TREE,
G_CUSTOM_CONTAINER TYPE REF TO
CL_GUI_CUSTOM_CONTAINER,
L_HIERARCHY_HEADER TYPE TREEV_HHDR.
Declaramos algunas variables. OK_CODE para guardar el código
de función, G_ALV_TREE que es un objeto de la clase
CL_GUI_ALV_TREE, G_CUSTOM_CONTAINER que es un
contenedor para el ALV y L_HIERARCHY_HEADER que sirve
para definir la cabecera.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE OF
T_PROGRAMAS,
<FS_HEADER> LIKE LINE OF
391
T_HEADER,
<FS_TREE> LIKE LINE OF
T_PROGRAMAS.
Declaramos algunos Field-Symbols.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM CARGAR_DATOS.
PERFORM INIT_TREE.
CALL SCREEN 0100.
Llamamos dos FORM’s CARGAR_DATOS e INIT_TREE,
llamamos a la pantalla 100.
*&----------------------------------------------------*
*&
Form
CARGAR_DATOS
*
*&----------------------------------------------------*
FORM CARGAR_DATOS.
SELECT LENGUAJE NOM_PROG ENTORNO
INTO TABLE T_PROGRAMAS
FROM ZVLENGUAJES_PROG.
LOOP AT T_PROGRAMAS
ASSIGNING <FS_PROGRAMAS>.
APPEND INITIAL LINE TO
T_HEADER ASSIGNING <FS_HEADER>.
<FS_HEADER>-LENGUAJE = <FS_PROGRAMAS>-LENGUAJE.
392
ENDLOOP.
SORT T_HEADER.
DELETE ADJACENT DUPLICATES FROM T_HEADER.
ENDFORM.
" CARGAR_DATOS
Seleccionamos los datos de la vista ZVLENGUAJES_PROG.
Luego asignamos el campo LENGUAJE a la tabla T_HEADER y
eliminamos los registros duplicados.
*&----------------------------------------------------*
*&
Module
STATUS_0100
OUTPUT
*
*&----------------------------------------------------*
MODULE STATUS_0100 OUTPUT.
SET PF-STATUS '100'.
SET TITLEBAR '100'.
ENDMODULE.
" STATUS_0100
OUTPUT
Establecemos el menú y título del programa.
*&----------------------------------------------------*
*&
Module
USER_COMMAND_0100
INPUT
*
*&----------------------------------------------------*
MODULE USER_COMMAND_0100 INPUT.
OK_CODE = SY-UCOMM.
CLEAR SY-UCOMM.
393
CASE OK_CODE.
WHEN 'BACK' OR 'STOP' OR 'CANCEL'.
SET SCREEN 0.
LEAVE SCREEN.
ENDCASE.
ENDMODULE.
" USER_COMMAND_0100
INPUT
Asignamos acciones para el código de funciones.
*&----------------------------------------------------*
*&
Form
INIT_TREE
*
*&----------------------------------------------------*
FORM INIT_TREE.
DATA: L_TREE_CONTAINER_NAME(30) TYPE C.
L_TREE_CONTAINER_NAME = 'CUSTOM_ALV'.
CREATE OBJECT G_CUSTOM_CONTAINER
EXPORTING
CONTAINER_NAME
=
L_TREE_CONTAINER_NAME
EXCEPTIONS
CNTL_ERROR
= 1
CNTL_SYSTEM_ERROR
= 2
CREATE_ERROR
= 3
LIFETIME_ERROR
= 4
LIFETIME_DYNPRO_DYNPRO_LINK = 5.
CREATE OBJECT G_ALV_TREE
394
EXPORTING
PARENT
= G_CUSTOM_CONTAINER
NODE_SELECTION_MODE
=
CL_GUI_COLUMN_TREE=>NODE_SEL_MODE_SINGLE
ITEM_SELECTION
= ''
NO_HTML_HEADER
= 'X'
NO_TOOLBAR
= ''
EXCEPTIONS
CNTL_ERROR
= 1
CNTL_SYSTEM_ERROR
= 2
CREATE_ERROR
= 3
LIFETIME_ERROR
= 4
ILLEGAL_NODE_SELECTION_MODE = 5
FAILED
= 6
ILLEGAL_COLUMN_NAME
= 7.
PERFORM FILL_CATALOG_TREE.
PERFORM BUILD_HIERARCHY_HEADER
CHANGING L_HIERARCHY_HEADER.
CALL METHOD G_ALV_TREE->SET_TABLE_FOR_FIRST_DISPLAY
EXPORTING
IS_HIERARCHY_HEADER = L_HIERARCHY_HEADER
CHANGING
IT_OUTTAB
= T_TREE[]
IT_FIELDCATALOG
= GT_FIELDCAT_TREE.
PERFORM CREATE_HIERARCHY.
ENDFORM.
" INIT_TREE
395
Creamos el contenedor G_CUSTOM_CONTAINER pasando
como parámetro el nombre de nuestro CUSTOM_CONTROL
contenido en la variable L_TREE_CONTAINER_NAME.
Creamos
el
objeto
G_ALV_TREE
asignándolo
a
nuestro
contenedor G_CUSTOM_CONTAINER.
Llamámos al FORM FILL_CATALOG_TREE donde llenaremos
el catálogo del ALV.
Llamámos al FORM BUILD_HIERARCHY_HEADER, donde
establecemos los detalles de la cabecera.
Llamámos al método SET_TABLE_FOR_FIRST_DISPLAY para
llamar al ALV.
Finalmemte llamámos al FORM CREATE_HIERARCHY, donde
creamos la estructura del ALV.
*&----------------------------------------------------*
*&
Form
fill_catalog_tree
*
*&----------------------------------------------------*
FORM FILL_CATALOG_TREE.
DATA: GS_FIELDCAT TYPE LVC_S_FCAT.
CLEAR GS_FIELDCAT.
GS_FIELDCAT-COL_POS
= 1.
GS_FIELDCAT-FIELDNAME = 'NOM_PROG'.
GS_FIELDCAT-SCRTEXT_S = 'Nom. Programa'.
GS_FIELDCAT-TABNAME
= 'T_PROGRAMAS'.
GS_FIELDCAT-OUTPUTLEN = 20.
APPEND GS_FIELDCAT TO GT_FIELDCAT_TREE.
396
CLEAR GS_FIELDCAT.
GS_FIELDCAT-COL_POS
= 2.
GS_FIELDCAT-FIELDNAME = 'ENTORNO'.
GS_FIELDCAT-SCRTEXT_S = 'Entorno'.
GS_FIELDCAT-TABNAME
= 'T_PROGRAMAS'.
GS_FIELDCAT-OUTPUTLEN = 15.
APPEND GS_FIELDCAT TO GT_FIELDCAT_TREE.
ENDFORM.
"fill_catalog_tree
Creamos el catálogo del ALV.
*&----------------------------------------------------*
*
FORM build_hierarchy_header
*
*&----------------------------------------------------*
FORM BUILD_HIERARCHY_HEADER CHANGING
P_HIERARCHY_HEADER
TYPE TREEV_HHDR.
CLEAR P_HIERARCHY_HEADER.
P_HIERARCHY_HEADER-HEADING = 'Código'(300).
P_HIERARCHY_HEADER-WIDTH = 60.
P_HIERARCHY_HEADER-WIDTH_PIX = ' '.
ENDFORM.
"BUILD_HIERARCHY_HEADER
La cabecera del ALV va a tener el título “Código”, una longitud de
60.
*&----------------------------------------------------*
*&
Form
create_hierarchy
*
397
*&----------------------------------------------------*
FORM CREATE_HIERARCHY.
DATA: L_ROOT_KEY TYPE LVC_NKEY,
L_NEXT_KEY TYPE LVC_NKEY,
L_LAST_KEY TYPE LVC_NKEY,
HEADER TYPE STRING,
W_MENGE_TEXT(13) TYPE C.
CLEAR L_ROOT_KEY.
LOOP AT T_HEADER ASSIGNING <FS_HEADER>.
HEADER = <FS_HEADER>-LENGUAJE.
CLEAR L_ROOT_KEY.
CLEAR L_NEXT_KEY.
PERFORM ADD_NODE USING HEADER L_ROOT_KEY
CHANGING L_NEXT_KEY.
LOOP AT T_PROGRAMAS ASSIGNING <FS_PROGRAMAS>
WHERE LENGUAJE EQ <FS_HEADER>-LENGUAJE.
MOVE-CORRESPONDING <FS_PROGRAMAS> TO T_TREE.
PERFORM ADD_LEAF USING T_TREE L_NEXT_KEY
CHANGING L_LAST_KEY.
ENDLOOP.
ENDLOOP.
CALL METHOD G_ALV_TREE->FRONTEND_UPDATE.
ENDFORM.
"CREATE_HIERARCHY
398
Recorremos los registros de la tabla T_HEADER, asignamos el
valor del campo LENGUAJE al campo HEADER (Esta variable
dará un texto al NODO). Llamamos al FORM ADD_NODE para
agregar un nuevo nodo.
Luego recorremos todos los registros de la tabla T_PROGRAMAS
buscando los registros que tengan el campo LENGUAJE igual que
el nodo, esto para poder agregarlos utilizando el FORM
ADD_LEAF.
Una
vez
que
terminamos
llamámos
al
método
FRONTEND_UPDATE para refrescar los valores del ALV.
*&----------------------------------------------------*
*
FORM ADD_NODE
*
*&----------------------------------------------------*
FORM ADD_NODE USING L_NAME L_ROOT_KEY
CHANGING L_NEXT_KEY.
DATA: L_NODE_TEXT TYPE LVC_VALUE,
LS_TREE TYPE TY_PROGRAMAS.
L_NODE_TEXT =
L_NAME.
CALL METHOD G_ALV_TREE->ADD_NODE
EXPORTING
I_RELAT_NODE_KEY = L_ROOT_KEY
I_RELATIONSHIP
=
CL_GUI_COLUMN_TREE=>RELAT_LAST_CHILD
I_NODE_TEXT
= L_NODE_TEXT
IS_OUTTAB_LINE
= LS_TREE
IMPORTING
E_NEW_NODE_KEY
= L_NEXT_KEY.
399
ENDFORM.
"ADD_NODE
Llamámos al método ADD_NODE para crear un nuevo nodo.
*&----------------------------------------------------*
*
FORM ADD_LEAF
*
*&----------------------------------------------------*
FORM ADD_LEAF USING L_TREE TYPE TY_PROGRAMAS
L_NEXT_KEY
CHANGING L_LAST_KEY.
CALL METHOD G_ALV_TREE->ADD_NODE
EXPORTING
I_RELAT_NODE_KEY = L_NEXT_KEY
I_RELATIONSHIP
=
CL_GUI_COLUMN_TREE=>RELAT_LAST_CHILD
IS_OUTTAB_LINE
= T_TREE
IMPORTING
E_NEW_NODE_KEY
ENDFORM.
= L_LAST_KEY.
"ADD_LEAF
Llamámos al método ADD_NODE para crear un nuevo nodo.
Aunque claro, pasamos otro parámetro para que se cree como
subnodo.
Grabamos, activamos y ejecutamos.
400
Agregar validaciones y eventos
Si bien el programa funciona, no es muy útil que digamos puesto
que no hace...nada...Así que vamos a agregar algunas cosas
adicionales...
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: OK_CODE TYPE SY-UCOMM,
G_ALV_TREE TYPE REF TO CL_GUI_ALV_TREE,
G_CUSTOM_CONTAINER TYPE REF TO
CL_GUI_CUSTOM_CONTAINER,
L_HIERARCHY_HEADER TYPE TREEV_HHDR,
G_LINE_BEHAVIOUR TYPE REF TO CL_DRAGDROP,
G_FAV_BEHAVIOUR TYPE REF TO CL_DRAGDROP,
L_LAST_KEY TYPE LVC_NKEY,
W_NODE_KEY TYPE LVC_NKEY.
401
Agregamos algunas variables, G_LINE_BEHAVIOUR objeto que
determina que el nodo puede moverse. G_FAV_BEHAVIOUR
objeto que determina que el folder puede aceptar un nuevo nodo.
L_LAST_KEY determina el nodo padre.
W_NODE_KEY variable para almacenar el nodo que se ha movido
de carpeta.
*-----------------------------------------------------*
*
CLASS LCL_DRAGDROPOBJ DEFINITION
*
*-----------------------------------------------------*
CLASS LCL_DRAGDROPOBJ DEFINITION.
PUBLIC SECTION.
DATA: CPS_PROGRAMAS TYPE TY_PROGRAMAS,
CP_NODE_TEXT TYPE LVC_VALUE,
CP_NODE_KEY TYPE LVC_NKEY.
ENDCLASS.
"LCL_DRAGDROPOBJ DEFINITION
Definimos algunas variables de clase. CPS_PROGRAMAS sirve
como una cabecera con los datos de los nodos.
CP_NODE_TEXT almacena el texto del nodo y CP_NODE_KEY
almacena el índice del nodo.
*-----------------------------------------------------*
*
CLASS lcl_dnd_event_receiver DEFINITION
*
*-----------------------------------------------------*
CLASS LCL_DND_EVENT_RECEIVER DEFINITION.
PUBLIC SECTION.
METHODS:
402
HANDLE_LINE_DRAG
FOR EVENT ON_DRAG
OF CL_GUI_ALV_TREE
IMPORTING SENDER NODE_KEY FIELDNAME
DRAG_DROP_OBJECT,
HANDLE_FAV_DROP
FOR EVENT ON_DROP
OF CL_GUI_ALV_TREE
IMPORTING SENDER NODE_KEY DRAG_DROP_OBJECT.
ENDCLASS.
"lcl_dnd_event_receiver DEFINITION
Declaramos dos métodos para manejar el Drag & Drop (Arrastrar y
soltar) en el ALV.
*-----------------------------------------------------*
*
CLASS lcl_dnd_event_receiver IMPLEMENTATION
*
*-----------------------------------------------------*
CLASS LCL_DND_EVENT_RECEIVER IMPLEMENTATION.
METHOD HANDLE_LINE_DRAG.
DATA: DATAOBJ TYPE REF TO LCL_DRAGDROPOBJ.
CREATE OBJECT DATAOBJ.
DATAOBJ->CP_NODE_KEY = NODE_KEY.
CALL METHOD SENDER->GET_OUTTAB_LINE
EXPORTING
I_NODE_KEY
= NODE_KEY
IMPORTING
E_OUTTAB_LINE = DATAOBJ->CPS_PROGRAMAS
E_NODE_TEXT
= DATAOBJ->CP_NODE_TEXT.
403
DRAG_DROP_OBJECT->OBJECT = DATAOBJ.
ENDMETHOD.
"HANDLE_LINE_DRAG
METHOD HANDLE_FAV_DROP.
DATA: DATAOBJ TYPE REF TO LCL_DRAGDROPOBJ,
L_NEW_KEY TYPE LVC_NKEY.
CATCH SYSTEM-EXCEPTIONS MOVE_CAST_ERROR = 1.
DATAOBJ ?= DRAG_DROP_OBJECT->OBJECT.
W_NODE_KEY = DATAOBJ->CP_NODE_KEY.
PERFORM ADD_LEAF USING DATAOBJ->CPS_PROGRAMAS
NODE_KEY
CHANGING DATAOBJ->CP_NODE_KEY.
CALL METHOD G_ALV_TREE->DELETE_SUBTREE
EXPORTING
I_NODE_KEY = W_NODE_KEY.
CALL METHOD SENDER->FRONTEND_UPDATE.
ENDCATCH.
IF SY-SUBRC <> 0.
CALL METHOD DRAG_DROP_OBJECT->ABORT.
ENDIF.
ENDMETHOD.
ENDCLASS.
"HANDLE_FAV_DROP
"lcl_dnd_event_receiver IMPLEMENTATION
En el método HANDLE_LINE_DRAG, creamos un objeto
DATAOBJ y le asignamos el contenido del nodo. Con el método
GET_OUTTAB_LINE
llenamos
las
variables
404
CPS_PROGRAMAS y CP_NODE_TEXT con los valores
correspondientes al nodo.
Asignamos el nodo al evento de Drag & Drop.
En el método HANDLE_FAV_DROP, hacemos un CATCH
SYSTEM-EXCEPTION por si hay algún problema al mover el
nodo de una carpeta a otra.
Asignamos el evento de Drag & Drop a una variable. Asignamos el
valor del nodo que hemos movido a una variable auxiliar
W_NODE_KEY.
Llamamos al FORM ADD_LEAF con el nodo que hemos movido
de una carpeta a otra.
Llamamos al método DELETE_SUBTREE con el valor de la
variable W_NODE_KEY para poder eliminar al nodo de su
posición original.
Llamamos al método FRONTEND_UPDATE para actualizar el
ALV.
PERFORM DEFINE_DND_BEHAVIOUR.
DATA: LT_EVENTS TYPE CNTL_SIMPLE_EVENTS,
L_EVENT TYPE CNTL_SIMPLE_EVENT,
L_DND_EVENT_RECEIVER TYPE REF TO
LCL_DND_EVENT_RECEIVER.
CALL METHOD G_ALV_TREE->GET_REGISTERED_EVENTS
IMPORTING
EVENTS = LT_EVENTS.
CALL METHOD G_ALV_TREE->SET_REGISTERED_EVENTS
405
EXPORTING
EVENTS
= LT_EVENTS
EXCEPTIONS
CNTL_ERROR
= 1
CNTL_SYSTEM_ERROR
= 2
ILLEGAL_EVENT_COMBINATION = 3.
CREATE OBJECT L_DND_EVENT_RECEIVER.
SET HANDLER
L_DND_EVENT_RECEIVER->HANDLE_FAV_DROP FOR
G_ALV_TREE.
SET HANDLER
L_DND_EVENT_RECEIVER->HANDLE_LINE_DRAG FOR
G_ALV_TREE.
CALL METHOD G_ALV_TREE->FRONTEND_UPDATE.
Al final de INIT_TREE, agregamos este código.
El FORM DEFINE_DND_BEHAVIOUR determina el tipo de
Drag & Drop que se utilizará tanto para los nodos como para las
carpetas.
Las
tablas
internas
LT_EVENTS,
L_EVENT,
L_DND_EVENT_RECIEVER
y
LCL_DND_EVENT_RECIEVER, nos sirven para registrar y
utilizar
los
eventos.
Para
ello,
utilizamos
los
métodos
GET_REGISTERED_EVENTS
SET_REGISTERED_EVENTS.
y
Además,
establecemos
los
Handlers (Manejadores) para los eventos HANDLE_FAV_DROP y
HANDLE_LINE_DRAG.
406
Finalmente, llamamos al método FRONTEND_UPDATE para
actualizar el ALV.
*&----------------------------------------------------*
*
FORM ADD_NODE
*
*&----------------------------------------------------*
FORM ADD_NODE USING L_NAME L_ROOT_KEY
CHANGING L_NEXT_KEY.
DATA: L_NODE_TEXT TYPE LVC_VALUE,
LS_TREE TYPE TY_PROGRAMAS,
L_LAYOUT_NODE TYPE LVC_S_LAYN,
L_HANDLE_FAVOURITE_FOLDER TYPE I.
CALL METHOD G_FAV_BEHAVIOUR->GET_HANDLE
IMPORTING
HANDLE = L_HANDLE_FAVOURITE_FOLDER.
L_LAYOUT_NODE-DRAGDROPID = L_HANDLE_FAVOURITE_FOLDER.
L_LAYOUT_NODE-ISFOLDER = 'X'.
L_NODE_TEXT =
L_NAME.
CALL METHOD G_ALV_TREE->ADD_NODE
EXPORTING
I_RELAT_NODE_KEY = L_ROOT_KEY
I_RELATIONSHIP
=
CL_GUI_COLUMN_TREE=>RELAT_LAST_CHILD
I_NODE_TEXT
= L_NODE_TEXT
IS_NODE_LAYOUT
= L_LAYOUT_NODE
IS_OUTTAB_LINE
= LS_TREE
IMPORTING
E_NEW_NODE_KEY
= L_NEXT_KEY.
407
ENDFORM.
"ADD_NODE
Modificamos un poco el FORM ADD_NODE. Las variables
L_LAYOUT y L_HANDLE_FAVOURITE_FOLDER, nos van a
ayudar para establecer que los nodos son carpetas, y que pueden
aceptar un subnodo dentro de ellas.
Utilizamos el método GET_HANDLE para obtener el manejador
que nos permita asignar un ID al nodo.
Pasamos
el
valor
de
L_HANDLE_FAVOURITE_FOLDER
la
hacia
variable
el
campo
DRAGDROPID de la variable L_LAYOUT_NODE, además,
asignamos el valor “X” al campo ISFOLDER de la misma variable.
Debemos pasar el parámetro IS_NODE_LAYOUT al método
ADD_NODE.
*&----------------------------------------------------*
*
FORM ADD_LEAF
*
*&----------------------------------------------------*
FORM ADD_LEAF USING L_TREE TYPE TY_PROGRAMAS
L_NEXT_KEY
CHANGING L_LAST_KEY.
DATA: L_LAYOUT_NODE TYPE LVC_S_LAYN,
L_HANDLE_LINE TYPE I.
CALL METHOD G_LINE_BEHAVIOUR->GET_HANDLE
IMPORTING
HANDLE = L_HANDLE_LINE.
408
L_LAYOUT_NODE-DRAGDROPID = L_HANDLE_LINE.
CALL METHOD G_ALV_TREE->ADD_NODE
EXPORTING
I_RELAT_NODE_KEY = L_NEXT_KEY
I_RELATIONSHIP
=
CL_GUI_COLUMN_TREE=>RELAT_LAST_CHILD
IS_OUTTAB_LINE
= L_TREE
IS_NODE_LAYOUT
= L_LAYOUT_NODE
IMPORTING
E_NEW_NODE_KEY
= L_LAST_KEY.
ENDFORM.
"ADD_LEAF
Modificamos un poco el FORM ADD_LEAF. Las variables
L_LAYOUT_NODE y L_HANDLE_LINE nos va a servir para
establecer que los nodos pueden moverse de una hacia otra carpeta.
Utilizando el método GET_HANDLE asignamos un manejador a la
variable
L_LAYOUT_NODE-DRAGDROPID.
Pasamos
el
parámetro IS_NODE_LAYOUT al método ADD_NODE.
*&----------------------------------------------------*
*&
Form
DEFINE_DND_BEHAVIOUR
**&---------------------------------------------------*
FORM DEFINE_DND_BEHAVIOUR.
DATA: EFFECT TYPE I.
CREATE OBJECT G_LINE_BEHAVIOUR.
EFFECT = CL_DRAGDROP=>COPY.
CALL METHOD G_LINE_BEHAVIOUR->ADD
409
EXPORTING
FLAVOR
= 'X'
DRAGSRC
= 'X'
"#EC NOTEXT
DROPTARGET = ' '
EFFECT
= EFFECT.
CREATE OBJECT G_FAV_BEHAVIOUR.
EFFECT = CL_DRAGDROP=>COPY.
CALL METHOD G_FAV_BEHAVIOUR->ADD
EXPORTING
FLAVOR
= 'X'
DRAGSRC
= ' '
"#EC NOTEXT
DROPTARGET = 'X'
EFFECT
= EFFECT.
ENDFORM.
Creamos
" DEFINE_DND_BEHAVIOUR
dos
objetos
G_LINE_BEHAVIOUR
y
G_FAV_BEHAVIOUR. Con cada uno de ellos, llamaremos al
método ADD, determinando cual se puede arrastrar y en donde se
puede soltar. Es decir, cual funciona como objeto y cual como
contenedor.
410
Crear un ALV Object Model
Así como el ABAP evolucionó para mejor, el ALV hizo lo propio y
ahora tenemos disponible el ALV Object Model, una forma de
crear ALV’s más orientada a objetos que la anterior (Si es que esto
puede ser posible).
Este es el código.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZVLENGUAJES_PROG-ID_PROG,
LENGUAJE TYPE ZVLENGUAJES_PROG-LENGUAJE,
411
NOM_PROG TYPE ZVLENGUAJES_PROG-NOM_PROG,
ENTORNO TYPE ZVLENGUAJES_PROG-ENTORNO,
END OF TY_PROGRAMAS.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE OF
TY_PROGRAMAS,
T_TABLE TYPE REF TO CL_SALV_TABLE,
T_FUNCTIONS TYPE REF TO CL_SALV_FUNCTIONS,
T_DSPSET TYPE REF TO CL_SALV_DISPLAY_SETTINGS,
LR_COLUMN TYPE REF TO CL_SALV_COLUMN_TABLE,
LV_SALV_COLUMNS_TABLE TYPE REF TO
CL_SALV_COLUMNS_TABLE.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM CARGAR_DATOS.
PERFORM LLAMAR_ALV.
*&----------------------------------------------------*
*&
Form
CARGAR_DATOS
*
*&----------------------------------------------------*
FORM CARGAR_DATOS.
SELECT ID_PROG LENGUAJE NOM_PROG ENTORNO
INTO TABLE T_PROGRAMAS
FROM ZVLENGUAJES_PROG.
412
ENDFORM.
" CARGAR_DATOS
*&----------------------------------------------------*
*&
Form
LLAMAR_ALV
*
*&----------------------------------------------------*
FORM LLAMAR_ALV.
TRY.
CL_SALV_TABLE=>FACTORY(
IMPORTING
R_SALV_TABLE
= T_TABLE
CHANGING
T_TABLE
= T_PROGRAMAS ).
CATCH CX_SALV_MSG .
ENDTRY.
LV_SALV_COLUMNS_TABLE = T_TABLE->GET_COLUMNS( ).
LR_COLUMN ?=
LV_SALV_COLUMNS_TABLE->GET_COLUMN( 'ID_PROG' ).
LR_COLUMN->SET_LONG_TEXT( 'Id' ).
LV_SALV_COLUMNS_TABLE = T_TABLE->GET_COLUMNS( ).
LR_COLUMN ?=
LV_SALV_COLUMNS_TABLE->GET_COLUMN( 'NOM_PROG' ).
LR_COLUMN->SET_LONG_TEXT( 'Programa' ).
T_FUNCTIONS = T_TABLE->GET_FUNCTIONS( ).
T_FUNCTIONS->SET_ALL( ABAP_TRUE ).
T_DSPSET = T_TABLE->GET_DISPLAY_SETTINGS( ).
T_DSPSET->SET_LIST_HEADER( 'Programas' ).
413
T_TABLE->DISPLAY( ).
ENDFORM.
" LLAMAR_ALV
Como se darán cuenta, es un código bastante pequeño, pero de todos
modos vamos a revisarlo un poco.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_PROGRAMAS,
ID_PROG TYPE ZVLENGUAJES_PROG-ID_PROG,
LENGUAJE TYPE ZVLENGUAJES_PROG-LENGUAJE,
NOM_PROG TYPE ZVLENGUAJES_PROG-NOM_PROG,
ENTORNO TYPE ZVLENGUAJES_PROG-ENTORNO,
END OF TY_PROGRAMAS.
Declaramos un TYPES con todos los campos de la vista
ZVLENGUAJES_PROG.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE OF
TY_PROGRAMAS,
T_TABLE TYPE REF TO CL_SALV_TABLE,
T_FUNCTIONS TYPE REF TO CL_SALV_FUNCTIONS,
T_DSPSET TYPE REF TO CL_SALV_DISPLAY_SETTINGS,
LR_COLUMN TYPE REF TO CL_SALV_COLUMN_TABLE,
LV_SALV_COLUMNS_TABLE TYPE REF TO
CL_SALV_COLUMNS_TABLE.
414
Declaramos varias tablas internas, T_PROGRAMAS que guardará
los datos que obtengamos de la vista. T_TABLE es un parámetro
para el método FACTORY y sirve para crear la estructura del ALV.
T_FUNCTIONS sirve para asignar las funciones de la barra de
menús del ALV. T_DSPSET nos sirve para las opciones de
visualización. LR_COLUMN nos permite tomar las características
de una columna del ALV. LV_SALV_COLUMNS_TABLE nos
permite tomar todas las columnas de ALV.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM CARGAR_DATOS.
PERFORM LLAMAR_ALV.
Tenemos dos FORM’s CARGAR_DATOS (Donde cargamos los
datos) y LLAMAR_ALV (Donde llamamos al ALV)...Bastante
obvio no?
*&----------------------------------------------------*
*&
Form
CARGAR_DATOS
*
*&----------------------------------------------------*
FORM CARGAR_DATOS.
SELECT ID_PROG LENGUAJE NOM_PROG ENTORNO
INTO TABLE T_PROGRAMAS
FROM ZVLENGUAJES_PROG.
415
ENDFORM.
" CARGAR_DATOS
Seleccionamos
todos
los
campos
de
la
vista
ZVLENGUAJES_PROG.
*&----------------------------------------------------*
*&
Form
LLAMAR_ALV
*
*&----------------------------------------------------*
FORM LLAMAR_ALV.
TRY.
CL_SALV_TABLE=>FACTORY(
IMPORTING
R_SALV_TABLE
= T_TABLE
CHANGING
T_TABLE
= T_PROGRAMAS ).
CATCH CX_SALV_MSG .
ENDTRY.
LV_SALV_COLUMNS_TABLE = T_TABLE->GET_COLUMNS( ).
LR_COLUMN ?=
LV_SALV_COLUMNS_TABLE->GET_COLUMN( 'ID_PROG' ).
LR_COLUMN->SET_LONG_TEXT( 'Id' ).
LV_SALV_COLUMNS_TABLE = T_TABLE->GET_COLUMNS( ).
LR_COLUMN ?=
LV_SALV_COLUMNS_TABLE->GET_COLUMN( 'NOM_PROG' ).
LR_COLUMN->SET_LONG_TEXT( 'Programa' ).
T_FUNCTIONS = T_TABLE->GET_FUNCTIONS( ).
T_FUNCTIONS->SET_ALL( ABAP_TRUE ).
416
T_DSPSET = T_TABLE->GET_DISPLAY_SETTINGS( ).
T_DSPSET->SET_LIST_HEADER( 'Programas' ).
T_TABLE->DISPLAY( ).
ENDFORM.
Llamámos
" LLAMAR_ALV
al
método
estático
FACTORY
de
la
clase
CL_SALV_TABLE, pasando dos únicos parámetros, T_TABLE
que contendrá la estructura del ALV y T_PROGRAMAS, que tiene
los registros obtenidos de la vista.
Utilizando
LV_SALV_COLUMNS_TABLE = T_TABLE->GET_COLUMNS( ).
Obtenemos todas las columnas del ALV.
LR_COLUMN ?= LV_SALV_COLUMNS_TABLE>GET_COLUMN( 'ID_PROG' ).
Obtenemos las características de la columna ID_PROG.
LR_COLUMN->SET_LONG_TEXT( 'Id' ).
Establecemos cual es texto de la columna.
Porque hacemos esto? Pues muy simple, tanto ID_PROG como
NOM_PROG no tienen asignado un elemento de datos, por lo
tanto, el ALV no puede determinar cual es el texto que le
corresponde, así que nos toca a nosotros asignarlo.
Continuando, utilizamos
T_FUNCTIONS = T_TABLE->GET_FUNCTIONS( ).
Para obtener las funciones standard del ALV
T_FUNCTIONS->SET_ALL( ABAP_TRUE ).
Establecemos que queremos utilizar todas las funciones.
T_DSPSET = T_TABLE->GET_DISPLAY_SETTINGS( ).
417
Obtenemos las características de salida.
T_DSPSET->SET_LIST_HEADER( 'Programas' ).
Asignamos un título al reporte.
T_TABLE->DISPLAY( ).
Llamamos y mostramos el ALV.
Agregar validaciones y eventos
Si bien el programa funciona, no es muy útil que digamos puesto
que no hace...nada...Así que vamos a agregar algunas cosas
adicionales...
CLASS LCL_HANDLE_EVENTS DEFINITION DEFERRED.
Vamos a redefinir a la clase LCL_HANDLE_EVENTS.
418
*-----------------------------------------------------*
*
CLASS lcl_handle_events DEFINITION
*
*-----------------------------------------------------*
CLASS LCL_HANDLE_EVENTS DEFINITION.
PUBLIC SECTION.
METHODS:
ON_DOUBLE_CLICK FOR EVENT DOUBLE_CLICK OF
CL_SALV_EVENTS_TABLE
IMPORTING ROW COLUMN.
ENDCLASS.
"lcl_handle_events DEFINITION
Definimos un método llamado ON_DOUBLE_CLICK.
*-----------------------------------------------------*
*
CLASS lcl_handle_events IMPLEMENTATION
*
*-----------------------------------------------------*
CLASS LCL_HANDLE_EVENTS IMPLEMENTATION.
METHOD ON_DOUBLE_CLICK.
PERFORM MOSTRAR_DETALLE USING ROW COLUMN.
ENDMETHOD.
"on_double_click
ENDCLASS.
Implementamos
"lcl_handle_events IMPLEMENTATION
el
método
llamado
al
FORM
MOSTRAR_DETALLE.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_PROGRAMAS TYPE STANDARD TABLE OF
TY_PROGRAMAS,
T_TABLE TYPE REF TO CL_SALV_TABLE,
419
T_FUNCTIONS TYPE REF TO CL_SALV_FUNCTIONS,
T_DSPSET TYPE REF TO CL_SALV_DISPLAY_SETTINGS,
LR_COLUMN TYPE REF TO CL_SALV_COLUMN_TABLE,
LV_SALV_COLUMNS_TABLE TYPE REF TO
CL_SALV_COLUMNS_TABLE,
GR_EVENTS TYPE REF TO LCL_HANDLE_EVENTS.
El objeto GR_EVENTS nos permitirá registrar los eventos del
ALV.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_PROGRAMAS> LIKE LINE OF
T_PROGRAMAS.
Declaramos un Field-Symbol.
PERFORM PROCESS_EVENTS.
T_TABLE->DISPLAY( ).
Dentro
de
LLAMAR_ALV,
agregamos
el
FORM
PROCESS_EVENTS, que lo que va a hacer es generar el evento
del Doble Clic.
420
*-----------------------------------------------------*
*&
Form
mostrar_detalle
*
*-----------------------------------------------------*
FORM MOSTRAR_DETALLE USING P_ROW
P_COLUMN.
DATA: MENSAJE TYPE SHKONTEXT-MELDUNG.
IF P_COLUMN EQ 'NOM_PROG'.
READ TABLE T_PROGRAMAS INDEX P_ROW
ASSIGNING <FS_PROGRAMAS>.
CONCATENATE <FS_PROGRAMAS>-ID_PROG
<FS_PROGRAMAS>-NOM_PROG
<FS_PROGRAMAS>-LENGUAJE
<FS_PROGRAMAS>-ENTORNO
INTO MENSAJE SEPARATED BY SPACE.
CALL FUNCTION 'MESSAGE_TEXT_DISPLAY_WITH_PARA'
EXPORTING
TEXT = MENSAJE.
ENDIF.
ENDFORM.
" mostrar_detalle
Si hemos hecho doble clic sobre un registro de la columna
NOM_PROG, entonces leemos el registro, concatenamos los
valores en un cadena y llamamos al módulo de funciones
MESSAGE_TEXT_DISPLAY_WITH_PARA para mostrar una
ventana con los datos.
421
*&----------------------------------------------------*
*&
Form
process_events
*
*&----------------------------------------------------*
FORM PROCESS_EVENTS.
DATA: LR_EVENTS TYPE REF TO CL_SALV_EVENTS_TABLE.
LR_EVENTS = T_TABLE->GET_EVENT( ).
CREATE OBJECT GR_EVENTS.
SET HANDLER GR_EVENTS->ON_DOUBLE_CLICK FOR LR_EVENTS.
ENDFORM.
" process_events
Utilizando el método estático GET_EVENT, obtenemos el evento
definido para nuestro ALV. Creamos el objeto GR_EVENTS y le
asignamos el HANDLER ON_DOUBLE_CLICK, es decir, le
decimos que reaccione ante el doble clic.
Este sería el resultado del reporte.
422
Cargar imágenes en Dynpros
Esto quizás no era muy común o requerido en los tiempos anteriores
a la orientación a objetos, pero ahora, creanmé que es muy útil.
Este es el código.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_GRAPHIC_TABLE,
LINE(255) TYPE X,
END OF TY_GRAPHIC_TABLE.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA:
T_GRAPHIC_TABLE TYPE STANDARD TABLE OF
TY_GRAPHIC_TABLE.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: OK_CODE TYPE SY-UCOMM,
URL(255) TYPE C,
CONTAINER1 TYPE REF TO CL_GUI_CUSTOM_CONTAINER,
PICTURE TYPE REF TO CL_GUI_PICTURE,
L_BYTECOUNT TYPE I,
L_CONTENT TYPE STANDARD TABLE OF BAPICONTEN
INITIAL SIZE 0,
GRAPHIC_SIZE TYPE I.
423
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
CALL SCREEN 0100.
*&----------------------------------------------------*
*&
Module
STATUS_0100
OUTPUT
*
*&----------------------------------------------------*
MODULE STATUS_0100 OUTPUT.
SET PF-STATUS '100'.
SET TITLEBAR '100'.
PERFORM LOAD_IMAGE.
ENDMODULE.
" STATUS_0100
OUTPUT
*&----------------------------------------------------*
*&
Module
USER_COMMAND_0100
INPUT
*
*&----------------------------------------------------*
MODULE USER_COMMAND_0100 INPUT.
OK_CODE = SY-UCOMM.
CLEAR SY-UCOMM.
CASE OK_CODE.
WHEN 'BACK' OR 'STOP' OR 'CANCEL'.
SET SCREEN 0.
LEAVE SCREEN.
ENDCASE.
ENDMODULE.
" USER_COMMAND_0100
INPUT
424
*&----------------------------------------------------*
*&
Form
load_image
*
*&----------------------------------------------------*
FORM LOAD_IMAGE.
CREATE OBJECT: CONTAINER1 EXPORTING CONTAINER_NAME =
'CUSTOM_ALV',
PICTURE EXPORTING PARENT = CONTAINER1.
CALL FUNCTION 'SAPSCRIPT_GET_GRAPHIC_BDS'
EXPORTING
I_OBJECT
= 'GRAPHICS'
I_NAME
= 'ENJOY'
I_ID
= 'BMAP'
I_BTYPE
= 'BCOL'
IMPORTING
E_BYTECOUNT
= L_BYTECOUNT
TABLES
CONTENT
= L_CONTENT
EXCEPTIONS
NOT_FOUND
= 1
BDS_GET_FAILED = 2
BDS_NO_CONTENT = 3
OTHERS
= 4.
CALL FUNCTION 'SAPSCRIPT_CONVERT_BITMAP'
EXPORTING
OLD_FORMAT
= 'BDS'
NEW_FORMAT
= 'BMP'
BITMAP_FILE_BYTECOUNT_IN = L_BYTECOUNT
IMPORTING
BITMAP_FILE_BYTECOUNT
= GRAPHIC_SIZE
425
TABLES
BDS_BITMAP_FILE
= L_CONTENT
BITMAP_FILE
= T_GRAPHIC_TABLE
EXCEPTIONS
OTHERS
= 1.
CALL FUNCTION 'DP_CREATE_URL'
EXPORTING
TYPE
= 'IMAGE'
SUBTYPE = 'BMP'
TABLES
DATA
= T_GRAPHIC_TABLE
CHANGING
URL
= URL.
CALL METHOD PICTURE->LOAD_PICTURE_FROM_URL
EXPORTING
URL = URL.
CALL METHOD PICTURE->SET_DISPLAY_MODE
EXPORTING
DISPLAY_MODE = PICTURE->DISPLAY_MODE_FIT_CENTER.
ENDFORM.
" load_image
Revisemos el código por partes. Es un código sencillo y corto, así
que será fácil entenderlo.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_GRAPHIC_TABLE,
LINE(255) TYPE X,
END OF TY_GRAPHIC_TABLE.
426
Declaramos un TYPE llamado TY_GRAPHIC_TABLE que
contiene un único campo, llamado LINE de 255 caracteres y de tipo
X. Esto es porque las imágenes de almacenan en formato
hexadecimal.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA:
T_GRAPHIC_TABLE TYPE STANDARD TABLE OF
TY_GRAPHIC_TABLE.
Declaramos una tabla interna de tipo TY_GRAPHIC_TABLE.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: OK_CODE TYPE SY-UCOMM,
URL(255) TYPE C,
CONTAINER1 TYPE REF TO CL_GUI_CUSTOM_CONTAINER,
PICTURE TYPE REF TO CL_GUI_PICTURE,
L_BYTECOUNT TYPE I,
L_CONTENT TYPE STANDARD TABLE OF BAPICONTEN
INITIAL SIZE 0,
GRAPHIC_SIZE TYPE I.
OK_CODE almacena el valor del código de función.
URL almacena una dirección donde se guarda la imagen en forma
temporal.
427
CONTAINER1
contiene
el
nombre
de
nuestro
CUSTOM_CONTROL.
PICTURE objeto que cargará y mostrará la imagen en el Dynpro.
L_BYTECOUNT cuenta el número de bytes de la imagen que
queremos visualizar.
L_CONTENT almacena el contenido de la imagen.
GRAPHIC_SIZE es el tamaño de la imagen luego de convetirla a
BMP.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
CALL SCREEN 0100.
Llamamos al Dynpro 100 para mostrar la imagen.
*&----------------------------------------------------*
*&
Module
STATUS_0100
OUTPUT
*
*&----------------------------------------------------*
MODULE STATUS_0100 OUTPUT.
SET PF-STATUS '100'.
SET TITLEBAR '100'.
PERFORM LOAD_IMAGE.
ENDMODULE.
" STATUS_0100
OUTPUT
428
Llamamos a la barra de menús y al título del programa. Además, en
el FORM LOAD_IMAGE vamos a cargar la imagen en el Dynpro.
*&----------------------------------------------------*
*&
Form
load_image
*
*&----------------------------------------------------*
FORM LOAD_IMAGE.
CREATE OBJECT: CONTAINER1 EXPORTING
CONTAINER_NAME = 'CUSTOM_ALV',
PICTURE EXPORTING PARENT = CONTAINER1.
CALL FUNCTION 'SAPSCRIPT_GET_GRAPHIC_BDS'
EXPORTING
I_OBJECT
= 'GRAPHICS'
I_NAME
= 'ENJOY'
I_ID
= 'BMAP'
I_BTYPE
= 'BCOL'
IMPORTING
E_BYTECOUNT
= L_BYTECOUNT
TABLES
CONTENT
= L_CONTENT
EXCEPTIONS
NOT_FOUND
= 1
BDS_GET_FAILED = 2
BDS_NO_CONTENT = 3
OTHERS
= 4.
CALL FUNCTION 'SAPSCRIPT_CONVERT_BITMAP'
EXPORTING
OLD_FORMAT
= 'BDS'
NEW_FORMAT
= 'BMP'
429
BITMAP_FILE_BYTECOUNT_IN = L_BYTECOUNT
IMPORTING
BITMAP_FILE_BYTECOUNT
= GRAPHIC_SIZE
TABLES
BDS_BITMAP_FILE
= L_CONTENT
BITMAP_FILE
= T_GRAPHIC_TABLE
EXCEPTIONS
OTHERS
= 1.
CALL FUNCTION 'DP_CREATE_URL'
EXPORTING
TYPE
= 'IMAGE'
SUBTYPE = 'BMP'
TABLES
DATA
= T_GRAPHIC_TABLE
CHANGING
URL
= URL.
CALL METHOD PICTURE->LOAD_PICTURE_FROM_URL
EXPORTING
URL = URL.
CALL METHOD PICTURE->SET_DISPLAY_MODE
EXPORTING
DISPLAY_MODE = PICTURE->DISPLAY_MODE_FIT_CENTER.
ENDFORM.
" load_image
Creamos los objetos CONTAINER1 (Asociado a nuestro
CUSTOM_CONTROL) y PICTURE (Mostrará la imagen en el
contenedor).
430
Llamamos a la función SAPSCRIPT_GET_GRAPHIC_BDS para
obtener los datos de la imagen que queremos mostrar, que en este
caso es “ENJOY”.
Llamamos a la función SAPSCRIPT_CONVERT_BITMAP para
convertir la imagen a un Bitmap.
Llamamos a la función DP_CREATE_URL para generar una
dirección donde se encuentra almacenada temporalmente la imagen
BMP.
Llamamos al método LOAD_PICTURE_FROM_URL para cargar
la imagen en memoria.
Llamamos al método SET_DISPLAY_MODE para mostrar la
imagen.
431
Leer PDF’s
Bueno, esto si que no creo que lo lleguen a necesitar alguna
vez...Pero bueno, vale la pena conocerlo.
Este es el código.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_FILETAB TYPE FILETABLE.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: OK_CODE TYPE SY-UCOMM,
MY_PDF_VIEWER TYPE REF TO CL_GUI_PDFVIEWER,
CONTAINER TYPE REF TO CL_GUI_CUSTOM_CONTAINER,
W_SUBRC TYPE SY-SUBRC,
V_URL TYPE CHAR255.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_FILETAB> LIKE LINE OF T_FILETAB.
*&----------------------------------------------------*
*&
SELECTION-SCREEN
*
*&----------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK TEST WITH FRAME.
PARAMETERS:
FILE LIKE RLGRAP-FILENAME.
432
SELECTION-SCREEN END OF BLOCK TEST.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
CALL SCREEN 0100.
*&----------------------------------------------------*
*&
AT SELECTION-SCREEN
*
*&----------------------------------------------------*
AT SELECTION-SCREEN ON VALUE-REQUEST FOR FILE.
CALL METHOD CL_GUI_FRONTEND_SERVICES=>FILE_OPEN_DIALOG
EXPORTING
WINDOW_TITLE
= 'Seleccionar archivo'
DEFAULT_FILENAME = '*.pdf'
FILE_FILTER
= '*.pdf'
CHANGING
FILE_TABLE
= T_FILETAB
RC
= W_SUBRC.
READ TABLE T_FILETAB INDEX 1
ASSIGNING <FS_FILETAB>.
FILE = <FS_FILETAB>.
IF FILE IS INITIAL.
EXIT.
ENDIF.
433
*&----------------------------------------------------*
*&
Module
STATUS_0100
OUTPUT
*
*&----------------------------------------------------*
MODULE STATUS_0100 OUTPUT.
SET PF-STATUS '100'.
SET TITLEBAR '100'.
IF CONTAINER IS INITIAL.
CREATE OBJECT CONTAINER
EXPORTING
CONTAINER_NAME = 'CUSTOM_ALV'
EXCEPTIONS
CNTL_ERROR
= 1
OTHERS
= 2.
CREATE OBJECT MY_PDF_VIEWER
EXPORTING
PARENT
= CONTAINER
EXCEPTIONS
CNTL_ERROR
= 1
CNTL_SYSTEM_ERROR = 2
OTHERS
= 3.
IF NOT MY_PDF_VIEWER->HTML_VIEWER IS INITIAL.
CLEAR V_URL.
V_URL = FILE.
CALL METHOD MY_PDF_VIEWER->OPEN_DOCUMENT
EXPORTING
URL = V_URL.
ENDIF.
434
ENDIF.
ENDMODULE.
" STATUS_0100
OUTPUT
*&----------------------------------------------------*
*&
Module
USER_COMMAND_0100
INPUT
*
*&----------------------------------------------------*
MODULE USER_COMMAND_0100 INPUT.
OK_CODE = SY-UCOMM.
CLEAR SY-UCOMM.
CASE OK_CODE.
WHEN 'BACK' OR 'STOP' OR 'CANCEL'.
SET SCREEN 0.
LEAVE SCREEN.
ENDCASE.
ENDMODULE.
" USER_COMMAND_0100
INPUT
Revisemos un poco el código.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_FILETAB TYPE FILETABLE.
Declaramos la tabla interna T_FILETAB de tipo FILETABLE,
que no es más que una tabla con un campo de 1024 caracteres de
longitud.
435
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: OK_CODE TYPE SY-UCOMM,
MY_PDF_VIEWER TYPE REF TO CL_GUI_PDFVIEWER,
CONTAINER TYPE REF TO CL_GUI_CUSTOM_CONTAINER,
W_SUBRC TYPE SY-SUBRC,
V_URL TYPE CHAR255.
OK_CODE almacena el código de función.
MY_PDF_VIEWER es un objeto que nos permite visualizar un
ALV.
CONTAINER
almacena
el
nombre
de
nuestro
CUSTOM_CONTROL.
W_SUBRC almacena el valor de retorno de llamar al método
FILE_OPEN_DIALOG.
V_URL contiene la ruta donde se encuentra el ALV.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_FILETAB> LIKE LINE OF T_FILETAB.
Declaramos un Field-Symbol para la tabla T_FILETAB.
436
*&----------------------------------------------------*
*&
SELECTION-SCREEN
*
*&----------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK TEST WITH FRAME.
PARAMETERS:
FILE LIKE RLGRAP-FILENAME.
SELECTION-SCREEN END OF BLOCK TEST.
Este parámetro, recogerá la ruta donde se encuentra el PDF que
queremos visualizar.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
CALL SCREEN 0100.
Llamamos a la pantalla 100 de nuestro Dynpro.
El evento AT-SELECION-SCREEN se ejecuta cuando llamamos
al parámetro FILE.
*&----------------------------------------------------*
*&
AT SELECTION-SCREEN
*
*&----------------------------------------------------*
AT SELECTION-SCREEN ON VALUE-REQUEST FOR FILE.
CALL METHOD CL_GUI_FRONTEND_SERVICES=>FILE_OPEN_DIALOG
EXPORTING
WINDOW_TITLE
= 'Seleccionar archivo'
DEFAULT_FILENAME = '*.pdf'
437
FILE_FILTER
= '*.pdf'
CHANGING
FILE_TABLE
= T_FILETAB
RC
= W_SUBRC.
READ TABLE T_FILETAB INDEX 1
ASSIGNING <FS_FILETAB>.
FILE = <FS_FILETAB>.
IF FILE IS INITIAL.
EXIT.
ENDIF.
El método FILE_OPEN_DIALOG nos muestra una ventana donde
podemos elegir la ubicación del archivo.
Debemos leer la tabla interna T_FILETAB y asignar su contenido
al parámetro FILE.
*&----------------------------------------------------*
*&
Module
STATUS_0100
OUTPUT
*
*&----------------------------------------------------*
MODULE STATUS_0100 OUTPUT.
SET PF-STATUS '100'.
SET TITLEBAR '100'.
IF CONTAINER IS INITIAL.
CREATE OBJECT CONTAINER
EXPORTING
CONTAINER_NAME = 'CUSTOM_ALV'
EXCEPTIONS
438
CNTL_ERROR
= 1
OTHERS
= 2.
CREATE OBJECT MY_PDF_VIEWER
EXPORTING
PARENT
= CONTAINER
EXCEPTIONS
CNTL_ERROR
= 1
CNTL_SYSTEM_ERROR = 2
OTHERS
= 3.
IF NOT MY_PDF_VIEWER->HTML_VIEWER IS INITIAL.
CLEAR V_URL.
V_URL = FILE.
CALL METHOD MY_PDF_VIEWER->OPEN_DOCUMENT
EXPORTING
URL = V_URL.
ENDIF.
ENDIF.
ENDMODULE.
" STATUS_0100
OUTPUT
Además de asignar la barra de menús y el título al programa,
creamos el objeto CONTAINER que hace referencia a nuesteo
CUSTOM_CONTROL, creamos el objeto MY_PDF_VIEWER
que se mostrará dentro del objeto CONTAINER y llamamos al
método OPEN_DOCUMENT para abrir el archivo PDF que
cargamos.
439
Comprimir (zip) archivos
Este es otro de esos programas que no utilizaremos casi nunca, pero
que definitivamente, agregan un gusto especial a la programación en
ABAP.
Este es el código.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: T_XLINE(2048) TYPE X.
440
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: DATA_TAB TYPE STANDARD TABLE OF T_XLINE,
T_FILETAB TYPE FILETABLE,
RESULT_TAB TYPE MATCH_RESULT_TAB.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: CL_ZIP TYPE REF TO CL_ABAP_ZIP,
SIZE TYPE I,
INPUT_X TYPE XSTRING,
OUTPUT_X TYPE XSTRING,
W_SUBRC TYPE SY-SUBRC,
W_FILE_IN TYPE STRING,
W_FILE_OUT TYPE STRING,
W_SIZE TYPE I,
W_LINE TYPE STRING,
W_EXT(4) TYPE C.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_FILETAB> LIKE LINE OF T_FILETAB,
<FS_RESULT_TAB> LIKE LINE OF RESULT_TAB.
*&----------------------------------------------------*
*&
SELECTION-SCREEN
*
*&----------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK TEST WITH FRAME.
PARAMETERS:
FILE_IN LIKE RLGRAP-FILENAME,
441
FILE_OUT LIKE RLGRAP-FILENAME.
SELECTION-SCREEN END OF BLOCK TEST.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
FIND ALL OCCURRENCES OF '\' IN W_FILE_IN
RESULTS RESULT_TAB.
DESCRIBE TABLE RESULT_TAB LINES W_SIZE.
READ TABLE RESULT_TAB INDEX W_SIZE
ASSIGNING <FS_RESULT_TAB>.
W_SIZE = STRLEN( W_FILE_IN ) –
<FS_RESULT_TAB>-OFFSET.
<FS_RESULT_TAB>-OFFSET = <FS_RESULT_TAB>-OFFSET +
<FS_RESULT_TAB>-LENGTH.
W_SIZE
= W_SIZE - <FS_RESULT_TAB>-LENGTH.
W_LINE = W_FILE_IN+<FS_RESULT_TAB>-OFFSET(W_SIZE).
CALL METHOD CL_GUI_FRONTEND_SERVICES=>GUI_UPLOAD
EXPORTING
FILENAME
= W_FILE_IN
FILETYPE
= 'BIN'
IMPORTING
FILELENGTH = SIZE
CHANGING
DATA_TAB
= DATA_TAB.
442
CALL FUNCTION 'SCMS_BINARY_TO_XSTRING'
EXPORTING
INPUT_LENGTH = SIZE
IMPORTING
BUFFER
= INPUT_X
TABLES
BINARY_TAB
= DATA_TAB.
IF CL_ZIP IS INITIAL.
CREATE OBJECT CL_ZIP.
ENDIF.
CALL METHOD CL_ZIP->ADD
EXPORTING
NAME
= W_LINE
CONTENT = INPUT_X.
CALL METHOD CL_ZIP->SAVE
RECEIVING
ZIP = OUTPUT_X.
REFRESH DATA_TAB.
CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'
EXPORTING
BUFFER
= OUTPUT_X
IMPORTING
OUTPUT_LENGTH = SIZE
TABLES
BINARY_TAB
= DATA_TAB.
443
CALL METHOD CL_GUI_FRONTEND_SERVICES=>GUI_DOWNLOAD
EXPORTING
BIN_FILESIZE = SIZE
FILENAME
= W_FILE_OUT
FILETYPE
= 'BIN'
CHANGING
DATA_TAB
= DATA_TAB.
*&----------------------------------------------------*
*&
AT SELECTION-SCREEN
*
*&----------------------------------------------------*
AT SELECTION-SCREEN ON VALUE-REQUEST FOR FILE_IN.
CALL METHOD CL_GUI_FRONTEND_SERVICES=>FILE_OPEN_DIALOG
EXPORTING
WINDOW_TITLE
= 'Seleccionar archivo'
DEFAULT_FILENAME = '*.*'
FILE_FILTER
= '*.*'
CHANGING
FILE_TABLE
= T_FILETAB
RC
= W_SUBRC.
READ TABLE T_FILETAB INDEX 1
ASSIGNING <FS_FILETAB>.
FILE_IN = <FS_FILETAB>.
W_FILE_IN = FILE_IN.
IF FILE_IN IS INITIAL.
EXIT.
ENDIF.
CLEAR T_FILETAB.
444
REFRESH T_FILETAB.
AT SELECTION-SCREEN ON VALUE-REQUEST FOR FILE_OUT.
CALL METHOD CL_GUI_FRONTEND_SERVICES=>FILE_OPEN_DIALOG
EXPORTING
WINDOW_TITLE
= 'Seleccionar archivo'
DEFAULT_FILENAME = '*.zip'
FILE_FILTER
= '*.zip'
CHANGING
FILE_TABLE
= T_FILETAB
RC
= W_SUBRC.
READ TABLE T_FILETAB INDEX 1
ASSIGNING <FS_FILETAB>.
FILE_OUT = <FS_FILETAB>.
W_FILE_OUT = FILE_OUT.
IF FILE_OUT IS INITIAL.
EXIT.
ELSE.
W_SIZE = STRLEN( W_FILE_OUT ).
W_SIZE = W_SIZE - 4.
W_EXT = W_FILE_OUT+W_SIZE(4).
IF W_EXT NE '.zip'.
CONCATENATE W_FILE_OUT '.zip'
INTO W_FILE_OUT.
FILE_OUT = W_FILE_OUT.
ENDIF.
ENDIF.
Revisemos un poco el código. Nos es complicado, pero algunas
cosas que pueden confundir un poco a simple vista.
445
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: T_XLINE(2048) TYPE X.
Declaramos un TYPE llamado T_XLINE de 2048 caracteres de
tipo X, es decir Hexadecimal.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: DATA_TAB TYPE STANDARD TABLE OF T_XLINE,
T_FILETAB TYPE FILETABLE,
RESULT_TAB TYPE MATCH_RESULT_TAB.
Declaramos una tabla interna llamada DATA_TAB que hace
referencia al TYPE T_XLINE, otra tabla llamada T_FILETAB que
hace referencia a FILETABLE, que nos sirve para guardar las rutas
de
los
archivos
y
finalmente
RESULT_TAB
de
tipo
MATCH_RESULT_TAB, que nos sirve para almacenar las
ocurrencias de una búsqueda de texto.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: CL_ZIP TYPE REF TO CL_ABAP_ZIP,
SIZE TYPE I,
INPUT_X TYPE XSTRING,
OUTPUT_X TYPE XSTRING,
446
W_SUBRC TYPE SY-SUBRC,
W_FILE_IN TYPE STRING,
W_FILE_OUT TYPE STRING,
W_SIZE TYPE I,
W_LINE TYPE STRING,
W_EXT(4) TYPE C.
CL_ZIP es un objeto que nos permitirá comprimir el documento de
entrada.
SIZE determina el tamaño del archivo que está siendo subido al
servidor.
INPUT_X guarda el contenido de convertir el archivo de Binario a
Hexadecimal (Archivo de entrada).
OUTPUT_X guarda el contenido de convertir el archivo de
Hexadecimal a Binario (Archivo de salida).
W_SUBRC almacena el resultado de ejecutar la acción de subir los
archivos.
W_FILE_IN almacena la ruta del archivo de entrada.
W_FILE_OUT almacena la ruta del archivo de salida.
W_SIZE almacena el tamaño en líneas de la tabla interna
RESULT_TAB.
W_LINE almacena el nombre final que tendrá el archivo de salida.
W_EXT almacena la extensión del archivo de salida.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
447
FIELD-SYMBOLS: <FS_FILETAB> LIKE LINE OF T_FILETAB,
<FS_RESULT_TAB> LIKE LINE OF RESULT_TAB.
Declaramos dos Field-Symbols, uno para FILETAB y otro para
RESULT_TAB.
*&----------------------------------------------------*
*&
SELECTION-SCREEN
*
*&----------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK TEST WITH FRAME.
PARAMETERS:
FILE_IN LIKE RLGRAP-FILENAME,
FILE_OUT LIKE RLGRAP-FILENAME.
SELECTION-SCREEN END OF BLOCK TEST.
Declaramos dos parámetros, uno de entrada y otro de salida.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
FIND ALL OCCURRENCES OF '\' IN W_FILE_IN
RESULTS RESULT_TAB.
DESCRIBE TABLE RESULT_TAB LINES W_SIZE.
READ TABLE RESULT_TAB INDEX W_SIZE
ASSIGNING <FS_RESULT_TAB>.
448
W_SIZE = STRLEN( W_FILE_IN ) –
<FS_RESULT_TAB>-OFFSET.
<FS_RESULT_TAB>-OFFSET = <FS_RESULT_TAB>-OFFSET +
<FS_RESULT_TAB>-LENGTH.
W_SIZE
= W_SIZE - <FS_RESULT_TAB>-LENGTH.
W_LINE = W_FILE_IN+<FS_RESULT_TAB>-OFFSET(W_SIZE).
CALL METHOD CL_GUI_FRONTEND_SERVICES=>GUI_UPLOAD
EXPORTING
FILENAME
= W_FILE_IN
FILETYPE
= 'BIN'
IMPORTING
FILELENGTH = SIZE
CHANGING
DATA_TAB
= DATA_TAB.
CALL FUNCTION 'SCMS_BINARY_TO_XSTRING'
EXPORTING
INPUT_LENGTH = SIZE
IMPORTING
BUFFER
= INPUT_X
TABLES
BINARY_TAB
= DATA_TAB.
IF CL_ZIP IS INITIAL.
CREATE OBJECT CL_ZIP.
ENDIF.
CALL METHOD CL_ZIP->ADD
EXPORTING
NAME
= W_LINE
CONTENT = INPUT_X.
449
CALL METHOD CL_ZIP->SAVE
RECEIVING
ZIP = OUTPUT_X.
REFRESH DATA_TAB.
CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'
EXPORTING
BUFFER
= OUTPUT_X
IMPORTING
OUTPUT_LENGTH = SIZE
TABLES
BINARY_TAB
= DATA_TAB.
CALL METHOD CL_GUI_FRONTEND_SERVICES=>GUI_DOWNLOAD
EXPORTING
BIN_FILESIZE = SIZE
FILENAME
= W_FILE_OUT
FILETYPE
= 'BIN'
CHANGING
DATA_TAB
= DATA_TAB.
Buscamos todas las ocurrencias del símbolo “\” dentro de la variable
W_FILE_IN y guardamos el resultado en la tabla interna
RESULT_TAB utilizando FIND ALL OCCURRENCES.
Utilizamos un DESCRIBE TABLE para determinar la cantidad de
registros generados y lo guardamos en W_SIZE.
Leemos el último registro de la tabla RESULT_TAB utilizando
como índice la cantidad de registros almacenados en la variable
W_SIZE.
450
Obtenemos la longitud del archivo de entrada con STRLEN y le
restamos el valor OFFSET de la tabla RESULT_TAB (Este valor
OFFSET es la ubicación del último símbolo “\” encontrado).
Reemplazamos el valor del OFFSET, sumándole a este el valor del
campo LENGTH (Es decir, la longitud del símbolo “\”).
Restamos a W_SIZE el valor del campo LENGTH.
Finalmente, guardamos en W_LINE la subcadena empezando con el
valor del OFFSET y tomando el valor de W_SIZE en cantidad de
caracteres.
Llamamos al método GUI_UPLOAD para subir el contenido del
archivo de entrada.
Con el módulo de funciones SCMS_BINARY_TO_XSTRING
convertimos el contenido del archivo de Binario a Hexadecimal.
Creamos el objeto CL_ZIP y llamamos al método ADD para
agregar el archivo de entrada al nuevo ZIP, llamamos al método
SAVE para crear el archivo ZIP con el archivo de entrada.
Con el método SCMS_XSTRING_TO_BINARY convertimos el
archivo ZIP de Hexadecimal a Binario y finalmente llamamos al
método GUI_DOWNLOAD para descargarlo con el nombre del
archivo de salida.
*&----------------------------------------------------*
*&
AT SELECTION-SCREEN
*
*&----------------------------------------------------*
AT SELECTION-SCREEN ON VALUE-REQUEST FOR FILE_IN.
CALL METHOD CL_GUI_FRONTEND_SERVICES=>FILE_OPEN_DIALOG
EXPORTING
WINDOW_TITLE
= 'Seleccionar archivo'
451
DEFAULT_FILENAME = '*.*'
FILE_FILTER
= '*.*'
CHANGING
FILE_TABLE
= T_FILETAB
RC
= W_SUBRC.
READ TABLE T_FILETAB INDEX 1
ASSIGNING <FS_FILETAB>.
FILE_IN = <FS_FILETAB>.
W_FILE_IN = FILE_IN.
IF FILE_IN IS INITIAL.
EXIT.
ENDIF.
CLEAR T_FILETAB.
REFRESH T_FILETAB.
Llamamos
al
método
FILE_OPEN_DIALOG
para
poder
almacenar la ruta del archivo de entrada.
452
Crear un Control de Texto
Este programa es bastante útil y necesario aunque que quizás no lo
utilicen mucho. En lo personal, yo lo he utilizado para proyectos
personales y no de trabajo.
Este es el código.
*=====================================================*
* DECLARACION DE CONSTANTES
*
*=====================================================*
CONSTANTS: LINE_LENGTH TYPE I VALUE 254.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: OK_CODE TYPE SY-UCOMM,
CUSTOM_CONTAINER TYPE REF TO
CL_GUI_CUSTOM_CONTAINER,
TEXT_EDITOR TYPE REF TO CL_GUI_TEXTEDIT.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
CALL SCREEN 0100.
453
*&----------------------------------------------------*
*&
Module
STATUS_0100
OUTPUT
*
*&----------------------------------------------------*
MODULE STATUS_0100 OUTPUT.
SET PF-STATUS '100'.
SET TITLEBAR '100'.
PERFORM CALL_EDITOR.
ENDMODULE.
" STATUS_0100
OUTPUT
*&----------------------------------------------------*
*&
Module
USER_COMMAND_0100
INPUT
*
*&----------------------------------------------------*
MODULE USER_COMMAND_0100 INPUT.
OK_CODE = SY-UCOMM.
CLEAR SY-UCOMM.
CASE OK_CODE.
WHEN 'BACK' OR 'STOP' OR 'CANCEL'.
SET SCREEN 0.
LEAVE SCREEN.
ENDCASE.
ENDMODULE.
" USER_COMMAND_0100
INPUT
454
*&----------------------------------------------------*
*&
Form
CALL_EDITOR
*
*&----------------------------------------------------*
FORM CALL_EDITOR .
IF TEXT_EDITOR IS INITIAL.
CREATE OBJECT CUSTOM_CONTAINER
EXPORTING
CONTAINER_NAME
= 'CUSTOM_ALV'
EXCEPTIONS
CNTL_ERROR
= 1
CNTL_SYSTEM_ERROR
= 2
CREATE_ERROR
= 3
LIFETIME_ERROR
= 4
LIFETIME_DYNPRO_DYNPRO_LINK = 5.
CREATE OBJECT TEXT_EDITOR
EXPORTING
WORDWRAP_MODE
=
CL_GUI_TEXTEDIT=>WORDWRAP_AT_FIXED_POSITION
WORDWRAP_POSITION
= LINE_LENGTH
WORDWRAP_TO_LINEBREAK_MODE =
CL_GUI_TEXTEDIT=>TRUE
PARENT
= CUSTOM_CONTAINER
EXCEPTIONS
ERROR_CNTL_CREATE
= 1
ERROR_CNTL_INIT
= 2
ERROR_CNTL_LINK
= 3
ERROR_DP_CREATE
= 4
GUI_TYPE_NOT_SUPPORTED
= 5
OTHERS
= 6.
ENDIF.
455
CALL METHOD CL_GUI_CFW=>FLUSH.
ENDFORM.
" CALL_EDITOR
Revisemos el código paso a paso.
*=====================================================*
* DECLARACION DE CONSTANTES
*
*=====================================================*
CONSTANTS: LINE_LENGTH TYPE I VALUE 254.
Declaramos una constante llamada LINE_LENGTH de tipo I con
el valor 254. Esto determina cada cuantos caracteres debería hacer
un salto de línea automático.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: OK_CODE TYPE SY-UCOMM,
CUSTOM_CONTAINER TYPE REF TO
CL_GUI_CUSTOM_CONTAINER,
TEXT_EDITOR TYPE REF TO CL_GUI_TEXTEDIT.
La variable OK_CODE almacena el código de función.
CUSTOM_CONTAINER es un objeto que creará el contenedor
para nuestro control de texto.
TEXT_EDITOR hace referencia al objeto que crea el control de
texto.
456
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
CALL SCREEN 0100.
Llamamos a la pantalla 100 del Dynpro.
*&----------------------------------------------------*
*&
Module
STATUS_0100
OUTPUT
*
*&----------------------------------------------------*
MODULE STATUS_0100 OUTPUT.
SET PF-STATUS '100'.
SET TITLEBAR '100'.
PERFORM CALL_EDITOR.
ENDMODULE.
" STATUS_0100
OUTPUT
Asignamos el menú y el título del programa. Llamamos al FORM
CALL_EDITOR. En donde llamamos al control de texto.
*&----------------------------------------------------*
*&
Form
CALL_EDITOR
*
*&----------------------------------------------------*
FORM CALL_EDITOR .
IF TEXT_EDITOR IS INITIAL.
CREATE OBJECT CUSTOM_CONTAINER
457
EXPORTING
CONTAINER_NAME
= 'CUSTOM_ALV'
EXCEPTIONS
CNTL_ERROR
= 1
CNTL_SYSTEM_ERROR
= 2
CREATE_ERROR
= 3
LIFETIME_ERROR
= 4
LIFETIME_DYNPRO_DYNPRO_LINK = 5.
CREATE OBJECT TEXT_EDITOR
EXPORTING
WORDWRAP_MODE
=
CL_GUI_TEXTEDIT=>WORDWRAP_AT_FIXED_POSITION
WORDWRAP_POSITION
= LINE_LENGTH
WORDWRAP_TO_LINEBREAK_MODE =
CL_GUI_TEXTEDIT=>TRUE
PARENT
= CUSTOM_CONTAINER
EXCEPTIONS
ERROR_CNTL_CREATE
= 1
ERROR_CNTL_INIT
= 2
ERROR_CNTL_LINK
= 3
ERROR_DP_CREATE
= 4
GUI_TYPE_NOT_SUPPORTED
= 5
OTHERS
= 6.
ENDIF.
CALL METHOD CL_GUI_CFW=>FLUSH.
ENDFORM.
Creamos
" CALL_EDITOR
el
contenedor
para
el
control
de
texto
CUSTOM_CONTAINER. Creamos el objeto TEXT_EDITOR.
458
Limpiamos los controles con el método FLUSH.
Como podemos ver, contamos con dos botones Load Local File
(Cargar archivo local)
archivo local)
y Save as Local File (Grabar como
. Lo cual significa que podemos cargar cualquier
archivo, modificarlo y guardarlo.
459
WebDynpro
Introducción
El WebDynpro es la nueva forma de desarrollar aplicaciones en
NetWeaver, esto es porque está completamente orientado a web y
respeta el modelo MVC (Model View Controller – Modelo Vista
Controlador).
Aunque el Dynpro no está aún muy difundido, es una herramienta muy
potente y bastante sencilla de usar.
Creando nuestro primer WebDynpro
El WebDynpro, para mi gran tristeza no cuenta con una transacción
propia, sino se desarrolla dentro del Object Navigator (Navegador de
Objetos), transacción SE80.
460
Elegimos de la lista Web Dynpro Comp. / Intf. (Componente o
Interface Web Dynpro).
Asignamos un nombre y presionamos el botón Display
.
Aceptamos en la ventana que nos muestra el sistema.
461
Asignamos una descripción y aceptamos.
Creamos una vista, donde van a estar los componentes que muestran
la información.
Para esto, hacemos un clic derecho sobre ZDUMMY_DYPRO y
seleccionamos Create Æ View (Crear Æ Vista).
462
Asignamos un nombre y una descripción a nuestra vista.
En este momento, el servicio tiene que conectarse con el servidor,
así que debemos ingresar nuestro usuario y password.
463
Una vez logeados, tenemos acceso al editor de vistas del
WebDynpro.
Debemos ir a la pestaña Context (Contexto) del editor de vistas.
Debemos hacer clic derecho en Context y elegir Create Æ Node
(Crear Æ Nodo)
464
Al nodo lo llamamos ZLENG_NODE y como Dictionary
Structure (Estructura del Diccionario) le pasamos la vista
ZVLENGUAJES_PROG.
Presionamos el botón Add Attribute from Structure (Agregar
atributo desde estructura)
.
Seleccionamos todos los campos.
465
Como parámetro de entrada para nuestro programa, vamos a tener
un campo que represente el ID, así que creamos un Attribute
(Atributo) llamado ID_PROG.
466
Grabamos y regresamos a la pestaña Layout (Disposición).
Seleccionamos Standard Container (Contenedor Estándar) del
menú izquierdo y luego Group (Grupo).
En el panel derecho, seleccionamos Caption (Título) y cambiamos
el atributo Text (Texto).
467
Seleccionamos
Create
Container
Form
(Crear
formulario
contenedor).
En la ventana que aparece a continuación, debemos presionar el
botón Context
para poder asignar un valor a nuestro
contenedor.
468
Elegimos el nodo ID_PROG.
Seleccionamos el nodo ID_PROG_LABEL.
Y le asignamos el texto Id en la propiedad Text.
469
Finalmente, tendríamos esto.
Si queremos probar como va quedando nuestro programa, debemos
hacer lo siguiente.
Seleccionamos ZDUMMY_DYNPRO con un doble clic.
Y arrastramos ZDUMMY_VIEW hacia ZDUMMY_DYNPRO.
470
Grabamos y al momento de activar veremos una ventana como esta.
471
Seleccionamos todos y aceptamos. Una vez que todo se ha activado,
debemos
crear
una
WebDynpro
Application
(Aplicación
WebDynpro).
Asignamos un nombre y una descripción y aceptamos.
En la siguiente pantalla, solamente debemos grabar y activar.
472
Presionamos el botón Test/Execute (Probar/Ejecutar)
o
presionamos F8.
El navegador nos muestra una pantalla de login, así que debemos
logearnos. Para esto, presionamos el botón Log On
.
473
Como podemos ver, nuestro parámetro de entrada aparece
perfectamente en el browser de internet.
En este caso, el campo ID_PROG no tiene una ayúda de búsqueda
asociada, por lo cual por mucho que presionemos F4, no va a pasar
nada.
Cerremos el browser y retornemos a nuestro programa.
474
Regresamos a la vista ZDUMMY_VIEW, pestaña Layout.
Elegimos el componente Table (Tabla) de la pestaña Standard
Complex (Complejos Estándar).
Le asignamos un título y hacemos clic derecho sobre el nodo
TABLE para seleccionar Create Binding (Crear Atadura).
475
Escogemos el Context ZLENG_NODE y dejamos todos los
registro seleccionados y continuamos.
Asignamos textos a las cabeceras y tendremos algo como esto.
476
Regresamos a la pestaña Standard Simple y seleccionamos el
control Button (Botón), el cual tendrá el texto “Mostrar!”.
477
Debemos asignar una acción al botón, por lo cual presionamos el
botón OnAction.
Asignamos el nombre SHOW y una descripción breve.
Nos vamos a la pestaña Actions (Acciones) y veremos nuestra
acción.
478
Debemos presionar el botón ABAP Routine (Rutina ABAP)
para poder asignar el código a nuestra acción.
METHOD ONACTIONSHOW.
DATA: NODE_LENG TYPE REF TO IF_WD_CONTEXT_NODE,
ITAB_LENG TYPE STANDARD TABLE OF
ZVLENGUAJES_PROG.
DATA: ID_PROG TYPE STRING.
WD_CONTEXT->GET_ATTRIBUTE(
exporting NAME = 'ID_PROG'
importing VALUE = ID_PROG ).
SELECT ID_PROG NOM_PROG LENGUAJE ENTORNO
INTO TABLE ITAB_LENG
FROM ZVLENGUAJES_PROG
WHERE ID_PROG EQ ID_PROG.
NODE_LENG =
479
WD_CONTEXT->GET_CHILD_NODE( NAME = 'ZLENG_NODE' ).
NODE_LENG->BIND_TABLE( ITAB_LENG ).
ENDMETHOD.
Declaramos la variable NODE_LENG que hace referencia a
IF_WD_CONTEXT_NODE, esta es una interface que controla los
nodos asociados a nuestro programa.
ITAB_LENG es una tabla interna que hace referencia a la vista
ZVLENGUAJES_PROG.
ID_PROG es una variable de tipo String.
WD_CONTEXT->GET_ATTRIBUTE(
exporting NAME = 'ID_PROG'
importing VALUE = ID_PROG ).
Obtenemos el valor de atributo ID_PROG, es decir, de nuestro
parámetro de entrada.
Seleccionamos el registro de la vista ZVLENGUAJES_PROG
siempre y cuando cumpla con el parámetro ID_PROG.
NODE_LENG =
WD_CONTEXT->GET_CHILD_NODE( NAME = 'ZLENG_NODE' ).
NODE_LENG->BIND_TABLE( ITAB_LENG ).
Con GET_CHILD_NODE obtenemos el contexto del nodo que
estamos utilizando y con BIND_TABLE enviamos los datos de la
tabla hacia nuestro table control.
480
Hay algo más que debemos hacer y que es muy importante.
Debemos ir a la pestaña Methods (Métodos) y seleccionar
WDDOINIT que es una acción que se lanza cuando se ejecuta el
programa por primera vez.
method WDDOINIT.
DATA: NODE_LENG TYPE REF TO IF_WD_CONTEXT_NODE,
ITAB_LENG TYPE STANDARD TABLE OF
ZVLENGUAJES_PROG.
APPEND INITIAL LINE TO ITAB_LENG.
NODE_LENG =
WD_CONTEXT->GET_CHILD_NODE( NAME = 'ZLENG_NODE' ).
NODE_LENG->BIND_TABLE( ITAB_LENG ).
endmethod.
Lo más resaltante de este código, es que agregamos una línea de
cabecera a nuestra tabla interna, para que el Table Control tenga
algo que mostrar cuando lanzemos el programa.
481
Todo se ve muy bien, pero estamos limitados a buscar solamente un
valor...Así que vamos a agregar otra caja de texto para poder crear
un rango de búsqueda.
482
Regresamos a la pestaña Context.
Renombramos el atributo ID_PROG a ID_PROG_INI, puesto que
ahora vamos a tener dos atributos distintos.
El nuevo atributo se llamará ID_PROG_FIN.
Ahora, creamos un nuevo nodo, llamado PARAMETROS que no
contendrá referencia a ningún tabla. Solamente nos servirá como
contenedor para los atributos.
483
Lo que hacemos es arrastrar los atributos a nuestro nodo
PARAMETROS.
484
Regresamos al Layout y eliminamos el campo de texto ID_PROG.
Creamos un nuevo Container Form.
Nuestro diseño quedará como se muestra a continuación, con dos
parámetros de entrada, que nos servirán para definir nuestro rango.
485
Regresamos a la pestaña Actions y al código del método Show.
METHOD ONACTIONSHOW.
DATA: NODE_LENG TYPE REF TO IF_WD_CONTEXT_NODE,
ITAB_LENG TYPE STANDARD TABLE OF
ZVLENGUAJES_PROG.
DATA: ID_PROG_INI TYPE STRING,
ID_PROG_FIN TYPE STRING.
DATA: ID_PROG TYPE RANGE OF ZVLENGUAJES_PROG-ID_PROG.
FIELD-SYMBOLS: <FS_IDPROG> LIKE LINE OF ID_PROG.
NODE_LENG = WD_CONTEXT->GET_CHILD_NODE(
NAME = 'PARAMETROS' ).
486
WD_CONTEXT->GET_ATTRIBUTE(
EXPORTING NAME = 'ID_PROG_INI'
IMPORTING VALUE = ID_PROG_INI ).
WD_CONTEXT->GET_ATTRIBUTE(
EXPORTING NAME = 'ID_PROG_FIN'
IMPORTING VALUE = ID_PROG_FIN ).
APPEND INITIAL LINE TO ID_PROG
ASSIGNING <FS_IDPROG>.
<FS_IDPROG>-SIGN = 'I'.
<FS_IDPROG>-OPTION = 'BT'.
<FS_IDPROG>-LOW = ID_PROG_INI.
<FS_IDPROG>-HIGH = ID_PROG_FIN.
SELECT ID_PROG NOM_PROG LENGUAJE ENTORNO
INTO TABLE ITAB_LENG
FROM ZVLENGUAJES_PROG
WHERE ID_PROG IN ID_PROG.
NODE_LENG =
WD_CONTEXT->GET_CHILD_NODE( NAME = 'ZLENG_NODE' ).
NODE_LENG->BIND_TABLE( ITAB_LENG ).
ENDMETHOD.
Veamos un poco el código.
DATA: ID_PROG_INI TYPE STRING,
ID_PROG_FIN TYPE STRING.
487
DATA: ID_PROG TYPE RANGE OF ZVLENGUAJES_PROG-ID_PROG.
FIELD-SYMBOLS: <FS_IDPROG> LIKE LINE OF ID_PROG.
Declaramos dos variables, ID_PROG_INI e ID_PROG_FIN, cada
cual almacenará el valor de su parámetro correspondiente.
Declaramos la variable ID_PROG de tipo RANGE (Rango), con lo
cual podremos definir un rango de valores para seleccionar de la
vista. Declaramos un Field-Symbol.
NODE_LENG = WD_CONTEXT->GET_CHILD_NODE(
NAME = 'PARAMETROS' ).
Asignamos
el
nodo
PARAMETROS
a
nuestra
variable
NODE_LENG.
NODE_LENG->GET_ATTRIBUTE(
EXPORTING NAME = 'ID_PROG_INI'
IMPORTING VALUE = ID_PROG_INI ).
NODE_LENG->GET_ATTRIBUTE(
EXPORTING NAME = 'ID_PROG_FIN'
IMPORTING VALUE = ID_PROG_FIN ).
Recuperamos los valores de ambos parámetros.
APPEND INITIAL LINE TO ID_PROG
ASSIGNING <FS_IDPROG>.
<FS_IDPROG>-SIGN = 'I'.
<FS_IDPROG>-OPTION = 'BT'.
488
<FS_IDPROG>-LOW = ID_PROG_INI.
<FS_IDPROG>-HIGH = ID_PROG_FIN.
Agregamos una línea de cabecera a nuestro rango, y asignamos los
valores correspondientes. SIGN es el tipo de valor, OPTION es el
tipo de correspondencia entre los datos, donde BT Æ Between
(Entre) y EQ Æ Equal (Igual). LOW es el valor inicial y HIGH es
el valor final.
SELECT ID_PROG NOM_PROG LENGUAJE ENTORNO
INTO TABLE ITAB_LENG
FROM ZVLENGUAJES_PROG
WHERE ID_PROG IN ID_PROG.
En el select, reemplazamos el EQ por un IN, con lo cual podemos
utilizar el rango para pasar más de un valor como parámetro.
489
490
BSP
Introducción
El BSP (Business Server Pages – Página del Servidor de
Aplicaciones), viene a ser un lenguaje script basado en ABAP, es
decir, una especio de ASP, JSP o PHP.
El BSP es anterior al WebDynpro, aunque esto no quiere decir que
sea obsoleto o menos completo, por el contrario, lo que no se puede
hacer con WebDynpro, se hace con BSP.
Creando nuestro primer BSP
Al igual que en el caso del WebDynpro, el BSP no tiene una
transacción propia (Otra queja más para SAP), así que nuevamente
debemos ir a la transacción SE80 y escoger BSP Application
(Aplicación BSP) en el menú.
491
Creamos nuestra aplicación ZDUMMY_BSP.
Creamos una página llamada index.bsp haciendo clic derecho
Create Æ Page (Crear Æ Página).
492
En la pestaña Layout (Disposición) colocamos el siguiente código.
<%@page language="abap"%>
<html>
<head>
</head>
<body>
<form method="post">
Id <input type="text" name="ID_PROG" value="">
<input type="submit" name="onInputProcessing(show)"
value="Enviar!">
</form>
<!--navigation->set_parameter( 'ID_PROG' )-->
</body>
</html>
Como podemos ver esta página, nos es más que HTML con un par
de características adicionales.
onInputProcessing(show) Æ Evento llamado cuando se ejecuta una
acción de procesamiento de datos. En este caso, show representa a
nuestro FORM.
navigation->set_parameter( ‘ID_PROG’ ) ÆIndica que estamos
estableciendo el campo ID_PROG como un parámetro de salida de
nuestra página.
Creamos una nueva página.
493
Este es el código de la página.
<%@page language="abap"%>
<html>
<body>
<%
data: w_id_prog type zvlenguajes_prog-id_prog,
t_leng type standard table of ZVLENGUAJES_PROG.
field-symbols: <fs_leng> like line of t_leng.
w_id_prog = request->get_form_field( 'ID_PROG' ).
select ID_PROG NOM_PROG LENGUAJE ENTORNO
INTO TABLE T_LENG
FROM ZVLENGUAJES_PROG
WHERE ID_PROG EQ W_ID_PROG.
%>
<table border=”1”>
<tr>
<b><td>Id</td><td>Nombre</td>
<td>Lenguaje</td><td>Entorno</td></b>
</tr>
<%
loop at t_leng assigning <fs_leng>.
%>
494
<tr>
<td><%= <fs_leng>-ID_PROG %></td>
<td><%= <fs_leng>-NOM_PROG %></td>
<td><%= <fs_leng>-LENGUAJE %></td>
<td><%= <fs_leng>-ENTORNO %></td>
</tr>
<%
endloop.
%>
</table>
</body>
</html>
Como podemos ver, en esta página hemos incluido código ABAP,
dentro de los tags <% y %>. Además hemos llamado variables
utilizando los tags <%= y %>. Esto es porque el BSP es un lenguaje
script y se integra perfectamente con el HTML.
Simplemente recibimos un parámetro de la página index.bsp,
seleccionamos datos y los mostramos en una tabla.
Hasta ahora, estas dos páginas no se comunican entre sí, así que
vamos a agregar algo de código para conectarlas.
En la página index.bsp nos vamos a la pestaña Event Handler
(Manejador
de
Eventos)
y
escogemos
del
menú
OnInputProcessing.
495
Agregamos el siguiente código.
CASE EVENT_ID.
WHEN 'show'.
NAVIGATION->SET_PARAMETER('ID_PROG').
NAVIGATION->NEXT_PAGE('SHOW_DATA').
ENDCASE.
EVENT_ID es una variable que almacena los “códigos de función”
de las páginas. Si llamamos a nuestro FORM de la página
index.bsp, establecemos el parámetro ID_PROG y llamamos a la
página SHOW_DATA (Cabe destacar que este es un alias que
definiremos en unos momentos).
Nos vamos a la aplicación con un doble clic.
496
Establecemos que index.bsp llama al alias SHOW_DATA que
representa a la página Show_Data.bsp
Finalmente, establecemos la página principal en la pestaña
Properties (Propiedades).
Grabamos, activamos y ejecutamos, presionando el botón Test
(Probar)
o presionando F8.
Debemos ingresar nuestro Usuario y Password para poder iniciar el
servidor.
497
498
Al ser HTML, podemos hacer lo que queramos con el formato de
salida de la tabla, pero lo que vamos a hacer, al igual que con el
WebDynpro, es crear un rango de valores para poder hacer una
mejor selección en el programa.
Regresamos a la página index.bsp y modificamos ligeramente el
código.
<%@page language="abap"%>
<html>
<head>
</head>
<body>
<form method="post">
Id desde <input type="text" name="ID_PROG_INI"
value="">
Id hasta <input type="text" name="ID_PROG_FIN"
value="">
<input type="submit" name="onInputProcessing(show)"
value="Enviar!">
</form>
<!--navigation->set_parameter( 'ID_PROG_INI' )-->
<!--navigation->set_parameter( 'ID_PROG_FIN' )-->
</body>
</html>
Tenemos ahora, dos parámetros ID_PROG_INI y ID_PROG_FIN.
Vamos
a
la
pestaña
Event
Handler
y
al
evento
OnInputProcessing.
499
CASE EVENT_ID.
WHEN 'show'.
NAVIGATION->SET_PARAMETER('ID_PROG_INI').
NAVIGATION->SET_PARAMETER('ID_PROG_FIN').
NAVIGATION->NEXT_PAGE('SHOW_DATA').
ENDCASE.
Enviamos ambos parámetros a la página Show_Data.bsp
En la pagína Show_Data.bsp hacemos un pequeños cambios.
<%@page language="abap" %>
<html>
<body>
<%
data: w_id_prog_ini type zvlenguajes_prog-id_prog,
w_id_prog_fin type zvlenguajes_prog-id_prog,
t_leng type standard table of ZVLENGUAJES_PROG,
r_leng type range of ZVLENGUAJES_PROG-ID_PROG.
field-symbols: <fs_leng> like line of t_leng,
<fs_r_leng> like line of r_leng.
append initial line to r_leng
assigning <fs_r_leng>.
w_id_prog_ini =
request->get_form_field( 'ID_PROG_INI' ).
w_id_prog_fin =
request->get_form_field( 'ID_PROG_FIN' ).
<fs_r_leng>-SIGN = 'I'.
<fs_r_leng>-OPTION = 'BT'.
<fs_r_leng>-LOW =
W_ID_PROG_INI.
<fs_r_leng>-HIGH = W_ID_PROG_FIN.
500
select ID_PROG NOM_PROG LENGUAJE ENTORNO
INTO TABLE T_LENG
FROM ZVLENGUAJES_PROG
WHERE ID_PROG in r_leng.
%>
<table border="1">
<tr>
<b><td>Id</td><td>Nombre</td>
<td>Lenguaje</td><td>Entorno</td></b>
</tr>
<%
loop at t_leng assigning <fs_leng>.
%>
<tr>
<td><%= <fs_leng>-ID_PROG %></td>
<td><%= <fs_leng>-NOM_PROG %></td>
<td><%= <fs_leng>-LENGUAJE %></td>
<td><%= <fs_leng>-ENTORNO %></td>
</tr>
<%
endloop.
%>
</table>
</body>
</html>
Creamos un rango R_LENG, un field-Symbol <FS_R_LENG>,
asignamos los parámetros de entrada W_ID_PROG_INI y
W_ID_PROG_FIN al rango y lo utilizamos para nuestro select.
501
502
ABAP y XML
El XML (Extensible Markup Language – Lenguaje extensible de
marcas) es un formato de intercambio de archivos muy utilizado en
al actualidad, puesto que es sumamente flexible.
Como era de esperarse, el ABAP nos permite trabajar con archivos
XML de una manera bastante simple.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_TAB,
LINE(100) TYPE C,
END OF TY_TAB.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_TAB TYPE STANDARD TABLE OF TY_TAB,
T_ZLENG TYPE STANDARD TABLE OF
ZVLENGUAJES_PROG,
T_FILETAB TYPE FILETABLE.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_FILETAB> LIKE LINE OF T_FILETAB.
503
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: XML_OUT TYPE STRING,
W_SUBRC TYPE SY-SUBRC,
SIZE TYPE I,
W_FILE TYPE STRING.
*&----------------------------------------------------*
*&
SELECTION-SCREEN
*
*&----------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK TEST WITH FRAME.
PARAMETERS:
FILE LIKE RLGRAP-FILENAME.
SELECTION-SCREEN END OF BLOCK TEST.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM OBTENER_DATOS.
PERFORM GENERAR_XML.
PERFORM DESCARGAR_XML.
*&----------------------------------------------------*
*&
AT SELECTION-SCREEN
*
*&----------------------------------------------------*
AT SELECTION-SCREEN ON VALUE-REQUEST FOR FILE.
CALL METHOD CL_GUI_FRONTEND_SERVICES=>FILE_OPEN_DIALOG
EXPORTING
WINDOW_TITLE
= 'Seleccionar archivo'
DEFAULT_FILENAME = '*.xml'
504
FILE_FILTER
= '*.xml'
CHANGING
FILE_TABLE
= T_FILETAB
RC
= W_SUBRC.
READ TABLE T_FILETAB INDEX 1
ASSIGNING <FS_FILETAB>.
FILE = <FS_FILETAB>.
IF FILE IS INITIAL.
EXIT.
ELSE.
W_FILE = FILE.
ENDIF.
*&----------------------------------------------------*
*&
Form
OBTENER_DATOS
*
*&----------------------------------------------------*
FORM OBTENER_DATOS.
SELECT ID_PROG NOM_PROG LENGUAJE ENTORNO
INTO TABLE T_ZLENG
FROM ZVLENGUAJES_PROG.
ENDFORM.
" OBTENER_DATOS
*&----------------------------------------------------*
*&
Form
GENERAR_XML
*
*&----------------------------------------------------*
FORM GENERAR_XML.
CALL TRANSFORMATION ('ID')
505
SOURCE TAB = T_ZLENG[]
RESULT XML XML_OUT.
ENDFORM.
" GENERAR_XML
*&----------------------------------------------------*
*&
Form
DESCARGAR_XML
*
*&----------------------------------------------------*
FORM DESCARGAR_XML.
CALL FUNCTION 'SWA_STRING_TO_TABLE'
EXPORTING
CHARACTER_STRING = XML_OUT
IMPORTING
CHARACTER_TABLE
= T_TAB.
CALL METHOD CL_GUI_FRONTEND_SERVICES=>GUI_DOWNLOAD
EXPORTING
BIN_FILESIZE = SIZE
FILENAME
= W_FILE
FILETYPE
= 'BIN'
CHANGING
DATA_TAB
= T_TAB.
ENDFORM.
" DESCARGAR_XML
Revisemos el código fuente.
506
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_TAB,
LINE(100) TYPE C,
END OF TY_TAB.
Declaramos un TYPE que contiene un solo campo llamado LINE
de 100 de longitud y tipo C.
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_TAB TYPE STANDARD TABLE OF TY_TAB,
T_ZLENG TYPE STANDARD TABLE OF
ZVLENGUAJES_PROG,
T_FILETAB TYPE FILETABLE.
Creamos algunas tablas internas, una para guardar el XML, otra para
guardar los datos de la Base de Datos y el último para guardar la ruta
del archivo de salida.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_FILETAB> LIKE LINE OF T_FILETAB.
Declaramos un field-symbol.
507
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: XML_OUT TYPE STRING,
W_SUBRC TYPE SY-SUBRC,
SIZE TYPE I,
W_FILE TYPE STRING.
XML_OUT es una cadena que contendrá la información para
generar el XML.
*&----------------------------------------------------*
*&
SELECTION-SCREEN
*
*&----------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK TEST WITH FRAME.
PARAMETERS:
FILE LIKE RLGRAP-FILENAME.
SELECTION-SCREEN END OF BLOCK TEST.
Declaramos un parámetro de entrada para poder obtener la ruta
donde se generará el archivo XML.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM OBTENER_DATOS.
PERFORM GENERAR_XML.
PERFORM DESCARGAR_XML.
508
OBTENER_DATOS nos sirve para seleccionar los datos de la
vista.
GENERAR_XML crea el archivo XML.
DESCARGAR_XML guarda el archivo en la ruta especificada.
*&----------------------------------------------------*
*&
AT SELECTION-SCREEN
*
*&----------------------------------------------------*
AT SELECTION-SCREEN ON VALUE-REQUEST FOR FILE.
CALL METHOD CL_GUI_FRONTEND_SERVICES=>FILE_OPEN_DIALOG
EXPORTING
WINDOW_TITLE
= 'Seleccionar archivo'
DEFAULT_FILENAME = '*.xml'
FILE_FILTER
= '*.xml'
CHANGING
FILE_TABLE
= T_FILETAB
RC
= W_SUBRC.
READ TABLE T_FILETAB INDEX 1
ASSIGNING <FS_FILETAB>.
FILE = <FS_FILETAB>.
IF FILE IS INITIAL.
EXIT.
ELSE.
W_FILE = FILE.
ENDIF.
Obtenemos la ruta del archivo a descargar.
509
*&----------------------------------------------------*
*&
Form
OBTENER_DATOS
*
*&----------------------------------------------------*
FORM OBTENER_DATOS.
SELECT ID_PROG NOM_PROG LENGUAJE ENTORNO
INTO TABLE T_ZLENG
FROM ZVLENGUAJES_PROG.
ENDFORM.
" OBTENER_DATOS
Seleccionamos los registros de la vista.
*&----------------------------------------------------*
*&
Form
GENERAR_XML
*
*&----------------------------------------------------*
FORM GENERAR_XML.
CALL TRANSFORMATION ('ID')
SOURCE TAB = T_ZLENG[]
RESULT XML XML_OUT.
ENDFORM.
" GENERAR_XML
CALL TRANSFORMATION nos sirve para llamar a una
transformación que se encarga de convertir los datos de la tabla
inerna en un cadena XML.
510
*&----------------------------------------------------*
*&
Form
DESCARGAR_XML
*
*&----------------------------------------------------*
FORM DESCARGAR_XML.
CALL FUNCTION 'SWA_STRING_TO_TABLE'
EXPORTING
CHARACTER_STRING = XML_OUT
IMPORTING
CHARACTER_TABLE
= T_TAB.
CALL METHOD CL_GUI_FRONTEND_SERVICES=>GUI_DOWNLOAD
EXPORTING
BIN_FILESIZE = SIZE
FILENAME
= W_FILE
FILETYPE
= 'BIN'
CHANGING
DATA_TAB
ENDFORM.
= T_TAB.
" DESCARGAR_XML
SWA_STRING_TO_TABLE nos sirve para convertir la cadena
XML_OUT a una tabla con los datos del XML.
El método GUI_DOWNLOAD nos permite descargar el archivo
XML.
511
Ahora que ya vimos como podemos generar un archivo XML a
partir de una tabla interna, veamos como hacer lo contrario, es decir,
generemos una tabla interna en base a un archivo XML.
El código es practicamente el mismo, solo cambia el orden.
*=====================================================*
* DECLARACION DE TYPES
*
*=====================================================*
TYPES: BEGIN OF TY_TAB,
LINE(100) TYPE C,
END OF TY_TAB.
512
*=====================================================*
* DECLARACION DE TABLAS INTERNAS
*
*=====================================================*
DATA: T_TAB TYPE STANDARD TABLE OF TY_TAB,
T_ZLENG TYPE STANDARD TABLE OF
ZVLENGUAJES_PROG,
T_FILETAB TYPE FILETABLE.
*=====================================================*
* DECLARACION DE FIELD-SYMBOLS
*
*=====================================================*
FIELD-SYMBOLS: <FS_FILETAB> LIKE LINE OF T_FILETAB,
<FS_ZLENG> LIKE LINE OF T_ZLENG.
*=====================================================*
* DECLARACION DE VARIABLES
*
*=====================================================*
DATA: XML_OUT TYPE STRING,
W_SUBRC TYPE SY-SUBRC,
SIZE TYPE I,
W_FILE TYPE STRING.
*&----------------------------------------------------*
*&
SELECTION-SCREEN
*
*&----------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK TEST WITH FRAME.
PARAMETERS:
FILE LIKE RLGRAP-FILENAME.
SELECTION-SCREEN END OF BLOCK TEST.
513
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM CARGAR_XML.
PERFORM GENERAR_TABLA_INTERNA.
PERFORM IMPRIMIR.
*&----------------------------------------------------*
*&
AT SELECTION-SCREEN
*
*&----------------------------------------------------*
AT SELECTION-SCREEN ON VALUE-REQUEST FOR FILE.
CALL METHOD CL_GUI_FRONTEND_SERVICES=>FILE_OPEN_DIALOG
EXPORTING
WINDOW_TITLE
= 'Seleccionar archivo'
DEFAULT_FILENAME = '*.xml'
FILE_FILTER
= '*.xml'
CHANGING
FILE_TABLE
= T_FILETAB
RC
= W_SUBRC.
READ TABLE T_FILETAB INDEX 1
ASSIGNING <FS_FILETAB>.
FILE = <FS_FILETAB>.
IF FILE IS INITIAL.
EXIT.
ELSE.
W_FILE = FILE.
ENDIF.
514
*&----------------------------------------------------*
*&
Form
GENERAR_TABLA_INTERNA
*
*&----------------------------------------------------*
FORM GENERAR_TABLA_INTERNA.
CALL TRANSFORMATION ('ID')
SOURCE XML XML_OUT
RESULT TAB = T_ZLENG[].
ENDFORM.
" GENERAR_TABLA_INTERNA
*&----------------------------------------------------*
*&
Form
CARGAR_XML
*
*&----------------------------------------------------*
FORM CARGAR_XML.
CALL METHOD CL_GUI_FRONTEND_SERVICES=>GUI_UPLOAD
EXPORTING
FILENAME
= W_FILE
FILETYPE
= 'BIN'
CHANGING
DATA_TAB
= T_TAB.
CALL FUNCTION 'SWA_STRING_FROM_TABLE'
EXPORTING
CHARACTER_TABLE
= T_TAB
IMPORTING
CHARACTER_STRING = XML_OUT.
ENDFORM.
" CARGAR_XML
515
*&----------------------------------------------------*
*&
Form
IMPRIMIR
*
*&----------------------------------------------------*
FORM IMPRIMIR.
LOOP AT T_ZLENG
ASSIGNING <FS_ZLENG>.
WRITE:/ <FS_ZLENG>-ID_PROG, <FS_ZLENG>-NOM_PROG,
<FS_ZLENG>-LENGUAJE, <FS_ZLENG>-ENTORNO.
ENDLOOP.
ENDFORM.
" IMPRIMIR
Revisemos el código, pero solamente las partes que han cambiado.
*=====================================================*
* START-OF-SELECTION
*
*=====================================================*
START-OF-SELECTION.
PERFORM CARGAR_XML.
PERFORM GENERAR_TABLA_INTERNA.
PERFORM IMPRIMIR.
CARGAR_XML carga el archivo y lo guarda en un tabla interna.
(Con formato XML claro está). Luego, lo transforma en una cadena
XML.
GENERAR_TABLA_INTERNA transforma la cadena XML en
una tabla interna.
IMPRIMIR muestra el resultado en pantalla.
516
*&----------------------------------------------------*
*&
Form
CARGAR_XML
*
*&----------------------------------------------------*
FORM CARGAR_XML.
CALL METHOD CL_GUI_FRONTEND_SERVICES=>GUI_UPLOAD
EXPORTING
FILENAME
= W_FILE
FILETYPE
= 'BIN'
CHANGING
DATA_TAB
= T_TAB.
CALL FUNCTION 'SWA_STRING_FROM_TABLE'
EXPORTING
CHARACTER_TABLE
= T_TAB
IMPORTING
CHARACTER_STRING = XML_OUT.
ENDFORM.
" CARGAR_XML
Utilizamos el método GUI_UPLOAD para cargar el archivo XML.
Con el módulo de funciones SWA_STRING_FROM_TABLE
convertimos la tabla interna en una cadena XML.
517
*&----------------------------------------------------*
*&
Form
GENERAR_TABLA_INTERNA
*
*&----------------------------------------------------*
FORM GENERAR_TABLA_INTERNA.
CALL TRANSFORMATION ('ID')
SOURCE XML XML_OUT
RESULT TAB = T_ZLENG[].
ENDFORM.
" GENERAR_TABLA_INTERNA
Utilizamos el CALL TRANSFORMATION para recibir una
cadena XML y retornar una tabla interna.
*&----------------------------------------------------*
*&
Form
IMPRIMIR
*
*&----------------------------------------------------*
FORM IMPRIMIR.
LOOP AT T_ZLENG
ASSIGNING <FS_ZLENG>.
WRITE:/ <FS_ZLENG>-ID_PROG, <FS_ZLENG>-NOM_PROG,
<FS_ZLENG>-LENGUAJE, <FS_ZLENG>-ENTORNO.
ENDLOOP.
ENDFORM.
" IMPRIMIR
Imprimimos el contenido de la tabla interna.
Como pueden ver, es bastante sencillo trabajar con XML y ABAP.
518
519
Scripting in a Box
Scripting in a Box es una herramienta creada por Craig Cmehil de
SAP AG.
Esta herramienta basada en Eclipse, reune a todos los lenguajes script
que pueden conectados con NetWeaver, como PHP, Ruby, Rails,
Perl y Python. De muy sencilla instalación, esta herramienta es
indispensable para el trabajo con lenguajes script.
La instalación es lo mejor de todo, puesto que no existe una
instalación, al igual que el Eclipse, el Scripting in a Box
simplementese descarga y se configura.
Como era de suponerse, cuenta además con varios ejemplos
(Incluyendo mi emulador de SE16 en PHP), lo cual lo hace excelente
para aquellas personas que nunca han hecho una integración con
NetWeaver.
Para descargarlo, debemos ingresar a la siguiente dirección (Debemos
ser usuarios del SDN – SAP Developer Network).
https://www.sdn.sap.com/irj/sdn/go/portal/prtroot/docs/library/uuid/e5
0bd86e-0a01-0010-53bd-857585234a6a
O, ingresar a http://sdn.sap.com Æ Downloads Æ More Downloadable
Tools for Developers Æ Scripting in a Box.
Veamos algunas imagenes de Scripting a in Box.
520
521
522
SAPLink
SAPLink es una herramienta creada por Daniel McWeeney y Ed
Herrmann de Colgate - Palmolive.
Esta herramienta, desarrollada en ABAP, nos permite crear una
verdadera librería de códigos para poder transportar o compartir con
los demás. Creando paquetes basados en código XML, podemos
almacenar Programas, Dynpros, WebDynpros, SmartForms y un
largo etcétera.
Si bien existen muchas herramientas para descargar y cargar elementos
de NetWeaver (Yo he creado varias de ellas), no hay hasta el
momento ninguna que pueda competir con SAPLink, puesto que
SAPLink nos brinda una interfaz muy sencilla, opción de descargar
Plug-Ins y un constante monitoreo de los posibles errores.
Podemos descargarla, debemos ingresar a la siguiente dirección.
http://code.google.com/p/saplink/
Y para los Plug-Ins, deberemos ingresar a la dirección.
http://code.google.com/p/saplink/wiki/pluginList
Veamos algunas cuantas imágenes.
523
524
525
526
Integración PHP-NetWeaver
Introducción
El PHP (PHP Preprocessor), es un lenguaje de programación para
páginas web dinámicas. Lleva varios años en el mercado, es open
source y en su versión 5 en adelante, es un lenguaje orientado a
objetos.
Su sintaxis es bastante similar a la de C++, por lo cual, conociendo
C++ o Java, es muy fácil de aprender.
Gracias a Eduard Koucky, existe un conector llamado SAPRFC, que
nos permite que las páginas web que desarrollemos en PHP, se
conecten con NetWeaver, para obtener datos de tablas, ejecutar
funciones, BAPI’s, etc. Es decir, hacer lo mismo que hacemos todos
los días, pero accediendo a través de una interface web.
Si se preguntan porque utilizar PHP con NetWeaver, en vez de
utilizar WebDynpro o BSP, la respuesta es muy simple: “PHP es
gratuito, fácil de aprender y configurar”. En todo caso, ya depende de
cada uno que tecnología utilizar.
Lo único que necesitamos para poder integrar PHP con NetWeaver es
lo siguiente (Si no hemos instalado Scripting in a Box claro está):
•
PHP versión 5.1.2 Æ http://www.php.net
•
Apache WebServer versión 2.0.55 Æ http://httpd.apache.org
•
SAPRFC 1.4-5.0.4 Æ http://saprfc.sourceforge.net/
•
NetWeaver Sneak Preview o SAP NetWeaver o MiniSAP o
MiniWas.
527
Como este es un libro sobre NetWeaver y no sobre PHP (Para eso
está, El Arte de Programar PHP), no vamos a enseñar a instalar el
PHP o el Apache, ni tampoco a programar en PHP, lo que vamos a
hacer es configurar el SAPRFC e integrarlo con NetWeaver.
Instalando el SAPRFC
Simplemente debemos descomprimir el archivo .ZIP y copiar el
archivo php_saprfc.dll, dentro de la carpeta /ext del PHP.
En el PHP.ini debemos agregar la siguiente línea.
extension = php_saprfc.dll;
Reiniciamos el servicio del Apache y estamos listos para comenzar.
Para
comprobar
que
todo
está
funcionando
correctamente,
simplemente llamamos a una página con el comando PHP_INFO().
<?php
phpinfo();
?>
528
Estableciendo la comunicación con NetWeaver
Vamos a crear un ejemplo simple (En realidad, es lo primero que hice
yo con PHP y NetWeaver), un emulador de SE16.
Lo primero que debemos hacer, es crear nuestra clase para poder
logearnos a NetWeaver. La llamamos Login_Class.php
<?php
Class Login
{
var $Login, $rfc;
function Login_Page()
{
PRINT("<DIV ALIGN='CENTER'><BR><BR><BR><BR><H1>PHP & SAP
- SE16 Emulator</H1>");
PRINT("<BR><TABLE BORDER='1' BORDERCOLOR='BLUE'
BGCOLOR='WHITE'>");
PRINT("<FORM ACTION='index.php' METHOD='POST'>");
PRINT("<TR><TD>Server</TD><TD><INPUT TYPE='TEXT'
NAME='Server'></TD></TR>");
PRINT("<TR><TD>System Number</TD><TD><INPUT TYPE='TEXT'
NAME='Sysnum' SIZE='3'></TD></TR>");
PRINT("<TR><TD>Client</TD><TD><INPUT TYPE='TEXT'
529
NAME='Client' SIZE='3'></TD></TR>");
PRINT("<TR><TD>User</TD><TD><INPUT TYPE='TEXT'
NAME='User'></TD></TR>");
PRINT("<TR><TD>Password</TD><TD><INPUT TYPE='PASSWORD'
NAME='Pass'></TD></TR>");
PRINT("<TR><TD COLSPAN='2' ALIGN='CENTER'><INPUT
TYPE='SUBMIT' value='Log In' NAME='LOG_IN'>");
PRINT("<INPUT TYPE='RESET' value='Clear'></TD></TR>");
PRINT("</FORM>");
PRINT("<TABLE>");
PRINT("</DIV>");
}
function Log_In($Server,$Sysnum,$Client,$User,$Pass)
{
$this->Login = array ("ASHOST"=>$Server,
"SYSNR"=>$Sysnum,
"CLIENT"=>$Client,
"USER"=>$User,
"PASSWD"=>$Pass,
"CODEPAGE"=>"1404");
return $this->Login;
}
function RFC_Connection($Login)
{
$this->rfc = saprfc_open($Login);
IF ( !$this->rfc )
{
ECHO "The connection fails with the following
530
error:".saprfc_error();
EXIT;
}
else
{
return $this->rfc;
}
}
}
?>
Lo que hacemos es crear algunas funciones.
Login_Page Æ Nos muestra la pantalla de logeo, donde debemos
ingresar el Usuario, Password, Servidor, Número de Sistema y Cliente.
Log_In Æ Nos conectamos a NetWeaver con los parámetros
ingresados.
RFC_Connection Æ Reestablecemos la conexión.
Ahora, debemos crear la clase SE16.php que es la que va hacer
todo el trabajo “sucio”.
<?php
Class SE16
{
var $fce;
531
function Show_Table($Table,$RFC_Me)
{
$this->fce = saprfc_function_discover($RFC_Me,
"RFC_READ_TABLE");
IF (! $this->fce )
{
ECHO "The function module had failed.";
EXIT;
}
$Table = STRTOUPPER($Table);
saprfc_import ($this->fce,"QUERY_TABLE",$Table);
saprfc_import ($this->fce,"DELIMITER","/");
saprfc_table_init ($this->fce,"OPTIONS");
saprfc_table_init ($this->fce,"FIELDS");
saprfc_table_init ($this->fce,"DATA");
$rfc_rc = "";
$rfc_rc = saprfc_call_and_receive ($this->fce);
if ($rfc_rc != SAPRFC_OK)
{
if ($rfc == SAPRFC_EXCEPTION )
echo ("Exception raised: ".saprfc_exception
($this->fce));
else
echo ("Call error: ".saprfc_error
($this->fce));
exit;
}
532
$data_row = saprfc_table_rows ($this->fce,"DATA");
$field_row = saprfc_table_rows ($this->fce,"FIELDS");
ECHO "<BR>";
ECHO "Table: " . $Table;
ECHO "<BR><BR>";
ECHO "<TABLE BORDER='1' BORDERCOLOR='BLUE'>";
ECHO "<TR>";
for($i=1; $i<=$field_row ; $i++)
{
$FIELDS = saprfc_table_read ($this->fce,"FIELDS",$i);
ECHO "<TD BGCOLOR='#4D80F6'>";
ECHO $FIELDS['FIELDNAME'];
ECHO "</TD>";
}
ECHO "</TR>";
for ($i=1; $i<=$data_row; $i++)
{
$DATA = saprfc_table_read ($this->fce,"DATA",$i);
$TEST = SPLIT("/",$DATA['WA']);
$rem = $i % 2;
if($rem == 0)
{
ECHO "<TR BGCOLOR='#ADDEF7'>";
}
else
{
ECHO "<TR BGCOLOR='#FFFFFF'>";
}
533
for($j=0; $j<$field_row; $j++)
{
ECHO "<TD>";
ECHO $TEST[$j];
ECHO "</TD>";
}
ECHO "</TR>";
}
ECHO "</TABLE>";
ECHO "<CENTER>";
PRINT("<FORM ACTION='index.php' METHOD='POST'>");
PRINT("<INPUT TYPE='HIDDEN' NAME='LOG_IN'><BR>");
PRINT("<INPUT TYPE='SUBMIT' value='Back' NAME='Back'>&nbs
p;  ");
PRINT("</FORM>");
ECHO "</CENTER>";
}
}
?>
Aquí tenemos una sola función, Show_Table, esta función recibe el
nombre de la tabla que queremos visualizar, se conecta con el
módulo de funciones RFC_READ_TABLE, obtiene los datos y los
muestra utilizando un formato de tabla de HTML.
Vamos a analizar algunos puntos clave del código.
$fce = saprfc_function_discover($rfc,”RFC_READ_TABLE”);
534
Verificamos que el módulo de funciones exista.
saprfc_import($fce,”QUERY_TABLE”,$Table);
Asignamos el nombre la tabla al parámetro QUERY_TABLE.
saprfc_table_init($fce,”OPTIONS”);
Inicializamos la tabla interna
$rc = saprfc_call_and_recieve($fce);
Ejecutamos la función y recibimos los parámetros IMPORT o
tablas internas de salida.
$data_row = saprfc_table_rows($fce,”DATA”);
Leemos la cantidad de líneas que tiene la tabla interna.
$DATA = saprfc_table_read($fce,”DATA”,$i);
Leemos el registro de la tabla por medio de un índice.
echo($DATA[WA].”\n”);
Imprimimos el contenido de la tabla especificando el campo.
535
saprfc_function_free($fce);
Liberamos la función de la memoria.
saprfc_close($fce);
Cerramos la conexión con SAP.
Ahora, podemos continuar con las páginas PHP. La primera, es
obviamente index.php
<?php
session_start();
include("Login_Class.php");
?>
<html>
<head>
<title>SE16 Simulation</title>
<style>
body {background: #F5F9FF;text-align: center;}
#login {background: #E5EAF6;border: 1px solid
#C7D3EA;}
</style>
</head>
<body>
<?php
if(isset($_POST['LOG_IN']) || (isset($_POST['Table'])))
536
{
if(!isset($_SESSION["Server"]))
{
$_SESSION["Server"] = $_POST["Server"];
$_SESSION["Sysnum"] = $_POST["Sysnum"];
$_SESSION["Client"] = $_POST["Client"];
$_SESSION["User"] = $_POST["User"];
$_SESSION["Pass"] = $_POST["Pass"];
}
$Login = new Login();
$Log_Me =
$Login->Log_In
($_SESSION["Server"],$_SESSION["Sysnum"],
$_SESSION["Client"],
$_SESSION["User"],$_SESSION["Pass"]);
$RFC_Me = $Login->RFC_Connection($Log_Me);
ECHO "<CENTER>";
PRINT("<FORM ACTION='Operation.php' METHOD='POST'>");
PRINT("<INPUT TYPE='TEXT' NAME='Table'><BR>");
PRINT("<INPUT TYPE='SUBMIT' value='Show Table'
NAME='Show_Table'>   ");
PRINT("</FORM>");
ECHO "<A HREF='index.php'>Log Out</A>";
ECHO "</CENTER>";
}
else
{
$_SESSION = array();
session_destroy();
537
$Login = new Login();
$Login->Login_Page();
}
?>
</body>
</html>
En esta página, lo que hacemos es llamar a nuestra clase Login para
poder conectarnos a NetWeaver y solicitar la tabla que queremos
visualizar.
La última página es Operation.php en donde llamamos a la clase
SE16 para mostrar los datos en la tabla.
<?php
session_start();
include("Login_Class.php");
include("SE16.php");
?>
<html>
<head>
<title>SE16 Simulation</title>
<style>
body {background: #F5F9FF;text-align: left;}
#login {background: #E5EAF6;border: 1px solid
#C7D3EA;}
</style>
</head>
<body>
538
<?php
$Login = new Login();
$Log_Me =
$Login-
>Log_In($_SESSION["Server"],$_SESSION["Sysnum"],
$_SESSION["Client"],
$_SESSION["User"],$_SESSION["Pass"]);
$RFC_Me = $Login->RFC_Connection($Log_Me);
$SE16 = new SE16();
if(isset($_POST['Table']))
{
$Table = $_POST['Table'];
$SE16->Show_Table($Table,$RFC_Me);
}
?>
</body>
</html>
Con esto terminamos y podemos ver el programa en ejecución.
539
540
Integración Ruby-NetWeaver
Introducción
Ruby es un lenguaje de programación japonés, creado hace más de 11
años por Yukihiro “Matz” Matsumoto.
Ruby es un lenguaje de fácil aprendizaje, totalmente orientado a
objetos y que hereda las características más importantes de Perl y
Python...Y como dice “Matz”, agrega todo lo que falta a esos dos
lenguajes.
Como no podía ser de otra forma, existe un conector para Ruby con
NetWeaver, llamado SAP::Rfc creado por el genial Piers Harding.
Lo único que necesitamos para poder integrar Ruby con NetWeaver
es lo siguiente.
•
Ruby Versión 1.8.5 Æ
http://rubyforge.org/frs/download.php/12751/ruby18521.exe
•
SAPRFC-0.19-mswin32.gem Æ
http://www.piersharding.com/download/ruby/saprfc-0.19mswin32.gem
•
SAPRFC.so.win32 Æ
http://www.piersharding.com/download/ruby/saprfc.so.wi
n32
541
Instalando el SAP::Rfc
Simplemente debemos hacer un GEM INSTALL SAPRFC-0.19mswin32.gem y listo.
Para comprobar la instalación basta con hacer un GEM LIST para
obtener la lista.
Estableciendo la comunicación con NetWeaver
La integración de Ruby con NetWeaver es bastante sencilla, y un
pequeño ejemplo (Emulador de SE16) nos tomará
solamente 63
líneas, que es bastante poco. Como este no es un libro sobre Ruby,
solamente vamos a revisar las líneas de código más importantes. (Para
aprender Ruby, pueden comprar “El Arte de Programar Ruby”).
542
require 'rubygems'
gem 'saprfc'
require "SAP/Rfc"
print "Host: "
$Host = gets
print "System Number: "
$Sysnr = gets
print "Client: "
$Client = gets
print "User: "
$User = gets
print "Password: "
$Password = gets
rfc = SAP::Rfc.new(:ashost => $Host.chop!,
:sysnr
=> $Sysnr.chop!.to_i,
:lang
=> "EN",
:client => $Client.chop!.to_i,
:user
=> $User.chop!,
:passwd => $Password.chop!,
:trace
=> 1)
# test the connection
# print "is_connected: ", rfc.is_connected(), "\n"
# lookup the interface for RFC_READ_TABLE
itab = rfc.discover("RFC_READ_TABLE")
print "\nEnter table: "
543
$Table = gets
print "\n"
itab.query_table.value = $Table.chop!
itab.delimiter.value = "|"
# do the RFC call
rfc.call(itab)
$Fields = Array.new
$Data = Array.new
$Data_Fields = Array.new
$Data_Split = Array.new
itab.fields.hashRows {|field| $Fields.push(field) }
$Fields_Len = $Fields.length
itab.data.hashRows {|field| $Data.push(
field.to_s.strip!) }
$Data_Len = $Data.length
for i in 0...$Data_Len
$Data_Fields = $Data[i]
$Data_Split = $Data_Fields.split("|")
for i in 1...$Fields_Len
print $Data_Split[i].to_s.strip, "|"
end
print "\n\n"
end
print "\nclose connection: ", rfc.close(), "\n"
print "Exit"
544
Veamos un poco el código.
require ‘rubygems’
gem ‘saprfc’
require ‘SAP/Rfc’
Llamamos a la librería de SAP::Rfc y especificamos que queremos
utilizar el GEM.
rfc = SAP::Rfc.new(:ashost => $Host.chop!,
:sysnr => $Sysnr.chop!.to_i,
:lang => "EN",
:client => $Client.chop!.to_i,
:user => $User.chop!,
:passwd => $Password.chop!,
:trace => 1)
Establecemos la conexión pasando los parámetros indicados por el
usuario.
itab = rfc.discover("RFC_READ_TABLE")
Verificamos que el módulo de funciones exista.
itab.query_table.value = $Table.chop!
itab.delimiter.value = "|"
545
Llenamos los parámetros del módulo de funciones.
rfc.call(itab)
Ejecutamos la llamada al módulo de funciones.
itab.fields.hashRows {|field| $Fields.push(field) }
$Fields_Len = $Fields.length
itab.data.hashRows {|field| $Data.push(field.to_s.strip!) }
$Data_Len = $Data.length
Obtenemos los datos que nos retorna la función.
for i in 0...$Data_Len
$Data_Fields = $Data[i]
$Data_Split = $Data_Fields.split("|")
for i in 1...$Fields_Len
print $Data_Split[i].to_s.strip, "|"
end
print "\n\n"
end
Imprimimos el resultado en pantalla.
Ahora, podemos ver algunas imágenes.
546
547
Donde conseguir el SAP NetWeaver Sneak
Preview
El SAP NetWeaver Sneak Preview es la versión para desarrolladores
de SAP AG.
Podemos conseguirla de dos maneras.
•
Comprando el libro ABAP Objects : ABAP Programming in
SAP NetWeaver de Horst Keller y Sascha Krüger en SAP
Press. http://www.sappress.com/product.cfm?account=&product=H1934
•
Descargandolo desde el SDN (SAP Developer Network –
http://sdn.sap.com) en Download Æ SAP NetWeaver Main
Releases Æ SAP NetWeaver 7.0 (2004s) – ABAP Trial
Version
https://www.sdn.sap.com/irj/sdn/go/portal/prtroot/docs/library/
uuid/80fd9a0a-e306-2a10-c896-b84c77c13ed2
548
Bibliografía y agradecimientos
•
ABAP Objects : ABAP Programming in SAP NetWeaver
de Horst Keller y Sascha Krüger – SAP Press.
http://www.sappress.com/product.cfm?account=&product=H1934
•
Next Generation ABAP Development de Rich Heilman y
Thomas Jung – SAP Press. http://www.sappress.com/product.cfm?account=&product=H1986
•
El Arte de Programar SAP R/3 de Alvaro Tejada Galindo
– Lulu Press. http://www.lulu.com/content/561744
Hay muchas personas a quienes debo agredecer, puesto que me han
apoyado mucho al momento de estar escribiendo este libro. Sin
orden de importancia, mis agradecimientos...
•
Mi esposa Milly y mi hija Tammy Luz, a quienes dedico este
libro.
•
A Thomas Jung y Rich Heilman (A quienes conocí en Las
Vegas TechEd 2007 y gracias a ellos escribí este libro).
•
A Craig Cmehil que siempre ha sido un Mentor para mi.
•
A Cathy Welsh por facilitarme información valiosa sobre
WebDynpro.
•
A Abesh Bathacharjee quien con su amistad y sentido del
humor me ayudó a ser más creativo.
549
•
Al SDN (SAP Developer Network) por haberme eligido
como Mentor de SAP.
•
A Mark Finnern por la confianza en mi que siempre ha
demostrado.
•
A Marilyn Pratt por haberme demostrado que “Las abuelitas
también pueden rockear”.
550
Enlaces Web
Para terminar, quiero agregar algunos enlaces web que pienso que
pueden ser bastante útiles.
•
SDN (SAP Developer Network) Æ http://sdn.sap.com
•
BPX (Business Process Expert) Æ http://bpx.sap.com
•
El Blog Tecnológico de Blag Æ http://atejada.blogspot.com
•
SAP ABAP en castellano Æ http://www.sap4.com
•
SAP Press Æ http://www.sap-press.com
•
SAP Marketplace Æ http://service.sap.com
•
SAP Help Æ http://help.sap.com
•
Alvaro Tejada Galindo’s SDN Blogs Æ
https://www.sdn.sap.com/irj/sdn/weblogs?blog=/pub/u/4802
4
•
SAP Æ http://www.sap.com
551
Descargar