Microsoft Office

Exploración de la API de JavaScript para Office: Enlace de datos y elementos XML personalizados

Stephen Oliver
Eric Schmidt

Descargar el ejemplo de código

Esta es la tercera parte de una serie de artículos que revisan a fondo la API de JavaScript para Office. En este artículo sigue el examen de los aspectos clave de la API y se centra en el enlace de datos y el trabajo con elementos XML personalizados. La primera parte, “Exploración de la nueva API de JavaScript para Office” (msdn.microsoft.com/magazine/jj891051) entrega una amplia visión general del modelo de objetos. La segunda parte, "Exploración de la API de JavaScript para Office: acceso a datos y eventos” (msdn.microsoft.com/magazine/jj991976) analiza de cerca el concepto importante de cómo obtener el contenido de un archivo y lleva a cabo una revisión minuciosa del modelo de eventos. A continuación del presente artículo, la cuarta parte se centrará solo en el tercer tipo de aplicación para Office: las aplicaciones de correo.

A lo largo de esta serie, frecuentemente hacemos mención de la documentación de referencia de la API de JavaScript para Office. Puede encontrar la documentación oficial, los ejemplos de código y los recursos de la comunidad en la vista previa de la página para desarrolladores de aplicaciones para Office y SharePoint en MSDN (dev.office.com).

Enlace de datos en una aplicación para Office

El enlace de datos ofrece una estrecha integración entre una región específica de datos en el documento y la aplicación. Los datos en la región están enlazados a un objeto denominado en la aplicación, para que de ese modo la aplicación pueda obtener acceso a los datos en la región denominada, incluso si el usuario ha seleccionado otra cosa.

Una vez creado, el enlace persiste incluso si la región se mueve a la página (en Word) o se copia a otra hoja de cálculo (en Excel). Por ejemplo, un enlace a una tabla persiste incluso si el usuario le cambia el nombre.

Cuando se modifican los datos en la región, el enlace genera un evento al que se puede enlazar la aplicación. Desde este evento, la aplicación puede llegar a los datos que han cambiado y reaccionar adecuadamente.

Enlaces y la "vista" de una aplicación Ciertamente, el enlace de datos en una aplicación para Office brinda a una aplicación acceso directo a un conjunto de datos dentro del archivo de Office, gracias a lo cual es más fácil que la aplicación analice esos datos sin tener que depender de una acción directa por parte del usuario. Así y todo, el enlace de datos hace más que solo permitir el acceso a datos dirigido: permite que el desarrollador incluya el archivo de Office mismo como un componente personalizable e integral de la aplicación.

Muchas aplicaciones para Office presentan a sus usuarios una interfaz contenida únicamente dentro de los confines del panel de tareas o de la IU de la aplicación de contenido y no hay nada malo en que así sea. Sin embargo, en un sentido muy simple, los datos y su presentación dentro del archivo de Office son, en sí mismos, una "vista" de la aplicación. Los usuarios interactúan con sus datos dentro del archivo de Office. Escriben datos nuevos, cambian los datos existentes y eliminan los datos innecesarios dentro del contenido del documento. Las aplicaciones de Office presentan una vista de los datos que los usuarios conocen y comprenden.

Las funcionalidades de enlace de datos en la API de JavaScript para Office permite aprovechar la vista de los datos que la aplicación de Office brinda en una aplicación. El desarrollador puede desarrollar una "interfaz" para la aplicación usando lo que ya se ofrece en Office. De este modo, puede crear el estilo de la vista de la aplicación con las características integradas de la aplicación de Office. El enlace de datos brinda los pilares que conectan la vista de la aplicación al "modelo" de lógica de negocios contenido en los archivos de JavaScript.

Por supuesto, también ocurre el proceso inverso. Puede usar el archivo de Office como su origen de datos, almacenando el contenido del modelo de datos. De ese modo, puede usar la aplicación para brindar una vista de los datos. Con la flexibilidad que brindan los enlaces, puede aplicar el patrón de controlador de vista de modelo (MVC) a una aplicación y a los archivos de Office según corresponda a sus necesidades.

Escenarios para el uso de enlaces Sin definir un límite estricto sobre la creatividad de los desarrolladores, una aplicación puede usar enlaces combinando de cualquier manera tres modos generalizados:

  • La aplicación reacciona cuando el usuario modifica los datos en la región.
  • La aplicación recoge los datos en la región, los analiza y presenta al usuario opciones para modelar o enviar los datos.
  • La aplicación inserta datos desde un origen de datos externo a la región enlazada.

Por ejemplo, tome una simple aplicación de tablero de cotizaciones insertada en un libro de Excel, en el que una columna del libro contiene símbolos bursátiles y otra contiene valores de acciones actuales. Con el enlace de datos, una aplicación podría enlazarse a la columna con los símbolos bursátiles para así recoger los que hay presentes en la columna. Luego la aplicación podría suscribirse a los cambios en el precio de esas acciones a través de un servicio web y analizar los resultados enviados desde el servicio. Finalmente, la aplicación podría enlazarse a la columna de precios en la hoja de cálculo y actualizar los valores en tiempo real.

En la próxima sección haremos justamente eso: crearemos un libro para un tablero de cotizaciones, para así examinar el objeto Binding.

