Использование Google Base и Google Gears для эффективной работы в автономном режиме

Первая статья из серии "Создание лучших приложений Ajax с помощью API Google".

Дион Алмаер и Памела Фокс, Google
июнь 2007 г.

Примечание редактора. API Google Gears больше недоступен .

Введение

Объединив Google Base с Google Gears, мы покажем, как создать приложение, которое можно использовать в автономном режиме. Прочитав эту статью, вы лучше познакомитесь с Google Base API, а также поймете, как использовать Google Gears для хранения и доступа к пользовательским настройкам и данным.

Понимание приложения

Чтобы понять это приложение, вы должны сначала ознакомиться с Google Base , которая представляет собой большую базу данных элементов, охватывающих различные категории, такие как продукты, обзоры, рецепты, события и многое другое.

Каждый элемент снабжен заголовком, описанием, ссылкой на исходный источник данных (если он существует) и дополнительными атрибутами, которые различаются в зависимости от типа категории. Google Base использует тот факт, что элементы в одной категории имеют общий набор атрибутов — например, все рецепты содержат ингредиенты. Элементы Google Base даже иногда появляются в результатах поиска Google в Интернете или в поиске продуктов Google.

Наше демонстрационное приложение Base with Gears позволяет сохранять и отображать распространенные поисковые запросы, которые вы можете выполнять в Google Base, например поиск рецептов со словом «шоколад» (ням) или персональные объявления со словом «прогулки по пляжу» (романтично!). Вы можете думать об этом как о «Google Base Reader», который позволяет подписаться на поиск и видеть обновленные результаты при повторном посещении приложения или когда приложение выходит на поиск обновленных каналов каждые 15 минут.

Разработчики, желающие расширить приложение, могут добавить дополнительные функции, такие как визуальное оповещение пользователя о том, что результаты поиска содержат новые результаты, позволить пользователю помечать избранные элементы (автономно + онлайн), а также разрешить пользователю выполнять поиск по атрибутам определенной категории, например Гугл База.

Использование фидов API данных Google Base

Google Base можно запрашивать программно с помощью API данных Google Base, который совместим с платформой Google Data API. Протокол API данных Google предоставляет простой протокол для чтения и записи в Интернете и используется многими продуктами Google: Picasa, электронными таблицами, Blogger, календарем, блокнотом и другими.

Формат Google Data API основан на XML и протоколе публикации Atom, поэтому большая часть операций чтения/записи выполняется в XML.

Пример фида Google Base на основе Google Data API:
http://www.google.com/base/feeds/snippets/-/products?bq=digital+камера

Тип ленты snippets дает нам общедоступную ленту элементов. -/products позволяет ограничить фид категорией продуктов. А параметр bq= позволяет нам дополнительно ограничить ленту только результатами, содержащими ключевое слово «цифровая камера». Если вы просмотрите этот канал в браузере, вы увидите XML, содержащий узлы <entry> с соответствующими результатами. Каждая запись содержит типичные элементы автора, заголовка, содержимого и ссылки, а также дополнительные атрибуты, относящиеся к категории (например, «цена» для товаров в категории продуктов).

Из-за междоменного ограничения XMLHttpRequest в браузере нам не разрешено напрямую читать фид XML с www.google.com в нашем коде JavaScript. Мы могли бы настроить прокси-сервер на стороне сервера для чтения XML и передачи его обратно в место в том же домене, что и наше приложение, но мы хотели бы вообще избежать программирования на стороне сервера. К счастью, есть альтернатива.

Как и другие API данных Google, API данных Google Base имеет параметр вывода JSON в дополнение к стандартному XML. Вывод фида, который мы видели ранее в формате JSON, будет по этому URL-адресу:
http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera&alt=json

JSON — это упрощенный формат обмена, допускающий иерархическую вложенность, а также различные типы данных. Но что еще более важно, выходные данные JSON сами по себе являются собственным кодом JavaScript, поэтому их можно загрузить на вашу веб-страницу, просто сославшись на них в теге скрипта, минуя междоменное ограничение.

