Javascript.RU

Google Gears в деталях

О Google Gears можно услышать довольно много чего интересного. Дескать, есть такой мегаплагин, добавляет кучу возможностей. И еще - с ним можно оптимизировать сайты.

Посмотрим подробнее, что это такое, для чего он нужен, что умеет.

Эта статья не ставит своей целью заменить документацию по Google Gears (которой, к сожалению, нет на русском языке). Ее цель - показать основные возможности Google Gears и существующие способы их применения, включая использование Gears в Joomla, Wordpress, Youtube.

UPDATE: в связи с тем, что технология умерла, статья имеет интерес разве что исторический.

Google Gears (или просто Gears) - плагин для браузера. На момент написания статьи работает во всех современных браузерах, кроме Safari 4 и Opera Desktop до 10 версии включительно.

Конечно, этот плагин надо ставить. Обычно браузер предлагает это сделать в виде выпадающего сообщения.

Gears добавляет в javascript:

  • Базу данных (SQLite)
  • Реальную многопоточность (точнее многопроцессность)
  • Возможность кросс-доменного общения для скриптов
  • Локальный сервер для работы оффлайн
  • Доступ из javascript к содержимому(только на чтение) выбранных пользователем файлов. Есть свой селектор для выбора нескольких файлов одновременно.
  • Взаимодействие с OS в плане создания ярлыков и drag'n'drop
  • Geolocation API для определения координат, включая устройства с GPS
  • Простейшие возможности по работе с изображениями в javascript.

Большинство этих возможностей требуют разрешения на использование от посетителя. Редкие, самые безопасные, сработают и без.

Разберем все это поподробнее.

В спецификации HTML 5 предусмотрено хранение и доступ к данным при помощи SQL: Offline Web Applications. Эта возможность поддерживается в Safari.

Google Gears пошел другим путем и предоставляет свой SQL-интерфейс Database API.

Основное отличие - в Gears запросы к базе синхронные, а в HTML 5 - асинхронные (назначается callback на получение результата). Это удобно, т.к. для асинхронности в gears можно использовать многопоточность там, где это нужно.

  • Удалены некоторые возможности SQLite - в основном это касается работы напрямую с файловой системой и системных опций.
  • Жестко задана кодировка: UTF-8.
  • Есть полнотекстовой поиск FTS2 (не для русского языка).
  • Файл с базой хранится внутри домашней директории пользователя. Таким образом, разграничение доступа между пользователями компьютера осуществляется средствами OS.
  • Одновременно в базу может писать только один процесс - иначе генерируется исключение.
  • Много баз данных. Но базы привязаны к текущему протоколу-порту-домену. То есть, базы с одинаковым именем для разных сайтов никак не пересекаются (Same Origin), и залезть с одного сайта в базу другого нельзя.

Доступ к базе требует разрешения от посетителя.

Следующий пример создаст базу и таблицу (если не существует) и добавит туда запись. Многократный запуск добавит много записей, который сохранятся в базе на локальном компьютере.

Пример: Записать в таблицу и сделать выборку
var db = google.gears.factory.create('beta.database');

db.open('js');
db.execute('create table if not exists test(phrase text, created datetime)');

var phrase = prompt("Введите фразу")

db.execute('insert into test values (?, datetime())', [phrase])
var rs = db.execute('select * from test order by created desc')

while (rs.isValidRow()) {
  alert(rs.field(0) + ' создано ' + rs.field(1))
  rs.next()
}
rs.close()
Пример: Удалить базу
var db = google.gears.factory.create('beta.database')
db.open('js')
db.remove()

Здесь, как и в дальнейших примерах, пропущена проверка ошибок выполнения. Например, посетитель может запретить Google Gears. При любых ошибках gears инициирует исключения, которые легко поймать конструкцией try..catch.

В SDK, которое предоставляет google, есть SQL-консоль для работы с базой.

Открыть консоль в ифрейме

API на сайте Google: http://code.google.com/apis/gears/api_database.html.

Многопоточность, а точнее, многопроцессность - пожалуй, самая интересная возможность Google Gears.

Каждый новый поток является отдельным процессом со своими переменными и средой выполнения.

Реализуется она при помощи WorkerPool API, которое представляет собой вольную переработку черновика Web Workers из HTML5.

Новые процессы запускаются вызовом createWorker/createWorkerFromUri класса WorkerPool.

// получить разрешение на доступ к WorkerPool
workerPool = google.gears.factory.create('beta.workerpool')

// создать новый процесс, загрузить для выполнения скрипт worker.js
int childId = workerPool.createWorkerFromUrl('/files/gears/worker.js')

Эти методы возвращают идентификатор, который мы можем использовать для общения с процессом.

Это делается методом workerPool.sendMessage:

workerPool.sendMessage("Ответь мне, процесс!", childId)

Чтобы принять сообщение, в скрипте дочернего процесса (worker.js) должен быть определен метод onmessage на неявно передаваемом в дочерний поток объекте google.gears.workerPool:

