如何创建自定义数据源 (HTML)
[ 本文适用于编写 Windows 运行时应用的 Windows 8.x 和 Windows Phone 8.x 开发人员。如果你要针对 Windows 10 进行开发,请参阅 最新文档 ]
适用于 JavaScript 的 Windows 库提供几种现成的数据源对象,可以使用这些对象用不同类型的数据填充 ListView 或 FlipView。存在用于访问数组和 JSON 数据的 WinJS.Binding.List 对象,存在用于访问关于文件系统的信息的 StorageDataSource。
但并不限于仅使用这些数据源。可以创建访问任何其他类型的数据的自己的自定义数据源,例如 XML 文件或 Web 服务。本主题介绍如何实现访问 Web 服务的自定义数据源。这种数据源使用 XHR 连接到 Bing 图像搜索服务并在 ListView 中显示结果。
(因为 Bing 服务要求每个应用有其自己的应用 ID 密钥,因此需要获取密钥才能使用此代码。有关如何获取应用 ID 密钥的详细信息,请参阅 Bing 开发人员中心。)
若要创建自定义数据源,则需要实现 IListDataAdapter 和 IListDataSource 接口的对象。WinJS 可提供用于实现 IListDataSource 的 VirtualizedDataSource 对象—你所需要做的就是从它继承 IListDataAdapter 并将其传递给基构造函数。你需要创建自己的实现 IListDataAdapter 接口的对象。
IListDataAdapter 直接与数据源进行交互以检索或更新项目。IListDataSource 连接到控件并操纵 IListDataAdapter。
先决条件
- 你应当熟悉 WinJS 对象和控件的使用方法。有关详细信息,请参阅快速入门:添加适用于 JavaScript 控件和样式的 Windows 库。
- 你应当了解如何创建 ListView 或 FlipView,并将其连接到数据源。若要开始使用 ListView,请参阅快速入门:添加 ListView 主题。若要开始使用 FlipView,请参阅快速入门:添加 FlipView。
- 你应当熟悉 Promise 对象。有关详细信息,请参阅使用 JavaScript 异步编程。
说明
步骤 1: 创建 JavaScript 文件以进行数据源自定义
- 使用 Microsoft Visual Studio 将 JavaScript 文件添加到项目中。在解决方案资源管理器中,右键单击项目的
js
文件夹,然后选择“添加”>“新建项”。****即会出现“添加新项”对话框。 - 选择“JavaScript 文件”****。将其命名为 "bingImageSearchDataSource.js"。 单击“添加”以创建文件。Visual Studio 创建一个名为 bingImageSearchDataSource.js 的空白 JavaScript 文件。
步骤 2: 创建 IListDataAdapter
下一步是创建实现 IListDataAdapter 接口的对象。IListDataAdapter 从数据源检索数据并将其提供给 IListDataSource。
IListDataAdapter 接口支持读和写权限以及更改通知。但不需要实现整个接口:可以通过仅实现 itemsFromIndex 和 getCount 方法创建一个简单的只读 IListDataAdapter。
打开 bingImageSearchDataSource.js,该文件是在上一步创建的 JavaScript 文件。
创建一个匿名函数并打开严格模式。
如为基本应用编写代码中所述,通过在匿名函数中包装 JavaScript 代码来封装该代码是一个好方法,并且使用严格模式同样也是一个好方法。
(function () { "use strict";
使用 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; },
WinJS.Class.define 函数需要使用的下一个参数是一个包含类的实例成员的对象。使用此对象来实现 itemsFromIndex 和 getCount 方法。
为此对象创建左括号。
// IListDataDapter methods // These methods define the contract between the IListDataSource and the IListDataAdapter. {
-
实现 itemsFromIndex 方法。itemsFromIndex 方法连接到数据源并将请求的数据返回为 IFetchResult。 itemsFromIndex 方法采用三个参数:要检索的项的索引、要检索的项之前的项数,以及要检索的项之后的项数。
itemsFromIndex: function (requestIndex, countBefore, countAfter) { var that = this;
确认请求的项 (requestIndex) 小于要检索的最大项数。如果不小于最大项数,则返回错误。
if (requestIndex >= that._maxCount) { return Promise.wrapError(new WinJS.ErrorFromName(UI.FetchError.doesNotExist)); }
使用 requestIndex、countBefore 和 countAfter 来计算第一个项的索引和请求的大小。countBefore 和 countAfter 参数是对要检索多少数据的建议。不要求检索请求的所有项。在此示例中,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; }
创建请求字符串。
// 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";
使用 WinJS.xhr 提交请求。WinJS.xhr 返回包含结果的 Promise。可以通过调用 Promise 对象的 then 方法处理该结果。
return WinJS.xhr({ url: requestStr }).then(
为成功的 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; } },
为不成功的 WinJS.xhr 操作创建回调。
// Called if the WinJS.xhr funtion returned an error. function (request) { return WinJS.UI.FetchError.noResponse; });
关闭 itemsFromIndex 方法。接下来将定义另一种方法,因此请在关闭 itemsFromIndex 之后添加一个逗号。
},
实现 getCount 方法。
getCount 方法不采用任何参数,并且针对 IListDataAdapter 对象的结果中的项目数返回 Promise。
// 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";
使用 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)); }); }
这是最后一个实例成员,因此请关闭创建的对象以包含它们。存在可以实现的其他 IListDataAdapter 方法,但不需要这些方法来创建只读数据源。
// setNotificationHandler: not implemented // itemsFromStart: not implemented // itemsFromEnd: not implemented // itemsFromKey: not implemented // itemsFromDescription: not implemented }
关闭对 WinJS.Class.define 的调用。
);
创建了一个名为
bingImageSarchDataAdapter
的类,该类实现 IListDataAdapter 接口。接下来,将创建一个 IListDataSource。
步骤 3: 创建 IListDataSource
IListDataSource 将控件(如 ListView)连接到 IListDataAdapter。IListDataSource 处理 IListDataAdapter,后者完成实际处理和检索数据的工作。在此步骤中,实现 IListDataSource。
WinJS 为你提供了 IListDataSource 接口的一个实现:VirtualizedDataSource 对象。可以使用此对象帮助实现 IListDataSource。正如稍后将看到的那样,此处没有太多工作需要处理。
使用 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)); });
使用 WinJS.Namespace.define 函数定义命名空间,并使该类可供公开访问。WinJS.Namespace.define 函数采用两个参数:要创建的命名空间的名称和包含一个或多个属性/值对的对象。每个属性是成员的公用名称,而每个值是在私有代码中要公开的基础变量、属性或函数。
WinJS.Namespace.define("DataExamples", { bingImageSearchDataSource: bingImageSearchDataSource });
现在,你已经实现了一个 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 });
})();