API данных Google также позволяют указать вывод «json-in-script» с функцией обратного вызова, которая будет выполняться после загрузки JSON. Это делает вывод JSON еще проще для работы, поскольку мы можем динамически добавлять теги скрипта на страницу и указывать разные функции обратного вызова для каждого из них.

Таким образом, для динамической загрузки JSON-канала Base API на страницу мы могли бы использовать следующую функцию, которая создает тег скрипта с URL-адресом канала (с добавлением значений callback alt ) и добавляет его на страницу.

function getJSON() {
  var script = document.createElement('script');

  var url = "http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera";
  script.setAttribute('src', url + "&alt=json-in-script&callback=listResults");
  script.setAttribute('type', 'text/JavaScript');
  document.documentElement.firstChild.appendChild(script);
}

Таким образом, наша функция обратного вызова listResults теперь может перебирать JSON, переданный в качестве единственного параметра, и отображать информацию о каждой записи, найденной в маркированном списке.

  function listTasks(root) {
    var feed = root.feed;
    var html = [''];
    html.push('<ul>');
    for (var i = 0; i < feed.entry.length; ++i) {
      var entry = feed.entry[i];
      var title = entry.title.$t;
      var content = entry.content.$t;
      html.push('<li>', title, ' (', content, ')</li>');
    }
    html.push('</ul>');

    document.getElementById("agenda").innerHTML = html.join("");
  }

Добавление Google Gears

Теперь, когда у нас есть приложение, которое может взаимодействовать с Google Base через Google Data API, мы хотим, чтобы это приложение работало в автономном режиме. Здесь на помощь приходит Google Gears.

Существуют различные варианты архитектуры, когда дело доходит до написания приложения, которое может работать в автономном режиме. Вы будете задавать себе вопросы о том, как приложение должно работать в сети и в автономном режиме (например, работает ли оно точно так же? Отключены ли некоторые функции, такие как поиск? Как вы будете обрабатывать синхронизацию?)

В нашем случае мы хотели убедиться, что пользователи браузеров без Gears по-прежнему могут использовать приложение, а пользователям, у которых есть подключаемый модуль, доступны преимущества автономного использования и более отзывчивый пользовательский интерфейс.

Наша архитектура выглядит так:

  • У нас есть объект JavaScript, который отвечает за хранение ваших поисковых запросов и возврат результатов этих запросов.
  • Если у вас установлен Google Gears, вы получаете версию Gears, в которой все хранится в локальной базе данных.
  • Если у вас не установлен Google Gears, вы получаете версию, которая сохраняет запросы в файле cookie и вообще не сохраняет полные результаты (отсюда немного более медленное время отклика), поскольку результаты слишком велики для сохранения в файле cookie.
Что хорошо в этой архитектуре, так это то, что вам не нужно выполнять проверки if (online) {} по всему магазину. Вместо этого в приложении есть одна проверка Gears, а затем используется правильный адаптер.


Использование локальной базы данных Gears

Одним из компонентов Gears является локальная база данных SQLite, встроенная и готовая к использованию. Существует простой API базы данных, который покажется вам знакомым, если вы ранее использовали API для серверных баз данных, таких как MySQL или Oracle.

Шаги по использованию локальной базы данных довольно просты:

  • Инициализировать объекты Google Gears
  • Получите объект фабрики базы данных и откройте базу данных
  • Начать выполнение SQL-запросов

Давайте быстро пройдемся по ним.


Инициализировать объекты Google Gears

Ваше приложение должно считывать содержимое /gears/samples/gears_init.js либо напрямую, либо путем вставки кода в свой собственный файл JavaScript. После запуска <script src="..../gears_init.js" type="text/JavaScript"></script> у вас есть доступ к пространству имен google.gears .


Получить объект фабрики базы данных и открыть базу данных
var db = google.gears.factory.create('beta.database', '1.0');
db.open('testdb');

Этот единственный вызов даст вам объект базы данных, который позволит вам открыть схему базы данных. Когда вы открываете базы данных, они регулируются одними и теми же правилами политики происхождения, поэтому ваша «testdb» не будет конфликтовать с моей «testdb».