Пример: worker.js
google.gears.workerPool.onmessage = function(a,b, message) {
  var reply = "Пришло: "+message.body

  // ответить пославшему 
  google.gears.workerPool.sendMessage(reply, message.sender);
}

У метода onmessage - три аргумента. Первые 2 устарели и не нужны, а третий - собственно, сообщение message со свойствами:

body
Переданное в sendMessage сообщение. В примере - строка, а вообще - может быть любой объект
sender
ID процесса, пославшего сообщение. В примере - ID родителя, основного javascript-процесса
origin
URL, с которого работает пославший сообщение процесс, в форме ПРОТОКОЛ://ДОМЕН(PORT), причем порт отсутствует для стандартных портов 80 и 443

В ответ наш основной процесс может вывести то, что получил:

workerPool.onmessage = function(a,b, message) {
  alert("От потомка: "+message.body)
}

Итак, полный код примера:

workerPool = google.gears.factory.create('beta.workerpool')

// работает синхронно, т.е. к следующей строке кода поток будет готов 
childId = workerPool.createWorkerFromUrl('/files/gears/worker.js')

workerPool.onmessage = function(a,b, message) {
  alert("От потомка: "+message.body)
}

workerPool.sendMessage("Ответь мне, процесс!", childId)

Файл worker.js: /files/gears/worker.js.

Скрипт, загруженный createWorkerFromUrl с другого URL, будет выполняться в контексте безопасности этого URL, и при этом общаться с основным процессом.

Соответственно, можно наладить кросс-доменную коммуникацию без всяких трюков с iframe и проксированием, и даже без postMessage/XDomainRequest.

Об этом написано в секции документации Cross-origin workers WorkerPool API.

  • Дочерние процессы не имеют доступа к объектам document и window. Для рисования используйте основной javascript-процесс. А вместо XmlHttpRequest и setTimeout/setInterval, которые есть в window, Gears предоставляет сходные аналоги: HttpRequest и Timer.
  • Невозможна одновременная запись в базу с разных потоков. Один запишет, другие получат исключения. Соответственно, нужно закладывать в код обработку исключений и повторение SQL-запроса.
  • Метод createWorkerFromUrl не работает из дочерних процессов. В них можно использовать метод createWorker, которому передается не URL, а текст скрипта.

API на сайте Google: http://code.google.com/intl/ru/apis/gears/api_workerpool.html.

Локальный сервер - пожалуй, самая востребованная часть Google Gears API.
По смыслу - это не совсем тот сервер, о котором привык слышать веб-разработчик.

Он умеет две основные вещи.

  1. Загружать одну страницу или их список в локальное хранилище
  2. Неявно перехватывать обращения браузера к загруженным страницам и отдавать их из локального хранилища.

Разумеется, поддерживается и обновление локального хранилища.

Хранилище работает в рамках стандартного контекста безопасности Same Origin, так что сохранить локально можно только страницы с текущего протокола-домена-порта.

Для инициализации хранилища используется фабричный класс LocalServer

var localServer = google.gears.factory.create('beta.localserver')

Этот вызов требует разрешения от посетителя.

Основные методы LocalServer:

  • createStore / openStore / removeStore - для управления хранилищем типа ResourceStore
  • createManagedStore / openManagedStore / removeManagedStore - для управления хранилищем типа ManagedResourceStore

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

Если запрошенная посетителем страница есть в хранилище - запрос будет перехвачен LocalServer'ом, и она будет взята из локального хранилища, а не с удаленного сервера.

Функции LocalServer предусматривают также атрибут requiredCookie, который может ограничивать доступ к хранилищу и требовать наличия, либо отсутствия у посетителя заданного cookie.

Таким образом, можно например привязать хранилище к ID посетителя:

var localServer = google.gears.factory.create('beta.localserver')
// создать или открыть, если есть
var store = localServer.createManagedStore('data', 'userid=123')

Данные этого хранилища будут доступны только посетителю с cookie: userid=123.
Конечно, эта мера скорее нацелена на удобство, нежели на безопасность.
Но часто бывает, что у одного человека есть несколько аккаунтов, скажем, почтовых, и здесь ограничение requiredCookie может быть как нельзя более удобно.

У каждого хранилища есть атрибут enabled, который позволяет включать или отключать его, при этом не удаляя данные. Он наиболее полезен при отладке, рабочие приложения его обычно не используют.

Это хранилище целиком настраивается на клиенте. Оно позволяет получать с сервера произвольные страницы (или сразу много страниц).

Основной метод: capture(urlOrUrlArray, callback) - получает и сохраняет страницы с сервера.

Для примера посмотрим на таймер. Это обычный div, для которого javascript при помощи xmlhttprequest раз в секунду запрашивает с сервера страницу time.php с текущим серверным временем и выводит ее в div с id="timer".

Кликните, пожалуйста, на кнопку запуска для активации кода таймера:

// используется jQuery функция load, получающая данные
// при помощи XMLHttpRequest
setInterval(function() { $('#timer').load('/files/gears/time.php') }, 1000)

После того, как вы запустите следующий код, время застынет, т.к. страница time.php, содержимое которой ежесекундно попадает в div, станет отдаваться не с сервера, а с вашей машины.

