如何建立自訂資料來源 (HTML)
[ 本文的目標對象是撰寫 Windows 執行階段 App 的 Windows 8.x 和 Windows Phone 8.x 開發人員。如果您正在開發適用於 Windows 10 的 App,請參閱 最新文件 ]
適用於 JavaScript 的 Windows Library 提供數個現成的資料來源物件,您可以使用它們填入具有不同類型資料的 ListView 或 FlipView。WinJS.Binding.List 物件可以存取陣列及 JSON 資料,而 StorageDataSource 物件則可以存取檔案系統的資訊。
您不只能使用這些資料來源,還可以建立自己的自訂資料來源,來存取任何其他類型的資料,如 XML 檔案或 Web 服務。這個主題示範如何實作可以存取 Web 服務的自訂資料來源。它使用 XHR 來連線 Bing 影像搜尋服務,並在 ListView 中顯示結果
(因為 Bing 服務需要每個應用程式都有自己的應用程式識別碼索引鍵,所以您需要先取得索引鍵才能使用此程式碼。如需如何取得應用程式識別碼索引鍵的詳細資訊,請參閱 Bing 開發人員中心。)
如果要建立自訂的資料來源,您需要實作 IListDataAdapter 和 IListDataSource 介面的物件。WinJS 提供一個 VirtualizedDataSource 物件,它可以實作 IListDataSource—,您唯一要做的就是繼承它,然後傳送 IListDataAdapter 給基底建構函式。您需要建立自己的物件,讓它實作 IListDataAdapter 介面。
IListDataAdapter 會直接與資料來源互動,以抓取或更新項目。IListDataSource 會連接到控制項並操縱 IListDataAdapter。
先決條件
- 您應該熟悉 WinJS 物件及控制項的使用方法。如需詳細資訊,請參閱快速入門:新增適用於 JavaScript 的 Windows Library 的控制項和樣式。
- 您必須知道如何建立 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 檔案。
建立匿名函式,然後開啟 strict 模式。
如撰寫基本應用程式的程式碼中所述,最好是將 JavaScript 程式碼包在匿名函式中進行封裝,並使用 strict 模式。
(function () { "use strict";
使用 WinJS.Class.define 函式建立您的 IListDataAdapter 實作。WinJS.Class.define 函式接受的第一個參數是類別建構函式。
這個 IListDataAdapter 將連線到 Bing 搜尋服務。Bing API 搜尋查詢會預期特定資料。我們將這項資料連同某些其他資料以下列類別成員的形式儲存在 IListDataAdapter 中:
- _minPageSize:每個頁面的最小項目數。
- _maxPageSize:每個頁面的最大項目數。
- _maxCount:要傳回的最大項目數。
- _devKey:應用程式識別碼。 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 個項目,因此我們要將要求大小限制為這個數目。
通常要求會在要求項目的前後要求一或兩個項目,並向另一端要求更大數量,所以當我們對伺服器提出要求時要將這些列入考慮。
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;
建立要求字串。因為 Bing 沒有明確要求計數的方式,所以我們會要求一個記錄並用來取得計數。
// 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 應用程式識別碼和搜尋查詢傳遞給建構函式:var myDataSrc = new DataExamples.bingImageSearchDataSource(devKey, searchTerm);
您現在可以使用 IListDataSource 搭配接受
bingImageSearchDataSource
的控制項 (例如 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 });
})();