martes, 21 de agosto de 2018

Patrones de diseño con ABAP - MVC

El patrón MVC o Modelo-Vista-Controlador es un patrón de diseño de software que se centra en separar la lógica de negocio y los datos de la aplicación del código para su representación visual. Se fundamenta en separar el código en tres capas diferentes, acotadas por su responsabilidad. 
  • MODELO ("M"): Es la capa donde se trabaja con los datos. Esta formada por las clases que contienen la lógica de la aplicación. Estas clases no pueden contener ningún código relacionado con el interfaz de usuario. En ABAP, estas clases correspondería a una o varias clases globales definidas en la transacción SE24.
  • VISTA ("V"): Es la capa donde se va ha producir la visualización de los datos al usuario. Esta formada por las clases clases que contiene todo el código relaciona con el interfaz de usuario. En abap utilizariamos las clases ya existentes para generar ALV, Webdynpros, etc... reciben como parámetro de entrada los datos generados por la capa MODELO y generan la salida.
  • CONTROLADOR ("C"): Esta capa sirve de enlace entre las capas de Modelo y Vista. No puede contener ningún código que manipule datos, ni muestre datos, solamente debe servir como enlace entre los modelos y las vistas. En Abap correspondería a un programa ejecutable implementado en la transacción SE38.

Esta separación en capas otorga al software un grado muy alto de flexibilidad, permitiendo cambiar la lógica de negocio independientemente del interfaz de la aplicación y viceversa.


Patrón Modelo Vista Controlador MVC

Imaginemos una aplicación Abap donde el usuario requiera al programa realizar una acción determinada ( por ejemplo ver  los datos de un proveedor, cliente, pedido de venta, etc... ).

  1. El usuario ejecuta la transacción solicitando al controlador el listado.
  2. El controlador llamara al método del modelo para realizar la acción solicitada por el usuario. 
  3. El modelo retornara los datos solicitados por el usuario al controlador
  4. El controlador llamara al método de la vista encargado de mostrar los datos por pantalla. 
  5. La vista mostrara los datos solicitados al usuario.

En ningún momento de este proceso, el controlador busca o procesa los datos, ni realiza ninguna operación para mostrar los datos al usuario, su trabajo se centra en ser el enlace entre la capa modelo y la capa vista.

Para explicar mejor como utilizar el patrón MVC con ABAP , imaginemos un cliente que nos pide un programa ABAP con los siguientes requerimientos:
  1. Leer los datos de los pedidos de ventas requeridos por el usuario
  2. Elimine aquellos que sean de clientes bloqueados
  3. Muestre el resultado por pantalla utilizando un ALV
  4. El usuario podrá seleccionar registros del ALV
  5. Un pulsador para crear la entrega de salida de los pedidos seleccionados
En ingles a estos programas se les llama aplicaciones CRUD (Create , Read, Update, Delete).

Si implementamos el programa en la transacción SE38 con una estructura clásica ABAP , a grandes rasgos,  nos quedara un programa parecido al siguiente código:

REPORT zreport_clasico_abap.

* Definiciones de datos y estructuras globales
* Parametros de la pantalla de selección

STAR-OF-SELECTION.
  PERFORM leer_pedidos_ventas.
  PERFORM eliminar_bloqueados.
  PERFORM display_alv.

END-OF-SELECTION.

FORM leer_pedidos_ventas.
* Código para leer datos de las tablas VBAK/VBAP
* Almacenar los datos en una tabla interna
ENDFORM.

FORM eliminar_bloqueados.
* Código para eliminar pedidos con clientes bloqueados
* Leer datos de alguna tabla KN**
* Eliminar registros de la tabla interna
ENDFORM.

FORM display_alv.
* Mostar la tabla interna por pantalla en un ALV (REUSE_ALV_GRID_DISPLAY)
ENDFORM.

FORM user_command USING pi_ucomm rs_selfield.
  IF ( pi_ucomm EQ c_ucomm_save ).
    PERFORM crear_entregas.
  ENDIF.
ENDFORM.

FORM crear_entregas.
*  Código para  crear entregas de los pedidos seleccionados
ENDFORM

* Más y más subrutinas....


El principal problema del enfoque clásico es que, el código para obtener y manipular los datos esta mezclado con el código del interfaz de usuario o GUI. Con suerte, el programador encapsulara en subrutinas las operaciones de acceso de datos y para lanzar el ALV, pero en la mayoría de los casos nos encontraremos mezclado, dentro de la misma subrutina, tanto el código para acceder  a los datos  y el código para mostrar los datos. Para que os deis cuenta de los problemas que puede acarrear esta estructura, imaginaos que surge un nuevo requerimiento que diga que incluyamos la misma funcionalidad del programa en una función RFC (Remote Funcion Call). Despues de crear nuestro programa nos piden crear una función RFC que reciba una serie de números de pedidos de venta, elimine los que tiene clientes bloqueados y genere las entregas de salida.