Пример: Загрузить страницу в хранилище
var localServer = google.gears.factory.create('beta.localserver')
var store = localServer.createStore('time-store')
store.capture(
  "/files/gears/time.php", 
  function() { alert('Страница загружена в хранилище') }
)

А этот код удалит страницу из хранилища, после чего таймер опять заработает.

Пример: Удалить страницу из хранилища
var localServer = google.gears.factory.create('beta.localserver')
var store = localServer.createStore('time-store')
store.remove("/files/gears/time.php")
alert('Страница удалена из хранилища')

Для хранилища нет разницы, как запрашивается страница: напрямую или через xmlhttprequest - оно проверяет точное совпадение URL и возвращает страницу из хранилища, если она там есть.

Это, пожалуй, самый востребованный тип хранилища. У него есть два важных свойства:

  1. Он получает список URL для локального-хранения с сервера, в виде файла-"манифеста" в формате JSON. Кроме того, манифест содержит версию.
  2. Каждый URL в манифесте может содержать дополнительные опции поиска совпадения:
    ignoreQuery
    при поиске в хранилище параметры (все после вопр. знака) будут игнорироваться
    matchQuery
    Набор условий на параметры запроса, при совпадении страница будет возвращена из хранилища

После получения манифеста Gears скачивает все перечисленные там страницы и сохраняет их локально.

При каждом изменении версии манифеста все файлы будут скачаны заново.

Типичный способ использования:

  1. Создать хранилище:
    var localServer = google.gears.factory.create('beta.localserver')
    var store = localServer.createManagedStore('store')
    
  2. Запросить с сервера манифест:
    store.manifestUrl = '/files/gears/manifest.php'
    
  3. Запустить процесс обновления:

    store.checkForUpdate()
    

    Метод checkForUpdate работает асинхронно, ход обновления можно узнать, прикрепив обработчики к хранилищу для событий onprogress/onerror/oncomplete.

Если не запускать обновления явным образом при помощи checkForUpdate - Gears будет проверять обновления (запрашивать сервер) самостоятельно при каждом получении страницы из хранилища, но не чаще чем раз в 10 секунд.

В этом - важное отличие от обычного ResourceStore, которое обновляется только "вручную".

Следующий пример манифеста содержит основные используемые опции с комментариями к ним.

{
  // версия манифеста
  "betaManifestVersion": 2,

  // версия манифеста
  "version": "123456789",

  // список страниц,
  // относительные пути - идут относительно URL манифеста 
  "entries": [
      // загрузить ресурс с сервера и сохранить локально
      { "url": "main.js" }

      // локальный URL: main.html, удаленный - main_offline.html
      // при запросе main.html, LocalServer вернет main_offline.html
      { "url": "main.html", "src": "main_offline.html" },

      // вместо src указана опция redirect
      // LocalServer перенаправит браузер на main.html со статусом 302
      { "url": ".", "redirect": "main.html" },

      // выдавать локально сохраненный formHandler.html
      // при любых запросах вида formHandler.html?param1=..&param2...
      { "url": "formHandler.html", "ignoreQuery": true }
    ]
}

Более подробную информацию вы можете получить в документации к манифесту.

Как правило, набор статических файлов (картинок, стилей, скриптов) - не меняется в пределах версии сайта/релиза/"апа"/"деплоя на продакшн".

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

Все картинки, скрипты и т.п. Gears будет возвращать из соответствующего ManagedResourceStore.

Таким образом оптимизируют свои админки контент-системы, такие как Joomla, Wordpress и т.п.

Грамотно настроенный веб-сервер/приложение, использующие технологии Умное Кеширование и Версионность в Javascript/CSS, могут быть так же эффективны, и при этом не нужно плагина на клиенте.

Хотя для такой настройки сервера есть ряд ограничений, в частности, в URL лучше не иметь знак вопроса - некоторые браузеры не будут жестко кешировать такие страницы, она вполне применима и отлично работает. Без Gears. Более подробно - описано в самой статье.

  • Умное кеширование не требует плагина.
  • ManagedResourceStore почти не требует изменения в приложении, просто пишется небольшой дополнительный код для создания и обновления манифеста.
  • Умное кеширование позволяет обновить один ресурс, не затрагивая остальные
  • ManagedResourceStore позволяет:
    • указать опции сравнения: ignoreQuery/matchQuery
    • выбирать хранилище по имени в методе openManagedStore
    • ограничить хранилище указанием requiredCookie

Как видите, есть место для обоих технологий.

Кстати, о плагинах...

В Firefox к Gears имеют доступ расширения. Это можно использовать. Например, Greasemonkey может запускать пользовательские скрипты для сайта.

Такой скрипт может загружать статику в локальное хранилище вне зависимости от поддержки Gears сайтом.
Таким образом оптимизация сайта может быть сделана на стороне посетителя, независимо от поддержки сайтом разных полезных технологий

Все начинается с класса Desktop, метода openFiles.

Он позволяет посетителю выбрать несколько файлов "родным" селектором операционной системы. Например, в Windows можно выделить файлы мышкой.