Начать выполнение SQL-запросов

Теперь мы готовы отправлять SQL-запросы к базе данных. Когда мы отправляем запросы «выбрать», мы возвращаем набор результатов, который мы можем перебирать для получения желаемых данных:

var rs = db.execute('select * from foo where name = ?', [ name ]);

Вы можете работать с возвращенным результирующим набором следующими методами:

boolean isValidRow()
void next()
void close()
int fieldCount()
string fieldName(int fieldIndex)
variant field(int fieldIndex)
variant fieldByName(string fieldname)

Дополнительные сведения см. в документации по API модуля базы данных. (Примечание редактора: Google Gears API больше не доступен ).


Использование GearsDB для инкапсуляции низкоуровневого API

Мы хотели инкапсулировать и сделать более удобными некоторые общие задачи базы данных. Например,

  • Мы хотели иметь хороший способ протоколирования SQL, сгенерированного при отладке приложения.
  • Мы хотели обрабатывать исключения в одном месте, а не try{}catch(){} повсюду.
  • Мы хотели иметь дело с объектами JavaScript вместо наборов результатов при чтении или записи данных.

Чтобы справиться с этими проблемами общим способом, мы создали GearsDB , библиотеку с открытым исходным кодом, которая является оболочкой для объекта базы данных. Теперь мы покажем, как использовать GearsDB.

Начальная настройка

В нашем коде window.onload нам нужно убедиться, что таблицы базы данных, на которые мы полагаемся, находятся на месте. Если у пользователя установлены Gears при запуске следующего кода, он создаст объект GearsBaseContent :

content = hasGears() ? new GearsBaseContent() : new CookieBaseContent();

Затем мы открываем базу данных и создаем таблицы, если они еще не существуют:

db = new GearsDB('gears-base'); // db is defined as a global for reuse later!

if (db) {
  db.run('create table if not exists BaseQueries' +
         ' (Phrase varchar(255), Itemtype varchar(100))');
  db.run('create table if not exists BaseFeeds' + 
         ' (id varchar(255), JSON text)');
}

На данный момент мы уверены, что у нас есть таблица для хранения запросов и каналов. Код new GearsDB(name) будет инкапсулировать открытие базы данных с заданным именем. Метод run является оболочкой для метода execute более низкого уровня, но также обрабатывает вывод отладки на консоль и перехватывает исключения.


Добавление условия поиска

Когда вы впервые запустите приложение, у вас не будет никаких поисков. Если вы попытаетесь найти Nintendo Wii в продуктах, мы сохраним этот поисковый запрос в таблице BaseQueries.

Версия метода addQuery для Gears делает это, беря входные данные и сохраняя их через insertRow :

var searchterm = { Phrase: phrase, Itemtype: itemtype };
db.insertRow('BaseQueries', searchterm); 

insertRow берет объект JavaScript ( searchterm ) и обрабатывает его INSERTing в таблицу для вас. Он также позволяет вам определять ограничения (например, вставка блока уникальности более чем одного «Боба»). Однако большую часть времени вы будете обрабатывать эти ограничения в самой базе данных.


Получение всех условий поиска

Чтобы заполнить ваш список прошлых поисков, мы используем удобную оболочку select с именем selectAll :

GearsBaseContent.prototype.getQueries = function() {
  return this.db.selectAll('select * from BaseQueries');
}

