lunes, 8 de abril de 2013

Control de datos obligatorios en programación BOL

En este post contaré como controlar los datos y hacerlos obligatorios en un bloque Z creado con programación BOL/GenIL.

En el bloque que voy a tratar tengo una tabla Z que guarda los datos y está incrustado en un componente estándar. Podéis ver entradas anteriores para ver como crear un bloque Z, empezando aqui.

Debemos modificar la vista en la que se encuentra nuestro bloque. En este post podéis ver como averiguar la vista y como modificarla.

El método que nos interesa modificar es el DO_FINISH_INPUT que se ejecuta al finalizar la entrada de los campos (presionando enter). Es necesario controlar que evento se está lanzando ya que el comportamiento debe ser diferente al grabar o al presionar el botón cancelar. Para controlar los eventos tenemos el parámetro GLOBAL_EVENT que se utiliza en el método. Para mandar mensajes al usuario utilizaremos el parámetro GLOBAL_MESSAGES. Es necesario definir las siguientes variables:

DATAlr_entity TYPE REF TO cl_crm_bol_entity,
     lv_comm_type 
TYPE string,
     lr_message_cont 
TYPE REF TO cl_crm_genil_global_mess_cont,
     ls_expos 
TYPE zexpo_bol_st.


Como en todos los métodos redefinidos primero llamamos al método de la clase superior.


CALL METHOD super->do_finish_input
 EXPORTING
  global_event    
global_event
  global_messages 
global_messages.


Para controlar los diferentes eventos utilizamos un case. La forma más rápida que he encontrado de averiguar el nombre del evento es debugando.


CASE global_event.
WHEN 'ZEXPOSICIONES/zvexposiciones.select'.


Si nos interesa tratar la posición en la que se esta trabajando o está marcada debemos coger la posición actual con la sentencia get_current:


lr_entity ?= me->typed_context->root->collection_wrapper->get_current( ).

En el caso de que queramos recorrer toda la tabla obtenemos la primera posición y navegamos hasta la última.


lr_entity ?= me->typed_context->root->collection_wrapper->get_first( ).
WHILE lr_entity IS BOUND. 
 lr_entity ?= me->typed_context->root->collection_wrapper->get_next( ).

ENDWHILE.


Muchas veces solo queremos que el control se ejecute en el modo modificación, en ese caso debemos revisar el atributo is_changeable:


IF lr_entity->is_changeable( ) = abap_true.

Para controlar los datos obtenemos el valor del campo con GET_PROPERTY_AS_VALUE


lr_entity->get_property_as_valueEXPORTING iv_attr_name  'ID'
                            IMPORTING ev_result lv_comm_type ).


Una vez revisado el valor si es necesario mandar un mensaje al usuario usamos el método ADD_MESSAGE:


global_messages->add_messagecondition co_mandatory_field_empty 
                    message   text-e01 "'Mensaje

                 severity  cl_bsp_messages=>co_severity_error
                 p1        
'TABLAS'
                 dummy     
me->component_id ).


También podemos modificar el valor del campo mediante el método SET_PROPERTY.


lr_entity->set_propertyEXPORTING iv_attr_name =  'TABLAS'
                             iv_value ls_expos-tablas ).


Con esto podemos trabajar y controlar un poco mejor los datos de nuestro bloque e informar a los usuarios de si están informando los datos correctamente antes de hacer los datos consistentes en la base de datos.

  

domingo, 16 de diciembre de 2012

Crear tabla interna dinámicamente

Hace poco me surgió un desarrollo en el que tenía que cargar un fichero. Hasta aquí no hay nada raro, el problema en este caso es que la estructura del fichero a cargar era variable y además dependía de los datos introducidos en una tabla Z que también podía variar en el tiempo. Es decir, hoy podía tener 10 campos, mañana 20 o tener 10 pero que se llamasen diferente, así que no podía guardar los datos mediante estructuras del diccionario ni crear una estructura fija en mi programa Abap.

La única opción que se me ocurrió para solucionar esto fué crear una tabla interna dinámicamente. Para esto encontre que algún método de los ALV orientados a objetos me podía ayudar.

Vamos con la parte interesante ahora. Lo primero que necesito es definir mis variables. Obviamente no todo puede ser estático y en este caso sé la tabla que contiene mis campos y necesito definir una tabla "fieldcat" que contendrá la estructura:

DATA: gs_estructura LIKE zTabla,
      gt_tabla LIKE STANDAR TABLE OF gs_estructura,
      gs_fielcat TYPE lvc_t_fcat,
      gt_fieldcat TYPE lvc_s_fcat.

A continuación leemos la tabla del diccionario que tiene la estructura de nuestra tabla interna a crear.