Первый аргумент - функция, которой будут передан
ы выбранные файлы, вторым можно указать объект опций с параметрами filter/singleFile (см. OpenFileOptions).

var desktop = google.gears.factory.create('beta.desktop');
desktop.openFiles(
  function (files) { alert('Выбрано файлов: ' + files.length) }
)

Вызвать эту функцию можно, например, при клике на кнопку, то есть вообще без элемента типа <input type="file">.

Файлы передаются в виде массива объектов типа File.
Каждый файл имеет имя name и содержимое blob.

Доступ к содержимому файла предоставляет наибольший интерес.
Это объект типа Blob.

Gears предоставляет следующие возможности для манипуляции Blob.

  • Canvas API позволяет делать операции crop/resize над файлами-изображениями.
  • Blob API дает возможность вырезать часть Blob'а, а BlobBuilder - добавить данные к существующему Blob
  • Локальное хранилище ResourceStore предоставляет метод getAsBlob(url), возвращающий полученный ресурс в виде Blob. Таким образом, Blob можно получить из файла с сервера
  • HttpRequest (аналог XmlHttpRequest) умеет отправлять Blob на сервер и возвращать текущее состояние процесса загрузки ProgressEvent.

Сетевые операции, как и все операции gears, выполняются только в контексте текущего протокола-домена-порта (same origin).

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

Что это дает на практике?

Для картинок - можно обрабатывать их локально (на уровне crop/resize). Объект Canvas умеет делать операции над Blob, но не умеет показывать картинку на экране.

Поэтому показ картинки (и вообще, типичный способ добавления Blob в DOM) состоит из следующих шагов:

  1. Подготовить объект типа Blob
  2. Загрузить его в локальное хранилище типа ResourceStore, используя метод captureBlob(Blob blob, string url, string optContentType)
  3. Вставить на страницу элемент, который прочитает данные с адреса url, например <img src="url">

Продемонстрируем это на примере.

Следующий пример масштабирует выбранную картинку до размера 150x150 без сохранения пропорций и показывает результат. А может отправить получившееся изображение на сервер и т.п.

Конечно, не всем нужно масштабировать картинки, но пример - типовой и демонстрирует один из вариантов использования разных компонент gears вместе.

Контейнер для картинки:

function resize() {
  var desktop = google.gears.factory.create('beta.desktop');
  // опция singleFile для выбора не более одного файла
  desktop.openFiles(resizeAndShow,
    { singleFile : true, filter: ['image/jpeg', 'image/gif', 'image/png'] }
  )
}

function resizeAndShow(files) {
  // (1)
  var canvas = google.gears.factory.create('beta.canvas');
  canvas.decode(files[0].blob)
  canvas.resize(150,150)
  var blob = canvas.encode("image/jpeg")

  // (2)
  var localServer = google.gears.factory.create('beta.localserver')
  var store = localServer.createStore('store')
  // (2.1)
  var url = '/img.jpg?'+Math.random()
  store.captureBlob(blob, url, "image/jpeg")  
  // (3)
  $('#resized').css('background-image', 'url('+url+')')
  $('#resized')[0].scrollIntoView()

  // (4)
  setTimeout(function (){store.remove(url)},0)
}

Функция resize запускает выбор файла, а resizeAndShow выполняет основные операции.
Рассмотрим ее более подробно.

  1. Декодировать картинку из содержимого первого (в нашем случае - единственного) выбранного файла, отмасштабировать ее, а затем обратно закодировать в Blob.
  2. Инициализовать хранилище
    1. Положить картинку в хранилище по временному адресу, чтобы затем браузер отобразил ее, обратившись по нему. Используется временный URL.

      Чтобы избежать возможного кеширования, он выбирается случайным образом.

      Хранилище ResourceStore возвратит картинку только при полном совпадении url, так что конфликта с обычным изображением img.jpg не возникнет.

      При записи картинки в хранилище явно указан тип содержания: image/jpeg

  3. Использовать URL с картинкой в качестве фона для контейнера. Вторая строчка прокручивает страницу, чтобы сделать контейнер видимым.
  4. Мы использовали хранилище с временным URL для показа картинки. После того, как картинка будет показана, стоит почистить хранилище. Оно хранится на компьютере посетителя, и не стоит забивать его излишним мусором.

    Если убрать setTimeout, то remove выполнится тут же, до того как браузер отрендерит background-image. Использование setTimeout откладывает вызов, так что он произойдет после показа картинки.

Как видно из примера, компоненты Gears отлично пригнаны друг к другу и замечательно взаимодействуют между собой.

Youtube давно использует Google Gears для закачивания больших файлов: http://www.youtube.com/my_videos_multiupload.
При помощи Gears это реализуется очень удобно.

Используемые компоненты Gears:

HttpRequest
Для коммуникации с сервером.
Desktop API
Для выбора файла посетителем.
Blob API
Для доступа к части файла, т.е. большой файл отправляется на сервер по частям.
Worker API
Для создания отдельного потока, который загружает файл.

Файлы, реализующие закачку, не обфусцированы и доступны напрямую по ссылкам (на всякий случай сделал зеркало).