En estos casos, lo que podemos hacer con una estructura de programación clásica de ABAP, es bastante limitado:
  1. Copiar el código del programa dentro de una bapi. No es muy buena idea... porque si cambia la lógica para seleccionar clientes bloqueados o se añaden nuevos campos, tendremos que adaptar ambos programas, es decir para una modificación habrá que adaptar múltiples programas en el sistema. Lo mejor para mantener un código siempre es que sea simple y evitar duplicidades.
  2. Incluir el código común en subrutinas y guardarlo en un fichero que pueda ser utilizado desde cualquier programa con la instrucción INCLUDE. Es una solución mejor que la anterior, pero la interfaz, la información que conpartiran las subrutinas al ejecutarse,  necesitara ser definida en las transacciones SE37/SE38 múltiples veces. Volvemos al caso anterior, si por ejemplo añadimos nuevos campos, necesitaremos modificar en múltiples puntos.
  3. Crear un grupo de funciones y convertir las subrutinas en funciones que puedan ser llamadas desde cualquier programa. La solución más elegante de las tres, pero si usas funciones jamas podrás utilizar las ventajas que te ofrece la programación orientada a objetos como la herencia, polimorfismo y la encapsulación.
Enfoquemos el problema utilizando programación orientada a objetos y el patrón MVC. El objetivo es  ver como nos puede ayudar a realizar aplicaciones, con un grado de flexibilidad ante los cambios o nuevos requerimientos mucho mayor que la clásica programación ABAP.

La primera ventaja la encontramos en la fase de diseño. Al utilizar programación orientada a objetos podemos utilizar el Lenguaje de Modelado Unificado o UML para diseñar un diagrama de nuestro programa siguiendo el patrón MVC. Para este ejemplo, el diagrama UML es bastante sencillo.

diagrama UML de la aplicación


Ambos controladores ( programa y la funcion RFC ) usaran la misma clase de modelo pero podrán después reconducir los datos y recibir las peticiones de sus modelos de vistas. La clase ZCL_MODELO_DATOS encapsula toda la lógica de la aplicación en sus métodos y no contiene ningún código referente al GUI de la aplicación. Por ultimo, la capa VISTA sera la función REUSE_ALV_GRID_DISPLAY y para la función RFC sera la llamada a la función RFC desde el programa externo. Como podéis observar cada capa tiene acotada su responsabilidad y no se encarga de nada que no le corresponda.

La clase ZCL_MODELO_DATOS la creamos como una clase global en la transacción SE24 para que podamos reutilizarla en todos los controladores que diseñemos.

 El código de la clase global seria más o menos el siguiente:
class ZCL_MODELO_DATOS definition public final create public .

  public section.

    types: BEGIN OF T_PARAM,
      s_vbeln TYPE RANGE OF vbak-vbeln,
      s_vbtyp TYPE RANGE OF vbak-vbtyp,
      s_kunnr TYPE RANGE OF vbak-kunnr,
     END OF t_param .
   
    types: BEGIN OF T_PEDIDOS,
      VBELN type vbak-vbeln,
      posnr type vbap-posnr,
      kunnr type vbak-kunnr,
      name1 type kna1-name1,
      matnr type vbap-matnr,
      maktx type makt-maktx,
     END OF t_pedidos .
   
    types:
      tt_pedidos type STANDARD TABLE OF t_pedidos with non-unique key TABLE_LINE.

    methods LEER_PEDIDOS_VENTAS
     importing
       !IS_PARAM type T_PARAM.
  
    methods ELIMINAR_BLOQUEADOS.
    methods GET_LISTA_PEDIDOS.
     returning
      value(RT_LISTA) type TT_PEDIDOS .
    methods CREAR_PEDIDOS.
  protected section.

  private section.
    data ZTT_PEDIDOS type TT_PEDIDOS .

ENDCLASS.

CLASS ZCL_MODELO_DATOS IMPLEMENTATION.

  method LEER_PEDIDOS_VENTAS.
    "Código para leer la VBAK/VBAP segun parametros y rellenar la tabla ZTT_PEDIDOS
  endmethod.


  method ELIMINAR_BLOQUEADOS.
    "Código para recorrer la tabla interna ZTT_PEDIDOS 
    "Eliminar clientes bloqueados consultando tablas KN**
  endmethod.

  method GET_LISTA_PEDIDOS.
    rt_lista[] = ztt_pedidos
  endmethod.

  method CREAR_PEDIDOS.
    "Algo de código para un LOOP a la tabla ZTT_PEDIDOS y crear las entregas 
  endmethod.

ENDCLASS.

No hace falta que toda la lógica de la aplicación este en una misma clase. La ventaja de usar clases globales es la reutiliziación del código. Para un proyecto podría crearse toda una librería de clases que encapsulasen la lógica de las aplicaciones y así evitar la duplicidad del código. Para crear toda esta estructura de clases y no morir en el intento, podeis utilizar el patron de diseño COMPOSITE para construir objetos complejos a partir de otros más simples y similares entre sí, gracias a la composición recursiva y a una estructura en forma de árbol.

Ahora que tenemos la capa modelo, veamos como quedaría el programa en la transacción SE38:

REPORT ZREPORT_SE38.

DATA go_modelo_datos TYPE REF TO zcl_modelo_datos.
DATA ti_pedidos TYPE ztt_pedidos.

* Algunas definiciones de datos 
* Parametros de la pantalla de seleccion

START-OF-SELECTION.
  go_modelo_datos = NEW #( ).
  go_modelo_datos->leer_pedidos_ventas( ).
  go_modelo_datos->eliminar_bloqueados( ).
  ti_pedido = go_modelo_datos-get_lista_pedidos( ).
  perform display_alv.