Uso del objeto Binding La magia subyacente del enlace de datos está contenida en la colección Bindings y en el objeto Binding.

  • La colección Bindings representa todos los enlaces creados entre el archivo de Office y la aplicación para Office. Una aplicación no tiene acceso a ningún enlace creado por otras aplicaciones.
  • El objeto Binding representa un enlace denominado entre una región en el archivo de Office y la aplicación. Expone varios miembros para obtener, leer y definir datos y para reaccionar a los cambios en la región enlazada.

Analizaremos más de cerca estos objetos a medida que construyamos la aplicación del tablero de cotizaciones.

Antes de que avancemos, echemos un vistazo rápido a los datos. La figura 1 muestra cómo se ve esta aplicación. Como puede ver, para esta demostración usamos símbolos bursátiles ficticios.

A Table Named “Stocks” in an Excel Workbook with Formulas and Conditional Formatting Applied
Figura 1 Una tabla llamada "Acciones" en un libro de Excel con fórmulas y formato condicional aplicado

Además, hemos agregado cierta "inteligencia" a este libro. El formato de la región de datos a la que queremos enlazar se realizó como una tabla y ahora tiene el nombre de "Acciones". Se agregó una fórmula personalizada a los valores en la columna de la derecha para comparar los otros valores de la tabla. También aplicamos formato condicional a la tabla para que los conjuntos de iconos aparezcan en la columna de la derecha.

Es importante observar que hemos agregado este libro a nuestra solución en Visual Studio 2012, para que así no tengamos que volver a crear nuestra tabla cada vez que depuramos la aplicación. Para agregar el libro a la solución, haga clic con el botón secundario en el proyecto de la aplicación en la solución (el primer proyecto que aparece en el explorador de soluciones cuando se usa la plantilla predeterminada), haga clic en Agregar elemento existente y seleccione el libro. Luego, en las propiedades del proyecto de la aplicación, defina la Acción de inicio en el archivo del libro. Cuando realice la depuración, deberá insertar manualmente la aplicación en el libro (ficha Insertar | botón Aplicaciones para Office).

Cuando se inicializa, la lógica de negocios de la aplicación debe definir el enlace y luego agregar un controlador de eventos al evento del enlace, Office.EventType.BindingDataChanged. La Figura 2 muestra el código. Observe que encapsulamos nuestro código dentro de una función anónima autoejecutable almacenada en la variable StockTicker. Tanto el nombre de la tabla en la hoja de cálculo como el nombre del enlace y el enlace se almacenan como campos de clase dentro de la "clase" StockTicker. La "clase" StockTicker solo expone un único miembro: initializeBinding.

Figura 2 Creación del enlace al libro de Excel y adición de un controlador al evento de datos cambiados en el enlace

var StockTicker = (function () {
  var tableName = "Sheet1!Stocks",
      bindingName = "Stocks",
      binding;
  // Create the binding to the table on the spreadsheet.
  function initializeBinding() {
    Office.context.document.bindings.addFromNamedItemAsync(
      tableName,
      Office.BindingType.Table,
      { id: bindingName },
      function (results) {
        binding = results.value;
        addBindingsHandler(function () { refreshData(); });
    });
  }
  // Event handler to refresh the table when the
  // data in the table changes.
  var onBindingDataChanged = function (result) {
    refreshData();
  }
  // Add the handler to the BindingDataChanged event of the binding.
  function addBindingsHandler(callback) {
    Office.select("bindings#" + bindingName).addHandlerAsync(
      Office.EventType.BindingDataChanged,
      onBindingDataChanged,
      function () {
        if (callback) { callback(); }
    });
  }
  // Other member methods of this "class" ...
  return {
    initializeBinding: initializeBinding
  };
})();

Para establecer un enlace entre la aplicación y la tabla en el libro, podemos usar uno de varios métodos distintos de la clase Document en la API de JavaScript, que incluye addFromNamedItemAsync, addFromPromptAsync y addFromSelectionAsync. (Observe que addFromPromptAsync solo está disponible en Excel y en Excel Web App).

Como sabemos el nombre de la región a la cual deseamos enlazar (es la tabla titulada "Acciones" en la Hoja1), usamos el método addFromNamedItemAsync para establecer el enlace. Pasamos el nombre de la tabla usando la notación de rango de Excel (Sheet1!Stocks). Los resultados de esta llamada a método incluyen una referencia al enlace mismo, lo que nos permite almacenar una referencia al enlace en nuestra variable de enlace (campo de clase).

En nuestro código, hemos pasado el valor Office.BindingType.Table para el parámetro bindingType del método. Esto especifica que deseamos crear un tipo "Table" para enlazar con nuestros datos, a pesar de que también se pudiese especificar un tipo de texto o matriz de enlace. El enlace a la región como una tabla nos brinda diversos beneficios. Por ejemplo, si el usuario agrega una nueva columna o fila a la tabla, el alcance de la región enlazada también aumenta. También funciona al inverso. El objeto TableBinding, que es subyacente al enlace, expone propiedades para agregar columnas, agregar filas e incluso eliminar todos los datos de la tabla.

(Consulte la sección titulada “Obtener acceso al contenido de archivos de Office desde una aplicación para Office” en el segundo artículo de esta serie para obtener detalles acerca de los tipos de datos de texto y matriz en la API de JavaScript para Office).