Общая схема загрузки файла на Youtube такова.

  1. Пользователь выбирает файлы при помощи desktop.openFiles, что дает нам содержимое файла в виде объекта Blob
  2. Для отправки файла на сервер инициализуется объект up.SingleUploader.
  3. Файл пересылается по частям, максимальный размер части задан константой up.CHUNK_SIZE. Пересылку каждой части выполняет метод upload_.
  4. Внутри upload_ первый, а затем и каждый следующий кусок выдирается из файла в виде строки вызовом Blob API: file.blob.slice. Текущая позиция в файле сохраняется в свойстве offset.
  5. Подготавливаются стандартные заголовки загрузки файла, включая Content-Disposition, а также добавляются заголовки с авторизационными данными и идентификатор пересылки.
  6. Данные файла, заголовки и каллбэки отправляются на выполнение Worker'у, который отсылает запрос на сервер
  7. По мере выполнения запроса HttpRequest вызывает событие onprogress. Это событие Worker через каллбэк транслирует основному процессу, который рисует progress bar.
  8. По завершении закачки Worker вызывает каллбэк основного процесса onUploadComplete_, который, если все в порядке, снова вызывает upload_ (см. 4).
  9. Метод upload_, как только увидит (сравнив offset с размером файла), что пересылка подошла к концу, вызывает finishUpload_
  10. Серверная часть отслеживает пересылку по идентификатору пересылки и собирает большой файл по частям.

Конкретные детали, если они вам понадобятся, вы без труда поймете из кода. Все реализуется корректно и без каких-либо хаков.

Надо сказать, идея Youtube не нова. Такой способ отсылки запросов на сервер можно использовать и со стандартным XMLHTTPRequest. Другое дело, что передать содержимое файла XMLHTTPRequest средствами стандартного javascript нельзя.

Да и остальные компоненты Gears здесь как нельзя кстати.

На этом описание возможностей Google Gears подошло к концу. Все основные возможности мы разобрали.

Остались всего несколько небольших.

  • Создание shortcut на рабочем столе с указанием иконки через URL. Делается desktop.createShortcut (Google demo)
  • Получение текущего местоположения позиции вызовом Geolocation API - работает, в том числе, и для мобильных устройств.
  • Принятие Drag'n'drop объектов на уровне OS (перенос объектов в окошко браузера) при помощи Desktop API.
    Эта часть HTML5 частично реализована в Firefox 3.5, но Gears позволяет читать содержимое перенесенных файлов.

Разобраться с этими возможностями вы легко сможете из примеров. Пока я не встречал их удачного применения в известных приложениях, но кто знает - может быть, вы будете первым.

Теперь и правда все. Gear it up, dude!

Следующие ссылки содержат основную информацию о разработке и Gears API.


Автор: itrelease (не зарегистрирован), дата: 6 октября, 2009 - 12:55
#permalink

Отличная статья, спасибо!


Автор: Rpsl (не зарегистрирован), дата: 8 октября, 2009 - 12:09
#permalink

Спасибо, отличная статья, а где еще можно посмотреть примеры?


Автор: Боб (не зарегистрирован), дата: 8 октября, 2009 - 12:29
#permalink

Очень-очень интересно написано! Отличная технология.


Автор: Lexmirnov (не зарегистрирован), дата: 8 октября, 2009 - 13:45
#permalink

Большое спасибо, Илья, очень интересно, недолго и по делу ) Посоветую друзьям.


Автор: Гость (не зарегистрирован), дата: 8 октября, 2009 - 19:30
#permalink

Автор: Assargin (не зарегистрирован), дата: 20 октября, 2009 - 10:05
#permalink

Статья просто великолепна, таких очень мало. Хочется узнать у народа, кто-нибудь делал что-нибудь стоящее на основе этой технологии? Как я понял, она вполне подходит, чтобы создавать не просто веб-приложения, а приложения с локальным хранилищем данных, например, для тех, у кого слабый или дорогой интернет, чтобы не постоянно быть онлайн, а типа регулярно "обновляться", скидывать наработанные в оффлайне данные на сервер? Я какбэ прав?))


Автор: special K, дата: 19 ноября, 2009 - 13:18
#permalink

Большое спасибо, а то бы я долго не смог врубиться что это за штука)


Автор: Mars787 (не зарегистрирован), дата: 22 сентября, 2010 - 15:39
#permalink

ок, пошел тестить, хватило бы только мозгов)


Автор: ba1ans (не зарегистрирован), дата: 17 января, 2011 - 11:40
#permalink

Спасибо за статью. Появился один вопрос - почему youtube не просит у пользователя установки плагина Gears, хотя использует Gears API?


Автор: Гость (не зарегистрирован), дата: 27 января, 2011 - 15:41
#permalink

