사용자 지정 데이터 원본을 만드는 방법(HTML)

[ 이 문서는 Windows 런타임 앱을 작성하는 Windows에서 8.x 및 Windows Phone 8.x 개발자를 대상으로 합니다. Windows 10용으로 개발하는 경우에는 최신 설명서를 참조하세요.]

JavaScript용 Windows 라이브러리에서 제공하는 바로 사용할 수 있는 여러 데이터 원본 개체를 사용하여 ListView 또는 FlipView여 여러 데이터 유형을 채울 수 있습니다. 배열 및 JSON 데이터에 액세스하는 WinJS.Binding.List 개체나 파일 시스템에 대한 정보에 액세스하는 StorageDataSource 개체를 예로 들 수 있습니다.

이러한 데이터 원본만이 아닙니다. 자신만의 사용자 지정 데이터 원본을 만들어 XML 파일이나 웹 서비스와 같은 다른 데이터 유형에 액세스할 수 있습니다. 이 항목에서는 웹 서비스에 액세스하는 사용자 지정 데이터 원본을 구현하는 방법을 보여 줍니다. 이 원본은 XHR을 사용하여 Bing 이미지 검색 서비스에 연결하고 결과를 ListView에 표시합니다.

(Bing 서비스를 사용하려면 각 앱에 고유한 앱 ID 키가 있어야 하므로 이 코드를 사용하기 전에 키를 얻어야 합니다. 앱 ID 키를 얻는 방법에 대한 자세한 내용은 bing 개발자 센터를 참조하세요.)

사용자 지정 데이터 원본을 만들려면 IListDataAdapterIListDataSource 인터페이스를 구현하는 개체가 필요합니다. WinJS에서는 IListDataSource를 구현하는 VirtualizedDataSource 개체를 제공합니다.— 이 개체로부터 상속하고 기본 생성자를 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. 이전 단계에서 만든 JavaScript 파일인 bingImageSearchDataSource.js를 엽니다.

  2. 익명 함수를 만들고 strict 모드를 켭니다.

    기본 앱 코딩에 설명된 대로 JavaScript 코드를 익명 함수에 래핑하여 코드를 캡슐화하고 strict 모드를 사용하는 것이 좋습니다.

    (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 메서드는 데이터 원본에 연결하고 요청된 데이터를 IFetchResult로 반환합니다. itemsFromIndex 메서드에 사용되는 세 매개 변수는 검색할 항목의 인덱스, 검색할 항목 이전의 항목 수 및 검색할 항목 이후의 항목 수입니다.

                  itemsFromIndex: function (requestIndex, countBefore, countAfter) {
                      var that = this;
      
    2. 요청된 항목(requestIndex)이 검색할 최대 항목 수보다 작은지 확인합니다. 그렇지 않으면 오류가 반환됩니다.

                      if (requestIndex >= that._maxCount) {
                          return Promise.wrapError(new WinJS.ErrorFromName(UI.FetchError.doesNotExist));
                      }
      
    3. requestIndex, countBeforecountAfter를 사용하여 첫 번째 항목 인덱스 및 요청의 크기를 계산합니다. 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에 대한 호출을 닫습니다.

            );
    

    IListDataAdapter 인터페이스를 구현하는 bingImageSarchDataAdapter라는 클래스를 만들었습니다. 다음으로 IListDataSource를 만듭니다.

단계 3: IListDataSource 만들기

IListDataSourceListView와 같은 컨트롤을 IListDataAdapter에 연결합니다. IListDataSource는 실제로 데이터 조작 및 검색 작업을 하는 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 앱 ID와 검색 쿼리를 전달합니다.

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

    이제 IListDataSource를 받는 컨트롤(예: ListView 컨트롤)과 함께 bingImageSearchDataSource를 사용할 수 있습니다.

전체 예제

다음은 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 }); 

})();