SELECT * INTO TABLE gt_tabla
  FROM zTabla
  WHERE condiciones.

Y ahora debemos recorrer nuestra tabla y asignar el campo a la estructura del fieldcat.

LOOP AT gt_tabla INTO gs_estructura.
  gs_fieldcat-col_pos = sy-tabix.

  gs_fieldcat-fieldname = gs_estructura-field.
  APPEND gs_fieldcat TO gt_fieldcat.

  CLEAR GS_FIELDCAT.
ENDLOOP.


A continuación llamamos al método que crea la tabla interna a partir del fieldcat.

CALL METHOD cl_alv_table_create=>create_dynamic_table
  EXPORTING
   it_fieldcatalog = gt_fieldcat
  IMPORTING
   ep_table = gt_tabla
  EXCEPTIONS
   generate_subpool_dir_full = 1
  OTHERS = 2.

Y por último utilizamos field symbols (no podría ser de otra forma) para asignar los datos de la tabla y las posiciones.

ASSIGN gt_tabla->* TO <fs_table>.
CREATE DATA ep_line LIKE LINE OF <fs_table>.
ASSIGN ep_line->* TO <fs_line>.
 
Con esto hemos creado una tabla en tiempo de ejecucíón con los datos definidos por una tabla interna.

miércoles, 17 de octubre de 2012

Programación BOL/GenIL. Crear nuevo bloque (7/7)

Para terminar con nuestro bloque es necesario que le pongamos los botones con el código necesario para realizar acciones. Los botones que vamos a crear son los de Insertar / Borrar y Modificar.

Para poder implementar los botones hay que definir atributos en la clase de implementación, definir los botones y los eventos controladores. En la clase de implementación hay que definir dos nuevos atributos, TB_BUTTON y GT_BUTTON. Estos serán los contenedores para nuestros botones. Definimos dos ya que así en modo visualización no tenemos opción de borrar o insertar y en modo modificación no tendremos el botón de modificar. Estos dos atributos deben definirse como atributos de instancia, públicos, y del tipo CRMT_THTMLB_BUTTON.

  • Botón Editar
    Para el funcionamiento del botón es necesario modificar el método DO_PREPARE_OUTPUT y crear el evento en la clase de implementación. El código a añadir en el método es el siguiente, habrá que añadir el código análogo para el resto de botones.:

    CLEAR: tb_button, ls_button.
    ls_button-text = 'Tratar lista'.
    ls_button-on_click = 'EDIT'.
    ls_button-enabled = me->view_group_context->is_view_in_display_mode( me ).
    APPEND ls_button TO tb_button.

    A continuación se crea el evento mediante el botón derecho en la carpeta de eventos. El nombre debe ser el mismo que hemos definido en el campo ls_button-on_click, en este caso "EDIT".
    El código a implementar en el evento es el siguiente:
    DATA: lr_tx TYPE REF TO if_bol_transaction_context,
      lr_entity TYPE REF TO cl_crm_bol_entity,
      lr_comp TYPE REF TO cl_bt125h_t_bspwdcomponen_impl.
    lr_comp ?= me->comp_controller.
    CHECK lr_comp IS BOUND.
    lr_entity ?= lr_comp->typed_context->btorder->collection_wrapper->get_current( ).

    CHECK lr_entity IS BOUND.
    IF lr_entity->lock( ) = abap_true.
      me->view_group_context->set_view_editable( me ).
    ENDIF.
    lr_entity ?= me->typed_context->root->collection_wrapper->get_first( ).
    WHILE lr_entity IS BOUND.
      lr_entity->lock( ).
      lr_entity ?= me->typed_context->root->collection_wrapper->get_next( ).
    ENDWHILE.

  • Botón insertar
    Al insertar creamos un nuevo ID en la tabla. Para esto se ha definido el método ZGET_ID en el nodo de contexto para obtener el nuevo ID.

    METHOD zget_id.
    DATA: lt_expos TYPE STANDARD TABLE OF zexpo_bol_st, 
          ls_expos LIKE LINE OF lt_expos,
          lv_id_old TYPE char10.
    FIELD-SYMBOLS <fs_expos> TYPE zexpo_bol_st.

    * Leo la tabla, ordeno y cubro huecos
    SELECT * INTO TABLE lt_expos FROM zexpos_bol.

    SORT lt_expos BY id ASCENDING.
    lv_id_old = '0000000001'.

    LOOP AT lt_expos INTO ls_expos.
      IF ls_expos-id = lv_id_old.
        lv_id_old = lv_id_old + 1.
        CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'       EXPORTING
            input = lv_id_old
          IMPORTING
            output = lv_id_old.
      ELSE.
        EXIT.
      ENDIF.

    ENDLOOP.
    ev_id = lv_id_old.

    ENDMETHOD.

    El método debe llevar un parámetro ev_id export de tipo char 10
    La implementación del evento es la siguiente:
    METHOD eh_oninsert.

    DATA : lr_entity TYPE REF TO cl_crm_bol_entity,

           lv_collection TYPE REF TO if_bol_bo_col,
           lr_comp TYPE REF TO cl_bt125h_t_bspwdcomponen_impl.

    DATA : lr_core TYPE REF TO cl_crm_bol_core,

           lr_fac TYPE REF TO cl_crm_bol_entity_factory,
           lt_params TYPE crmt_name_value_pair_tab,
           ls_params TYPE crmt_name_value_pair,
           lr_ent TYPE REF TO cl_crm_bol_entity,
           lv_cmp_guid TYPE crmt_object_guid.

    DATA lv_id TYPE zexpos_bol-id.


    lr_comp ?= me->comp_controller.

    CHECK lr_comp IS BOUND.
    lr_entity ?= lr_comp->typed_context->btorder->collection_wrapper->get_current( ).

    me->typed_context->root->deselect_all( ).


    IF lr_entity->is_changeable( ) = abap_true.

       lr_core = cl_crm_bol_core=>get_instance( ).
       lr_fac = lr_core->get_entity_factory( 'Root' ).
       TRY.
    * Para crear la entrada le paso el GUID de la relación
         lr_entity->get_property_as_value( EXPORTING iv_attr_name = 'CRM_GUID'
            IMPORTING ev_result = lv_cmp_guid ).

    * Obtengo el ID

         ls_params-name = 'ID'.
         me->typed_context->root->zget_id( IMPORTING ev_id = lv_id ).
         ls_params-value = lv_id.
         APPEND ls_params TO lt_params.
         ls_params-name = 'ACT_GUID'.
         ls_params-value = lv_cmp_guid.
         APPEND ls_params TO lt_params.
         lr_core->root_create( iv_object_name = 'Root'
         iv_create_param = lt_params
         iv_number = 1 ).

         me->root_query( ).

         me->eh_onedit( ).


       CATCH cx_crm_genil_model_error.

         EXIT.
       CATCH cx_sy_ref_is_initial.
      ENDTRY.
    ENDIF.

    ENDMETHOD.


  • Botón borrar:
    El evento de borrado comprueba si el objeto se puede bloquear y lo borra. El método es el siguiente:

    METHOD eh_ondelete.

     DATA: lr_core TYPE REF TO cl_crm_bol_core,
      lr_docflow TYPE REF TO cl_crm_bol_entity.

    lr_docflow ?= me->typed_context->root->collection_wrapper->get_current( ).
    CHECK lr_docflow IS BOUND.

    IF NOT lr_docflow->is_locked( ) EQ abap_true.
      lr_docflow->lock( ).
    ENDIF.

    CHECK lr_docflow->is_locked( ) EQ abap_true.
    lr_core = cl_crm_bol_core=>get_instance( ).

    lr_core->modify( ).
    lr_docflow->delete( ).

    lr_core = cl_crm_bol_core=>get_instance( ).
    lr_core->modify( ).

    ENDMETHOD.