function resizeAndShow(files) {
// (1)
var canvas = google.gears.factory.create('beta.canvas');

canvas.decode(files[0].blob)
canvas.resize(150,150)
var blob = canvas.encode("image/jpeg")

// (2)
var localServer = google.gears.factory.create('beta.database')
var store = localServer.createStore('database')
// (2.1)
var url = '/img.jpg?'+Math.random()
store.captureBlob(blob, url, "image/jpeg")
// var rs = db.execute('select * from Demo order by Timestamp desc');

if (!google.gears.factory || !db) {
return;
}
//db.execSQL("CREATE TABLE IF NOT EXISTS "+dbTable+"(image BLOB);");
//InputStream xmlInputStream = this.getResources().openRawResource(R.drawable.abc);
//byte[] bb = (xmlInputStream.toString()).getBytes();
// db.execSQL("INSERT INTO "+ dbTable + "(image)" + " VALUES (\""+bb+"\");");

/// db.execute('insert into Demo (t) values (?) where Timestamp=?',[getAsBlob( blob)]), rs.field(1)]);

// db.execute('insert into Demo (getAsBlob( blob)) values (?)where Timestamp=?',[ rs.field(1)]);

db.execute('insert into Demo (getAsBlob( blob)) values (?)',canvas.encode("image/jpeg"));

// (3)
$('#resized').css('background-image', 'url('+url+')')
$('#resized')[0].scrollIntoView()

// (4)
//setTimeout(function (){store.remove(url)},0)
}

Для того, чтобы добавить в ImageList картинки необходимо: 1. Установить размер добавляемых картинок.


Автор: Гость (не зарегистрирован), дата: 27 января, 2011 - 15:41
#permalink

function resizeAndShow(files) {
// (1)
var canvas = google.gears.factory.create('beta.canvas');

canvas.decode(files[0].blob)
canvas.resize(150,150)
var blob = canvas.encode("image/jpeg")

// (2)
var localServer = google.gears.factory.create('beta.database')
var store = localServer.createStore('database')
// (2.1)
var url = '/img.jpg?'+Math.random()
store.captureBlob(blob, url, "image/jpeg")
// var rs = db.execute('select * from Demo order by Timestamp desc');

if (!google.gears.factory || !db) {
return;
}
//db.execSQL("CREATE TABLE IF NOT EXISTS "+dbTable+"(image BLOB);");
//InputStream xmlInputStream = this.getResources().openRawResource(R.drawable.abc);
//byte[] bb = (xmlInputStream.toString()).getBytes();
// db.execSQL("INSERT INTO "+ dbTable + "(image)" + " VALUES (\""+bb+"\");");

/// db.execute('insert into Demo (t) values (?) where Timestamp=?',[getAsBlob( blob)]), rs.field(1)]);

// db.execute('insert into Demo (getAsBlob( blob)) values (?)where Timestamp=?',[ rs.field(1)]);

db.execute('insert into Demo (getAsBlob( blob)) values (?)',canvas.encode("image/jpeg"));

// (3)
$('#resized').css('background-image', 'url('+url+')')
$('#resized')[0].scrollIntoView()

// (4)
//setTimeout(function (){store.remove(url)},0)
}

Для того, чтобы добавить в ImageList картинки необходимо: 1. Установить размер добавляемых картинок.


Автор: Гость (не зарегистрирован), дата: 27 января, 2011 - 15:42
#permalink

function resizeAndShow(files) {
// (1)
var canvas = google.gears.factory.create('beta.canvas');

canvas.decode(files[0].blob)
canvas.resize(150,150)
var blob = canvas.encode("image/jpeg")

// (2)
var localServer = google.gears.factory.create('beta.database')
var store = localServer.createStore('database')
// (2.1)
var url = '/img.jpg?'+Math.random()
store.captureBlob(blob, url, "image/jpeg")
// var rs = db.execute('select * from Demo order by Timestamp desc');

if (!google.gears.factory || !db) {
return;
}
//db.execSQL("CREATE TABLE IF NOT EXISTS "+dbTable+"(image BLOB);");
//InputStream xmlInputStream = this.getResources().openRawResource(R.drawable.abc);
//byte[] bb = (xmlInputStream.toString()).getBytes();
// db.execSQL("INSERT INTO "+ dbTable + "(image)" + " VALUES (\""+bb+"\");");

/// db.execute('insert into Demo (t) values (?) where Timestamp=?',[getAsBlob( blob)]), rs.field(1)]);

// db.execute('insert into Demo (getAsBlob( blob)) values (?)where Timestamp=?',[ rs.field(1)]);

db.execute('insert into Demo (getAsBlob( blob)) values (?)',canvas.encode("image/jpeg"));

// (3)
$('#resized').css('background-image', 'url('+url+')')
$('#resized')[0].scrollIntoView()

// (4)
//setTimeout(function (){store.remove(url)},0)
}

Для того, чтобы добавить в ImageList картинки необходимо: 1. Установить размер добавляемых картинок.


Автор: Гость (не зарегистрирован), дата: 27 января, 2011 - 15:42
#permalink

