Implementar AJAX con DXL es tarea fácil

...

Desarrollar una aplicación web dinámica cross-browser sobre una plataforma IBM Lotus Domino es una tarea simple si sabemos aprovechar las posibilidades que nos ofrece Domino XML, los conceptos de AJAX y Sarissa.

Nivel: Intermedio

Agosto 2006

Introducción

Muchas veces nos encontramos con aplicaciones en las que desearíamos poder utilizar nuevas tecnologías para mejorar la experiencia de los usuarios. Sin embargo no siempre nos atrevemos a innovar porque no contamos con el tiempo que requiere la investigación de dicha tarea o tal vez porque desconocemos las dificultades que puede generar su implementación. En la actualidad, cada vez es más frecuente la utilización de tecnologías dinámicas y asincrónicas en la comunicación entre los clientes y el servidor debido a que de esta manera se logra disminuir el ancho de banda en la comunicación como así también mejorar los tiempos de respuesta y la usabilidad del sistema. Al mismo tiempo la necesidad de abarcar a mayor cantidad de clientes ha pasado a ser un objetivo prioritario para el cual AJAX y Sarissa resultan imprescindibles a la hora de lograr que la aplicación esté soportada por diferentes navegadores. A partir de la versión 6.0 del servidor Domino contamos con la posibilidad de trabajar con cualquier elemento de una base de datos a través de una interfaz común en formato XML. Este artículo tiene por objetivo profundizar en las tecnologías presentadas y los casos en que resulta provechosa su aplicación, como así también dar a conocer un diálogo de selección múltiple, similar al "Dialog window" disponible en los clientes IBM Lotus Notes. Ambas propuestas están dirigidas a desarrolladores Notes no muy experimentados con conocimientos básicos de HTML, JavaScript y XML.


DXL

Domino XML (DXL) es un conjunto de herramientas que ofrece Lotus Domino para trabajar con los elementos de una base de datos en formato XML. Con DXL se pueden intercambiar datos tanto de documentos como elementos de diseño. Esto quiere decir que podemos consultar o modificar documentos, vistas, formulario e incluso la ACL de una base de datos. El XML utilizado en las comunicaciones tiene un formato pre-definido (en un DTD), y su estructura es muy simple. Los tags son descriptivos y fáciles de asociar, por lo cual DXL resulta ser muy similar al XML generado por alguna aplicación propietaria. A continuación se muestra un extracto de DXL, correspondiente a un documento:

Ejemplo 1 - Formato de DXL

 
<document form ="ContactInfo">
  <item name ="UNID">
    <text>A6706DF66CEA473D0325719C0059DB17</text>
  </item>
  <item name="firstname">
    <text>John</text>
  </item>
  <item name="lastname">
    <text>Smith</text>
  </item>
  <item name="email">
    <text>john.simth @ mycompany.com</text>
  </item>
</document>
 

Al momento de tener que transportar información de un sistema Lotus Notes a otro (cualquiera sea la plataforma), es altamente recomendable el uso de DXL, ya que la multiplicidad de herramientas que nos brinda Lotus Domino reduce notablemente el esfuerzo de la implementación. Dedicaremos los próximos párrafos a demostrar la simplicidad con la que se pueden obtener datos de un documento y modificar al mismo tiempo cualquier campo. La simplificación de tareas es posible porque con DXL no es necesario generar el código que interprete el XML y luego modifique los documentos, pues este trabajo ya está hecho a través de las clases disponibles. De forma similar, también resulta muy fácil consultar una vista y obtener los campos de cualquier documento. Para obtener más información sobre Domino XML consulte la sección de referencias.


AJAX

AJAX es el acrónimo de "Asynchronous Javascript And XML", es decir comunicaciones asincrónicas mediante JavaScript y XML. Cuando piensan en AJAX, la mayor parte de los desarrolladores no suele asociarlo con Domino. Sin embargo, esto es totalmente viable ya que Domino posee herramientas suficientes para integrar el servidor con el browser de una forma muy sencilla. El objetivo principal de AJAX es lograr que la interfaz del usuario no se refresque cada vez que se desee actualizar algún dato en el servidor, sino que por el contrario sea posible realizar cualquier consulta o modificación en forma dinámica. De esta manera, AJAX mejora la experiencia del usuario quien puede seguir trabajando sin verse obligado a esperar a que la página se actualice. Las comunicaciones asincrónicas con el servidor permiten que el control de la aplicación no se pierda aunque el usuario esté obteniendo un gran volumen de datos. El componente principal utilizado en AJAX es el objeto XMLHTTPRequest, el cual permite generar comunicaciones con el servidor (con métodos GET o POST) y obtener el resultado en un objeto DOM. Cada vez que se obtenga o envíe información al servidor, el motor AJAX se encargará de generar una comunicación con el sistema pertinente e informará a nuestra aplicación cuando la misma se encuentre disponible. Para obtener más información sobre AJAX consulte la sección de referencias.


