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)

lunes, 2 de julio de 2012

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

Continuamos con la creación del bloque. 
Vamos ahora a configurar la parte de webui para que nuestra clase sea visible mediante un bloque.

Conocido el componente a ampliar es necesario crear una nueva vista y asignarla al componente y grupo de pantallas. Hay que redefinir los métodos de la vista y crear nuevos eventos para que el bloque se comporte como deseamos. También hay que relacionar la vista con el GUID del objeto raíz. En este punto vamos a crear la vista mediante el asistente.

Entramos en la transacción BSP_WD_CMPWB en el componente que nos interesa. Para este caso vamos a ampliar las tareas por lo que nuestro componente es el BT125H_TASK. Pinchamos en la opción "Browser de modelo BOL". Si hemos realizado bien la configuración desplegando el objeto raiz deberíamos ver nuestro componente. 

Vamos a crear ahora la vista. 
Para crear la vista utilizamos el asistente y debemos incluir el objeto con el que vamos a relacionar la tabla. Este objeto nos aparece también en los objetos raiz del browser de modelo BOL. Debemos elegir el que nos convenga. En este caso concreto el que nos interesa el el objeto BTOrder.
Vamos al "Browser de estructura de componente" y en las vistas presionamos el botón derecho y crear. Nos muestra un asistente. En el primer paso debemos asignarle un nombre a la vista. Personalmente me gusta empezarlos por Z pero el nombre del completamente es libre. En el paso siguiente de nodos modelos asignamos nuestro nodo Root y el nodo con el que enlaza que hemos decidido que sea BTOrder:




En el paso de nodos de valor no indicamos nada. Pásamos al paso de atributos modelo.
Aqui presionamos el botón + () del asistente. Nos aparece una pantalla con los nodos que hemos introducido en el paso de nodos modelo con todos sus campo.

Seleccionamos los campos que queremos que aparezcan en la vista. Por norma deben ser todos los de la tabla Z y se debe incluir el campo de relación con el objeto estándar. En este paso es posible que algún campo genere conflicto de nombres. En caso de que se nos muestre un conflicto de nombres simplemente cambiamos el nombre del campo, por ejemplo poniendole una Z delante.

Se nos muestra una lista con todos los campos seleccionados. Pasamos al siguiente paso y continuamos sin indicar nada.
Seguimos con el paso de "crear enlaces para controlador custom". En este caso vamos un poco a ciegas. Tenemos que encontrar la relación entre el nodo de contexto BTOrden con los datos de la actividad (o los objetos que nos interese). Con un poco de sentido común y ayuda de MR. Google encontraremos la relación correcta. Siempre tenemos la opción de prueba-error. Una vez decidamos continuamos y debemos seleccionar el tipo de vista. Para nuestro caso nos interesa una vista de tabla, que sea confirgurable y modificable. Estos son los datos:


Presionamos continuar y se nos presenta el resumén de la configuración por si queremos cambiar algo. Presionamos finalizar y tenemos la vista creada.

Ya tenemos nuestra vista creada y relacionada con el componente. Vamos a publicarla. Para ello tenemos que añadir la vista al ViewSet que nos interese. Vamos al "Editor de repository en tiempo de validez" y presionamos el botón de modificar. En este caso vamos a modificar el ViewSet TaskOVViewset. En el viewArea presionamos el botón derecho y añadimos nuestra vista.
A continuación hacemos doble click en el viewSet y vamos a la pestaña de configuración. Añadimos la vista a los bloques que ya son visibles. Este paso lo podemos hacer en el WebGUI.
Volvemos al "Browser de estructura del componenete" y hacemos doble click en la vista. En la pestaña de configuración mostramos los campos que nos interesen.

Con esto nuestra vista está visible en web. En el siguiente post veremos como hacer que se comporte como nos interesa.

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