Implementado Reportes Anidados en Nuestros Informes Clásicos - Oracle APEX 5.0

Oracle Community

Implementado Reportes Anidados en Nuestros Informes Clásicos - Oracle APEX 5.0

Written by Clarisa Maman Orfali

En este artículo quiero compartir con todos un nuevo plugin llamado “Pretius APEX Nested Reports” correspondiente a la categoría de Acciones Dinámicas, que lo he probado y me parece verdaderamente genial para darle un aspecto diferente a nuestros Informes Clásicos. Este plugin implementa a través de una acción dinámica informes anidados dentro de los Informes Clásicos. Esto es algo que no trae nativamente Apex y con este plugin lo podemos implementar. En primera instancia, quiero agradecer a su autor, Bartosz Ostrowski por su gran aporte a la comunidad de desarrolladores APEX.

Pretius APEX Nested Reports

He re-creado el Informe Clásico del demo que presenta Bartosz en la página del Plugin y en este artículo quiero mostrarte paso a paso cómo realizarlo. El resultado final al que llegaremos será el que se visualiza en las imágenes de abajo. Por un lado, trabajaremos con la columna Customer y mostraremos el detalle de los clientes y luego trabajaremos con la columna Details para mostrar el icono de carrito y allí mostrar el detalle de la Orden realizada por el cliente.

  

Para realizar este demo vamos a necesitar tener en nuestro Espacio de Trabajo las siguientes tablas:

  • DEMO_ORDERS
  • DEMO_CUSTOMERS
  • DEMO_ORDER_ITEMS
  • DEMO_PRODUCT_INFO

Estas tablas son parte de la app “Sample Database Application” que viene instalada en cada versión de APEX o en el caso de que lo hayas borrado, puedes instalarlo desde Aplicaciones Empaquetadas.

Crear Informe Clásico

  1. Crear una Página en blanco en nuestra aplicación

  2. Crear una región de tipo Informe Clásico que la llamaremos Custom callback

    1. En la consulta de Origen ingresamos la siguiente consulta SQL:

      select
        distinct
          DO.ORDER_ID,
          DO.CUSTOMER_ID,
          DC.CUST_FIRST_NAME||' '||CUST_LAST_NAME customer,
          count( ORDER_ITEM_ID ) over (partition by do.order_id) items,
          DO.ORDER_TOTAL,
          cast(DO.ORDER_TIMESTAMP as date) order_date
      from
        DEMO_ORDERS DO
      left join
        DEMO_CUSTOMERS DC
      on
        DO.CUSTOMER_ID = DC.CUSTOMER_ID
      left join
        DEMO_ORDER_ITEMS DOI
      on
        DO.ORDER_ID = DOI.ORDER_ID
       
  3. En las Propiedades del Informe Clásico, sección Avanzada, asignamos el “Identificador Estático” como CUSTOMER_ORDERS.


Este es nuestro resultado hasta el momento:



Para hacer que nuestro informe clásico sea como el que se muestra en el demo, vamos a ocultar las columnas ORDER_ID y CUSTOMER_ID. Por otro lado queremos que la columna del Customer pueda ser un enlace que permita abrir un popover emergente con la información del cliente. El popover es similar al tooltips; es un pop-up box que aparece cuando el usuario hace clic en un elemento, la diferencia es que el popover puede tener mucho más contenido que el tooltips.

Tanto las columnas del Customer_ID y la columna del Order_ID, las vamos a ocultar, importante es recalcar que no vamos a eliminarlas de la consulta SQL ya que se necesita tener las referencias de las mismas en otras columnas.

Trabajando con las columnas del Informe Clásico

Desde el Diseñador de Páginas, expandimos las columnas del Informe Clásico.

Columna del carrito de la compra

  1. Seleccionamos la columna ORDER_ID y en Tipo seleccionamos “Columna Oculta”
     
  2. Creamos una nueva columna derivada o virtual (hacemos clic con el botón derecho del mouse sobre Columnas)


     
    1. En Tipo seleccionamos “Texto sin Formato”
       
    2. En Cabecera ingresar el nombre “Details”
       
    3. Cambiar Alineación de Columna a “Centro”
       
    4. En Formato de Columna ingresar en Expresión HTML:
       
      <span class="ORDER_ID" style="display: none">#ORDER_ID#</span>
      <a href="javascript: void(0);" class="listOrderDetails">
      <span class="fa fa-shopping-cart"></span></a>

       
    5. Finalmente reordenamos la columna y la colocamos en primer lugar