Nuestro código agrega entonces un controlador al evento BindingDataChanged del enlace. Cuando cambian los datos en la región enlazada, es decir, cuando el usuario cambia los datos de la región, queremos llamar una función refreshData definida localmente para comenzar el proceso que actualiza la tabla. Además, como la tabla todavía no se actualiza con datos provenientes del origen de datos, desearemos llamar a refreshData después de agregar el controlador de eventos.

Podrá observar que la función addBindingsHandler usa el método Office.select para obtener el enlace, a pesar de que podríamos haber usado el método Bindings.getByIdAsync en su lugar. La mayor diferencia entre los dos métodos es el nivel de acceso a los datos que se devuelve en los resultados. El método Office.select devuelve un objeto Binding promise al código de llamada. Si el método se realiza correctamente, el objeto Binding devuelto tiene solo un número limitado de miembros disponibles para su uso. Si se selecciona el enlace usando Office.select, podemos llamar inmediatamente a los miembros desde el objeto Binding. De este modo, no tenemos que agregar una devolución de llamada a una función que obtiene el enlace para agregar un controlador al enlace.

(Es posible que piense que podríamos simplemente haber usado la variable de "enlace" local que captura la referencia al enlace y tiene razón, podríamos haberlo hecho. Escribimos este código de esta manera para la demostración).

La figura 3 muestra las funciones refreshData y getBindingData. La función refreshData simplemente comienza la cadena de llamadas asincrónicas que obtiene los datos de la tabla desde la hoja de cálculo llamando a getBindingData. La función getBindingData contiene una llamada al método Binding.getDataAsync y devuelve los datos como un objeto TableData.

Figura 3 Obtención de los datos desde el enlace de la tabla y llamada al servicio web

var StockTicker = (function () {
  // Other members of this "class"...
  // Refresh the data displayed in the bound table of the workbook.
  // This function begins a chain of asynchronous calls that
  // updates the bound table.
  function refreshData() {
    getBindingData();
  }
  // Get the stock symbol data from the bound table and
  // then call the stock quote information service.
  function getBindingData() {
    binding.getDataAsync(
      {
        startRow: 0,
        startColumn: 0,
        columnCount: 1
      },
      function (results) {
        var bindingData = results.value,
            stockSymbols = [];
        for (var i = 0; i < bindingData.rows.length; i++) {
          stockSymbols.push(bindingData.rows[i][0]);
        }
        getStockQuotes(stockSymbols);
    });
  }
  return {
    // Exposed members of the "class."
  };
})();

En la llamada a getDataAsync que aparece en la figura 3, podríamos haber especificado el tipo de datos para recuperar (o haber cambiado el tipo de datos) de manera explícita al pasar un objeto anónimo, {coercionType: Office.CoercionType.Table}, para el parámetro options. Como no hemos especificado un tipo de datos para recuperar, la llamada getDataAsync devuelve los datos de enlace en su tipo de datos original (un objeto TableData).

El objeto TableData, tal como vimos en el segundo artículo, brinda más estructura a los datos con los que trabajamos, a saber, un encabezado y una propiedad de filas que podemos usar para seleccionar datos desde la tabla. En este ejemplo, solo necesitamos obtener los símbolos bursátiles desde la primera columna de la tabla. Tal como puede recordar, la propiedad de filas almacena los datos en la tabla como una matriz de matrices, donde cada elemento de la primera matriz corresponde a una fila de la tabla.

Cuando trabajamos con un enlace a un objeto TableData, podemos especificar un subconjunto de las filas y columnas para obtener desde el enlace, usando los parámetros startRow y startColumn. Ambos parámetros especifican puntos de partida basados en cero para los datos que se extraerán de la tabla, donde la esquina superior izquierda de la tabla es el punto de origen. (Tenga en cuenta que debe usar los parámetros startRow y startColumn en conjunto o, de lo contrario, se generará una excepción). Como solo necesitamos la primera columna de datos de la tabla, también pasamos el parámetro columnCount definido en 1.

Una vez que tenemos esa columna de datos, insertamos cada valor en una matriz de una dimensión. En la figura 3, puede ver que llamamos a una función getStockQuotes que acepta la matriz de símbolos bursátiles como argumento. En la figura 4, usamos la función getStockQuotes para recuperar datos desde un servicio web de cotizaciones. (Para esta demostración, dejamos fuera el código para el servicio web). Una vez que analizamos los resultados provenientes del servicio web, llamamos al método removeHandler definido localmente.

Figura 4 Llamada al servicio web y eliminación del controlador de eventos BindingDataChanged

var StockTicker = (function () {
  // Other members of this "class"...
  // Call a Web service to get new stock quotes.
  function getStockQuotes(stockSymbols) {
    var stockValues = [];
    // Make a call to the Web service and parse the results.
    // The results are stored in the stockValues variable, which
    // contains an array of arrays that include the stock symbol
    // with the current value.
    removeHandler(function () {
      updateTable(stockValues);
    });
  }
  // Disables the BindingDataChanged event handler
  // while the table is being updated.
  function removeHandler(callback) {
    binding.removeHandlerAsync(
      Office.EventType.BindingDataChanged,
      { handler: onBindingDataChanged },
      function (results) {
        if (results.status == Office.AsyncResultStatus.Succeeded) {
           if (callback) { callback(); }
        }
    });
  }
  return {
    // Exposed members of the "class."
  };
})();

