Как создать пользовательский источник данных (HTML)

[ Эта статья адресована разработчикам приложений среды выполнения Windows для Windows 8.x и Windows Phone 8.x. При разработке приложений для Windows 10 см. раздел последняя документация]

Библиотека Windows для JavaScript предоставляет несколько готовых объектов источников данных, которые можно использовать для заполнения ListView или FlipView данными различного типа. WinJS.Binding.List — это объект для доступа к массивам и данным JSON, а StorageDataSource организует доступ к информации о файловой системе.

Но не обязательно ограничиваться только этими источниками данных. Вы можете создать собственный пользовательский источник данных, который будет обеспечивать доступ к данным любого другого типа, например XML-файлу или веб-службе. В этом разделе будет показано, как реализовать пользовательский источник данных, обеспечивающий доступ к веб-службе. Он использует XHR для подключения к службе поиска изображений Bing и отображает результаты в ListView.

(Поскольку служба Bing требует, чтобы у каждого приложения был собственный ключ идентификатора, до начала использования данного кода необходимо получить такой ключ. Дополнительные сведения о том, как получить ключ идентификатора приложения, см. на странице Центр разработки для Bing.)

Чтобы создать пользовательский источник данных, нужны объекты, реализующие интерфейсы IListDataAdapter и IListDataSource. Библиотека WinJS предоставляет объект VirtualizedDataSource, реализующий интерфейс IListDataSource. Все, что необходимо сделать, — это унаследовать собственный объект от него и передать конструктору базового класса интерфейс IListDataAdapter. Вам нужно будет создать собственный объект, реализующий интерфейс IListDataAdapter.

Интерфейс IListDataAdapter напрямую взаимодействует с источником данных для извлечения или обновления элементов. Интерфейс IListDataSource подключается для управления или манипулирования интерфейсом IListDataAdapter.

Необходимые условия

Инструкции

Этап 1: Создание файла JavaScript для настраиваемого источника данных

  1. Для добавления файла JavaScript в проект используйте Microsoft Visual Studio. В окне обозревателя решений щелкните правой кнопкой мыши папку js и выберите команду Добавать > Создать элемент. Откроется диалоговое окно Добавление нового элемента.
  2. Выберите Файл JavaScript. Назовите его "bingImageSearchDataSource.js". Чтобы создать файл, нажмите кнопку Добавить. Visual Studio создаст пустой файл JavaScript с именем bingImageSearchDataSource.js.

Этап 2: Создание объекта IListDataAdapter

На следующем этапе мы должны создать объект, реализующий интерфейс IListDataAdapter. IListDataAdapter получает данные из источника данных и передает их IListDataSource.

Интерфейс IListDataAdapter поддерживает доступ на чтение и запись, а также уведомления об изменениях. Тем не менее не нужно реализовывать весь интерфейс: вы можете создать простой, доступный только для чтения IListDataAdapter, реализовав только методы itemsFromIndex и getCount.

  1. Откройте bingImageSearchDataSource.js, файл JavaScript, который вы создали на предыдущем этапе.

  2. Создайте анонимную функцию и включите строгий режим.

    Как описано в разделе Создание простейших приложений, полезно инкапсулировать свой код JavaScript, заключив его в анонимную функцию. Рекомендуется также использовать строгий режим.

    (function () {
        "use strict"; 
    
  3. Для создания собственной реализации объекта IListDataAdapter используйте функцию WinJS.Class.define. Первый параметр, который принимает функция WinJS.Class.define — это конструктор класса.

    Объект IListDataAdapter будет подключаться к поисковой службе Bing. Поисковый запрос с использованием Bing API предполагает передачу определенных данных. Эти данные наряду с некоторыми дополнительными данными мы сохраним в IListDataAdapter в качестве членов класса:

    • _minPageSize: минимальное количество элементов на страницу.
    • _maxPageSize: максимальное количество элементов на страницу.
    • _maxCount: максимальное количество возвращаемых элементов.
    • _devKey: ИД приложения. Bing API требует указывать ключ AppID для идентификации приложения.
    • _query: строка поиска.

    Создайте конструктор, который принимает в качестве аргументов AppID для Bing API и поисковый запрос и присваивает значения остальным членам класса.

    
        // 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 — это объект, содержащий члены экземпляра класса. Этот объект используется для реализации методов itemsFromIndex и getCount.

    Создайте открывающую скобку для этого объекта.

            // 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, 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;
                      }
      
    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, в котором содержится результат. Результат можно обработать, вызвав метод then объекта Promise.

                      return WinJS.xhr({ url: requestStr }).then(
      
    6. Создайте обратный вызов на случай успешного выполнения операции WinJS.xhr. Эта функция обрабатывает результаты и возвращает их в виде элемента IFetchResult. Объект IFetchResult имеет три свойства:

      • items: массив объектов IItem, представляющих результаты запроса.
      • offset: индекс запрошенного элемента в массиве элементов.
      • totalCount: общее число элементов в массиве элементов.

      Каждый объект IItem должен иметь свойство key, содержащее идентификатор этого элемента, и свойство data, содержащее данные элемента:

      { 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 не принимает параметров и возвращает Promise для числа элементов в результатах объекта IListDataAdapter.

                  // 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) к IListDataAdapter. IListDataSource управляет IListDataAdapter, который отвечает за фактическую обработку и загрузку данных. На этом этапе вы реализуете IListDataSource.

WinJS предоставляет одну реализацию интерфейса IListDataSource: объект VirtualizedDataSource. Этот объект вы можете использовать для реализации IListDataSource. Как вы вскоре увидите, сделать для этого нужно будет совсем немного.

  1. Используйте функцию WinJS.Class.derive для создания класса, наследующего VirtualizedDataSource. Для второго параметра функции определите конструктор, принимающий идентификатор приложения Bing и строку запроса. Пусть этот конструктор вызовет конструктор базового класса и передаст ему новый 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, который присвоен вашему приложению, и поисковый запрос:

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

})();