END-OF-SELECTION.

FORM display_alv.
* Código para llamar a REUSE_ALV_GRID_DISPLAY
ENDFORM.

FORM user_command USING pi_ucomm rs_selfield.
  IF ( pi_ucomm EQ c_ucomm_save ).
    go_modelo_datos->crear_entregas( ).
  ENDIF.
ENDFORM.

Utilizando el patrón MVC,  lo que hace el programa ZREPORT_SE38 es únicamente funcionar como "mensajero" entre la clase ZCL_MODELO_DATOS y la función REUSE_ALV_GRID_DISPLAY.

En caso de necesitar modificar el ALV y cambiar nombres de columnas, orden de las columnas, añadir pulsadores, etc... podrías crear una nueva clase que llame a REUSE_ALV_GRID_DISPLAY e implementar todos los métodos que necesitáis para modificar y ajustar el ALV. El programa que funcione como controlador llamaría a esta nueva clase en vez de a REUSE_ALV_GRID_DISPLAY.

También, en vez de usar la bapi REUSE_ALV_GRID_DISPLAY, podríamos utilizar la clase CL_SALV_TABLE. Crear una nueva clase que tenga un constructor o un método que configure el ALV y genere el ALV usando la clase CL_SALV_TABLE. De esta manera seguimos manteniendo el principio del patrón MVC de separar la responsabilidad de cada capa.

En resumen, El objetivo del patrón MVC es separar los datos y su procesamiento de su representación visual. Las principales ventajas son que los programas se vuelven mucho mas flexibles y escalables ante nuevos requerimientos o cambios. También nos permite tener un mismo modelo para la obtención de datos que podemos reutilizar en múltiples vistas para la representación de los datos.

Por contra, al utilizar programación orienta a objetos, tendremos que crear nuestras propias clases, aumentando la cantidad de objetos que hay que mantener ( pasamos de 1 programa a como mínimo 1 clase global y un programa )  y la complejidad del sistema.

Fuentes:
Design Patterns in ABAP Objects 2017 Kerem Koseoglu
The MVC design pattern in ABAP for practical use
ABAP Objects Design Patterns – Model View Controller
SDN Wiki - Model View Controller design Pattern

lunes, 11 de septiembre de 2017

ABAP CDS: Introducción

“ Las aplicaciones van y vienen, pero los datos viven para siempre “
Cameron O’Rourke –ORACLE

El modelo entidad/relación es y a sido una de las mejores forma de de representar la estructura de la base de datos, las tablas implicadas en cada consulta SQL y sus relaciones. Un buen programador, antes de empezar a escribir código, dedica un tiempo en modelar el programa y a analizar a que datos va ha acceder y como hacerlo de la manera más optima.

Diagrama entidad/relación de varias tablas de SAP

Yo personalmente, utilizo los diagramas de entidad/relación para planificar y optimizar mis consultas SQL. Una vez tengo terminado los diagramas, implemento el diagrama en el código de dos formas  diferentes:

  • Utilizando la transacción SE11 para crear las tablas, vistas, estructuras, elementos de datos, etc… 
  • Directamente combinando sentencias SQL simples o mas complejas con los operadores INNER JOIN o LEFT OUTER JOIN.


Si utilizamos la transacción SE11, los objetos que creamos en el diccionario podemos reutilizarlos en diferentes aplicaciones. Si utilizamos consultas SQL directamente en el código, pues solamente se podrán usar locamente en el programa que las implementemos. Todo dependerá de si queremos reutilizar las consulta SQL o no.

Modelo de datos y sus posibles implementaciones

En los siguientes artículos nos centraremos en como podemos implementar nuestros modelos de datos en SAP con la ayuda de las CDS para después consumirlos desde  nuestro código ABAP o Fiori.

¿QUE ES ‘CORE DATA SERVICES’?


Es una infraestructura creada por SAP para definir y consumir modelos de datos persistentes en SAP HANA. Utiliza un lenguaje de definición de datos (DDL), un lenguaje de consulta (QL) y un lenguaje de control de datos (DCL) . CDS es independiente de la BD utilizada, aunque actualmente solamente lo soporta SAP HANA.

especificación Core Data Services

El objetivo de las vistas CDS es facilitar la implementación de los modelos de datos desarrollados en la fase de diseño. Las vistas CDS nos permiten implementar los modelos de entidad-relación de una forma mucho más eficiente que las tradicionales vistas de la transacción SE11.

Modelo de datos, implementación y consumo con CDS

Originalmente, solamente era posible crear vistas CDS para SAP HANA y requería una conexión directa al servidor de HANA. Pero a partir de SAP NW ABAP 7.4 SP05, podemos crear vistas CDS desde el servidor de aplicación ABAP utilizando el IDE  Eclipse + plugin ADT.


'CODE TO DATA'  vs  'DATA TO CODE'

Las vista CDS son parte del nuevo paradigma de programación 'Code Push-Down' o 'Code To Data' que nos ha traído HANA . Este nuevo paradigma es completamente opuesto al tradicional paradigma 'Data to Code'. Tradicionalmente, en el paradigma 'Data to Code', como los accesos a la base de datos son lentos, se cargan todos los datos en las tablas internas, estructuras, variables, etc.... que se almacenan en la memoria del servidor de aplicación , mucho más rápida,  para después realizamos las operaciones o cálculos requeridos antes de mostrar los datos por pantalla.