La función removeHandler llama al método binding.removeHandlerAsync, lo que elimina el controlador de eventos en el evento BindingDataChanged. Ahora bien, si hubiésemos dejado ese controlador adjunto al evento, el evento aparecería cuando se actualizara la tabla. De ese modo, se habría llamado nuevamente al controlador de eventos y se habría actualizado la tabla, provocando así un bucle infinito. Una vez que actualicemos la tabla con los datos nuevos, agregaremos el controlador de eventos nuevamente al evento.

(Por supuesto, también hubiésemos podido crear enlaces distintos a columnas independientes en la tabla, usando el tipo de coerción de matriz. Y luego podríamos haber enlazado a eventos solo en las columnas que los usuarios pudieran editar).

El método removeHandlerAsync toma un parámetro, handler, que especifica el nombre del controlador que se eliminará. Un procedimiento recomendado es usar el parámetro handler para eliminar controladores desde eventos de enlace.

En la figura 5, actualizaremos la tabla con los nuevos valores de acciones llamando a la función updateTable definida localmente.

Figura 5 Obtención de los datos desde el enlace de la tabla y llamada al servicio web

var StockTicker = (function () {
  // Other members of this "class"...
  // Update the TableData object referenced by the binding
  // and then update the data in the table on the worksheet.
  function updateTable(stockValues) {
    var stockData = new Office.TableData(),
        newValues = [];
    for (var i = 0; i < stockValues.length; i++) {
      var stockSymbol = stockValues[i],
          newValue = [stockSymbol[1]];
      newValues.push(newValue);
    }
    stockData.rows = newValues;
    binding.setDataAsync(
      stockData,
      {
        coercionType: Office.CoercionType.Table,
        startColumn: 3,
        startRow: 0
      },
      function (results) {
        if (results.status == Office.AsyncResultStatus.Succeeded) {
          addBindingsHandler();
        }
    });  
  }
  return {
    // Exposed members of the "class."
  };
})();

La función updateTable toma los datos pasados desde el servicio web y vuelve a escribirlos en la tabla enlazada. En este ejemplo, el parámetro stockValues contiene otra matriz de matrices, en la que cada elemento de la primera matriz es una matriz que contiene un símbolo bursátil y su precio actual. Para definir estos datos nuevamente en la tabla enlazada, creamos un objeto TableData nuevo e insertamos los datos del valor de la acción en él.

Debemos tener cuidado con que los datos que definimos en la propiedad TableData.rows coincidan con la forma de los datos que estamos insertando en el enlace. Si definimos a ciegas un objeto TableData nuevo en la tabla enlazada, corremos el riesgo de perder algunos de los datos en la tabla como, por ejemplo, las fórmulas. En la figura 5, agregamos los datos al objeto TableData como una sola columna de datos (una matriz de matrices, donde cada submatriz contiene un solo elemento). Cuando volvemos a insertar estos datos en la tabla enlazada, necesitamos insertar esta columna de datos actualizada en la columna correspondiente.

Aquí nuevamente usamos las propiedades startRow y startColumn. La función updateTable contiene una llamada a binding.setDataAsync que inserta los TableData nuevamente en la tabla de la hoja de cálculo y especifica los parámetros startColumn y startRow. El parámetro startColumn se define en 3, lo que significa que el objeto TableData insertado insertará sus datos a partir de la cuarta columna de la tabla. En la devolución de llamada para el método setDataAsync, llamamos nuevamente a la función addBindings­Handler para volver a aplicar el controlador de eventos al evento.

Cuando el método binding.setDataAsync se completa correctamente, los nuevos datos de tabla se insertan en la región enlazada y se muestran de inmediato. La experiencia es transparente desde la perspectiva del usuario. El usuario escribe datos en una celda de la tabla, presiona Intro y, a continuación, la columna Valor de la tabla se actualiza automáticamente.

Elementos XML personalizados

Una característica especialmente notable que es compatible con la API de JavaScript para Office es la capacidad de crear y manipular elementos de XML personalizados en Word. Con el fin de apreciar el profundo potencial de la API de JavaScript para Office para los elementos de XML personalizados, es útil contar con algo de información. Específicamente, debe comprender cómo el formato de archivo Office Open XML (OOXML u OpenXML), los elementos XML personalizados, los controles de contenido y la asignación de XML se pueden combinar para crear soluciones realmente poderosas, a saber, soluciones que implican la creación de documentos dinámicos de Word.

Formatos OOXML Office 2007 presentó el nuevo formato de archivo OOXML para documentos de Office, que ahora es el formato de archivo predeterminado para Office 2010 y Office 2013. (Puede distinguir cuáles son los documentos de Office con formato OOXML porque las extensiones de esos documentos ahora tienen cuatro letras, muchas de las cuales terminan en "x", por ejemplo, ".docx" para un documento de Word, ".xlsx" para una hoja de cálculo de Excel o ".pptx" para un documento de PowerPoint).

