Como criar uma fonte de dados personalizada (HTML)

[ Este artigo destina-se aos desenvolvedores do Windows 8.x e do Windows Phone 8.x que escrevem aplicativos do Windows Runtime. Se você estiver desenvolvendo para o Windows 10, consulte documentação mais recente]

A Biblioteca do Windows para JavaScript fornece vários objetos de fontes de dados prontos para uso que podem ser usados para preencher uma ListView ou FlipView com diferentes tipos de dados. Há o objeto WinJS.Binding.List para acessar dados de JSON e matrizes, há a StorageDataSource para acessar informações sobre o sistema de arquivos.

Você não está limitado apenas a essas fontes de dados. É possível criar sua própria fonte de dados para acessar quaisquer dados, como um arquivo XML ou serviço da web. Este tópico mostra como implementar uma fonte de dados personalizados que acessa um serviço da web. Ele usa XHR para conectar o serviço de pesquisa de imagens Bing e exibe o resultado em ListView.

(Como o serviço Bing requer que cada aplicativo tenha sua própria chave ID de aplicativo, você precisa obter uma chave antes de usar esse código. Para obter mais informações sobre a obtenção de uma chave ID de aplicativo, consulte o centro de desenvolvimento do bing.)

Para criar uma fonte de dados personalizados, você precisa de objetos que implementem as interfaces IListDataAdapter e IListDataSource. A WinJS fornece um objeto VirtualizedDataSource que implementa IListDataSource—- tudo o que você precisa fazer é herdar do objeto e passar um IListDataAdapter ao construtor base. Você precisa criar seu próprio objeto que implementa a interface IListDataAdapter.

A interface IListDataAdapter interage diretamente com a fonte de dados para recuperar ou atualizar os itens. A IListDataSource conecta um controle e manipula o IListDataAdapter.

Pré-requisitos

Instruções

Etapa 1: Criar um arquivo JavaScript para a fonte de dados personalizados

  1. Use o Microsoft Visual Studio para adicionar um arquivo JavaScript ao seu projeto. No Gerenciador de Soluções, clique com o botão direito do mouse na pasta js do seu projeto e selecione Adicionar > Novo Item. A caixa de diálogo Adicionar Novo Item aparece.
  2. Escolha Arquivo JavaScript. Dê a ele o nome "bingImageSearchDataSource.js". Clique em Adicionar para criar o arquivo. O Visual Studio cria um arquivo JavaScript em branco chamado bingImageSearchDataSource.js.

Etapa 2: Criar um IListDataAdapter

A próxima etapa é criar um objeto que implementa a interface IListDataAdapter. Um IListDataAdapter recupera dados de uma fonte de dados e os fornece para uma IListDataSource.