Por último hay que retocar un poco el HTML de la página base. El código a incluir es el siguiente:

<%@page language="abap" %>
<%@extension name="thtmlb" prefix="thtmlb" %>
<%@extension name="chtmlb" prefix="chtmlb" %>
<%@extension name="bsp" prefix="bsp" %>
<thtmlb:areaFrameSetter toolbarButtons = "<%= controller->tb_button %>"
maxButtonNumber = "2"
displayMode = "<%= controller->view_group_context->is_view_in_display_mode( controller ) %>" />
<chtmlb:configTable actionsMaxInRow = "3"
allRowsEditable = "TRUE"
displayMode = "<%= controller->view_group_context->is_view_in_display_mode( controller ) %>"
downloadToExcel = "TRUE"
id = "Table1"
onRowSelection = "select"
personalizable = "TRUE"
selectedRowIndex = "<%= Root->SELECTED_INDEX %>"
selectedRowIndexTable = "<%= Root->SELECTION_TAB %>"
selectionMode = "<%= Root->SELECTION_MODE %>"
table = "//Root/Table"
usage = "ASSIGNMENTBLOCK"
visibleFirstRow = "<%= Root->VISIBLE_FIRST_ROW_INDEX %>"
visibleRowCount = "6"
width = "100%"
xml = "<%= controller->configuration_descr->get_config_data( ) %>"
actions = "<%= controller->gt_button %>"/>       


jueves, 30 de agosto de 2012

Programación BOL/GenIL. Crear nuevo bloque (6/7)