Columna Details

Desde el Diseñador de Páginas:

  1. Seleccionamos la columna CUSTOMER_ID y en Tipo seleccionamos “Columna Oculta”
     
  2. En Formato de Columna ingresar en Expresión HTML:
     
    <span class="CUSTOMER_ID" style="display:none">#CUSTOMER_ID#</span>#CUSTOMER#

Guardamos los cambios y vemos los resultados:

 

Hemos llegado a tener el Informe Clásico de la misma forma que nuestro demo inicial.

Archivos Bootstrap

Antes de implementar el plugin necesitamos disponer de los estilos CSS bootstrap y los archivos JavaScript para usar el popover en una Aplicación en APEX.

Podemos bajar los archivos desde la página de Bootstrap AQUÍ o el autor ha preparado los archivos que se pueden bajar desde AQUÍ

Importante Aviso: si nosotros descargamos los archivos desde el repositorio oficial, tengamos cuidado de que los archivos de estilos CSS de bootstrap pueden sobre-escribir las reglas CSS de nuestro Tema Universal con estilos redundantes. Necesitamos quitar los estilos redundantes en forma manual para que no cambie la apariencia del Tema Universal. Los archivos preparados por el autor están libres de ese problema, así que yo les aconsejo descargar los archivos del segundo enlace.

Una vez descargado el archivo comprimido, lo descomprimimos y tenemos los siguientes archivos:

  • bootstrap.css
  • bootstrap.min.js

Tenemos diferentes formas de cargar los archivos en APEX para ser utilizados, una opción es en la sección de página colocar las URLs del CSS y del JavaScript. La otra opción es editar el template de la página y agregar la sección de JavaScript y Cascading Style Sheet. Otra forma es desde los Componentes Compartidos en “Atributos de Interfaz de Usuario” editamos en Escritorio y en la sección JavaScript y Hojas de Estilo en Cascada podemos colocar las URLs correspondientes.

Cargar Archivos a nuestro Espacio de Ttrabajo

Ahora vamos a cargar los archivos en nuestro Espacio de Trabajo y utilizar las variables de sustitución para hacer la referencia a los mismos, según sea el caso, si lo cargamos en “Archivos de Aplicación Estáticos” usaremos la variable #APP_IMAGES# y en el caso que lo carguemos en “Archivos de Espacio de Trabajo Estáticos” usaremos la variable de sustitución #WORKSPACE_IMAGES”.

En mi caso lo subiré en “Archivos de Aplicación Estáticos”, las referencias son las siguientes:

  • #APP_IMAGES#bootstrap.css
  • #APP_IMAGES#bootstrap.min.js

Ahora vamos a referenciar los archivos para que nuestra aplicación los pueda leer. Para ello desde la Página de Apex donde tenemos el Informe Clásico en el panel de propiedades vamos a la sección JavaScript e ingresamos lo siguiente en el recuadro URL de Archivo:

#APP_IMAGES#bootstrap.min.js.

Luego procedemos a la sección CSS y colocamos en el recuadro de URL de Archivo, lo siguiente:

#APP_IMAGES#bootstrap.css.

Descargar el Plugin

Primeramente descargamos el plugin desde AQUÍ

Estaría muy agradecida si entre todos ponemos nuestro granito de arena y agradecemos al autor por compartir su trabajo. Para ello, podemos poner nuestro comentario y calificar el plugin en la página de apex-plugin.com, te dejo el enlace AQUÍ

Es momento de importar el plugin a nuestro Espacio de Trabajo:

Desde los Componentes Compartidos hacemos clic en Plugins y allí lo importamos.

Configuración del Plugin

Columna Customer

