Cómo crear un origen de datos personalizado (HTML)

[ Este artículo está destinado a desarrolladores de Windows 8.x y Windows Phone 8.x que escriben aplicaciones de Windows en tiempo de ejecución. Si estás desarrollando para Windows 10, consulta la documentación más reciente

La biblioteca de Windows para JavaScript proporciona varios objetos de origen de datos listos para usar que puedes utilizar para rellenar una ListView o una FlipView con diferentes tipos de datos. Está el objeto WinJS.Binding.List para acceder a matrices y datos JSON, y está el StorageDataSource para acceder a información acerca del sistema de archivos.

Pero no estás limitado solo a estos orígenes de datos. Puedes crear tu propio origen de datos personalizado que acceda a cualquier otro tipo de datos, como un archivo XML o un servicio web. En este tema se muestra cómo implementar un origen de datos personalizado que accede a un servicio web. Usa XHR para conectarse al servicio de búsqueda de imágenes Bing y muestra el resultado en una ListView.

(Dado que el servicio Bing requiere que cada aplicación tenga su propia clave de id. de aplicación, debes obtener una clave antes de poder usar este código. Para obtener más información sobre cómo obtener una clave de id. de aplicación, consulta el Centro para desarrolladores de Bing).

Para crear un origen de datos personalizado, necesitas objetos que implementen las interfaces de IListDataAdapter y IListDataSource. WinJS proporciona un objeto VirtualizedDataSource que implementa IListDataSource; todo lo que debes hacer es heredar de él y pasarle al constructor base un IListDataAdapter. Debes crear tu propio objeto que implemente la interfaz de IListDataAdapter.

IListDataAdapter interactúa directamente con el origen de datos para recuperar o actualizar elementos. IListDataSource se conecta a un control y manipula IListDataAdapter.

Requisitos previos

Instrucciones

Paso 1: Crear un archivo JavaScript para el origen de datos personalizado

  1. Usa Microsoft Visual Studio para agregar un archivo JavaScript a tu proyecto. En el Explorador de soluciones, haz clic con el botón secundario en la carpeta js del proyecto y selecciona Agregar > Nuevo elemento. Se abre el cuadro de diálogo Agregar nuevo elemento.
  2. Selecciona Archivo JavaScript. Asígnale el nombre "bingImageSearchDataSource.js". Haz clic en Agregar para crear el archivo. Visual Studio crea un archivo JavaScript en blanco denominado bingImageSearchDataSource.js.

Paso 2: Crear un IListDataAdapter

El siguiente paso es crear un objeto que implemente la interfaz de IListDataAdapter. IListDataAdapter recupera datos de un origen de datos y se los proporciona a IListDataSource.

La interfaz de IListDataAdapter admite acceso de lectura y escritura, así como notificaciones de cambios. Sin embargo, no es necesario que implementes toda la interfaz: puedes crear un simple IListDataAdapter de solo lectura al implementar solo los métodos itemsFromIndex y getCount.

  1. Abre bingImageSearchDataSource.js, el archivo JavaScript creado en el paso anterior.

  2. Crea una función anónima y activa el modo strict.

    Tal y como se describe en el tema sobre codificación de aplicaciones básicas, es recomendable encapsular el código JavaScript en una función anónima, así como usar el modo strict.

    (function () {
        "use strict"; 
    
  3. Usa la función WinJS.Class.define para crear la implementación de IListDataAdapter. El primer parámetro que toma la función WinJS.Class.define es el constructor de clase.

    Este IListDataAdapter se conectará al servicio de búsqueda de bing. La consulta de búsqueda de la API de Bing espera ciertos datos. Almacenaremos estos datos, junto con otros datos adicionales en el IListDataAdapter como miembros de clase:

    • _minPageSize: el número mínimo de elementos por página.
    • _maxPageSize: el número máximo de elementos por página.
    • _maxCount: el número máximo de elementos a devolver.
    • _devKey: el identificador de la aplicación La API de Bing requiere que una clave AppID identifique la aplicación.
    • _query: la cadena de búsqueda.

    Crea un constructor que tome un AppID para la API de Bing y una consulta de búsqueda y proporcione valores para los otros miembros.

    
        // Definition of the data adapter
        var bingImageSearchDataAdapter = WinJS.Class.define(
            function (devkey, query) {
    
                // Constructor
                this._minPageSize = 10;  // based on the default of 10
                this._maxPageSize = 50;  // max request size for bing images
                this._maxCount = 1000;   // limit on the bing API
                this._devkey = devkey;
                this._query = query;
            },
    
  4. El parámetro de texto que espera la función WinJS.Class.define es un objeto que contiene los miembros de instancia de la clase. Este objeto se usa para implementar los métodos itemsFromIndex y getCount.

    Crea la llave de apertura para este objeto.

            // IListDataDapter methods
            // These methods define the contract between the IListDataSource and the IListDataAdapter.
            {
    
    1. Implementa el método itemsFromIndex. El método itemsFromIndex se conecta al origen de datos y devuelve los datos solicitados como un IFetchResult. El método itemsFromIndex toma tres parámetros: el índice de un elemento para recuperar, el número de elementos antes de ese elemento para recuperar y el número de elementos después de ese elemento para recuperar.

                  itemsFromIndex: function (requestIndex, countBefore, countAfter) {
                      var that = this;
      
    2. Comprueba que el elemento solicitado (requestIndex) sea menor que el número máximo de elementos para recuperar. De lo contrario, devuelve un error.

                      if (requestIndex >= that._maxCount) {
                          return Promise.wrapError(new WinJS.ErrorFromName(UI.FetchError.doesNotExist));
                      }
      
    3. Usa requestIndex, countBefore y countAfter para calcular el índice del primer elemento y el tamaño de la solicitud. Los parámetros countBefore y countAfter son recomendaciones acerca de la cantidad de datos que hay que recuperar: no es necesario que recuperes todos los elementos solicitados. En este ejemplo, Bing tiene un tamaño de solicitud máximo de 50 elementos, así que queremos limitar nuestro tamaño de solicitud a esa cifra.

      Por lo general, una solicitud pedirá uno o dos elementos antes o después del elemento solicitado, y un número mayor del lado opuesto, así que queremos tener eso en cuenta al averiguar qué le pedimos al servidor.

                      var fetchSize, fetchIndex;
      
                      // See which side of the requestIndex is the overlap.
                      if (countBefore > countAfter) {
                          // Limit the overlap
                          countAfter = Math.min(countAfter, 10);
      
                          // Bound the request size based on the minimum and maximum sizes.
                          var fetchBefore = Math.max(
                              Math.min(countBefore, that._maxPageSize - (countAfter + 1)),
                              that._minPageSize - (countAfter + 1)
                              );
                          fetchSize = fetchBefore + countAfter + 1;
                          fetchIndex = requestIndex - fetchBefore;
                      } else {
                          countBefore = Math.min(countBefore, 10);
                          var fetchAfter = Math.max(Math.min(countAfter, that._maxPageSize - (countBefore + 1)), that._minPageSize - (countBefore + 1));
                          fetchSize = countBefore + fetchAfter + 1;
                          fetchIndex = requestIndex - countBefore;
                      }
      
    4. Crea la cadena de solicitud.

                      // Create the request string. 
                      var requestStr = "http://api.bing.net/json.aspx?"
                      + "AppId=" + that._devkey
                      + "&Query=" + that._query
                      + "&Sources=Image"
                      + "&Version=2.0"
                      + "&Market=en-us"
                      + "&Adult=Strict"
                      + "&Filters=Aspect:Wide"
                      + "&Image.Count=" + fetchSize
                      + "&Image.Offset=" + fetchIndex
                      + "&JsonType=raw";
      
    5. Usa WinJS.xhr para enviar la solicitud. WinJS.xhr devuelve un objeto Promise que contiene el resultado. Puedes procesar el resultado al llamar al método then del objeto Promise.

                      return WinJS.xhr({ url: requestStr }).then(
      
    6. Crea una devolución de llamada para una operación WinJS.xhr correcta. Esta función procesa los resultados y los devuelve como un elemento IFetchResult. El IFetchResult contiene tres propiedades:

      • items: una matriz de objetos IItem que representan los resultados de la consulta.
      • offset: el índice del elemento de solicitud en la matriz de elementos.
      • totalCount: el número total de elementos en la matriz de elementos.

      Cada IItem debe tener una propiedad key que contenga un identificador para cada elemento y una propiedad data que contenga los datos del elemento:

      { clave: key1, datos : { field1: value, field2: value, ... }}

      Así se vería la matriz de los objetos IItem:

      [{ key: key1, data : { field1: value, field2: value, ... }}, { key: key2, data : {...}}, ...];

                          function (request) {
                              var results = [], count;
      
                              // Use the JSON parser on the results (it's safer than using eval).
                              var obj = JSON.parse(request.responseText);
      
                              // Verify that the service returned images.
                              if (obj.SearchResponse.Image !== undefined) {
                                  var items = obj.SearchResponse.Image.Results;
      
                                  // Create an array of IItem objects:
                                  // results =[{ key: key1, data : { field1: value, field2: value, ... }}, { key: key2, data : {...}}, ...];
                                  for (var i = 0, itemsLength = items.length; i < itemsLength; i++) {
                                      var dataItem = items[i];
                                      results.push({
                                          key: (fetchIndex + i).toString(),
                                          data: {
                                              title: dataItem.Title,
                                              thumbnail: dataItem.Thumbnail.Url,
                                              width: dataItem.Width,
                                              height: dataItem.Height,
                                              linkurl: dataItem.Url
                                          }
                                      });
                                  }
      
                                  // Get the count.
                                  count = obj.SearchResponse.Image.Total;
      
                                  return {
                                      items: results, // The array of items.
                                      offset: requestIndex - fetchIndex, // The index of the requested item in the items array.
                                      totalCount: Math.min(count, that._maxCount), // The total number of records. Bing will only return 1000, so we cap the value.
                                  };
                              } else {
                                  return WinJS.UI.FetchError.doesNotExist;
                              }
                          },
      
    7. Crea una devolución de llamada para una operación de WinJS.xhr incorrecta.

                          // Called if the WinJS.xhr funtion returned an error. 
                          function (request) {
                              return WinJS.UI.FetchError.noResponse;
                          });
      
    8. Cierra el método itemsFromIndex. Definirás otro método a continuación, así que agrega una coma después de cerrar itemsFromIndex.

                  },
      
  5. Implementa el método getCount.

    1. El método getCount no adopta ningún parámetro y devuelve Promise para el número de elementos en los resultados del objeto IListDataAdapter.

                  // Gets the number of items in the result list. 
                  // The count can be updated in itemsFromIndex.
                  getCount: function () {
                      var that = this;
      
    2. Crea la cadena de solicitud. Dado que Bing no tiene una forma explícita de pedir el recuento, solicitamos un registro y lo utilizamos para obtener la cuenta.

      
                      // Create up a request for 1 item so we can get the count
                      var requestStr = "http://api.bing.net/json.aspx?";
      
                      // Common request fields (required)
                      requestStr += "AppId=" + that._devkey
                      + "&Query=" + that._query
                      + "&Sources=Image";
      
                      // Common request fields (optional)
                      requestStr += "&Version=2.0"
                      + "&Market=en-us"
                      + "&Adult=Strict"
                      + "&Filters=Aspect:Wide";
      
                      // Image-specific request fields (optional)
                      requestStr += "&Image.Count=1"
                      + "&Image.Offset=0"
                      + "&JsonType=raw";
      
    3. Usa WinJS.xhr para enviar la solicitud. Procesa los resultados y devuelve el recuento.

                      // Make an XMLHttpRequest to the server and use it to get the count.
                      return WinJS.xhr({ url: requestStr }).then(
      
                          // The callback for a successful operation.
                          function (request) {
                              var data = JSON.parse(request.responseText);
      
                              // Bing may return a large count of items, 
                              /// but you can only fetch the first 1000.
                              return Math.min(data.SearchResponse.Image.Total, that._maxCount);
                          },
                          function (request) {
                              return WinJS.Promise.wrapError(new WinJS.ErrorFromName(WinJS.UI.FetchError.doesNotExist));
                          });
                  }
      
  6. Ese es el último miembro de la instancia, así que cierra el objeto que creaste para contenerlos. Hay otros métodos IListDataAdapter que puedes implementar, pero no los necesitas para crear un origen de datos de solo lectura.

                // setNotificationHandler: not implemented
                // itemsFromStart: not implemented
                // itemsFromEnd: not implemented
                // itemsFromKey: not implemented
                // itemsFromDescription: not implemented
            }
    
  7. Cierra la llamada a WinJS.Class.define.

            );
    

    Creaste una clase denominada bingImageSarchDataAdapter que implementa la interfaz de IListDataAdapter. A continuación, crearás un IListDataSource.

Paso 3: Crear un IListDataSource

IListDataSource conecta un control (como un ListView) a un IListDataAdapter. IListDataSource manipula IListDataAdapter, el cual hace el trabajo de manipular y recuperar datos. En este paso, implementas un IListDataSource.

WinJS te proporciona una implementación de la interfaz de IListDataSource: el objeto VirtualizedDataSource. Puedes usar este objeto para implementar tu IListDataSource. Como verás en un momento, no hay mucho trabajo que hacer.

  1. Usa la función WinJS.Class.derive para crear una clase que herede de VirtualizedDataSource. Para el segundo parámetro de la función, define un constructor que tome un identificador de aplicación de Bing y una cadena de consulta. Haz que el constructor llame al constructor de clase base y le pase un nuevo bingImageSarchDataAdapter (el objeto definido en el paso anterior).

        var bingImageSearchDataSource = WinJS.Class.derive(WinJS.UI.VirtualizedDataSource, function (devkey, query) {
            this._baseDataSourceConstructor(new bingImageSearchDataAdapter(devkey, query));
        });
    
  2. Usa la función WinJS.Namespace.define para definir un espacio de nombres y haz que la clase sea accesible públicamente. La función WinJS.Namespace.define toma dos parámetros: el nombre del espacio de nombres para crear y un objeto que contiene uno o más pares de propiedad/valor. Cada propiedad es el nombre público del miembro, y cada valor es la variable, propiedad o función subyacente en el código privado que deseas exponer.

        WinJS.Namespace.define("DataExamples", { bingImageSearchDataSource: bingImageSearchDataSource });  
    
  3. Acabas de implementar un IListDataAdapter y un IListDataSource. Has finalizado con bingImageSearchDataSource.js, de modo que puedes cerrar la función anónima exterior.

    })();
    

    Para usar el origen de datos personalizado, crea una nueva instancia de la clase bingImageSearchDataSource. Pasa al constructor el identificador de aplicación de Bing y una cadena de consulta:

    var myDataSrc = new DataExamples.bingImageSearchDataSource(devKey, searchTerm);
    

    Ahora puedes usar bingImageSearchDataSource con controles que toman un IListDataSource, como el control ListView.

Ejemplo completo

Este es el código completo para bingImageSearchDataSource.js. Para obtener la muestra completa, consulta la muestra Trabajo con orígenes de datos.

// Bing image search data source example
//
// This code implements a datasource that will fetch images from Bing's image search feature
// Because the Bing service requires a developer key, and each app needs its own key, you must
// register as a developer and obtain an App ID to use as a key. 
// For more info about how to obtain a key and use the Bing API, see
// https://bing.com/developers and https://msdn.microsoft.com/en-us/library/dd251056.aspx


(function () {

    // Define the IListDataAdapter.
    var bingImageSearchDataAdapter = WinJS.Class.define(
        function (devkey, query) {

            // Constructor
            this._minPageSize = 10;  // based on the default of 10
            this._maxPageSize = 50;  // max request size for bing images
            this._maxCount = 1000;   // limit on the bing API
            this._devkey = devkey;
            this._query = query;
        },

        // IListDataDapter methods
        // These methods define the contract between the IListDataSource and the IListDataAdapter.
        // These methods will be called by vIListDataSource to fetch items, 
        // get the number of items, and so on.
        {
            // This example only implements the itemsFromIndex and count methods

            // The itemsFromIndex method is called by the IListDataSource 
            // to retrieve items. 
            // It will request a specific item and hints for a number of items before and after the
            // requested item. 
            // The implementation should return the requested item. You can choose how many
            // additional items to send back. It can be more or less than those requested.
            //
            //   This funtion must return an object that implements IFetchResult. 
            itemsFromIndex: function (requestIndex, countBefore, countAfter) {
                var that = this;
                if (requestIndex >= that._maxCount) {
                    return Promise.wrapError(new WinJS.ErrorFromName(UI.FetchError.doesNotExist));
                }

                var fetchSize, fetchIndex;

                // See which side of the requestIndex is the overlap.
                if (countBefore > countAfter) {
                    // Limit the overlap
                    countAfter = Math.min(countAfter, 10);

                    // Bound the request size based on the minimum and maximum sizes.
                    var fetchBefore = Math.max(
                        Math.min(countBefore, that._maxPageSize - (countAfter + 1)),
                        that._minPageSize - (countAfter + 1)
                        );
                    fetchSize = fetchBefore + countAfter + 1;
                    fetchIndex = requestIndex - fetchBefore;
                } else {
                    countBefore = Math.min(countBefore, 10);
                    var fetchAfter = Math.max(Math.min(countAfter, that._maxPageSize - (countBefore + 1)), that._minPageSize - (countBefore + 1));
                    fetchSize = countBefore + fetchAfter + 1;
                    fetchIndex = requestIndex - countBefore;
                }

                // Create the request string. 
                var requestStr = "http://api.bing.net/json.aspx?"
                + "AppId=" + that._devkey
                + "&Query=" + that._query
                + "&Sources=Image"
                + "&Version=2.0"
                + "&Market=en-us"
                + "&Adult=Strict"
                + "&Filters=Aspect:Wide"
                + "&Image.Count=" + fetchSize
                + "&Image.Offset=" + fetchIndex
                + "&JsonType=raw";

                // Return the promise from making an XMLHttpRequest to the server.
                return WinJS.xhr({ url: requestStr }).then(

                    // The callback for a successful operation. 
                    function (request) {
                        var results = [], count;

                        // Use the JSON parser on the results (it's safer than using eval).
                        var obj = JSON.parse(request.responseText);

                        // Verify that the service returned images.
                        if (obj.SearchResponse.Image !== undefined) {
                            var items = obj.SearchResponse.Image.Results;

                            // Create an array of IItem objects:
                            // results =[{ key: key1, data : { field1: value, field2: value, ... }}, { key: key2, data : {...}}, ...];
                            for (var i = 0, itemsLength = items.length; i < itemsLength; i++) {
                                var dataItem = items[i];
                                results.push({
                                    key: (fetchIndex + i).toString(),
                                    data: {
                                        title: dataItem.Title,
                                        thumbnail: dataItem.Thumbnail.Url,
                                        width: dataItem.Width,
                                        height: dataItem.Height,
                                        linkurl: dataItem.Url
                                    }
                                });
                            }

                            // Get the count.
                            count = obj.SearchResponse.Image.Total;

                            return {
                                items: results, // The array of items.
                                offset: requestIndex - fetchIndex, // The index of the requested item in the items array.
                                totalCount: Math.min(count, that._maxCount), // The total number of records. Bing will only return 1000, so we cap the value.
                            };
                        } else {
                            return WinJS.UI.FetchError.doesNotExist;
                        }
                    },

                    // Called if the WinJS.xhr funtion returned an error. 
                    function (request) {
                        return WinJS.UI.FetchError.noResponse;
                    });
            },


            // Gets the number of items in the result list. 
            // The count can be updated in itemsFromIndex.
            getCount: function () {
                var that = this;

                // Create up a request for 1 item so we can get the count
                var requestStr = "http://api.bing.net/json.aspx?";

                // Common request fields (required)
                requestStr += "AppId=" + that._devkey
                + "&Query=" + that._query
                + "&Sources=Image";

                // Common request fields (optional)
                requestStr += "&Version=2.0"
                + "&Market=en-us"
                + "&Adult=Strict"
                + "&Filters=Aspect:Wide";

                // Image-specific request fields (optional)
                requestStr += "&Image.Count=1"
                + "&Image.Offset=0"
                + "&JsonType=raw";

                // Make an XMLHttpRequest to the server and use it to get the count.
                return WinJS.xhr({ url: requestStr }).then(

                    // The callback for a successful operation.
                    function (request) {
                        var data = JSON.parse(request.responseText);

                        // Bing may return a large count of items, 
                        /// but you can only fetch the first 1000.
                        return Math.min(data.SearchResponse.Image.Total, that._maxCount);
                    },
                    function (request) {
                        return WinJS.Promise.wrapError(new WinJS.ErrorFromName(WinJS.UI.FetchError.doesNotExist));
                    });
            }

  

            // setNotificationHandler: not implemented
            // itemsFromStart: not implemented
            // itemsFromEnd: not implemented
            // itemsFromKey: not implemented
            // itemsFromDescription: not implemented
        }
        );

    var bingImageSearchDataSource = WinJS.Class.derive(WinJS.UI.VirtualizedDataSource, function (devkey, query) {
        this._baseDataSourceConstructor(new bingImageSearchDataAdapter(devkey, query));
    });

    WinJS.Namespace.define("DataExamples", { bingImageSearchDataSource: bingImageSearchDataSource }); 

})();