Los documentos de Office con formato OOXML son básicamente archivos .zip. Cada archivo .zip contiene una colección de archivos XML, llamados "partes" que, en conjunto, conforman el documento de Office. Si cambia el nombre de un documento de Office, como un documento .docx de Word, a .zip, examine los archivos que se encuentran en el interior; podrá ver que el documento es en realidad solo una colección de archivos XML independientes, organizados en carpetas, dentro de un paquete .zip, tal como se ve en la figura 6.

File Structure of an Office Open XML Format Document
Figura 6 Estructura de archivos de un documento con formato Office Open XML

Aspectos fundamentales de los elementos XML personalizados A pesar de que existen elementos XML estándar que las aplicaciones de Office siempre crean para cada documento nuevo de Office en formato OOXML (por ejemplo, hay un elemento XML integrado que describe las propiedades principales de un documento), lo interesante es que también puede agregar sus propios elementos "XML personalizados" a un documento de Word, a un libro de Excel o a una presentación de PowerPoint. Los elementos XML personalizados se agregan a la colección de archivos XML dentro del paquete .zip que conforma el documento de Office. Un elemento XML personalizado se almacena dentro de la estructura de archivos del documento, pero no se muestra al usuario final. Esto le permite insertar datos profesionales que acompañan a una instancia específica de un documento de Office oculto dentro de la estructura de archivos. De ese modo puede trabajar con ese XML personalizado en su aplicación y eso es exactamente lo que admite la API de JavaScript para Office.

Controles de contenido Además del formato OOXML y su estructura de archivos que permite la inclusión de XML personalizado en un documento, Word 2007 agregó controles de contenido, una característica que complementa de manera enriquecida los elementos XML personalizados.

Los controles de contenido son una manera de definir regiones fijas en un documento de Word que contienen ciertos tipos de datos, como texto sin formato, texto enriquecido, imágenes, fechas e incluso datos repetidos. El aspecto clave de los controles de contenido que complementa los elementos XML personalizados es el enlace de datos con asignación XML.

Asignación XML Un control de contenido se puede enlazar o "asignar" a un elemento en el XML en un elemento XML contenido en el documento. Por ejemplo, un negocio podría insertar datos profesionales desde un sistema back-end como un elemento XML personalizado en un documento de Word que contiene controles de contenido asignados al elemento XML personalizado. Los controles de contenido están enlazados a nodos específicos en el elemento XML personalizado por lo que, cuando el usuario final abre el documento, los controles de contenido asignados por XML se rellenan con datos desde el elemento XML personalizado. O bien, si invertimos el escenario, un negocio podría usar el mismo documento de Word con controles de contenido asignados pero hace que el usuario final ingrese datos en los controles de contenido. Cuando se guarda el documento, los datos en los controles de contenido asignados se vuelven a guardar en el archivo XML. Una aplicación podría entonces separar los datos del elemento XML personalizado en el documento guardado e insertarlos en un sistema back-end. La API de JavaScript para Office brinda una compatibilidad enriquecida para desarrollar aplicaciones exactamente como las descritas. 

Uso de la API de JavaScript para Office para trabajar con elementos XML personalizados La mejor manera de recorrer algunos de los elementos más significativos de la API de elementos XML personalizados en las aplicaciones para el modelo de objeto JavaScript para Office es con un ejemplo. En esta sección, usamos el ejemplo de "administrador de facturas" (bit.ly/YRdlwt) del área de Ejemplos de las aplicaciones para el portal para desarrolladores de Office y SharePoint para que pueda seguir. El ejemplo del administrador de facturas es un ejemplo de un escenario dinámico de documentos donde un negocio desea generar documentos que extraen datos desde un sistema back-end para generar facturas. En este caso, los datos son el nombre y la dirección de envío de un cliente y una lista asociada de las compras del cliente.  

El ejemplo incluye un documento de plantilla que se usa para crear facturas nuevas. El documento de plantilla tiene un diseño con el nombre del cliente, su dirección y una tabla con las compras del cliente. Las secciones de nombre de cliente, domicilio y compras en el documento son controles de contenido. Cada control de contenido se asigna a un nodo en el esquema que se creó para contener datos de factura del cliente, tal como se muestra en la figura 7.

Content Controls on the Document Surface Mapped to a Custom XML Part
Figura 7 Controles de contenido en la superficie del documento asignada a un elemento XML personalizado

La IU de la aplicación de ejemplo del administrador de facturas es sencilla, tal como se aprecia en la figura 8.

The UI for the Invoice Manager Sample App
Figura 8 La IU para la aplicación de ejemplo del administrador de facturas

El usuario final elige un número de factura desde el cuadro desplegable en la IU de la aplicación y los datos del cliente asociados con el número de factura aparecen en el cuerpo de la aplicación, tal como aparece en la figura 9.

The Invoice Manager UI Populated with Data from a Custom XML Part
Figura 9 La IU del administrador de facturas rellena con datos provenientes de un elemento XML personalizado

