如何创建自定义数据源 (HTML)

[ 本文适用于编写 Windows 运行时应用的 Windows 8.x 和 Windows Phone 8.x 开发人员。如果你要针对 Windows 10 进行开发,请参阅 最新文档 ]

适用于 JavaScript 的 Windows 库提供几种现成的数据源对象,可以使用这些对象用不同类型的数据填充 ListViewFlipView。存在用于访问数组和 JSON 数据的 WinJS.Binding.List 对象,存在用于访问关于文件系统的信息的 StorageDataSource

但并不限于仅使用这些数据源。可以创建访问任何其他类型的数据的自己的自定义数据源,例如 XML 文件或 Web 服务。本主题介绍如何实现访问 Web 服务的自定义数据源。这种数据源使用 XHR 连接到 Bing 图像搜索服务并在 ListView 中显示结果。

(因为 Bing 服务要求每个应用有其自己的应用 ID 密钥,因此需要获取密钥才能使用此代码。有关如何获取应用 ID 密钥的详细信息,请参阅 Bing 开发人员中心。)

若要创建自定义数据源,则需要实现 IListDataAdapterIListDataSource 接口的对象。WinJS 可提供用于实现 IListDataSourceVirtualizedDataSource 对象—你所需要做的就是从它继承 IListDataAdapter 并将其传递给基构造函数。你需要创建自己的实现 IListDataAdapter 接口的对象。

IListDataAdapter 直接与数据源进行交互以检索或更新项目。IListDataSource 连接到控件并操纵 IListDataAdapter

先决条件

说明

步骤 1: 创建 JavaScript 文件以进行数据源自定义

  1. 使用 Microsoft Visual Studio 将 JavaScript 文件添加到项目中。在解决方案资源管理器中,右键单击项目的 js 文件夹,然后选择“添加”>“新建项”。****即会出现“添加新项”对话框。
  2. 选择“JavaScript 文件”****。将其命名为 "bingImageSearchDataSource.js"。 单击“添加”以创建文件。Visual Studio 创建一个名为 bingImageSearchDataSource.js 的空白 JavaScript 文件。

步骤 2: 创建 IListDataAdapter

下一步是创建实现 IListDataAdapter 接口的对象。IListDataAdapter 从数据源检索数据并将其提供给 IListDataSource