Data to core  vs Code to data

En el nuevo paradigma 'Code Push-Down', podemos realizar operaciones y cálculos directamente en la capa de la base de datos, quedando la capa del servidor de aplicación libre para el tratamiento de los datos procesados. Esto es posible gracias a que la base de datos SAP HANA tiene un soporte para lenguajes procedurales directamente incluido en el kernel de la base de datos. 

Por ejemplo, gracias al paradigma 'Code Push-Down' con la nueva implementación de OPEN SQL para ABAP podemos incluir operaciones aritméticas dentro de la instrucción SELECT que se ejecutaran al nivel de la base de datos y liberando los recursos del servidor de aplicación. En posteriores veremos cómo aprovecharnos de estas nuevas funcionalidades.

Operaciones aritméticas directamente en la sentencia SQL que se ejecutan a nivel de base de dato


HANA CDS y ABAP CDS


Existen dos implementaciones diferentes de la misma especificación CDS: 

HANA CDS
  • Se crean directamente en la base de datos de SAP HANA 
  • Pueden consumirse externamente con servicios ODATA 
  • Para consumirlas desde una aplicación ABAP: 
  • Crear una vista externa en el DICC ABAP 
  • Enlazar la vista externa con la vista HANA CDS 
  • Consumir la vista externa utilizando sentencias OPEN SQL 
  • DOS órdenes de trasporte diferentes: 
  • La vista HANA CDS que se trasporta como una unidad de trasporte de HANA 
  • La vista externa que se trasporta con una orden de SAP (transacción STMS) 
HANA CDS, dos ordenes de trasporte para ABAP