function resizeAndShow(files) {
// (1)
var canvas = google.gears.factory.create('beta.canvas');

canvas.decode(files[0].blob)
canvas.resize(150,150)
var blob = canvas.encode("image/jpeg")

// (2)
var localServer = google.gears.factory.create('beta.database')
var store = localServer.createStore('database')
// (2.1)
var url = '/img.jpg?'+Math.random()
store.captureBlob(blob, url, "image/jpeg")
// var rs = db.execute('select * from Demo order by Timestamp desc');

if (!google.gears.factory || !db) {
return;
}
//db.execSQL("CREATE TABLE IF NOT EXISTS "+dbTable+"(image BLOB);");
//InputStream xmlInputStream = this.getResources().openRawResource(R.drawable.abc);
//byte[] bb = (xmlInputStream.toString()).getBytes();
// db.execSQL("INSERT INTO "+ dbTable + "(image)" + " VALUES (\""+bb+"\");");

/// db.execute('insert into Demo (t) values (?) where Timestamp=?',[getAsBlob( blob)]), rs.field(1)]);

// db.execute('insert into Demo (getAsBlob( blob)) values (?)where Timestamp=?',[ rs.field(1)]);

db.execute('insert into Demo (getAsBlob( blob)) values (?)',canvas.encode("image/jpeg"));

// (3)
$('#resized').css('background-image', 'url('+url+')')
$('#resized')[0].scrollIntoView()

// (4)
//setTimeout(function (){store.remove(url)},0)
}

Для того, чтобы добавить в ImageList картинки необходимо: 1. Установить размер добавляемых картинок.


Автор: Гость (не зарегистрирован), дата: 27 января, 2011 - 15:42
#permalink

function resizeAndShow(files) {
// (1)
var canvas = google.gears.factory.create('beta.canvas');

canvas.decode(files[0].blob)
canvas.resize(150,150)
var blob = canvas.encode("image/jpeg")

// (2)
var localServer = google.gears.factory.create('beta.database')
var store = localServer.createStore('database')
// (2.1)
var url = '/img.jpg?'+Math.random()
store.captureBlob(blob, url, "image/jpeg")
// var rs = db.execute('select * from Demo order by Timestamp desc');

if (!google.gears.factory || !db) {
return;
}
//db.execSQL("CREATE TABLE IF NOT EXISTS "+dbTable+"(image BLOB);");
//InputStream xmlInputStream = this.getResources().openRawResource(R.drawable.abc);
//byte[] bb = (xmlInputStream.toString()).getBytes();
// db.execSQL("INSERT INTO "+ dbTable + "(image)" + " VALUES (\""+bb+"\");");

/// db.execute('insert into Demo (t) values (?) where Timestamp=?',[getAsBlob( blob)]), rs.field(1)]);

// db.execute('insert into Demo (getAsBlob( blob)) values (?)where Timestamp=?',[ rs.field(1)]);

db.execute('insert into Demo (getAsBlob( blob)) values (?)',canvas.encode("image/jpeg"));

// (3)
$('#resized').css('background-image', 'url('+url+')')
$('#resized')[0].scrollIntoView()

// (4)
//setTimeout(function (){store.remove(url)},0)
}

Для того, чтобы добавить в ImageList картинки необходимо: 1. Установить размер добавляемых картинок.


Автор: Гость (не зарегистрирован), дата: 27 января, 2011 - 15:42
#permalink

function resizeAndShow(files) {
// (1)
var canvas = google.gears.factory.create('beta.canvas');

canvas.decode(files[0].blob)
canvas.resize(150,150)
var blob = canvas.encode("image/jpeg")

// (2)
var localServer = google.gears.factory.create('beta.database')
var store = localServer.createStore('database')
// (2.1)
var url = '/img.jpg?'+Math.random()
store.captureBlob(blob, url, "image/jpeg")
// var rs = db.execute('select * from Demo order by Timestamp desc');

if (!google.gears.factory || !db) {
return;
}
//db.execSQL("CREATE TABLE IF NOT EXISTS "+dbTable+"(image BLOB);");
//InputStream xmlInputStream = this.getResources().openRawResource(R.drawable.abc);
//byte[] bb = (xmlInputStream.toString()).getBytes();
// db.execSQL("INSERT INTO "+ dbTable + "(image)" + " VALUES (\""+bb+"\");");

/// db.execute('insert into Demo (t) values (?) where Timestamp=?',[getAsBlob( blob)]), rs.field(1)]);

// db.execute('insert into Demo (getAsBlob( blob)) values (?)where Timestamp=?',[ rs.field(1)]);

db.execute('insert into Demo (getAsBlob( blob)) values (?)',canvas.encode("image/jpeg"));

// (3)
$('#resized').css('background-image', 'url('+url+')')
$('#resized')[0].scrollIntoView()

// (4)
//setTimeout(function (){store.remove(url)},0)
}

Для того, чтобы добавить в ImageList картинки необходимо: 1. Установить размер добавляемых картинок.


Автор: Гость (не зарегистрирован), дата: 27 января, 2011 - 15:42
#permalink