Cuando el usuario elige el botón Rellenar, la aplicación inserta los datos mostrados como un elemento XML personalizado en el documento. Como los controles de contenido se asignan a nodos en el elemento XML personalizado, tan pronto como el elemento XML personalizado se inserta en el documento, los controles de contenido muestran inmediatamente los datos para cada nodo de XML al cual están asignados. Puede reemplazar el elemento XML personalizado sobre la marcha (como hicimos aquí), pero siempre que el elemento conforme el esquema al cual se asignan los controles de contenido, los controles de contenido mostrarán los datos asignados. La figura 10 muestra el formulario Albarán del administrador de facturas cuando los controles de contenido se asignan a un elemento XML personalizado en el documento.

Content Controls Mapped to Nodes in a Custom XML Part Showing Bound Data
Figura 10 Controles de contenido asignados a nodos en un elemento XML personalizado que muestra datos enlazados

El objeto CustomXmlParts

CustomXmlParts.addAsync El primer paso para trabajar con elementos XML personalizados es aprender a agregarlos a un documento con la API de JavaScript para Office. La única manera de hacer esto es con el método customXml­Parts.addAsync. Como su nombre sugiera, el método customXmlParts.addAsync agrega de manera asincrónica un elemento XML personalizado y tiene la siguiente firma:

Office.context.document.customXmlParts.addAsync(xml [, options], callback);

Observe que el primer parámetro requerido para la función es una cadena de XML. Este ese el XML para el elemento XML personalizado. Tal como ya se mencionó, el administrador de facturas usa elementos XML personalizados que se asignan a los controles de contenido en la superficie del documento, pero primero debe obtener datos del cliente para insertarlos como XML personalizado. En el archivo InvoiceManager.js, que contiene la lógica para toda la aplicación, la aplicación simula obtener los datos del cliente desde un sistema back-end usando la función setupMyOrders definida por el usuario. Esta función crea una matriz de tres objetos que representan tres pedidos del cliente. Por supuesto, puede imaginar cualquier cantidad de maneras en que un negocio puede almacenar y obtener el historial de compras de un cliente (por ejemplo, una base de datos SQL), pero para mantener la simplicidad, la aplicación crea tres pedidos del cliente "integrados" dentro de la aplicación.

Una vez que se crean los objetos de los pedidos, los datos que representan se deben presentar en XML para que se puedan usar en la llamada a custom­XmlParts.addAsync. Es lo que sucede en la función initializeOrders, que también configura la IU de la aplicación e integra los controladores de eventos a los controles en la IU. Es importante observar en el código jQuery que inserta un controlador de eventos para que el evento Click del botón Rellenar, tal como aparece en la figura 11.

Figura 11 Inserción de un controlador de eventos para el evento Click del botón Rellenar

$("#populate").click(function () {
  var selectedOrderID = parseInt($("#orders option:selected").val());
  _document.customXmlParts.getByNamespaceAsync("", function (result) {
    if (result.value.length > 0) {
      for (var i = 0; i < result.value.length; i++) {
        result.value[i].deleteAsync(function () {
        });
      }
    }
  });
  var xml = $.json2xml(findOrder(myOrders, selectedOrderID));
  _document.customXmlParts.addAsync(xml, function (result) { });
});

Básicamente, la función anónima que sirve como el controlador de eventos para el evento Click del botón Rellenar convierte un objeto de pedido (que se creó con la función setupMyOrders) en una cadena de XML y luego llama al método customXmlParts.addAsync y pasa la cadena de XML que contiene la información de pedido como el primer parámetro requerido.

El otro parámetro para customXml­Parts.addAsync es una devolución de llamada. Por supuesto, puede ser una referencia a un método que se haya definido en otra parte del código o puede ser una función anónima. El ejemplo del administrador de facturas usa una función anónima en línea:

_document.customXmlParts.addAsync(xml,
  function (result) { });

Al igual que para todas las devoluciones de llamada en la API de Java­Script para Office, un objeto AsyncResult se pasa como el único argumento para la devolución de llamada. Para customXmlParts.addAsync y todas las funciones customXmlParts, puede usar el objeto AsyncResult para:

  • obtener una referencia al elemento XML personalizado creado recientemente con la propiedad Async­Result.value
  • obtener el resultado de la solicitud con la propiedad AsyncResult.status
  • obtener información acerca de un error (si se produjo) con la propiedad Async­Result.error
  • obtener sus propios datos de estado (si incluyó alguno en la llamada a custom­XmlParts.addAsync) con la propiedad AsyncResult.asyncContext

Para el último elemento, recuerde que el parámetro other en el método customXmlParts.addAsync fue un objeto options opcional:

Office.context.document.customXmlParts.addAsync(
  xml [, options], callback);

El objeto options se brinda como una manera de pasar su propio objeto definido por el usuario en la llamada para la devolución de llamada.

Tal como puede ver en el ejemplo del administrador de facturas, la función anónima en la llamada a customXmlParts.addAsync no hace nada, pero en un entorno de producción probablemente querría realizar una comprobación de errores para controlar una instancia de manera limpia si, por alguna razón, el elemento XML personalizado no se agrega correctamente.

CustomXmlParts.getByNamespaceAsync Otro elemento clave de la API de JavaScript para Office para trabajar con elementos XML personalizados que se ha demostrado en el ejemplo del administrador de facturas es el uso del método customXmlParts.getByNamespaceAsync, que puede ver en el código del controlador de eventos del evento Click para el botón Rellenar. La firma para customXmlParts.getByNamespaceAsync es:

Office.context.document.customXmlParts.getByNamespaceAsync(
  ns [, options], callback);

El primer parámetro requerido, ns, es una cadena que especifica el espacio de nombres de los elementos XML personalizados que desea obtener. Por lo tanto, customXmlParts.getByNamespaceAsync devuelve una matriz de elementos XML personalizados en el documento con el espacio de nombres que especificó. Como los elementos XML personalizados creados en el ejemplo del administrador de facturas no usan espacios de nombres, la llamada a customXmlParts.getByNamespaceAsync pasa una cadena vacía como el argumento para el parámetro del espacio de nombres, tal como se muestra en la figura 12.

Figura 12 Uso del método CustomXmlParts.getByNamespaceAsync

$("#populate").click(function () {
  var selectedOrderID = parseInt($("#orders option:selected").val());
  _document.customXmlParts.getByNamespaceAsync("", function (result) {
    if (result.value.length > 0) {
      for (var i = 0; i < result.value.length; i++) {
        result.value[i].deleteAsync(function () {
        });
      }
                    }
     });
     var xml = $.json2xml(findOrder(myOrders, selectedOrderID));
     _document.customXmlParts.addAsync(xml, function (result) { });
   });
   var selOrder = $("#orders option:selected");
   popOrder(selOrder.val());

Al igual que las funciones asincrónicas en la API, customXmlParts.getByNamespaceAsync tiene parámetros opcionales (option) y callback (devolución de llamada).

CustomXmlParts.getByIdAsync La última manera programática para obtener un elemento XML personalizado en un documento es customXmlParts.getByIdAsync. La firma es:

Office.context.document.customXmlParts.getByIdAsync(id [, options], callback);

Esta función obtiene un elemento XML personalizado con el GUID del elemento. Encontrará el GUID para el elemento XML personalizado en el archivo itemPropsn.xml dentro del paquete de documentos. También puede obtener el GUID si usa la propiedad id de customXmlPart. Un aspecto clave que se debe observar aquí es que la cadena del GUID debe contener llaves ("{}") alrededor del GUID.

El ejemplo del administrador de facturas no usa la función customXml­Parts.getByIdAsync, pero el siguiente código lo demuestra claramente:

function showXMLPartBuiltId() {
  Office.context.document.customXmlParts.getByIdAsync(
    "{3BC85265-09D6-4205-B665-8EB239A8B9A1}", function (result) {
    var xmlPart = result.value;
    write(xmlPart.id);
  });
}
// Function that writes to a div with id='message' on the page.
function write(message){
  document.getElementById('message').innerText += message;
}

Además del parámetro de identificación, como customXmlParts.addAsync y customXmlParts.getByNamespaceAsync, el método customXml­Parts.getByIdAsync también tiene el parámetro opcional options y el parámetro requerido callback y se usan igual que las demás funciones.

El objeto CustomXmlPart El objeto customXmlPart representa un elemento XML personalizado único. Una vez que obtiene una referencia a un customXmlPart con los métodos del objeto customXmlParts, tendrá disponibles varias propiedades, tal como puede observar en la figura 13.

Figure 13 Propiedades de CustomXmlPart

Name Descripción
builtIn Obtiene un valor que indica si el customXmlPart está integrado.
id Obtiene el GUID del customXmlPart.
namespaceManager Obtiene el conjunto de asignaciones de prefijo de espacio de nombres (customXmlPrefixMappings) que se usa contra el customXmlPart actual.

CustomXmlPart también tiene eventos asociados, los que se muestran en la figura 14.

Figure 14 Eventos de CustomXmlPart

Name Descripción
nodeDeleted Se produce cuando se elimina un nodo.
nodeInserted Se produce cuando se inserta un nodo.
nodeReplaced Se produce cuando se reemplaza un nodo.

Pero en lo que respecta a este artículo, deseamos centrarnos en algunos métodos clave del objeto customXmlPart que los desarrolladores usarán a menudo. Se muestran en la figura 15.

Figura 15 Métodos de CustomXmlPart

Name Descripción
addHandlerAsync Agrega un controlador de eventos de manera asincrónica para un evento de objeto customXmlPart.
deleteAsync Elimina de manera asincrónica este elemento XML personalizado de la colección.
getNodesAsync Obtiene de manera asincrónica cualquier customXmlNodes de este elemento XML personalizado que coincida con el XPath especificado.
getXmlAsync Obtiene de manera asincrónica el XML dentro de este elemento XML personalizado.

CustomXMLPart.addHandlerAsync El método customXmlPart.add­HandlerAsync es clave para enlazar controladores de eventos que respondan a los cambios en el elemento XML personalizado. La firma del método customXmlPart.addHanderAsync es la siguiente:

customXmlPart.addHandlerAsync(eventType, handler [, options], callback);

Observe que el primer parámetro requerido es una enumeración de Office.EventType, que especifica el tipo de evento en las aplicaciones para el modelo de objeto de Office que desea controlar. El siguiente parámetro requerido es el controlador para el evento. Aquí lo importante es que, cuando se invoca el controlador, la API de JavaScript para Office pasará un parámetro de argumentos de evento específico para el tipo de evento que se controla (NodeDeletedEventArgs, NodeInsertedEventArgs o NodeReplacedEventArgs). De este modo, al igual que en todas las funciones asincrónicas en la API, tendrá, de manera opcional, parámetros de opciones y de devolución de llamada.