Это вернет массив объектов JavaScript, которые соответствуют строкам в базе данных (например [ { Phrase: 'Nintendo Wii', Itemtype: 'product' }, { ... }, ...] .

В этом случае можно вернуть полный список. Но если у вас много данных, вы, вероятно, захотите использовать обратный вызов в вызове select, чтобы вы могли работать с каждой возвращаемой строкой по мере ее поступления:

 db.selectAll('select * from BaseQueries where Itemtype = ?', ['product'], function(row) {
  ... do something with this row ...
});

Вот некоторые другие полезные методы выбора в GearsDB:

selectOne(sql, args) Вернуть первый/один соответствующий объект JavaScript
selectRow(table, where, args, select) Обычно используется в простых случаях для игнорирования SQL
selectRows(table, where, args, callback, select) То же, что и selectRow, но для нескольких результатов.

Загрузка фида

Когда мы получим ленту результатов из Google Base, нам нужно сохранить ее в базе данных:

content.setFeed({ id: id, JSON: json.toJSONString() });

... which calls ...

GearsBaseContent.prototype.setFeed = function(feed) {
  this.db.forceRow('BaseFeeds', feed);
}

Сначала мы берем фид JSON и возвращаем его как строку с помощью метода toJSONString . Затем мы создаем объект feed и передаем его в метод forceRow . forceRow ВСТАВИТ запись, если она еще не существует, или ОБНОВИТ существующую запись.


Отображение результатов поиска

Наше приложение отображает результаты данного поиска на правой панели страницы. Вот как мы получаем ленту, связанную с поисковым запросом:

GearsBaseContent.prototype.getFeed = function(url) {
  var row = this.db.selectRow('BaseFeeds', 'id = ?', [ url ]);
  return row.JSON;
}

Теперь, когда у нас есть JSON для строки, мы можем eval() вернуть объекты:

eval("var json = " + jsonString + ";");

Мы приступаем к гонкам и можем начать загружать контент innerHTML из JSON на нашу страницу.


Использование хранилища ресурсов для автономного доступа

Поскольку мы получаем контент из локальной базы данных, это приложение также должно работать в автономном режиме, верно?

Ну нет. Проблема в том, что для запуска этого приложения вам необходимо загрузить его веб-ресурсы, такие как JavaScript, CSS, HTML и изображения. В настоящее время, если ваш пользователь предпринял следующие шаги, приложение может по-прежнему работать: запустить онлайн , выполнить поиск, не закрывать браузер, выйти в автономный режим . Это может работать, поскольку элементы все еще будут в кеше браузера. Но что, если это не так? Мы хотим, чтобы наши пользователи могли получить доступ к приложению с нуля, после перезагрузки и т. д.

Для этого мы используем компонент LocalServer и захватываем наши ресурсы. Когда вы захватываете ресурс (например, HTML и JavaScript, необходимые для запуска приложения), Gears сохраняет эти элементы, а также перехватывает запросы браузера на их возврат. Локальный сервер выступит в роли гаишника и вернет сохраненное содержимое из магазина.

Мы также используем компонент ResourceStore, который требует, чтобы вы вручную сообщали системе, какие файлы вы хотите захватить. Во многих сценариях вы хотите изменить версию своего приложения и разрешить обновления транзакционным способом. Набор ресурсов вместе определяет версию, и когда вы выпускаете новый набор ресурсов, вы хотите, чтобы ваши пользователи могли беспрепятственно обновлять файлы. Если это ваша модель, вы будете использовать ManagedResourceStore API.

Чтобы захватить наши ресурсы, объект GearsBaseContent будет:

  1. Настройте массив файлов, который необходимо захватить
  2. Создать локальный сервер
  3. Откройте или создайте новый ResourceStore
  4. Вызовите, чтобы захватить страницы в магазине
// Step 1
this.storeName = 'gears-base';
this.pageFiles = [
  location.pathname,
  'gears_base.js',
  '../scripts/gears_db.js',
  '../scripts/firebug/firebug.js',
  '../scripts/firebug/firebug.html',
  '../scripts/firebug/firebug.css',
  '../scripts/json_util.js',    'style.css',
  'capture.gif' ];

// Step 2
try {
  this.localServer = google.gears.factory.create('beta.localserver', '1.0');
} catch (e) {
  alert('Could not create local server: ' + e.message);
  return;
}

// Step 3
this.store = this.localServer.openStore(this.storeName) || this.localServer.createStore(this.storeName);

// Step 4
this.capturePageFiles();

... which calls ...

GearsBaseContent.prototype.capturePageFiles = function() {
  this.store.capture(this.pageFiles, function(url, success, captureId) {
    console.log(url + ' capture ' + (success ? 'succeeded' : 'failed'));
  });
}

Здесь важно отметить, что вы можете захватывать ресурсы только в своем собственном домене. Мы столкнулись с этим ограничением, когда попытались получить доступ к файлу JavaScript GearsDB непосредственно из исходного файла «gears_db.js» в его транке SVN. Решение, конечно, простое: нужно скачать любые внешние ресурсы и разместить их под своим доменом. Обратите внимание, что перенаправления 302 или 301 не будут работать, так как LocalServer принимает только коды сервера 200 (успешно) или 304 (не изменено).

Это имеет последствия. Если вы разместите свои изображения на images.yourdomain.com, вы не сможете их захватить. www1 и www2 не видят друг друга. Вы можете настроить прокси-серверы на стороне сервера, но это помешает разделению вашего приложения на несколько доменов.

Отладка автономного приложения

Отладка автономного приложения немного сложнее. Теперь есть больше сценариев для тестирования:

  • Я в сети с приложением, полностью работающим в кеше
  • Я в сети, но не заходил в приложение, и в кеше ничего нет
  • Я не в сети, но получил доступ к приложению
  • Я не в сети и никогда не заходил в приложение (не самое подходящее место!)

Чтобы упростить жизнь, мы использовали следующий шаблон:

  • Мы отключаем кеш в Firefox (или выбранном вами браузере), когда нам нужно убедиться, что браузер не просто берет что-то из кеша.
  • Мы отлаживаем с помощью Firebug (и Firebug Lite для тестирования в других браузерах); мы используем console.log() повсюду и обнаруживаем консоль на всякий случай
  • Мы добавляем вспомогательный код JavaScript в:
    • позвольте нам очистить базу данных и дать нам чистый лист
    • удалите захваченные файлы, поэтому при перезагрузке он выходит в Интернет, чтобы получить их снова (полезно, когда вы повторяете разработку;)

Виджет отладки отображается в левой части страницы, только если у вас установлен Gears. В нем есть выноски для очистки кода:

GearsBaseContent.prototype.clearServer = function() {
  if (this.localServer.openStore(this.storeName)) {
    this.localServer.removeStore(this.storeName);
    this.store = null;
  }
}

GearsBaseContent.prototype.clearTables = function() {
  if (this.db) {
    this.db.run('delete from BaseQueries');
    this.db.run('delete from BaseFeeds');
  }
  displayQueries();
}

Заключение

Вы можете видеть, что работать с Google Gears на самом деле довольно просто. Мы использовали GearsDB, чтобы сделать компонент базы данных еще проще, и использовали ручное хранилище ResourceStore, которое отлично сработало для нашего примера.

Область, на которую вы тратите больше всего времени, — это определение стратегии того, когда получать данные онлайн и как хранить их в автономном режиме. Важно потратить время на определение схемы базы данных. Если вам потребуется изменить схему в будущем, вам нужно будет управлять этим изменением, поскольку ваши текущие пользователи уже будут иметь версию базы данных. Это означает, что вам нужно будет поставлять код сценария с любым обновлением базы данных. Очевидно, это помогает свести это к минимуму, и вы можете попробовать GearShift , небольшую библиотеку, которая может помочь вам управлять ревизиями.

Мы также могли бы использовать ManagedResourceStore для отслеживания наших файлов со следующими последствиями:

  • Мы были бы хорошими гражданами и версионировали наши файлы, чтобы обеспечить возможность чистых обновлений в будущем.
  • Существует функция ManagedResourceStore, которая позволяет использовать псевдоним URL-адреса для другого фрагмента контента. Допустимым выбором архитектуры будет использование gears_base.js версии, отличной от Gears, и псевдонима, чтобы сама Gears загружала gears_base_withgears.js, который имел бы всю автономную поддержку.
Для нашего приложения мы решили, что проще иметь один интерфейс и реализовать его двумя способами.

Мы надеемся, что вы нашли приложение Gear up простым и увлекательным занятием! Присоединяйтесь к нам на форуме Google Gears , если у вас есть вопросы или приложение, которым вы хотите поделиться.