miércoles, 22 de febrero de 2017

SAPUI5: Maneras de cargar un servicio oData y hacer el binding

Gracias al Web IDE de SAP HCP, nos podemos crear una aplicación rápidamente consumiendo un servicio oData sin picar casi código. Tiramos tan pocas líneas, que llega un punto en el que creamos aplicaciones sin saber realmente como funcionan sus tripas, como si fuésemos imperiales del Warhammer 40K.

Así que voy a contar aquí un par de cosas sobre como leer un servicio oData para poder tratar el resultado y como mostrar los datos en la vista mediante el binding. Muy básico todo ello. Si buscas algo complicado... sigue buscando.

Creando la aplicación


Me he creado una aplicación SAPUI5 sencilla que consulta un servicio oData de mi Back-End. En este post se puede ver el servicio oData cutre de ejemplo que me había creado, y en este otro una aplicación basada en ese servicio. En este ejemplo la aplicación era de tipo SAPUI5 y después he añadido en el Component.js la ubicación del servicio oData, en "serviceConfig".

Vaaaale, lo suyo sería añadirlo en el manifest.json,
pero estaba en plan perezoso

Y en el neo-app.json añadimos el mapeo para usar el destination correcto cuando usamos una cadena de tipo /sap/opu/odata:



El servicio oData tiene una única colección llamada TextosGeneralesSet, con varias propiedades, entre ellas una llamada Texto para mostrar una línea de texto. En nuestra aplicación vamos a mostrar una tabla para mostrar todas esas líneas.

Pero, además, queremos mostrar debajo de la tabla el total de líneas que tiene dicha colección. De esta forma tan tonta vamos a ver dos formas de recuperar los datos y enlazarlos.

La vista es la siguiente, y lo que nos interesa son los atributos que vienen con llaves, {}. Esos son las propiedades que estamos "bindeando"... que digo yo que "enlazando" suena mejor.


Entendiendo el binding


Para mostrar los datos en la vista, nos crearemos uno o varios modelos en el controlador, y en ellos almacenaremos la información. Después, enlazaremos (binding) dichos modelos con la vista.

Para crear un modelo de tipo oData, podemos usar la clase ODataModel, con la que indicaremos la URL en la que está nuestro servicio oData. Después, gracias a este modelo, podremos consultar las distintas colecciones del mismo para obtener los datos (y cada vez que solicitemos una colección, en el back-end se ejecutará la magia del ABAP correspondiente).

Pero como no sólo de oData vive el desarrollador de SAPUI5, también nos podemos crear modelos de tipo JSONModel, cosa que haremos normalmente cuando calculemos valores en javascript y los queramos volcar en la vista. Muy a menudo, por cierto.

Finalmente, para enlazar (binding) el modelo con la vista, tenemos que hacer dos cosas:

  • La primera, asignar el modelo a la vista.
  • La segunda, enlazar los campos (propiedades) que queremos mostrar en la vista

Asignar el modelo a la vista

El modelo se puede crear como una simple variable, instanciando la clase oDataModel o la clase JSONModel, según nos interese. Algo como:

var oModelo = new sap.ui.model.json.JSONModel();

Después, asignamos el modelo a la vista, con una sentencia como la siguiente:

this.getView().setModel(oModelo, "mis_lineas");

El segundo parámetro es el nombre que le queramos dar al modelo. Y es un parámetro opcional, ya que podemos elegir usar un modelo "sin nombre":

this.getView().setModel(oModelo);

Enlazar los campos (o hacer el binding)

Para poder mostrar esta información en la vista, lo que hacemos es enlazarla con el elemento XML donde queramos mostrarlo, con lo que se llama binding.

La pista de que estamos haciendo un binding nos la dan las llaves, {}, que se usan para indicar el modelo enlazado y la propiedad a mostrar. El formato con el que se indica el datos es

{nombre_del_modelo>/propiedad/subpropiedad/etc}

Por ejemplo, si tenemos un modelo llamado "mis_lineas" con los siguientes datos:


  linea: {
       texto : "hola, que tal",
       id: 1
  }
}

Lo podemos enlazar en un elemento de texto de la siguiente forma:

<Text text="{mis_lineas>/linea/texto}" />

Si resulta que usamos el modelo por defecto, sin nombre, se podría dejar así:

<Text text="{/linea/texto}" />

La barra (/) se usa siempre como punto de inicio (raíz) del modelo, y para separar las propiedades anidadas. Igual que si fuesen carpetas y subcarpetas en un sistema operativo.

Pero en ocasiones podemos encontrarnos conque no se indica la primera barra y nos aparece algo así:

<Text text="{texto}" />

Cuando esto ocurre es porque ese elemento XML está anidado en otro elemento XML (por ejemplo, en una tabla). Es este segundo elemento XML el que tiene la navegación inicial:

<Table items="{/linea}">

Así que lo único que tenemos que hacer es, mentalmente, concatenar ambos binding para saber a qué hace referencia.

Después de esta pseudoexplicación teórica que me he cascado, vamos a ver si explicando el ejemplo lo entendemos mejor.

Explicamos la vista

Volvemos a poner la imagen para tenerla a mano:



En ella tenemos una tabla que hace un binding a {/TextosGeneralesSet}. En nuestro caso, es la colección principal del servicio oData, así que ya nos podemos imaginar que ahí vamos a enlazar directamente un modelo con esos datos. Por cierto, es el modelo Voldemort... digoooo, que me lío... el modelo "innombrable".