A interface IListDataAdapter suporta o acesso de leitura e gravação e as notificações de alteração. Porém, não é necessário implementar a interface completa: você pode criar um IListDataAdapter simples, somente-leitura, implementando apenas os métodos itemsFromIndex e getCount.

  1. Abra bingImageSearchDataSource.js, o arquivo JavaScript que você criou na etapa anterior.

  2. Crie uma função anônima e ative o modo restrito.

    Conforme descrito em Codificando aplicativos básicos, é uma ótima ideia encapsular o código JavaScript, através da respectiva quebra automática em uma função anônima, o uso do modo restrito também é uma ideia excelente.

    (function () {
        "use strict"; 
    
  3. Use a função WinJS.Class.define para criar sua implementação de IListDataAdapter. O primeiro parâmetro que a função WinJS.Class.define usa é o construtor de classe.

    Este IListDataAdapter conectará ao serviço de pesquisa Bing. A consulta de pesquisa da API Bing espera determinados dados. Nós armazenaremos esses dados, assim como alguns dados adicionais, no IListDataAdapter como membros de classe:

    • _minPageSize: o número mínimo de itens por página.
    • _maxPageSize: o número máximo de itens por página.
    • _maxCount: o número máximo de itens a serem retornados.
    • _devKey: ID do aplicativo. A API Bing requer uma chave AppID para identiicar o aplicativo.
    • _query: a cadeia de caracteres de pesquisa.

    Crie um construtor que use um AppID para a API Bing e uma consulta de pesquisa e forneça os valores para os outros membros.

    
        // 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. O próximo parâmetro que a função WinJS.Class.define espera é um objeto que contém os membros da instância da classe. Use esse objeto para implementar os métodos itemsFromIndex e getCount.

    Crie a chave de abertura desse objeto.

            // IListDataDapter methods
            // These methods define the contract between the IListDataSource and the IListDataAdapter.
            {
    
    1. Implemente o método itemsFromIndex. O método itemsFromIndex conecta a fonte de dados e retorna os dados solicitados como IFetchResult. O método itemsFromIndex considera três parâmetros: o índice de um item a ser recuperado, o número de itens antes do item a ser recuperado e o número de itens após o item de recuperação.

                  itemsFromIndex: function (requestIndex, countBefore, countAfter) {
                      var that = this;
      
    2. Verifique se o item solicitado (requestIndex) é menor do que o número máximo de itens a serem recuperados. Caso negativo, retorne o erro.

                      if (requestIndex >= that._maxCount) {
                          return Promise.wrapError(new WinJS.ErrorFromName(UI.FetchError.doesNotExist));
                      }
      
    3. Use requestIndex, countBefore, e countAfter para calcular o índice do primeiro item e o tamanho da solicitação. Os parâmetros countBefore e countAfter são recomendações para a quantidade de dados a serem recuperados: não precisa recuperar todos os itens que você solicitou. Neste exemplo, bing possui um tamanho máximo de solicitação de 50 itens, desejamos limitar nosso tamanho de solicitação dessa forma.

      Normalmente uma solicitação pede um ou dois itens antes ou depois do item solicitado e um número maior do lado oposto, assim levamos isso em consideração quando imaginamos o que pedir ao 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. Crie a sequência solicitada.

                      // 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. Use WinJS.xhr para enviar a solicitação. O WinJS.xhr retorna uma Promise que contém o resultado. Você pode processar o resultado chamando o método then do objeto Promise.

                      return WinJS.xhr({ url: requestStr }).then(
      
    6. Crie um retorno de chamada para uma operação WinJS.xhr bem-sucedida. Esta função processa os resultados e retorna-os como um itemIFetchResult. O IFetchResult contém três propriedades:

      • itens: uma matriz de objetos IItem que representa os resultados da pesquisa.
      • offset: o índice do item de solicitação na matriz de itens.
      • totalCount: o número total de itens na matriz de itens.

      Cada IItem deve ter uma propriedade de chave que contenha um identificador para esse item e uma propriedade de dados que contenha os dados do item:

      { chave: key1, dados : { field1: value, field2: value, ... }}

      Aqui a matriz dos objetos IItem aparece como será exibida:

      [{ chave: key1, dados : { field1: value, field2: value, ... }}, { chave: key2, dados : {...}}, ...];

                          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. Crie um retorno de chamada para uma operação mal-sucedida WinJS.xhr.

                          // Called if the WinJS.xhr funtion returned an error. 
                          function (request) {
                              return WinJS.UI.FetchError.noResponse;
                          });
      
    8. Feche o método itemsFromIndex. Você definirá outro método próximo, então adicione uma vírgula após fechar itemsFromIndex.

                  },
      
  5. Implemente o método getCount.

    1. O método getCount não utiliza qualquer parâmetro e retorna um Promise para o número de itens nos resultados do objeto IListDataAdapter.

                  // Gets the number of items in the result list. 
                  // The count can be updated in itemsFromIndex.
                  getCount: function () {
                      var that = this;
      
    2. Crie a sequência solicitada. Por o Bing não ter uma forma explícita de solicitar a contagem, nós solicitamos um registro e o usamos para obter a contagem.

      
                      // 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. Use WinJS.xhr para enviar a solicitação. Processe os resultados e retorne a contagem.

                      // 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. Este é o último membro de instância, muito próximo do objeto que você criou para contê-los. Há outros métodos IListDataAdapter que podem ser implementados, mas não são necessários para criar uma fonte de dados somente-leitura.

                // setNotificationHandler: not implemented
                // itemsFromStart: not implemented
                // itemsFromEnd: not implemented
                // itemsFromKey: not implemented
                // itemsFromDescription: not implemented
            }
    
  7. Feche a chamada para WinJS.Class.define.

            );
    

    Você criou uma classe denominada bingImageSarchDataAdapter que implementa a interface IListDataAdapter. Em seguida, você criará uma IListDataSource.

Etapa 3: Criar uma IListDataSource

Uma IListDataSource conecta um controle (como uma ListView) com um IListDataAdapter. A IListDataSource manipula o IListDataAdapter, que realmente executa o trabalho de manipulação e recuperação de dados. Nessa etapa, você implementa uma IListDataSource.

A WinJS fornece uma implementação da interface IListDataSource para você: o objeto VirtualizedDataSource. Use esse objeto para ajudar na implementação de IListDataSource. Como você pode ver, não há muito trabalho a fazer.

  1. Use a função WinJS.Class.derive para criar uma classe que herda de VirtualizedDataSource. Para o segundo parâmetro da função, defina um construtor que assume uma ID de aplicativo Bing e uma sequência de consulta. Mantenha a chamada do construtor de classe base e passe-a para um novo bingImageSarchDataAdapter (o objeto definido na etapa anterior).

        var bingImageSearchDataSource = WinJS.Class.derive(WinJS.UI.VirtualizedDataSource, function (devkey, query) {
            this._baseDataSourceConstructor(new bingImageSearchDataAdapter(devkey, query));
        });
    
  2. Use a função WinJS.Namespace.define para definir um namespace e torne a classe publicamente acessível. A função WinJS.Namespace.define assume dois parâmetros: o nome do namespace para criar e um objeto que contém um ou mais pares de propriedades/valores. Cada propriedade é o nome público do membro e cada valor é uma variável, propriedade ou função subjacente no código particular que você deseja expor.

        WinJS.Namespace.define("DataExamples", { bingImageSearchDataSource: bingImageSearchDataSource });  
    
  3. Agora você implementou um IListDataAdapter e uma IListDataSource. E concluiu bingImageSearchDataSource.js, então pode fechar a função anônima externa.

    })();
    

    Para usar a fonte de dados personalizados, crie uma nova instância da classe bingImageSearchDataSource. Passe o construtor ID de aplicativo Bing para o aplicativo e uma consulta de pesquisa:

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

    Você pode usar bingImageSearchDataSourcecom controles que assumem uma IListDataSource, como o controle ListView.

Exemplo completo

Aqui está o código completo para bingImageSearchDataSource.js. Para obter a amostra completa, consulte Trabalhando com a amostra de fontes de dados.

// 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 }); 

})();