Vamos a trabajar primero en la columna de “Customer”, la idea es que en el popover se pueda ver la información del cliente. Para hacer esto, necesitamos crear una Acción Dinámica con una Acción Verdadera que disparará el plugin.

  1. En la Página del Diseñador, desde la ficha de Acciones Dinámicas, creamos una nueva Acción Dinámica:

    Nombre: Customer details

    Evento: Clic

    Tipo de Selección: Selector de jQuery

    Selector de jQuery: td[headers=CUSTOMER]

    Condición: - Seleccionar -

    Ámbito de Evento: Dinámico

    Contenedor Estático (Selector de jQuery): #CUSTOMER_ORDERS

  2. Configurar la Acción Verdadera:

    Acción: Pretius APEX Nested Reports [Plug-In]

    Mode: Custom template & custom callback

    Details query:

    select
      CUST_FIRST_NAME||' '||CUST_LAST_NAME as "Customer name",
      CUST_STREET_ADDRESS1||', '||CUST_CITY||' '||CUST_POSTAL_CODE as "Adress",
      NVL(CUST_EMAIL, 'Not provided') as "E-mail",
      PHONE_NUMBER1 as "Phone number",
      CREDIT_LIMIT as "Credit"
    from
      DEMO_CUSTOMERS
    where
      customer_id = #CUSTOMER_ID#

    Settings: [x] Cache results [x] Loading indicator

    Custom Template:

    {{#data}}
    <div class="customer_row">
      <div class="field">
        <div class="label">Customer name</div>
        <div class="value">
          <span class="fa fa-user"></span> {{Customer name}}
        </div>
      </div>
      <div class="field">
        <div class="label">Credit</div>
        <div class="value">
          <span class="fa fa-credit-card"></span> {{Credit}}
        </div>
      </div>
      <div class="field">
        <div class="label">Phone number</div>
        <div class="value">
          <span class="fa fa-phone"></span> {{Phone number}}
        </div>
      </div>
      <div class="field">
        <div class="label">E-mail</div>
        <div class="value">
          <span class="fa fa-envelope"></span> {{E-mail}}
        </div>
      </div>
      <div class="field">
        <div class="label">Adress</div>
        <div class="value">
          <span class="fa fa-globe"></span> {{Adress}}
        </div>
      </div>
    </div>
    {{/data}}

    Custom callback:

    var
      //triggering element - cell in column CUSTOMER
      self = this.callback.triggeringElement,
      //width of the sidebar - will be needed to calculate popover padding in popover viewport parameteter
      tBodyNavWidth = $('#t_Body_nav').outerWidth(),
      //reference to elements that has popover shown
      other = $('.hasPopover', this.callback.affectedReport ).not(self),
      //custom padding of popover
      popoverPadding = tBodyNavWidth == 200 ? 220 : tBodyNavWidth + 20,
      //modified popover template - it will contain X button to manualy close the popover
      popoverTemplate = ''+
        '<div class="popover orderList" role="tooltip">'+
        '<div class="arrow"></div><span class="popoverCloseBtn fa fa-icon fa-close"></span>'+
        '<h3 class="popover-title"></h3>'+
        '<div class="popover-content"></div>'+
        '</div>';
     
    //hide other popovers and unmark their triggering elements
    other.popover('hide').removeClass('hasPopover detailsShown');
     
    if (self.is('.hasPopover')) {
      //if triggering element has popover it will be destroyed and hidden
      self.popover('hide').removeClass('hasPopover detailsShown');
    } else {
      //recreate popover with custom settings
      //API: http://getbootstrap.com/javascript/#popovers-options
      self.popover('destroy').popover({
      animation: true,
      container: 'body',
      content: this.callback.renderedTemplate,
      delay: 0,
      html: true,
      placement: 'bottom',
      selector: false,
      template: popoverTemplate,
      title: 'Customer details',
      trigger: 'manual',
      viewport: {
        selector: 'body',
        padding: popoverPadding
      }
     }).popover('show').addClass('hasPopover detailsShown'); //force showing the popover and mark it as it has popover shown
    }

    Atributos Afectados --- Tipo de Selección: Región

    Región: Custom callback

    Arrancar al cargar la Página: No

    Guardamos los cambios y ejecutamos la página.



    Podemos observar que al acercar el mouse a la columna del Customer no se muestra el puntero del mismo como que hay un enlace, y por otro lado al hacer clic en el nombre del cliente se abre el popover pero algunos estilos CSS se han perdido en el template personalizado. También podemos ver que la X que cierra la ventana no funciona.

    Desde la página del Diseñador:

    1)      Ir a los atributos de página

    2)      Añadir el siguiente JavaScript en “Declaración de Función y Variable Global”

        $(document).on('click', '.popoverCloseBtn', function(){
        $(this).closest('.popover').popover('hide');
        $('.hasPopover').removeClass('hasPopover detailsShown');
        });

    3)      Añadimos los siguientes estilos CSS para el template personalizado, en la sección CSS En línea:

        td[headers="CUSTOMER"] {
         cursor: pointer;
       }
     
       /* custom template */
       .popover-content .field {
         margin-left: 10px;
         padding: 5px;
         display: block;
       }
       .popover-content .label {
         font-weight: bold;
       }
     
       .popover-content  .value {
         line-height:30px;
       }
       /* popover */
       .popover.orderList {
         max-width: 315px;
         min-width: 315px;
       }
       .popover-content {
         max-height:400px;
         overflow: auto;
       }
       span.popoverCloseBtn {
         position: absolute;
         right: 10px;
         top: 10px;
         cursor: pointer;
       }

    Guardamos y ejecutamos la página.



    Hasta aquí es todo el ejemplo que el autor ha desarrollado para implementar su plugin. Quiero agradecer a Bartosz Ostrowski porque le he solicitado si podía pasarme los códigos que usó para implantar la parte del carrito de la compra y muy amablemente me lo ha pasado para compartirlo con toda la comunidad Hispana!