Además, dentro de la tabla quiero mostrar la propiedad {Texto}. Como lo asigno en un elemento anidado en la tabla (un Text), no es necesario que indique toda la ruta, ya que la parte inicial me lo proporciona el items de la tabla.. Pero es como si estuviese leyendo /TextosGeneralesSet/Texto.

Finalmente, fuera de la tabla, añadiemos un elemento de texto que pone "Total de líneas". Ahí ya estamos usando un modelo con nombre, "datosVarios". Aquí podemos pensar que es un modelo al que vamos a dar un tratamiento adicional en Javascript.

Y ahora vamos a por el controlador


Ahora pasamos al controlador. Vamos a dividirlo en dos bloques: el del binding directo y tratamiento de los datos.

Binding directo


En el primero, nos creamos un modelo a partir de un servicio oData. Como no nos interesa tratar los datos, sino sólo mostrarlos, hacemos directamente el binding. Es esto mismo lo que hace el Web IDE cuando usamos un Template que usa oData. Echemos un ojo a la función cargarTabla:



A tener en cuenta que en el onInit me creo una variable this.oView y otra this.oComponent...
para no tener que llamar varias veces a los mismos métodos a lo largo del código.

Las dos primeras líneas de cargarTabla nos permite recuperar la URL del servicio oData que usaremos (y que habremos definido en el Component.js). Básicamente nos recupera /sap/opu/odata/sap/ZMIS_TEXTOS_SRV, que es lo que me había definido en el Component.js.

En la tercera línea (var oDataModel) nos creamos el modelo a partir de nuestro servicio oData, gracias a la clase ODataModel.

Finalmente, en la cuarta, asignamos ese modelo a la vista. Básicamente, hacemos el binding. Como hacemos el setModel sin especificar un nombre, estamos creando el modelo por defecto.

En cuanto la vista XML se cargue, al crearse la tabla (que tiene items="{TextosGeneralesSet}"), se llamará al servicio oData solicitando dicha colección. Por detrás, en el back-end, se cargará el ABAP. Y habremos recuperado los datos en un visto y no visto.

Binding con tratamiento adicional


¿Pero qué ocurre si queremos darle un tratamiento adicional a los datos que nos vienen del Back-End antes de enlazaros? En ese caso, podemos usar el método read de la clase ODataModel, como se hace en la función cargarDatosVarios:



Nos creamos un modelo que llame a un servicio oData. Como ya lo habíamos creado y asignado en la función anterior (esto es un poco de trampa :P) , lo recuperamos con this.getView().getModel("nombre_del_modelo"). O, si es el modelo sin nombre, con this.getView().getModel().

Ya tenemos el modelo. Ahora consultamos la colección que nos interesa con el método read y le pasamos dos parámetros:
  • El primer parámetro es el nombre de la colección que vamos a consultar, /TextosGeneralesSet.
  • El segundo parámetro es un objeto con dos propiedades:
    • success, que ejecutará una función que le pasemos si la llamada tiene éxito.
    • error, que ejecutará una función que le pasemos si la llamada falla.
Ese segundo parámetro, con su success y su error, lo cargamos como variable de tipo JSON, de la siguiente forma:

{
  success : function(los_datos) : { código para tratar lo datos },
  error : function( ) : { código para gestionar el error}


Una vez se reciben los datos con éxito del servicio oData, se ejecuta la función success, que recibe los datos recibidos en el parámetro de entrada (yo a ese parámetro lo he llamado data). En nuestro ejemplo, en data.results tenemos un array con los datos recibidos del servicio oData, y podemos hacer con ellos lo que queramos.


En este debug se ve lo que me devuelve el método al llamar con éxito al servicio oData:

Siguiendo con el ejemplo, como lo que me interesa es mostrar el número de líneas, recupero ese valor y lo asigno en un nuevo modelo de datos, de tipo JSON. Dicho modelo de datos se lo asigno a la vista como un nuevo modelo. Pero como ya había usado el modelo sin nombre, a éste le tengo que poner un nombre:

this.oView.setModel(new JSONModel( { aqui los datos } ), "datosVarios")

Y ya está. En cuanto ese dato se cargue, estará disponible en la vista.

El resultado final es algo así:


Vale, no es una aplicación muy elaborada, pero ¿y todo lo que hemos aprendido?

Algunos detalles más

Pues eso, que aquí pongo algunas cosas que no he querido añadir en medio del tutorial porque ya tenía tela, pero hay que tenerlo en cuenta:
  • El método read hace la llamada de forma asíncrona. Eso quiere decir que el read se ejecuta pero no espera a que se ejecute el success o el error. En su lugar, el código que venga detrás del read se continuaría ejecutando. El success o el error se ejecutaría en cuanto estuviese disponible. ¿Cómo nos afecta eso? Pues nos puede afectar si justo detrás del read añadimos código que depende del valor que éste nos devuelva. Eso nunca debemos hacerlo. O lo hacemos dentro del success, o tenemos que asociar un manejador de eventos para que se ponga a la "escucha" de esa operación. En general, si nuestro código es sencillo, con meter el código interesante en el success nos bastará.
  • Para poder entender un poco mejor, podemos revisar el concepto de Modelo-Vista-Controlador (MVC), en el que vamos a tener tres elementos:

    • La vista (view), que es "lo que se pinta". Por defecto en SAPUI5 será un XML.
    • El controlador (controller), que es el que ejecuta la funcionalidad.
    • El modelo (model), que es donde tenemos la información.

No hay comentarios:

Publicar un comentario