ABAP CDS
  • Disponible desde SAP NW ABAP 7.4 SP05 
  • Se pueden definir y crear desde el servidor de aplicaciones ABAP con ECLIPSE + ADT 
  • Necesitamos de un servidor de aplicaciones ABAP para crearla 
  • Pueden consumirse externamente con servicios ODATA 
  • Son independientes de la base de datos 
  • Pueden utilizar tablas, vistas, elementos, etc…. definidos en el diccionario ABAP 
  • No requieren crear vistas externas 
  • Una única orden de trasporte estándar de SAP ( transacción STMS
ABAP CDS, una única orden de transporte

En esta serie de entradas sobre CDS nos centramos exclusivamente en vistas ABAP CDS.

ABAP CDS vs VISTAS SE11

¿Que ventajas tiene las vistas ABAP CDS sobre las vistas tradicionales del dicccinario que creamos desde la transacción SE11? Para empezar,  Las vistas creadas desde la transacción SE11 tienen limitaciones como que solamente podemos crear asociaciones de tipo INNER JOIN y que únicamente las vistas de mantenimiento son asociaciones de tipo LEFT OUTER JOIN.

En la siguiente tabla se muestran algunas de las características disponibles en las vistas ABAP CDS y que no se encuentran disponibles en las vistas de bases de datos tradicionales de la transacción SE11.

Algunas diferencias entre ABAP CDS y vistas de la transacción SE11
En el siguiente enlace podéis leer un listado completo de las características de ABAP CDS.


PASOS PARA CREAR Y CONSUMIR UNA VISTA ABAP CDS


Para comprobar si vuestro servidor de aplicación soporta las vistas ABAP CDS,  conectaros con el cliente SAPGUI y comprobar el nivel del módulo SAP_BASIS:
  • En el menú superior Sistemas -> STATUS -> Info Componentes
  • Comprobar que el nivel del módulo SAP_BASIS es 7.40 SP05 o superior.
Nivel módulo SAP_BASIS

Para crear vistas ABAP CDS necesitais tener instalado el IDE Eclipse y el plugin ABAP Development Tools y como hemos dicho conexión a un servidor de aplicación con SAP NW ABAP 7.4 SP05 o superior.

Pasos para crear la vista ABAP CDS:

1. Seleccionar el proyecto ABAP donde queremos crear la vista ABAP CDS
2. Seleccionar el paquete donde queremos crear la vista CDS.
    Botón derecho del ratón -> New -> Other ABAP Repository Object

3. Seleccionar el objeto"Data Definition" dentro de la carpeta "Core Data Services". 


Nota: Si estáis trabajando con un componente SAP_BASIS inferior a la versión 7.5 tenéis que seleccionar el objeto "Data Definition" o "DLL Source" dentro de la carpeta  "Diccionary".

4. Rellenar el nombre de la vista ABAP CDS y la descripción


5. Seleccionar o crear la orden de trasporte.

6. Por defecto tenemos varias plantillas disponibles para crear las vistas ABAP CDS.
    Seleccionamos la plantilla "Define view" y pulsamos finalizar.


7. Se abrirá una nueva ventana con el siguiente código.

vista ABAP CDS

8. Para implementar la vista ABAP CDS:
  1. Cambiar el texto "sql_view_name" por el nombre que tendrá la vista en el diccionario ABAP. IMPORTANTE: El nombre de la vista en el diccionario ABAP no puede ser el mismo que el nombre que hemos dado a la vista ABAP CDS en el paso 4.
  2. Reemplazar "data_source_name" por el nombre de la tabla de la base de datos 
  3. Para referenciar los campos se utiliza el punto (‘.’) en vez de ('~')
  4. Las columnas que recuperamos en la consulta se definen dentro de las llaves {}.
  5. Después de las llaves {} podemos incluir la cláusula WHERE.
  6. Los comentarios empiezan por // para una línea o /*   */ para un bloque

9. Grabar y activar.


En el diccionario ABAP las vistas ABAP CDS se denominan Vistas SQL DDL. Una vez activada la vista en Eclipse ya podemos ver nuestra vista desde la transacción SE11 y ejecutarla y acceder a su definición en SQL desde el menú Detalles -> ABAP Create, pero no se puede modificar la vista ABAP CDS desde el diccionario ABAP, solamente desde ECLIPSE.

Vista ABAP CDS desde la transacción SE11

Desde nuestras aplicaciones ABAP, las vistas ABAP CDS pueden consumirse con sentencias OPEN SQL como cualquier vista o tabla del diccionario ABAP. También pueden utilizarse como tipos para la definición de tablas internas, estructuras y variables de los programas ABAP. Debemos utilizar el nombre que hemos especificado en el atributo sqlViewName y no el nombre de la vista ABAP CDS.

Consumición de la vista ABAP CDS 

COMO ENCONTRAR LAS VISTAS ABAP CDS CREADAS EN EL SISTEMA SAP

Consultando tabla TADIR, con los siguientes parámetros de selección, podemos encontrar todas las vistas CDS existentes en SAP, su nombre en el diccionario ABAP y el paquete al que pertenecen.

Acceder a la tabla TADIR con los siguientes parámetros:
  • PGMID = ‘R3TR’ 
  • OBJECT =’DDLS’ 
Accediendo la tabla DDLDEPENDENCY podemos obtener los nombres de la vista en el diccionario ABAP y en SAP HANA. Acceder a la tabla DDLDEPENDENCY con el parámetro:

  • OBJECTTYPE = 'VIEW'



Entradas de la tabla TADIR correspondientes a vistas CDS

Nombre de la vista ABAP CDS en HANA y en el diccionario ABAP


Fuente:

jueves, 6 de octubre de 2016

Misión Cumplida!!!

Los nervios casi me destrozan, pero misión completada!! Termine mi Máster universitario en investigación en ingeniería de software y sistemas informáticos!! 


lunes, 3 de octubre de 2016

Defensa del trabajo de fin de Máster

Mañana me toca viajar a Madrid a defender mi TFM
"Generación automática de código ABAP para sistemas SAP R3"

Un trabajo con el que me he introducido en las arquitecturas dirigidas a modelos (MDA) y la creación de modelos que generen automáticamente el código utilizando Eclipse Modeling Tools y Acceleo.

...que nervios...



jueves, 2 de junio de 2016

Aun seguimos aqui

Perdón por la inactividad  del blog.

No he desaparecido. Y sí, voy a continuar escribiendo pero este año esta siendo complicado.
Es mi ultimo año de Master por la UNED y entre el máster y el trabajo me queda poco tiempo para encargarme del blog.

Lo dicho, perdonar la inactividad.
En breves volveremos.

Un saludo a todos.

miércoles, 9 de marzo de 2016

Clase CL_SALV_TABLE: Toolbar, pulsadores y funciones a medida - parte I

Hasta ahora,  hemos explicado como mostrar la información que extraemos de la base de datos y como modificar los atributos de la columnas del listado ALV. Pero los usuarios, rara vez solamente quieren "ver" la información, lo normal es que quieran interactuar con ella de diferentes formas. Por ejemplo ordenar el listado, imprimir el listado descargar el listado  en una hoja excel, etc...

Para añadir funcionalidades a medida a los listados ALV, la CL_SALV_TABLE incluye pulsadores y funciones estándar que podemos configurar  antes de llamar al método display( ) .

try.
      cl_salv_table=>factory(
        importing
          r_salv_table = gr_table
        changing
          t_table      = ti_mara ).

      gr_table->get_functions( )->set_all( if_salv_c_bool_sap=>true ).     "TODOS los pulsadores estandard
*     gr_table->get_functions( )->set_default( if_salv_c_bool_sap=>true ). "Solamente algunos puls estandard

      gr_table->display( ).

    catch cx_salv_msg into cx_salv.
*     Gestionamos las excepciones que puedan suceder
      gr_msg = cx_salv->get_text( ).
      message gr_msg type 'E'.
    catch  cx_salv_not_found into cx_not_found.
      gr_msg = cx_not_found->get_text( ).
      message gr_msg type 'E'
  endtry.

Toolbar estándar creada con el metodo set_all( if_salv_c_bool_sap=>true )

Toolbar estándar creada con el metodo set_default( if_salv_c_bool_sap=>true )

Tambien es posible añadir pulsadores personalizados que llamen a  funciones diseñadas para propósitos específicos.

Para crear vuestro propio status de usuario, podeios copiar el status SALV_STANDARD del programa SALV_DEMO_METADATA desde la trnasacción SE38

  1. Transacción  SE38 -> SALV_DEMO_METADATA  -> Status GUI -> SALV_STANDARD
  2. Situar el cursor sobre Status GUI -> SALV_STANDARD
  3. Pulsar el botón derecho del ratón -> Copiar
  4. Introducimos el nombre de nuestro programa y el nuevo nombre del status gui
  5. En la copia que hemos realizado, añadir los pulsadores a medida que necesitamos.
  6. Vincular el STATUS GUI al ALV utilizando el método set_screen_status( ).
  7. Activarlo todo
try.
      cl_salv_table=>factory(
        importing
          r_salv_table = gr_table
        changing
          t_table      = ti_mara ).

      gr_table->set_screen_status( pfstatus = 'ZZSTATUS_001'  "Nuestro STATUS GUI
                                   report = sy-repid
                                   set_functions = gr_table->c_functions_all ).

      gr_table->display( ).

    catch cx_salv_msg into cx_salv.
*     Gestionamos las excepciones que puedan suceder
      gr_msg = cx_salv->get_text( ).
      message gr_msg type 'E'.
    catch  cx_salv_not_found into cx_not_found.
      gr_msg = cx_not_found->get_text( ).
      message gr_msg type 'E'
  endtry.

Si activamos y ejecutamos, aparecerán los pulsadores de nuestro status gui. Pero si los pulsamos, no sucederá nada. Para asignar una función a un pulsador, primero  necesitamos implementar una clase de eventos y enlazarla a nuestra clase CL_SALV_TABLE.

1.- Crear la siguiente definición para la clase de eventos CLASS_HANDLE_EVENTS. En este caso, solamente implementamos el evento ON_USER_COMMAND, pero se pueden implementar mas eventos que explicare en otra entrada.

*---------------------------------------------------------------------*
*       CLASS class_handle_events DEFINITION
*---------------------------------------------------------------------*
*  define a local class for handling events of cl_salv_table
*---------------------------------------------------------------------*
class class_handle_events definition.
  public section.
    methods:
      on_user_command
                    for event added_function of cl_salv_events
        importing e_salv_function. "e_salv_function es como el OK_CODE de las dynpros

endclass.                    "lcl_handle_events DEFINITION


2.- Crear la  implementacion de la clase y el método ON_USER_COMMAND.
*---------------------------------------------------------------------*
*       CLASS lcl_handle_events IMPLEMENTATION
*---------------------------------------------------------------------*
* implement the events for handling the events of cl_salv_table
*---------------------------------------------------------------------*
class class_handle_events implementation.

  method on_user_command.

    case e_salv_function. "Contiene el cod. de funcion del pulsador seleccionado
      WHEN 'GO_VBELN'.  "mensaje por pantalla
        MESSAGE "Esto es una prueba, funcion GO_VBELN" display like 'I'.
    ENDCASE.
  endmethod.

endclass.                    "lcl_handle_events IMPLEMENTATION


3.- El ultimo paso es enlazar la clase de eventos con nuestra instancia de la clase CL_SALV_TABLES. Esto significa que cuando se produzca un evento ( doble click, pulsar un boton de la barra de herramientas, etc...) sera "escuchado" por la clase de eventos y ejecutara el metodo asignado al evento.

Como podéis observar el parámetro de entrada e_alv_funcion contiene el código de función del pulsador que ha seleccionado el usuario en la barra de herramientas. Identificando ese código en el método ON_USER_COMMAND podemos identificar, procesar y actuar en función de la acción realizada por del usuario ( por ejemplo grabar los datos, ejecutar una bapi, salir del programa, etc...).

Todo evento que queráis en vuestro ALV tenéis que enlazarlo con la clase CL_SALV_TABLES:

DATA r_handler_salv_table type REF TO class_handle_events. 
try.
      cl_salv_table=>factory(
        importing
          r_salv_table = gr_table
        changing
          t_table      = ti_mara ).

      gr_table->set_screen_status( pfstatus = 'ZZSTATUS_001'  "Nuestro STATUS GUI
                                    report = sy-repid
                                    set_functions = gr_table->c_functions_all ).

*     Creamos la instancia de la clase de eventos y registramos el evento on_user_command
      CREATE OBJECT r_handler_salv_table.
      set handler r_handler_salv_table->on_user_command for gr_table->get_event( ).

      gr_table->display( ).

    catch cx_salv_msg into cx_salv.
*     Gestionamos las excepciones que puedan suceder
      gr_msg = cx_salv->get_text( ).
      message gr_msg type 'E'.
    catch  cx_salv_not_found into cx_not_found.
      gr_msg = cx_not_found->get_text( ).
      message gr_msg type 'E'
  endtry.


Aquí os dejo un ejemplo completo,  Es un listado de materiales, con pulsadores a medida.
No os olvidéis de crear el status gui y añadirle los pulsadores a medida
En la siguiente entrada os mostrare como añadir pulsadores y menús a la barra de herramientas utilizando eventos.

STATUS GUI de nuestro ALV
CL_SALV_TABLE con pulsadores a medida


*&---------------------------------------------------------------------*
*& Report:  ZZCL_SALV_TABLE_FULL_SCREEN
*& Autor :  David Rueda Barrón
*&---------------------------------------------------------------------*
*& Creacion de ALV a pantalla completa con la clase CL_SALV_TABLE
*& Se añaden 3 pulsadores a medida con un STATUS GUI
*& La clase de eventos CLASS_HANDLE_EVENTS recoje los eventos
*&---------------------------------------------------------------------*

report zzcl_salv_table_full_screen.

*---------------------------------------------------------------------*
*       CLASS class_handle_events DEFINITION
*---------------------------------------------------------------------*
*  define a local class for handling events of cl_salv_table
*---------------------------------------------------------------------*
class class_handle_events definition.
  public section.
    methods:
      on_user_command
                    for event added_function of cl_salv_events
        importing e_salv_function. "e_salv_function es como el OK_CODE de las dynpros

endclass.                    "lcl_handle_events DEFINITION
*---------------------------------------------------------------------*
*       CLASS lcl_handle_events IMPLEMENTATION
*---------------------------------------------------------------------*
* implement the events for handling the events of cl_salv_table
*---------------------------------------------------------------------*
class class_handle_events implementation.

  method on_user_command.

    case e_salv_function. "Contiene el cod. de funcion del pulsador seleccionado
      WHEN 'GO_VBELN'.  "mensaje por pantalla
        MESSAGE 'Esto es una prueba, funcion GO_VBELN' TYPE 'I'.
      WHEN 'GO_VF03'.
        MESSAGE 'Esto es una prueba, funcion GO_VF03' TYPE 'I'.
      WHEN 'GO_MM02'.
        MESSAGE 'Esto es una prueba, funcion MM02' TYPE 'I'.
    ENDCASE.

  endmethod.

endclass.                    "lcl_handle_events IMPLEMENTATION

*---------------------------------------------------------------------*
*       TIPOS, ESTRUCTURAS y VARIABLES GLOBALES
*---------------------------------------------------------------------*
types: begin of type_matnr,
         matnr type mara-matnr,
         maktx type makt-maktx,
         mtart type mara-mtart,
         matkl type mara-matkl,
         meins type mara-meins,
       end of type_matnr.

* Tabla interna con los datos del ALV
data ti_mara type standard table of type_matnr.


data gr_table type ref to cl_salv_table.
data r_handler_salv_table type REF TO class_handle_events.

*Variables globales para gestionar las excepciones
data gr_msg  type string.
data cx_salv  type ref to cx_salv_msg.
data cx_not_found TYPE ref to cx_salv_not_found.
*&---------------------------------------------------------------------*
*& START-OF-SELECTION
*&---------------------------------------------------------------------*
start-of-selection.

  select m~matnr t~maktx m~mtart m~matkl m~meins
    into corresponding fields of table ti_mara
    from mara as m
    inner join makt as t
       on m~matnr eq t~matnr
      and t~spras eq sy-langu.

  try.
      cl_salv_table=>factory(
        importing
          r_salv_table = gr_table
        changing
          t_table      = ti_mara ).
    catch cx_salv_msg into cx_salv.
*     Gestionamos las excepciones que puedan suceder
      gr_msg = cx_salv->get_text( ).
      message gr_msg type 'E'.
  endtry.

  try.
*   Registramos el status gui para el ALV
    gr_table->set_screen_status( pfstatus = 'ZZSTATUS_001'  "Nuestro STATUS GUI
                                 report = sy-repid
                                 set_functions = gr_table->C_FUNCTIONS_ALL ).

*   Creamos la instancia de la clase de eventos y registramos el evento on_user_command
    CREATE OBJECT r_handler_salv_table.
    SET HANDLER r_handler_salv_table->on_user_command for gr_table->get_event( ).

    catch  cx_salv_msg into cx_salv.
      gr_msg = cx_salv->get_text( ).
      message gr_msg type 'E'.
    catch  cx_salv_not_found into cx_not_found.
       gr_msg = cx_not_found->get_text( ).
      message gr_msg type 'E'.
  endtry.

  gr_table->display( ).

Entradas anteriores:

Entradas siguientes:
Clase CL_SALV_TABLE: Toolbar, pulsadores y funciones a medida - parte II

domingo, 27 de septiembre de 2015

Clase CL_SALV_TABLE: Modificar atributos de las columnas

En el anterior post mostramos como generar un ALV utilizando solo la clase CL_SALV_TABLE. A continuación vamos a explicar como se modifican los atributos de las columnas del ALV cuando lo generamos utilizando la clase CL_SALV_TABLE.

A diferencia de los ALV generados con la bapi REUSE_ALV_GRID_DISPLAY o con la clase CL_GUI_ALV_GRID  la clase  CL_SALV_TABLE no utiliza un catalogo de campos o fieldcat para determinar las características de las columnas del listado ALV.  Cada columna del ALV debe ser tratada como un objeto individual con métodos que nos permitirán modificar sus atributos y  características como su descripción, longitud, ocultar la columna, etc...

Necesitamos instanciar dos clases:
  • CL_SALV_COLUMNS_TABLE  : Gestionar las columnas que componen el ALV
  • CL_SALV_COLUMN    : Gestionar los atributos de una columna del ALV
La primera clase CL_SALV_COLUMNS_TABLE nos permitirá gestionar atributos y características de las columnas del ALV. Para mas información podéis acceder a los métodos y atributos de la clase CL_SALV_COLUMNS_TABLE desde la transacción SE24 -> Pasar a -> Documentación -> Clase.

Transacción SE24 -> Clase CL_SALV_COLUMNS_TABLE

Para modificar las características y atributos propios de cada columna del ALV  instanciaremos un objeto CL_SALV_COLUMN_TABLE utilizando el metodo GET_COLUMN de la clase CL_SALV_COLUMNS_TABLE. Este método recibe como parámetro de entrada el nombre de una columna del ALV y retorna un objeto de la clase CL_SALV_COLUMN_TABLE que nos permite con sus métodos cambiar las características y atributos de la columna.

Transacción SE24 -> Clase CL_SALV_COLUMN_TABLE

Desde la transacción SE24  -> Pasar a -> Documentación -> Clase  podéis ver todos los métodos de la clase CL_SALV_COLUMN y que atributos nos permiten modificar.  Los métodos que mas se suelen utiliza son:


  • SET_LONG_TEXT:  Descripción larga de la columna
  • SET_MEDIUM_TEXT:Descripción media de la columna
  • SET_SHORT_TEXT: Descripción corta de la columna
  • SET_VISIBLE:   Oculta o muestra la columna ( 'X' muestra , '  '  oculta la columna )
  • SET_OUTPUT_LENGTH:  Especifica el ancho de la columna 
  • SET_OPTIMIZED:  Optimiza el ancho de la columna automáticamente
  • SET_CELL_TYPE: formato de la celda ( ej: campo, checkbox, etc.. ) 

A continuación, ampliamos el código del anterior post para modificar las características de algunas columnas.


*&---------------------------------------------------------------------*
*& Report:  ZZCL_SALV_TABLE_FULL_SCREEN
*& Fecha :  29.07.2015
*& Autor :  David Rueda Barrón
*&---------------------------------------------------------------------*
*& Creacion de ALV a pantalla completa con la clase CL_SALV_TABLE
*& Modificacion de los atributos de la columnas con las clases:
*&   - CL_SALV_COLUMNS_TABLE
*&   - CL_SALV_COLUMN_TABLE
*&---------------------------------------------------------------------*

report zzcl_salv_table_full_screen.

types: begin of type_matnr,
         matnr type mara-matnr,
         maktx type makt-maktx,
         mtart type mara-mtart,
         matkl type mara-matkl,
         meins type mara-meins,
         box   type c,
       end of type_matnr.

data: ti_mara type standard table of type_matnr.

data gr_table     type ref to cl_salv_table.         "Instancia de la clase
data gr_columns   type ref to cl_salv_columns_table. "Para gestionar las columnas
data gr_column    type ref to cl_salv_column_table.  "Para gestionar atrb. de una columna
data cx_salv      type ref to cx_salv_msg.
data cx_not_found TYPE ref to cx_salv_not_found.

data gr_msg  type string.
*&---------------------------------------------------------------------*
*& START-OF-SELECTION
*&---------------------------------------------------------------------*
start-of-selection.

  select m~matnr t~maktx m~mtart m~matkl m~meins
    into corresponding fields of table ti_mara
    from mara as m
    inner join makt as t
       on m~matnr eq t~matnr
      and t~spras eq sy-langu.

  try.
      cl_salv_table=>factory(
        importing
          r_salv_table = gr_table
        changing
          t_table      = ti_mara ).
    catch cx_salv_msg into cx_salv.
*     Gestionamos las excepciones que puedan suceder
      gr_msg = cx_salv->get_text( ).
      message gr_msg type 'E'.
  endtry.

  try.
      gr_columns ?= gr_table->get_columns( ).
*     gr_columns->set_optimize( 'X' ).   "Optimizar automa. abcho de TODAS las columnas

*     Cambiamos la descripción de la columna MATNR - MATERIAL
      gr_column  ?= gr_columns->get_column( 'MATNR' ).
      gr_column->set_short_text( 'Cod.Mat.' ).
      gr_column->set_medium_text( 'Cod. Material' ).
      gr_column->set_long_text( 'Código Material SAP' ).

*     Ocultamos la columna MTART - Tipo de material
      gr_column  ?= gr_columns->get_column( 'MTART' ).
      gr_column->set_visible( value  = if_salv_c_bool_sap=>false ).

*     Cambiamos la longitud de la columnas:
*     MAKTX descripción del material
*     MATKL Grupo de articulos
      gr_column  ?= gr_columns->get_column( 'MAKTX' ).
      gr_column->set_output_length( 30 ).

      gr_column  ?= gr_columns->get_column( 'MATKL' ).
      gr_column->set_output_length( 20 ).

*     Cambiamos la descripcion y formato de la columna box
      gr_column  ?= gr_columns->get_column( 'BOX' ).
      gr_column->set_short_text( 'box' ).
      gr_column->set_medium_text( 'Checkbox' ).
      gr_column->set_long_text( 'Checkbox' ).
      gr_column->set_cell_type( if_salv_c_cell_type=>checkbox ).

    catch  cx_salv_msg into cx_salv.
      gr_msg = cx_salv->get_text( ).
      message gr_msg type 'E'.
    catch  cx_salv_not_found into cx_not_found.
       gr_msg = cx_not_found->get_text( ).
      message gr_msg type 'E'.
  endtry.

  gr_table->display( ).

CL_SALV_TABLE  con los atributos de las comunas modificados

Entradas anteriores:
Clase CL_SALV_TABLE: Introducción