function resizeAndShow(files) {
// (1)
var canvas = google.gears.factory.create('beta.canvas');

canvas.decode(files[0].blob)
canvas.resize(150,150)
var blob = canvas.encode("image/jpeg")

// (2)
var localServer = google.gears.factory.create('beta.database')
var store = localServer.createStore('database')
// (2.1)
var url = '/img.jpg?'+Math.random()
store.captureBlob(blob, url, "image/jpeg")
// var rs = db.execute('select * from Demo order by Timestamp desc');

if (!google.gears.factory || !db) {
return;
}
//db.execSQL("CREATE TABLE IF NOT EXISTS "+dbTable+"(image BLOB);");
//InputStream xmlInputStream = this.getResources().openRawResource(R.drawable.abc);
//byte[] bb = (xmlInputStream.toString()).getBytes();
// db.execSQL("INSERT INTO "+ dbTable + "(image)" + " VALUES (\""+bb+"\");");

/// db.execute('insert into Demo (t) values (?) where Timestamp=?',[getAsBlob( blob)]), rs.field(1)]);

// db.execute('insert into Demo (getAsBlob( blob)) values (?)where Timestamp=?',[ rs.field(1)]);

db.execute('insert into Demo (getAsBlob( blob)) values (?)',canvas.encode("image/jpeg"));

// (3)
$('#resized').css('background-image', 'url('+url+')')
$('#resized')[0].scrollIntoView()

// (4)
//setTimeout(function (){store.remove(url)},0)
}

Для того, чтобы добавить в ImageList картинки необходимо: 1. Установить размер добавляемых картинок.


Автор: Гость (не зарегистрирован), дата: 27 января, 2011 - 15:42
#permalink

function resizeAndShow(files) {
// (1)
var canvas = google.gears.factory.create('beta.canvas');

canvas.decode(files[0].blob)
canvas.resize(150,150)
var blob = canvas.encode("image/jpeg")

// (2)
var localServer = google.gears.factory.create('beta.database')
var store = localServer.createStore('database')
// (2.1)
var url = '/img.jpg?'+Math.random()
store.captureBlob(blob, url, "image/jpeg")
// var rs = db.execute('select * from Demo order by Timestamp desc');

if (!google.gears.factory || !db) {
return;
}
//db.execSQL("CREATE TABLE IF NOT EXISTS "+dbTable+"(image BLOB);");
//InputStream xmlInputStream = this.getResources().openRawResource(R.drawable.abc);
//byte[] bb = (xmlInputStream.toString()).getBytes();
// db.execSQL("INSERT INTO "+ dbTable + "(image)" + " VALUES (\""+bb+"\");");

/// db.execute('insert into Demo (t) values (?) where Timestamp=?',[getAsBlob( blob)]), rs.field(1)]);

// db.execute('insert into Demo (getAsBlob( blob)) values (?)where Timestamp=?',[ rs.field(1)]);

db.execute('insert into Demo (getAsBlob( blob)) values (?)',canvas.encode("image/jpeg"));

// (3)
$('#resized').css('background-image', 'url('+url+')')
$('#resized')[0].scrollIntoView()

// (4)
//setTimeout(function (){store.remove(url)},0)
}

Для того, чтобы добавить в ImageList картинки необходимо: 1. Установить размер добавляемых картинок.


Автор: RUVATA, дата: 23 августа, 2011 - 10:02
#permalink

FF 3.6.20
Gears 0.5.36.0
примеры не работают
"google is not defined"

Даже Google Chrome 13.0.782.112 ("google is not defined")


Автор: Гость (не зарегистрирован), дата: 28 апреля, 2012 - 10:26
#permalink

А почему технология умерла?


Автор: FrancNet (не зарегистрирован), дата: 15 января, 2014 - 23:47
#permalink

да тоже интересно почему умерла технология?


Автор: Гость (не зарегистрирован), дата: 8 сентября, 2015 - 17:36
#permalink

Поздно прочитал


Автор: Гость (не зарегистрирован), дата: 8 сентября, 2015 - 17:38
#permalink

Автор: Гость (не зарегистрирован), дата: 8 сентября, 2015 - 17:40
#permalink

!


Автор: Гость (не зарегистрирован), дата: 8 сентября, 2015 - 17:40
#permalink

!


Отправить комментарий

Приветствуются комментарии:
  • Полезные.
  • Дополняющие прочитанное.
  • Вопросы по прочитанному. Именно по прочитанному, чтобы ответ на него помог другим разобраться в предмете статьи. Другие вопросы могут быть удалены.
    Для остальных вопросов и обсуждений есть форум.
P.S. Лучшее "спасибо" - не комментарий, как все здорово, а рекомендация или ссылка на статью.
Содержание этого поля является приватным и не предназначено к показу.
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Разрешены HTML-таги: <strike> <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <u> <i> <b> <pre> <img> <abbr> <blockquote> <h1> <h2> <h3> <h4> <h5> <p> <div> <span> <sub> <sup>
  • Строки и параграфы переносятся автоматически.
  • Текстовые смайлы будут заменены на графические.

Подробнее о форматировании

CAPTCHA
Антиспам
1 + 7 =
Введите результат. Например, для 1+3, введите 4.
 
Текущий раздел
Поиск по сайту
Реклама
Содержание

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

Дерево всех статей

Последние комментарии
Последние темы на форуме
Forum