Etiqueta

9 de octubre de 2014

Construcción de aplicaciones para iOS, Android y BlackBerry con Omnis Studio

Además de utilizar la nueva tecnología “JavaScript Client” en la construcción de aplicaciones ejecutables sobre el navegador de cualquier ordenador, tablet o dispositivo móvil, podemos usarla en la creación de aplicaciones totalmente terminadas para dispositivos iOS, Android y BlackBerry. Dichas aplicaciones podrán incluso operar completamente “off-line”, es decir, sin necesidad de conexión a ningún tipo de servidor, en éste caso tan sólo requerirá de un número de serie especial instalado en su SDK Omnis, el “Serverless Client Serial”.

Para la construcción de dichas aplicaciones terminadas, disponemos de tres aplicaciones "esqueleto" construidas a medida de cada sistema, denominadas “wrapper” y presentadas en la forma de proyectos JavaScript: una para iOS, una para BlackBerry10 y otra para Android.

Dichos proyectos posibilitarán la creación de aplicaciones personalizadas, mediante una sencilla integración de clases “remote-form”, que permitirán a su vez el acceso a gran parte de las funcionalidades implementadas de modo nativo en el dispositivo, tales como la lista de contactos, función GPS y cámara de fotos.

Mediante las sucesivas entregas que iré publicando en éste blog, comprenderemos los diferentes pasos necesarios para crear y desplegar aplicaciones personalizadas para cada una de las plataformas móviles mencionadas. Estudiaremos paso a paso, todo lo que necesitaremos saber, tanto para la creación de la aplicación, como para su despliegue a los usuarios finales, ya sea que se haga a través de la tienda de aplicaciones del propio dispositivo o de manera autónoma.

Les sugiero que se suscriban a "Aula Omnis" a  través de cualquiera de las redes disponibles Facebook, Twitter, Linkedin, Google+, RSS FeedBurner o como seguidores de éste blog, a fin de no perderse las sucesivas entregas.

Reciban un cordial saludo.

8 de octubre de 2014

Creación automática de tablas en el servidor

En algunos casos, puede tener sentido la ejecución de un proceso, que cree las tablas que falten en el servidor automáticamente tras iniciar la sesión con la base de datos, la intención sería, causar que las tablas necesarias para el correcto funcionamiento de la aplicación existan. Dicho proceso podría lanzarse cuando la aplicación es ejecutada por primera vez, cuando se entrega una nueva versión de la base de datos, cuando se cambie a un nuevo servidor de base de datos o a una base de datos diferente.

Puesto que estamos haciendo uso de clases "schema" durante el desarrollo de nuestra aplicación, es posible compararlas con las tablas correspondientes en el servidor, para saber si existen realmente y en caso necesario crearlas.

Partiendo de la base de que tenemos una variable "task" denominada "tSessObj" con una conexión válida a una base de datos. Crearemos un "statementobject" que más tarde utilizaremos para enviar las sentencias SQL necesarias a la base de datos, lo haremos sobre la variable local "lStatObj" (de tipo "Object" y sin subtipo).

