共用方式為


如何建立自訂資料來源 (HTML)

[ 本文的目標對象是撰寫 Windows 執行階段 App 的 Windows 8.x 和 Windows Phone 8.x 開發人員。如果您正在開發適用於 Windows 10 的 App,請參閱 最新文件 ]

適用於 JavaScript 的 Windows Library 提供數個現成的資料來源物件,您可以使用它們填入具有不同類型資料的 ListViewFlipViewWinJS.Binding.List 物件可以存取陣列及 JSON 資料,而 StorageDataSource 物件則可以存取檔案系統的資訊。

您不只能使用這些資料來源,還可以建立自己的自訂資料來源,來存取任何其他類型的資料,如 XML 檔案或 Web 服務。這個主題示範如何實作可以存取 Web 服務的自訂資料來源。它使用 XHR 來連線 Bing 影像搜尋服務,並在 ListView 中顯示結果

(因為 Bing 服務需要每個應用程式都有自己的應用程式識別碼索引鍵,所以您需要先取得索引鍵才能使用此程式碼。如需如何取得應用程式識別碼索引鍵的詳細資訊,請參閱 Bing 開發人員中心。)

如果要建立自訂的資料來源,您需要實作 IListDataAdapterIListDataSource 介面的物件。WinJS 提供一個 VirtualizedDataSource 物件,它可以實作 IListDataSource—,您唯一要做的就是繼承它,然後傳送 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. 建立匿名函式,然後開啟 strict 模式。

    撰寫基本應用程式的程式碼中所述,最好是將 JavaScript 程式碼包在匿名函式中進行封裝,並使用 strict 模式。

    (function () {
        "use strict"; 
    
  3. 使用 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;
            },
    
  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. 使用 requestIndexcountBefore 以及 countAfter 來計算第一個項目的索引及要求的大小。countBeforecountAfter 參數是要抓取的資料數量建議:您不需要抓取所要求的全部項目。在這個範例中,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;
                      }
      
    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. 建立要求字串。因為 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";
      
    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. 您現在已經實作 IListDataAdapterIListDataSource。您已經完成 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 }); 

})();