Continuamos con nuestro bloque en WebGui. A continuación veremos las modificaciones necesarias en la clase de visualización para que nuestros datos sean visibles y modificables. 

Para ello primero es necesario modificar el nodo de contexto. Los métodos a modificar en el nodo de contexto son GET_TABLE_LINE_SAMPLE y  IF_BSP_MODEL~INIT.
  • Método  GET_TABLE_LINE_SAMPLE
    En este método definimos la estructura del nodo. El código a implementar es el siguiente
    TYPESBEGIN OF line.

     
    include type ZEXPO_BOL_ST.

    TYPES: END OF line.

    CREATE DATA rv_sample TYPE line.
  • Método IF_BSP_MODEL~INIT
    Este método
    inicializa la colección en el inicio. El código a implementar es el siguiente:
    TYPES ltype_attr_struct TYPE zexpo_bol_st.

    DATAlrf_data_struct TYPE REF TO ltype_attr_struct,
          lrf_value_node 
    TYPE REF TO cl_bsp_wd_value_node,
          lrf_bol_collection 
    TYPE REF TO if_bol_bo_col.

    super
    ->if_bsp_model~initid id
                              owner 
    owner ).

      
    CREATE DATA lrf_data_struct.

      
    CREATE OBJECT lrf_value_node
        
    EXPORTING
          iv_data_ref 
    lrf_data_struct.

      
    CREATE OBJECT lrf_bol_collection TYPE cl_crm_bol_bo_col.

      lrf_bol_collection
    ->addlrf_value_node ).
      me
    ->set_collectionlrf_bol_collection ).
A continuación se deben modificar los métodos de recuperación de datos. Para recuperar los datos es necesario crear el método ROOT_QUERY en la clase de implementación de nuestro bloque y redefinir el método DO_PREPARE_OUTPUT para que llame al nuevo método.
  • Método ROOT_QUERY
    El método debe ser de instancia y privado.
    No tiene parámetros. El código a implementar es el siguiente:
    DATA
    lrf_advanced_query TYPE REF TO cl_crm_bol_dquery_service,
          lrf_root_result_collection 
    TYPE REF TO if_bol_entity_col,
          lrf_root_result_bol_entity 
    TYPE REF TO cl_crm_bol_entity,
          lrf_current_root 
    TYPE REF TO if_bol_bo_property_access,
          lrf_current_root_col 
    TYPE REF TO if_bol_bo_col,
          lrf_current_btorder 
    TYPE REF TO if_bol_bo_property_access,
          lt_params 
    TYPE crmt_name_value_pair_tab,
          ls_params 
    TYPE crmt_name_value_pair,
          lv_low_value 
    TYPE string,
              ls_root_properties TYPE zexpo_bol_st,
          ls_btorder_properties 
    TYPE crmst_header_admin_btil,
          lv_index 
    TYPE i.

     
    CLEAR lv_index.

      ls_params
    -name 'MAX_HITS'.
      ls_params
    -value '10'"Solo un resultado.
      
    APPEND ls_params TO lt_params.

      lrf_current_btorder ?= me
    ->typed_context->btorder->collection_wrapper->get_current( ).

      
    CHECK lrf_current_btorder IS BOUND.lrf_current_btorder->get_propertiesIMPORTING es_attributes ls_btorder_properties ).

      
    CHECK ls_btorder_properties-header_guid IS NOT INITIAL.
      lv_low_value = ls_btorder_properties-header_guid.
      lrf_advanced_query = cl_crm_bol_dquery_service=>get_instance( 'Search' ).
      lrf_advanced_query->set_query_parameters( it_parameters = lt_params ).
      lrf_advanced_query->add_selection_param( iv_attr_name = 'ACT_GUID'
                                               iv_sign = 'I'
                                               iv_option = 'EQ'
                                               iv_low = low_value
                                               iv_high = '' ).
      lrf_root_result_coll
    ection = lrf_advanced_query->get_query_result( ).
     
      me->typed_context->root->set_collection( lrf_root_result_collection ).
  • Método DO_PREPARE_OUTPUT
    La programación básica para la recuperación de datos es la siguiente:

    DATA
    :  ls_button    TYPE crmt_thtmlb_button.

      
    CALL METHOD super->do_prepare_output
        
    EXPORTING
          iv_first_time 
    iv_first_time.

      me
    ->root_query( ).
Con esto nuestros datos son visibles. 

En el próximo post (y último) crearemos los botones necesarios para dar funcionalidad al bloque. 

Relacionado:
Programación BOL/GenIL (1/7)
Programación BOL/GenIL (2/7)
Programación BOL/GenIL (3/7)
Programación BOL/GenIL (4/7)
Programación BOL/GenIL.(5/7)
Programación BOL/GenIL.(7/7)