Sarissa

Sarissa es un framework de desarrollo que está compuesto por un conjunto de librerías ECMAScript que nos brindan una capa de abstracción de las APIs de XML nativas en diferentes entornos. Su empleo en el manejo de XML resulta muy eficaz porque entre otras posibilidades nos permite la instanciación de objetos DOMDocument, carga de XML desde una URL, o desde strings, la transformación con XSLT, consultas X-Path. Algunos de los exploradores que lo soportan son Mozilla - Firefox y familia, Internet Explorer con el parser MSXML versión 3.0 o mayor, Konqueror (KDE 3.3+), Safari y Opera. Es importante considerar que si bien el framework puede ser utilizado en una gama bastante amplia de navegadores, en alguno de ellos las funcionalidades son limitadas tal como resulta en el caso de Konqueror, Safari y Opera los cuales no soportan la utilización de XSLT/Xpath. La utilización de este framework no requiere de un esfuerzo mayor al de incluir sus librerías. La codificación se ve sustancialmente simplificada, pues evitamos las evaluaciones que corresponden al tipo del explorador utilizado por el cliente al mismo tiempo que podemos estandarizar la forma en que son desarrolladas las aplicaciones. Dicha normalización es muy importante debido a que fomenta la reutilización. A continuación se dan algunos ejemplos de la utilización de Sarissa.

Ejemplo 2 - Instanciación de un objeto DOMDocument

 
var oDomDoc = Sarissa.getDomDocument();
 
// en Mozilla este método es equivalente a: 
var oDomDoc = document.implementation.createDocument(); 
 
// en Internet Explorer este método es equivalente al siguiente, dependiendo de la versión del parser de XML: 
var oDomDoc = new ActiveXObject("Msxml2.DOMDocument.4.0");
 

Ejemplo 3 - Carga asincrónica de un objeto DOMDocument de una fuente remota

 
var oDomDoc = Sarissa.getDomDocument(); 
oDomDoc.async = true; // true es el valor por defecto 
 
// agregamos un listener el evento que se dispara cuando la carga se completa 
function loadHandler(){ 
  if(oDomDoc.readyState == 4) 
    alert(oDomDoc.xml); 
}; 
oDomDoc.onreadystatechange = loadHandler; 
oDomDoc.load("someDocument.xml");
 

Ejemplo 4 - Creación de un objeto XMLHttpRequest 

 
var xmlhttp = new XMLHttpRequest();
 

Ejemplo 5 - Carga asincrónica de un objeto XMLHttpRequest de una fuente remota

 
var xmlhttp = new XMLHttpRequest(); 
xmlhttp.open("GET", "someDocument.xml", true); 
 
// agregamos un listener el evento que se dispara cuando la carga se completa 
function loadHandler(){ 
  if(xmlhttp.readyState == 4) 
    alert(xmlhttp.responseXML.xml); 
}; 
 
xmlhttp.onreadystatechange = Function("loadHandler()"); // otra forma de asignar el listener del evento 
xmlhttp.send(null);
 

En el ejemplo se puede observar que se utiliza el método GET para la carga del recurso. En nuestro desarrollo utilizaremos el método POST, ya que se enviará información al servidor para ser procesada, según la recomendación del W3C.

Ejemplo 6 - Transformación de un objeto DOM Document con un XSLT

 
// obtención del documento fuente 
var oDomDoc = Sarissa.getDomDocument(); 
oDomDoc.async = false; 
oDomDoc.load("someDocument.xml"); 
// obtención del xslt 
var oXslDoc = Sarissa.getDomDocument(); 
oXslDoc.async = false; oXslDoc.load("aStylesheet.xsl"); 
// transformación, el resultado se obtiene como un String 
var sResult = oDomDoc.transformNode(oXslDoc); 
alert(sResult);
 