IListDataAdapter 接口支持读和写权限以及更改通知。但不需要实现整个接口:可以通过仅实现 itemsFromIndexgetCount 方法创建一个简单的只读 IListDataAdapter

  1. 打开 bingImageSearchDataSource.js,该文件是在上一步创建的 JavaScript 文件。

  2. 创建一个匿名函数并打开严格模式。

    为基本应用编写代码中所述,通过在匿名函数中包装 JavaScript 代码来封装该代码是一个好方法,并且使用严格模式同样也是一个好方法。

    (function () {
        "use strict"; 
    
  3. 使用 WinJS.Class.define 函数创建 IListDataAdapter 的实现。WinJS.Class.define 函数带有的第一个参数就是类构造函数。

    IListDataAdapter 将连接到 Bing 搜索服务。Bing API 搜索查询需要使用某些数据。我们会将此数据以及某些其他数据存储在 IListDataAdapter 中作为类成员:

    • _minPageSize:每页的最小项目数。
    • _maxPageSize:每页的最大项目数。
    • _maxCount:要返回的最大项目数。
    • _devKey:应用 ID。 Bing API 需要一个 AppID 密钥用于标识应用。
    • _query:搜索字符串。

    创建一个构造函数,该函数得有 Bing API 的一个 AppID 并为其他成员提供值。

    
        // 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. WinJS.Class.define 函数需要使用的下一个参数是一个包含类的实例成员的对象。使用此对象来实现 itemsFromIndexgetCount 方法。

    为此对象创建左括号。

            // IListDataDapter methods
            // These methods define the contract between the IListDataSource and the IListDataAdapter.
            {
    
    1. 实现 itemsFromIndex 方法。itemsFromIndex 方法连接到数据源并将请求的数据返回为 IFetchResultitemsFromIndex 方法采用三个参数:要检索的项的索引、要检索的项之前的项数,以及要检索的项之后的项数。

                  itemsFromIndex: function (requestIndex, countBefore, countAfter) {
                      var that = this;
      
    2. 确认请求的项 (requestIndex) 小于要检索的最大项数。如果不小于最大项数,则返回错误。

                      if (requestIndex >= that._maxCount) {
                          return Promise.wrapError(new WinJS.ErrorFromName(UI.FetchError.doesNotExist));
                      }
      
    3. 使用 requestIndexcountBeforecountAfter 来计算第一个项的索引和请求的大小。countBeforecountAfter 参数是对要检索多少数据的建议。不要求检索请求的所有项。在此示例中,Bing 的最大请求大小为 50 个项,因此我们希望将我们的请求大小限制为 50 个。

      通常请求将请求在请求的项之前或之后的一个或两个项,以及另一侧的一个较大的数字,因此我们希望在弄清楚我们请求服务器的内容时将该问题考虑在内。

                      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. 创建请求字符串。

                      // 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. 使用 WinJS.xhr 提交请求。WinJS.xhr 返回包含结果的 Promise。可以通过调用 Promise 对象的 then 方法处理该结果。

                      return WinJS.xhr({ url: requestStr }).then(
      
    6. 为成功的 WinJS.xhr 操作创建回调。该函数处理结果并以 IFetchResult 项目的形式返回这些结果。IFetchResult 包含三个属性:

      • items:表示查询结果的 IItem 对象的数组。
      • offset:项目数组中请求项目的索引。
      • totalCount:项目数组中项目的总数。

      每个 IItem 都必须包含一个密钥属性(该属性包含该项目的标识符)和一个数据属性(该属性包含该项目的数据):

      { key: key1, data : { field1: value, field2: value, ... }}

      下面是 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. 为不成功的 WinJS.xhr 操作创建回调。

                          // Called if the WinJS.xhr funtion returned an error. 
                          function (request) {
                              return WinJS.UI.FetchError.noResponse;
                          });
      
    8. 关闭 itemsFromIndex 方法。接下来将定义另一种方法,因此请在关闭 itemsFromIndex 之后添加一个逗号。

                  },
      
  5. 实现 getCount 方法。

    1. getCount 方法不采用任何参数,并且针对 IListDataAdapter 对象的结果中的项目数返回 Promise

                  // Gets the number of items in the result list. 
                  // The count can be updated in itemsFromIndex.
                  getCount: function () {
                      var that = this;
      
    2. 创建请求字符串。由于必应没有询问计数的明确方法,我们请求一个记录,并使用它获取计数。

      
                      // 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. 使用 WinJS.xhr 提交请求。处理结果并返回计数。

                      // 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. 这是最后一个实例成员,因此请关闭创建的对象以包含它们。存在可以实现的其他 IListDataAdapter 方法,但不需要这些方法来创建只读数据源。

                // setNotificationHandler: not implemented
                // itemsFromStart: not implemented
                // itemsFromEnd: not implemented
                // itemsFromKey: not implemented
                // itemsFromDescription: not implemented
            }
    
  7. 关闭对 WinJS.Class.define 的调用。

            );
    

    创建了一个名为 bingImageSarchDataAdapter 的类,该类实现 IListDataAdapter 接口。接下来,将创建一个 IListDataSource

步骤 3: 创建 IListDataSource

IListDataSource 将控件(如 ListView)连接到 IListDataAdapterIListDataSource 处理 IListDataAdapter,后者完成实际处理和检索数据的工作。在此步骤中,实现 IListDataSource

WinJS 为你提供了 IListDataSource 接口的一个实现:VirtualizedDataSource 对象。可以使用此对象帮助实现 IListDataSource。正如稍后将看到的那样,此处没有太多工作需要处理。

  1. 使用 WinJS.Class.derive 函数创建从 VirtualizedDataSource 继承的类。对于该函数的第二个参数,请定义一个构造函数,该构造函数采用一个 Bing App ID 和一个查询字符串。让该构造函数调用基类构造函数并将该函数传递给新 bingImageSarchDataAdapter(在上一步中定义的对象)。

        var bingImageSearchDataSource = WinJS.Class.derive(WinJS.UI.VirtualizedDataSource, function (devkey, query) {
            this._baseDataSourceConstructor(new bingImageSearchDataAdapter(devkey, query));
        });
    
  2. 使用 WinJS.Namespace.define 函数定义命名空间,并使该类可供公开访问。WinJS.Namespace.define 函数采用两个参数:要创建的命名空间的名称和包含一个或多个属性/值对的对象。每个属性是成员的公用名称,而每个值是在私有代码中要公开的基础变量、属性或函数。

        WinJS.Namespace.define("DataExamples", { bingImageSearchDataSource: bingImageSearchDataSource });  
    
  3. 现在,你已经实现了一个 IListDataAdapter 和一个 IListDataSource。你已完成对 bingImageSearchDataSource.js 的处理,因此你可以关闭外部的匿名函数。

    })();
    

    若要使用自定义数据源,请创建 bingImageSearchDataSource 类的一个新实例。向构造函数传递应用的 Bing 应用 ID 和一个搜索查询:

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

    现在,你可以使用 bingImageSearchDataSource,它具有带有 IListDataSource 的控件,如 ListView 控件。

完整示例

以下是 bingImageSearchDataSource.js 的完整代码。有关完整的示例,请参阅使用数据源示例。

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

})();