Considere el escenario en que un documento se usa como un formulario para entrada de datos. El usuario ingresa datos en el formulario y luego el formulario se examina para obtener los datos. El formulario contiene un control de contenido Repeating Section para que cada vez que el usuario ingrese un elemento repetido, se agregue un nodo nuevo al elemento XML personalizado subyacente. Cada vez que se agrega o se inserta un nodo, se activa el evento NodeInserted y se puede reaccionar ante él (y ante todos los eventos customXmlPart) con customXmlPart.addHandlerAsync.

La figura 16 muestra cómo se podría responder al evento NodeInserted.

Figura 16 Inserción de un controlador de eventos para el evento CustomXmlPart.NodeInserted

function addNodeInsertedEvent() {
  Office.context.document.customXmlParts.getByIdAsync(
    "{3BC85265-09D6-4205-B665-8EB239A8B9A1}", function (result) {
    var xmlPart = result.value;
    xmlPart.addHandlerAsync(Office.EventType.NodeInserted,
      function (eventArgs) {
        write("A node has been inserted.");
    });
  });
}
// Function that writes to a div with id='message' on the page.
function write(message){
  document.getElementById('message').innerText += message;
}

CustomXMLPart.deleteAsync Por supuesto, junto con saber cómo agregar un elemento XML personalizado, también es importante saber cómo eliminar uno. El método customXmlPart.deleteAsync proporciona dicha funcionalidad. CustomXmlPart.deleteAsync es una función asincrónica con la siguiente firma:

customXmlPart.deleteAsync([options ,] callback);

Volvamos al ejemplo del administrador de facturas: puede ver una demostración de customXMLPart.deleteAsync: 

$("#populate").click(function () {
  var selectedOrderID = parseInt($("#orders option:selected").val());
  _document.customXmlParts.getByNamespaceAsync("", function (result) {
    if (result.value.length > 0) {
      for (var i = 0; i < result.value.length; i++) {
        result.value[i].deleteAsync(function () {
        });
      }
    }
});

Dentro del controlador de eventos de Click para el botón Rellenar, la lógica del programa revise si existe algún elemento XML personalizado con espacios de nombres "en blanco". Si es así, elimina cada uno de ellos con el método custom­XmlPart.deleteAsync.

Hay mucho más en lo que se refiere al trabajo con elementos XML personalizados, pero lo que hemos visto en este artículo debería ser suficiente para darle una visión de la compatibilidad enriquecida que la API de JavaScript para Office brinda para los elementos XML personalizados.

Siguiente paso: Aplicaciones de correo

En este tercer artículo de la serie, revisamos algunas técnicas avanzadas para trabajar con datos en aplicaciones para Office. Le mostramos cómo agregar inteligencia adicional a una tabla en Excel a través del uso de enlaces de datos. También exploramos cómo aprovechar los elementos XML personalizados en una aplicación para Word a fin de facilitar la creación automatizada de documentos.

En el próximo artículo y final de esta serie, examinaremos cómo la API de JavaScript para Office se aplica a las aplicaciones de correo. Las aplicaciones de correo representan un conjunto de capacidades único dentro de la API de JavaScript para Office, que permite que los desarrolladores de aplicaciones y administradores de Exchange construyan poderosas herramientas para trabajar con los elementos del correo electrónico.

Stephen Oliver es escritor de programación de la División Office y un desarrollador profesional certificado de Microsoft (SharePoint 2010). Escribe la documentación para desarrolladores para los servicios Excel y los servicios de automatización de Word, junto con la documentación para desarrolladores de servicios de automatización de PowerPoint. Ayudó a curar y diseñar el sitio de Excel Mashup site en ExcelMashup.com.

Eric Schmidt es escritor de programación de la División Office. Ha creado varios ejemplos de código para aplicaciones de Office, incluido el popular ejemplo de Persistencia de la configuración personalizada. Además, ha escrito artículo y creado vídeos acerca de otros productos y tecnologías dentro de la capacidad de programación de Office.

Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Mark Brewster (Microsoft), Shilpa Kothari (Microsoft) y Juan Balmori Labra (Microsoft)
Mark Brewster se licenció en Matemáticas y Ciencias informáticas en la Universidad de Arizona en 2008 y trabaja como desarrollador de software en Microsoft desde hace cuatro años. Anda en bicicleta por diversión y por dinero, le gusta beber cerveza y escuchar música envasada.

Shilpa Kothari (Bhavsar) es ingeniera en software en prueba en Microsoft. Ha trabajado en varios productos de Microsoft, como Bing Mobile, Visual Studio y Office. Le apasiona el aseguramiento de la calidad en el software y la experiencia de usuario y se la puede ubicar en shilpak@microsoft.com.

Juan Balmori Labra es Director de programa y durante los últimos tres años trabaja en la API de JavaScript para Microsoft Office. Anteriormente trabajó en la versión de Office 2010, donde lanzó los Servicios de conectividad empresarial y Duet. Antes de realizar su sueño de trasladarse a Redmond, Juan trabajó en Microsoft México como Arquitecto principal para la Práctica de consulta para el sector público.