Para descargas y más información sobre Sarissa consulte la sección de referencias.


Hands On

Presentación de la aplicación

A continuación presentaremos el desarrollo del componente dinámico, y lo iniciaremos con la exhibición de nuestra implementación de referencia a través de una pantalla.

Figura 1 - Formulario de carga de contacto / selección de opciones (Mozilla Firefox - Windows)

Nuestra implementación de referencia consiste en un sistema de administración de contactos que utiliza un formulario de carga de contacto en el que existen campos multivalor que son completados con la utilización del diálogo de selección. Este diálogo de selección nos permite crear nuevas opciones y modificar las existentes.


La estructura de la aplicación

Para comprender como está construida la aplicación se presenta una tabla con los elementos de diseño y sus dependencias.Tabla

(*) Estos elementos son los que forman parte del componente. Para ver un panorama más amplio de la relación entre los elementos de diseño consulte la documentación del sistema con diagramas de UML


El superagente

El agente que procesará las peticiones AJAX se llama AJAXProccesor. Y se encargará de atender los requerimientos del interfaz de usuario para luego devolver una respuesta. El agente que procesará las peticiones AJAX se llama AJAXProccesor. Y se encargará de atender los requerimientos del interfaz de usuario para luego devolver una respuesta. En nuestro ejemplo, solamente procesaremos creaciones y actualizaciones de documentos aunque esto no excluye la posibilidad de extender su capacidad para eliminar documentos entre otras funcionalidades posibles. Conozcamos las tareas realizadas por el agente: Código 1 - Agente AJAXProccesor

 
 
Sub Initialize Dim dContext As NotesDocument 
Dim requestContent As String 
Dim response As String 
Set session = New NotesSession 
Set dContext = session.DocumentContext 
requestContent = dContext.GetItemValue("Request_Content")(0) 
response = Import(requestContent) 
Print "Content-Type: text/xml;" 
Print response 
Set dContext = Nothing 
End Sub
 

Al inicio del agente, obtenemos el documento de contexto que contendrá todos los campos del requerimiento HTTP. De estos campos nos interesa destacar Request_Content que es donde se aloja la información transmitida al hacer un requerimiento POST. En otras palabras, en la variable Request_Content se enviará el XML con los documentos a crear o actualizar. Luego invocamos a la función Import que será la encargada de traducir el XML a documentos Domino. Al finalizar esta función devolveremos otro XML como respuesta que indicará si la importación tuvo éxito. Por último eliminamos los objetos generados, para liberar la memoria. Ahora veremos que hace la función Import:

Código 2 - Método Import

 
Function Import(requestContent As String) As String
On Error Goto errorHandler
 
Dim db As NotesDatabase
Dim stream As NotesStream
Dim importer As NotesDXLImporter
 
Set db = session.CurrentDatabase
Set stream = session.CreateStream
 
stream.WriteText(requestContent)
 
If stream.Bytes = 0 Then
  Import = "<!--?xml version='1.0'?-->Cannot read input" &amp; _
  "stream!<code>-1</code>" Exit Function End If   Set importer = session.CreateDXLImporter(stream, db) importer.DocumentImportOption = DXLIMPORTOPTION_UPDATE_ELSE_CREATE Call importer.Process   Import = "<!--?xml version='1.0'?-->"   Set db = Nothing Set stream = Nothing Set importer = Nothing   Exit Function   errorHandler: Import = "<!--?xml version='1.0'?-->" &amp; Error(Err) &amp; _ "<code>" &amp; Err &amp; "</code>" Exit Function End Function