Columna Details (icono carrito)

  1. En la Página del Diseñador, desde la ficha de Acciones Dinámicas, creamos una nueva Acción Dinámica:

    Nombre: Details

    Evento: Clic

    Tipo de Selección: Selector de jQuery

    Selector de jQuery: td[headers="DERIVED$01"]

    Condición: - Seleccionar -

    Ámbito de Evento: Dinámico

    Contenedor Estático (Selector de jQuery): #CUSTOMER_ORDERS

  2. Configurar la Acción Verdadera:

    Acción: Pretius APEX Nested Reports [Plug-In]

    Mode: Custom template & custom callback

    Details query:


    select
      ROWNUM,
      DPI.CATEGORY,
      decode(
        DPI.CATEGORY,
        'Womens', 'fa-female',
        'Mens', 'fa-male',
        'Accessories', 'fa-paperclip',
        'fa-question'
      ) CATEGORY_ICON,
      DPI.PRODUCT_NAME,
      DPI.PRODUCT_DESCRIPTION,
      DPI.LIST_PRICE
    from
      DEMO_ORDER_ITEMS DOI
    left join
      DEMO_PRODUCT_INFO DPI
    on
      DOI.PRODUCT_ID = DPI.PRODUCT_ID
    where
      order_id = #ORDER_ID#

    Settings: [x] Cache results [x] Loading indicator

    Custom Template:

    {{#data}}
    <div class="product_row">
     
        <div class="product_category column">
          <span class="fa {{CATEGORY_ICON}}"></span>
        </div>
        <div class="product_info column">
          <div class="title">
             {{PRODUCT_NAME}}
          </div>
          <div class="body">
            {{PRODUCT_DESCRIPTION}}
          </div>
        </div>
        <div class="product_price column">
          <span class="fa fa-tag"></span>
          <span class="price">
            {{LIST_PRICE}}
          </span>
          <span class="currency">
            &euro;
          </span>
        </div>
      <hr class="clear">
    </div>
    {{/data}}  

    Custom callback:

    var
      self = this.callback.triggeringElement,
      popoverMaxWidth = $(document).outerWidth() - self.offset().left - 100,
      t_Body_nav = $('#t_Body_nav').outerWidth();
      t_Body_nav = t_Body_nav == 200 ? 150 : t_Body_nav + 10,
      popoverTemplate = ''+
        '<div class="popover orderList" style="max-width: ' + popoverMaxWidth + 'px" role="tooltip">'+
        '<div class="arrow"></div><span class="popoverCloseBtn fa fa-icon fa-close"></span>'+
        '<h3 class="popover-title"></h3>'+
        '<div class="popover-content"></div>'+
        '</div>',
      other = $('.hasPopover', this.callback.affectedReport ).not(self);
    other.popover('hide');
    other.removeClass('hasPopover detailsShown');
    if (self.is('.hasPopover')) {
      self.popover('hide');
      self.removeClass('hasPopover detailsShown');
     
    } else {
     
      self.popover('destroy');
      self.popover({
        animation: true,
        container: 'body',
        content: this.callback.renderedTemplate,
        delay: 0,
        html: true,
        placement: 'bottom',
        selector: false,
        template: popoverTemplate,
        title: 'List of ordered products',
        trigger: 'manual',
        viewport: {
            selector: 'body',
            padding: 50
        }
      }).popover('show');
      self.addClass('hasPopover detailsShown');
    }

    Atributos Afectados --- Tipo de Selección: Región

    Región: Custom callback

    Arrancar al cargar la Página: No

    Guardamos los cambios.


    Solo nos resta colocar algunos estilos CSS en los atributos de la Página.

    Desde el Diseñador de Páginas vamos al recuadro de CSS En Línea y agregamos lo siguiente:

    .product_row {
      clear: both;
      overflow: hidden;
    }
    .product_row:nth-child(even) {
      background-color: #e8e8e8;
    }
    .product_row:nth-child(odd) {
      background-color: #f3f2f2;
    }
    .product_row:hover {
      background: #ffffff;
    }
    .product_row:nth-child(even) .fa {
      opacity: 0.8;
    }
    .product_row:nth-child(odd) {
      opacity: 0.8;
    }
    .column {
      max-width: 70%;
      display: block;
      padding: 10px;
      margin: 0px;
      float: left;
    }
    .product_category {
      font-size: 40px;
      line-height: 50px;
      margin-left: 20px;
      width: 55px;
    }
    .popover-content .product_info {
      width: 350px;
    }
    .title {
      line-height: 140%;
      font-size: 140%;
    }
    .product_price {
      float: right;
      position: relative;
      transform: rotate(45deg);
      margin-right: 20px;
    }
    .product_price .fa {
      font-size: 60px;
      transform: rotate(-45deg);
    }
    span.price {
      font-size: 15px;
      color: white;
      position: absolute;
      top: 28px;
      left: 23px;
    }
    span.currency {
      top: 25px;
      position: absolute;
      left: 48px;
      color: white;
      font-size: 10px;
    }   
    span.popoverCloseBtn {
      position: absolute;
      right: 10px;
      top: 10px;
      cursor: pointer;


    Guardamos los cambios y ejecutamos la página.

De esta manera, hemos implementado la funcionalidad de reportes anidados haciendo uso del Plugin Pretius APEX Nested Reports.

La verdad me gustó mucho agregar esta funcionalidad a nuestros informes clásicos y es por eso que lo comparto con toda la comunidad.

 

1492 2 /
Follow / 3 Mar 2017 at 12:17pm

Buen dia Ing. Clarisa Maman Orfali:

Estuve viendo e intentando  detenidamente hacer funcionar el plugin pero no he conseguido resultado alguno,he seguido el paso por paso como lo indica en su articulo pero no consigo que se dispare la accion dinamica.

Tendra alguna sugerencia,o que punto deberia  reveer o repasar , a fin de hacer funcionar el plugin ?

Agradecido desde ya

Follow / 3 Apr 2017 at 7:38pm

Saludos Clarisa

Primero por agradecer al creador de este plugin, y a ti por permitirnos comprender mejor en nuestro lenguaje nativo,

He realizado todos los pasos que se indican, pero no logro realizar el reporte como se muestra; me sale un error que cuando presiono el campo CUSTOMER me aparece un simbolo de ! en un triangulo invertido, por favor si me ayuda dandome un camino para poder completarlo.

Gracias