Do tSessObj.$newstatement(#CT) Returns lStatObj

La variable "Object" "lStatObj" ahora dispone de los métodos y propiedades necesarios y que pueden ser localizados mediante el "Interface Manager" (opción disponible, mediante pulsar el botón derecho del ratón sobre la variable) si lo deseamos.

A continuación, utilizaríamos el código que mostramos más abajo para obtener las tablas alojadas en el servidor de base de datos, recogiéndolas sobre la variable de tipo lista "lDBTableList", tal como sigue:

Do lStatObj.$tables(kStatementServerTable)

Tenga en cuenta que sólo podrá ejecutar éste código mientras disponga de una instancia activa, es decir, mientras exista una conexión válida con la base de datos. Por lo que, si desea depurar el código, deberá marcar un punto de interrupción en el lugar que desee de la "Startup_Task", para cargar sobre la lista el resultado del método anterior, escriba:

Do lStatObj.$fetch(lDBTableList,kFetchAll)

El segundo paso será crear otra lista, que contenga las clases "schema" existentes en nuestra librería. Para ello usaremos también dos variables locales que la definirán, tal y como sigue:

Do lTablesForAppList.$define(lTableName,lSchemaClassName)
Do $schemas.$appendlist(lTablesForAppList,$ref.$servertablename,$ref.$name)

Ahora tendremos que ejecutar un bucle "For" que ira recorriendo la lista de clases "schema" y creando las nuevas tablas en el servidor según sea necesario:


For lTablesForAppList.$line from 1 to lTablesForAppList.$linecount() step 1
   Do lTablesForAppList.$loadcols()
   ; carga la línea actual en los campos correspondientes lTableName y lSchemaClassName
   Do lDBTableList.$search(lDBTableList.TableOrViewName=lTableName,kTrue,kFalse,kFalse,kFalse)
   If not(lDBTableList.$line) ;; cuando no existe la tabla
      Do lRow.$definefromsqlclass(lSchemaClassName)
      ; define una variable local de tipo "row" con el nombre del "schema" que se acaba de cargar
      Do lRow.$sessionobject.$assign(tSessObj) ;; ¡importante! hace que $createnames funcionen correctamente
      ; crea la nueva tabla en el servidor:
      Do lStatObj.$execdirect(con('CREATE TABLE ',lRow.$servertablenames(),'
(',lRow.$createnames(),') ')) Returns lStatus
      If not(lStatus)
         Breakpoint -> implementar aquí el controlador de errores
      End If
   End If
End For

Naturalmente este código deberá ejecutarse después de abrir la sesión con la base de datos sobre el método $construct de la "Startup_Task", cómo puede observar, se comprueba si existe la tabla en el servidor en correspondencia con lo definido en su clase "schema" y si no existe será automáticamente creada.

1 de octubre de 2014

Uso de las clases "table"

Los desarrolladores a menudo nos preguntamos si no sería mejor utilizar clases "object" en lugar de clases "table" a la hora de construir código SQL, ya que al fin y al cabo, es posible programar en modo orientado a objetos, aún sin utilizar clases "table".

A primera vista, esto parece una propuesta razonable, ya que al utilizar las clases "object", disponemos de acceso directo al código y no será necesario escribir métodos con el código SQL sobre la clase "table", sino que podremos hacerlo en cualquier lugar.

De hecho, (según me consta) es un práctica extendida entre los programadores el hacer uso de las clases "schema" directamente, en lugar usar clases "table" conectadas a clases "schema". Seguramente debido a que con las clases "table", el programador debe decidir si introducir el código SQL a ejecutar dentro de la propia clase o simplemente hacer uso de los métodos y propiedades predefinidos, además de que, en cierto modo, las clases "table" son similares a clases "object".

Esencialmente la diferencia está, en que las clases "table" tienen propiedades adicionales. Por ejemplo; cuando una instancia, de una clases "table" ha sido definida con las columnas de una variable de tipo "list" o "row" y añadido a ella los métodos SQL que deseemos. Debemos tener en cuenta que dicha instancia es también un objeto de datos, es decir, una variable "list" o "row". Esto significa que en realidad estamos incluyendo métodos dentro de objetos de datos. El ejemplo siguiente, muestra un método en una clase "table", del cual obtenemos su valor de retorno:

If myRow.$checkFields()
   Do myRow.$insert()
Else
   OK message "El campo name esta vacío"
Endif


El método $checkFields() en la clase "table" podría ser:

If len($cinst.name) = 0 ;;name está vacío
   Quit method kFalse
Else
   Quit method kTrue
Endif

En nuestro ejemplo $cinst hace referencia a la fila actual de la variable "list" utiliza al definir la clase "table".

Algunos podrían argumentar que puede lograrse el mismo resultado usando una clase "object". Pero, esto no sería estrictamente cierto ya que requeriría del uso de otra capa de código, además del necesario uso de la clase "object" para acceder a la variable "list" o "row", mientras que en la clase "table" se aúnan ambas cosas.

Además (como en el caso de las clases "object") se puede hacer uso de los métodos predefinidos a la hora de gestionar la "list" o "row" de datos ya que, como tal, las clases "table" son autónomas. Esto nos permite además hacer uso de las propiedades y métodos de uso general, al mismo tiempo que decidir si haremos uso o no, de los definidos por nosotros, al actuar como super-clase de la clase "table". Atendiendo a nuestro ejemplo, colocaríamos el método $checkFields() en la super-clase, con lo que tendríamos la opción de anular este método desde la clase "table" derivada en caso necesario, para ello simplemente colocaríamos el comando siguiente en el método en la super-clase:

Quit method kTrue

En otras palabras, si no se hereda el método checkFields() de la clase "table", siempre devolverá kTrue.

Por supuesto usted es libre de usar clases "object" o "table", o ambas a la vez según lo considere oportuno, a la hora de incorporar el código SQL a su aplicación. La única cosa que deberá tener siempre en cuenta es, que la definición que hagamos mediante el método $definefromsqlclass() sobre la variable "list" o "row", será quien determine, donde deberá añadirse el código SQL. Por ejemplo:

Do myList.$definefromsqlclass($tables.T_Address,'Name','Firstname','City','ADDRESS_ID')

En éste ejemplo, "myList" es definida mediante la clase "table" "T_Address" y con las columnas; "Name", "Firstname", "City" y "ADDRESS_ID".

Cuando desee escribir su propio código SQL con estos nombres de columna, deberá hacerlo mediante la implementación de un método dentro de la propia clase "table" y que hará uso de su propio "statementobject" al ejecutarse. Tenga en cuenta que para que esto funcione, el objeto de sesión (sessionobject) deberá haber sido asignado a la clase "table", cosa que (dicho sea de paso) habremos hecho en el método $construct() de la super-clase.

Método: $loadExample()

Begin Statement
    STA: SELECT [$cinst.$selectnames()]
    STA FROM ... (su propio SQL)
End Statement
Do $cinst.$statementobject().$prepare()
Do $cinst.$statementobject().$execute() Returns Ok
If not(Ok)
    Do $cinst.$sqlerror()
    Quit method kFalse
Else
    Do $cinst.$fetch(kFetchAll) ;; Cargas todos los registros en el propio objeto
    Quit method kTrue
Endif

Note que $cinst.$selectnames() devolverá los datos de las columnas de la variable "list" o "row" utilizada en el momento de definir el instancia para la clase "table".

El método sería invocado desde la clase "table", del modo siguiente:

If myList.$loadExample()
   Do $cinst.$redraw()
Else
   Ok message "No se encontraron datos: [myList.$getErrorText()]
Endif

Utilizando este enfoque, el objeto cargará sus datos de forma independiente. al mismo tiempo que nos permite obtener el mensaje de error que pudiera producirse, lo cual también podría ser implementado en la super-clase si optáramos por un control de errores más generalizado, algo así como:

Método: $getErrorText()

Quit method $cinst.$statementobject().$nativeerrortext()

Esto completaría el círculo, ya que ahora estaríamos accediendo directamente las propiedades del "statementobject" ubicado dentro del propio objeto de datos. El método $getErrorText() devuelve el texto nativo del error, para ser mostrado el mensaje.

Pensemos ahora en otra ventaja al usar las clases "table", cuando hacemos uso de nuestros propios métodos para insertar datos solemos hacer uso de variables "bind" (ya hemos hablado de ellas en un artículo anterior de éste blog). Las variables "bind" hacen uso de caracteres especiales, con el fin de ahorrarnos la necesidad de poner comillas para encerrar textos. Pero en el caso de las clases "table", y si hacemos uso del método $insert() por defecto, (es decir no personalizado) el objeto utilizará automáticamente las variables tomándolas directamente de la variable "list" o "row" por lo que no tendremos que preocuparnos de nada en absoluto.

Además, el uso de clases "table", no impide el control sobre el código SQL que enviamos al servidor. Para ejemplo:

Calculate mySQLText as $cinst.$statementobject().$sqltext

Si decidiéramos no pasar ciertas columnas, podríamos excluirlas de los $insert() desde el método $construct() de la super-clase "table", del modo siguiente:

Do $cinst.$cols.ident.$excludefrominsert.$assign(kTrue)

En este ejemplo, la columna "ident" sería excluida de la inserción.

Por cierto, el método integrado $sqlerror() en una clase "table" es ejecutado automáticamente, cuando cualquiera de los métodos incluidos tales como $insert(), $update(), $delete() o $select() falla por cualquier razón. Por lo que, podría tener sentido anular este método también de la super-clase "table", creando un controlador de errores SQL más genérico en nuestra aplicación, en el manual Omnis Studio en Castellano podrá encontrar más información al respecto.