Como primer paso, generaremos un objeto NotesStream con el texto que obtuvimos al inicio del agente. Las clases de DXL solo soportan objetos stream como parámetros de entrada, por lo tanto estamos obligados a realizar este paso. Acto seguido comprobamos que el objeto se haya generado en forma exitosa, de no ser así se devuelve un mensaje de error en formato XML. Con el fin de lograr convertir XML en objetos Domino, DXL posee la clase NotesDXLImporter. Para generar un nuevo objeto, deberemos pasarle como parámetros el stream a procesar y la base de datos donde actuar (en este caso la base actual). Luego, especificamos de qué forma se importarán los documentos. Nosotros elegimos "Actualizar, sino crear". Esta opción hace que se cree un nuevo documento con los campos especificados en caso de que el servidor no pueda encontrar el documento para actualizar. Finalmente invocamos el método Process, para que se lleve acabo la importación. De acuerdo a lo previsto, el XML que suministramos se importará a la base de datos y actualizará los documentos deseados. Por lo tanto devolvemos una respuesta exitosa en un XML predefinido. Como se puede apreciar, gracias a la utilización de las librerías de DXL (Dominio XML), para procesar XML no es necesario realizar trabajo de codificación ya que esta tarea la realiza el servidor Domino. De esta manera se vuelve innecesario dedicar esfuerzos a interpretar el XML de entrada, o ubicar documentos y campos, entre otros. Simplemente obtenemos la información que queremos importar y la procesamos. En muy pocas líneas tenemos resuelto un problema que de haber sido implementado con XML ad-hoc, nos hubiera generado mayor inversión de tiempo. A demás, si nosotros quisiéramos actualizar más de un documento a la vez, o crear 3 documentos y modificar otros tantos, no deberemos modificar ni una sola línea en nuestro agente.


La magia de AJAX

La acción comienza al hacer click en el botón que realiza la apertura del diálogo, al ejecutar la siguiente función:

Código 3 - Apertura del diálogo

 
//Encabezado de la función 
function openPickList(dbPath, category, fieldName) { 
  //... 
} 
//Por ej, llamada desde el botón del campo 'Subscriptions' 
openPickList(getFieldValue('DbPath'), 'S', 'subscriptions');
 

Esta función recibe como parámetros de entrada el path de la base, la categoría de la cual se desean ver las opciones y el identificador del campo en donde se debe guardar el resultado de la selección.

Nota: El identificador del campo debe coincidir con el valor de la propiedad 'id'. Una vez abierto nuestro formulario de selección se lleva a cabo la carga de las opciones a través de una llamada a la siguiente función getOptions(). Vale la pena revisarla en detalle.

Código 4 - Carga de opciones del diálogo

 
function getOptions() { 
  var dbPath = getFieldValue("DbPath"); //Path de la base de datos 
  var category = getFieldValue("category"); //Categoría de las opciones 
  var url = dbPath + "/LookUpValues?ReadViewEntries&amp;RestrictToCategory="+category; 
  processAJAXRequest("GET", url, "", "processOptions()"); 
}
 

Podemos observar que se obtienen los valores del path de la base de datos y la categoría de las opciones que se desea consultar. Con estos datos se arma una URL para consultar la vista LookUpValues. Aquí es importante destacar dos cosas:

  • La vista se consulta utilizando el comando ReadViewEntries, para acceder a la información en formato XML.
  • Se utiliza el comando RestrictToCategory, para cargar las opciones de la categoría deseada.

Finalmente, se realizada la llamada a la función processAJAXRequest(), quien, en nuestro caso, es la encargada de la comunicación con el servidor. A continuación se describe su funcionamiento.

Código 5 - Método processAJAXRequest

 
function processAJAXRequest(method, url, xml, returnFunction) { 
  var timeStamp = new Date().getTime(); 
  url += "&amp;timestamp=" + timeStamp; 
  xmlhttp = new XMLHttpRequest(); 
  xmlhttp.open(method.toUpperCase(), url, true); 
  xmlhttp.onreadystatechange = Function("processAJAXRequestLoaded('"+returnFunction+"')"); 
  xmlhttp.send(xml); 
} 
function processAJAXRequestLoaded(returnFunction) { 
  if(xmlhttp.readyState == 4){ 
    eval(returnFunction); 
  } 
}
 

Observe que esta función recibe los siguientes parámetros:

  • method: El método a utilizar al hacer el requerimiento al servidor (GET o POST, según se desee obtener información o realizar algún tipo de procesamiento del lado del servidor)
  • url: La URL del recurso a consultar.
  • xml: El contenido a enviar con el requerimiento.
  • returnFunction: Función a ejecutar al obtener la respuesta del servidor.

En ella se instancia un objeto XMLHttpRequest y se lleva a cabo el procesamiento asincrónico del recurso solicitado. Es importante destacar que no es necesario hacer verificaciones sobre el tipo de cliente. Nótese que la utilización de Sarissa nos permite que el código sea cross browser Al obtener una respuesta se llama a la función processAJAXRequestLoaded(). Debido a que el estado de la respuesta cambia más de una vez, es necesario que dicha función realice un chequeo del mismo. Los valores que puede tomar son:

    0 = uninitialized 1 = loading 2 = loaded 3 = interactive 4 = complete

Cada vez que el estado cambia, se dispara el evento onreadystatechange, en este caso sólo procesaremos la respuesta al completar el requerimiento con el fin de conservar la simplicidad del desarrollo. Por este motivo procesaremos la respuesta cuando el estado sea 4. Las funciones processAJAXRequest0 y processAJAXRequest Loaded en conjunto, son nuestras herramientas para realizar la comunicación con el servidor al estilo AJAX. Retomaremos el desarrollo del diálogo cuando tiene lugar la consulta de opciones. Esta consulta nos entrega los datos de la vista LookUpValues en formato XML, como se muestra a continuación:

Ejemplo 7 - -Consulta de LookUpValues

 
<?xml version="1.0" encoding="UTF-8"?>
<viewentries toplevelentries="17">
  <viewentry position="1" unid="5D2D3FE74805400403257199006BE5BD" noteid="A12" siblings="17">
    <entrydata columnnumber="0" name="value">
      <text>Argentina</text>
    </entrydata>
  </viewentry>
  <viewentry position="2" unid="1642D64F90F8A90B03257199006A488C" noteid="9EE" siblings="17">
    <entrydata columnnumber="0" name="value">
      <text>Brasil</text>
    </entrydata>
  </viewentry>
  ...
</viewentries>

Esta información es procesada por la función processOptions(), que recorre los nodos del XML y arma el HTML correspondiente a la lista de opciones. Hasta aquí hemos explicado como se lleva a cabo la carga inicial de las opciones del diálogo y el armado de la interfaz. Vimos cuales son las funciones que intervienen y la estructura del XML consultado. Ahora es cuando el asunto comienza a ponerse más interesante. A continuación se describe la forma de dar de alta nuevas opciones y de modificar las existentes. Para agregar o modificar un elemento de la lista usamos la función processAction(). El siguiente diagrama detalla la secuencia de ejecución de funciones involucradas en dicha acción:

 

Función processAction

Figura 2 - Función processAction

Para agregar una nueva entrada, habrá que completar el campo al final del diálogo del picklist, y presionar el botón de agregar. Al presionar este botón, se llamará a la función processAction() indicando que se quiere agregar una nueva entrada.

Código 6 - Función processAction

 
function processAction(action, obj) {
  // Obtengo los datos claves para crear el documento
  var dbReplicaID = getFieldValue("DbReplicaID");
  var docUNID = "";
  var docNoteID = "";
  var fieldValue = obj.value;
  var xmlRequest = "";
  var urlRequest = getFieldValue("DbPath") + "/AJAXProcessor?OpenAgent";
  var category = getFieldValue("category");
 
  // Verifico que acccion realizar
  switch(action) {
    case "edit":
      docUNID = obj.getAttribute("unid");
      docNoteID = obj.getAttribute("noteID");
      break;
    case "add":
      // Al agregar un documento, hay que especificar un UNID y Note ID inexistentes
      docUNID = "01234567890123456789012345678901";
      docNoteID = "01234567";
      break;
  }
 
  // Genero el XML necesario para DXL
  xmlRequest = composeDXLRequest(dbReplicaID, docUNID, docNoteID, category, fieldValue);
 
  // Hago la peticion a AJAX para crear / actulizar el documento
 processAJAXRequest("POST", urlRequest, xmlRequest, "getResponse()");
}
 

Primero se obtienen algunos datos como el replicaID de la base, el valor que se desea agregar, etc. Luego se llama a composeDXLRequese() pasándole como parámetros todos los datos obtenidos anteriormente. Como respuesta, obtendremos el XML necesario para que el importador de DXL genere un nuevo documento.

 
function composeDXLRequest(dbReplicaID, docUNID, docNoteID, category, fieldValue) {
  var dxlDocument = "";
  // Genero el XML con el formato especificado segun la DTD de DXL
  dxlDocument += "<?xml version='1.0' encoding='ISO-8859-1'?>";
  dxlDocument += "<document xmlns='http://www.lotus.com/dxl' version='6.0' replicaid='" + dbReplicaID + "' form='Option'>";
  dxlDocument += "<noteinfo noteid='" + docNoteID + "' unid='" + docUNID + "' sequence='1' />";
  dxlDocument += "<item name='category'><text>" + category + "</text></item>";
  dxlDocument += "<item name='value'><text>" + fieldValue + "</text></item>";
  dxlDocument += "</document>";
  return dxlDocument;
}
 

Enviamos este XML a processAJAXRequest() indicando que se ejecute la función getResponse() cuando el servidor haya procesado los datos.

Nota: en caso de crearse un nuevo documento, debemos informar a DXL un UNID y Note ID que no se correspondan con ningún documento de la base de datos. Una vez que el servidor creó el documento, nuestro motor AJAX, invocará a la función getResponse() la cual, luego de validar si hubieron errores, refrescará la lista de valores llamando a getOptions(). A continuación se muestra como crear una nueva entrada.

Diálogo de PickList

Figura 3 - Diálogo de PickList con una nueva entrada (Mozilla 1.0.1 - SUSE 9)

El proceso para editar una entrada es idéntico al explicado, con la salvedad que al invocar a la función processAction(), especificaremos el método "edit". En la siguiente figura se muestra como modificar una entrada existente:

Diálogo de PickList modificado

Figura 4 - Diálogo de PickList modificando una entrada (Internet Explorer 6 - Windows)

Finalmente, al hacer click en el botón aceptar se cargan las opciones seleccionadas en el campo de la ventana desde la cual se abrió el diálogo.


Conclusiones

Hemos demostrado a través de múltiples ejemplos la simplicidad con la que se integran el back-end y el front-end de una aplicación Domino como consecuencia de la utilización de DXL y Sarissa, bajo un patrón de diseño alineado con los conceptos de AJAX. A dicha simplicidad se suman los beneficios que nos ofrece el desarrollo de aplicaciones de este tipo en aspectos tales como la disminución del ancho de banda en las comunicaciones entre el cliente y el servidor, las mejoras en los tiempos de respuesta y en la usabilidad del sistema, la simplificación en la codificación, como así también la inducción a la estandarización y reutilización de librerías. Por último quisiéramos dedicar unas líneas a señalar que si bien la utilización de AJAX resulta ser una herramienta muy provechosa, en ocasiones no resulta ser una buena opción. Por ejemplo, la introducción de AJAX puede complicar sustancialmente su implementación en aplicaciones demasiado simples como para ser desarrolladas bajo el concepto de comunicación sincrónica entre el cliente y el servidor, es decir aplicaciones que por su simplicidad en su desarrollo o codificación no ameritan la inversión de tiempo que supone el desarrollo de las funciones del cliente bajo el concepto de comunicación asincrónica entre el cliente y el servidor. En el caso de la demo que hemos desarrollado en el artículo, por ejemplo, no hubiera tenido sentido que el formulario de carga de contactos fuera asincrónico. Los motivos quedan a la vista si nos detenemos a comparar el tiempo que puede llevar el desarrollo de un formulario contra el tiempo que requiere el desarrollo de las funciones del cliente para la carga del mismo en forma asincrónica y para el armado de la interfaz. Por lo tanto, si el objetivo es mejorar la experiencia de los usuarios con la utilización de las nuevas tecnologías es importante no perder de vista que los mejores resultados no se obtienen de su uso indiscriminado sino que se originan a partir de evaluar y seleccionar en qué momentos precisos de un sistema resulta conveniente utilizar las tecnologías dinámicas y asincrónicas en la comunicación entre los clientes y el servidor.


Referencias



Modificado por última vez en Lunes, 08 Julio 2013 12:57

Acerca de los Autores

Andrés Sommariva

Andrés cuenta con más de diez años de experiencia liderando proyectos en diversas tecnologías, actualmente se desempeña como Gerente de Operaciones de MicroGestion Software.

Matias Moncho

Matias Moncho es desarrollador Senior del area de consultoría de MicroGestion, especializado en IBM Lotus Domino (CLP R5 y R6) ha incoporado desde el año 2002 tecnologías como DHTML, XML y AJAX para el desarrollo de aplicaciones web centradas en el usuario.