PersistJS и TaffyDB. Как поселить почти настоящую базу данных в браузер. Часть 2

June 9, 2009Comments Off on PersistJS и TaffyDB. Как поселить почти настоящую базу данных в браузер. Часть 2

Ключевой частью любого приложения, и веб-приложение не исключение, является хранение данных. Помимо того, что данные нужно сохранять между перезапусками браузера или компьютера клиента, не менее важным является наличие средств быстрого и удобного поиска информации. И эти средства есть. Есть, средства предусмотренные стандартом html5, пусть и не поддерживаемые пока всеми браузерами. Есть средства, созданные как несовместимые расширения, доступные только в определенных версиях браузеров. В крайнем случае, есть возможность организовать хранение данных с помощью flash. И для того, чтобы рядовой веб-разработчик не занимался складыванием паззла из десятка возможных альтернатив технологий хранения данных и поддерживающих их браузеров, появились javascript-библиотеки, предлагающие унифицированный интерфейс взаимодействия с доступными в браузере технологиями хранения данных.

В прошлый раз я начал рассказ об одном из самых известных javascript-библиотек, прячущих за собой различные средства хранения данных внутри браузера. Библиотека persistjs представляет собой “обертку” над следующими технологиями 'gears', 'localstorage', 'whatwg_db', 'globalstorage', 'flash', 'ie', 'cookie'. Последовательно перебирая эти методики, persistjs находит ту, которая доступна у конкретного клиента. Дальнейшая работа с библиотекой не зависит от того, какой механизм хранения данных используется внутри persistjs. Важным моментом является то, что перечисленные выше 8 технологий хранения данных далеко неравнозначны и отличаются как по объему хранящейся информации, так и по алгоритмам хранения и поиска. Проще говоря, всегда помните, что из всех возможных механизмов хранения данных у клиента в худшем случае может быть включена поддержка только cookie, а, значит, в вашем распоряжении будет не более чем 4 килобайта места для хранения данных. Также не стоит даже близко сравнивать возможности хранилища данных построенных на использовании настоящей реляционной базы данных sqlite (вариант, используемый в технологиях google gears и whatwg_db). Помните, что “наименьшим общим знаменателем” является тройка функций для того, чтобы сохранить информацию, загрузить и удалить ее. В следующем примере я покажу, как можно подключить к html-файлу библиотеку persistjs (предварительно загруженную с сайта http://pablotron.org/software/persist-js/). Кроме файла persist.js я подключил еще два javascript-файла: swfobject.js и gears_init.js. Эти файлы необходимы для того, чтобы persistjs мог организовать хранение данных с помощью gears или с помощью flash-хранилища. Технически, чтобы perisistjs мог сохранить данные в таблице gears необходимо выполнение двух условий: наличие установленного плагина google gears и подключение к html-файлу скрипта gears_init.js. Файлы gears_init.js и swfobject.js изначально присутствуют в дистрибутиве persistjs и вам не нужно ни откуда их дополнительно скачивать. Если вы никогда не встречались с swfobject.js, то это значит, что наверняка вы никогда не работали с flash, т.к. более известного решения для автоматизации внедрения в html-странички роликов flash просто нет. Кроме того, если речь идет об браузере chrome, то приятным сюрпризом будет не только наличие предустановленного google gears, но и отсутствие необходимости подключения файла gears_init.js (это как раз и не удивительно, учитывая, что и google gears и chrome – продукты одной компании). Обратите внимание на то, что порядок подключения javascript-файлов важен: так в следующем примере я должен подключать файл persist.js всегда последним: после того как подключил swfobject.js и gears_init.js. Причина этого тривиальна: сразу после того как код persistjs был загружен он начинает проверку доступных в браузере клиента технологий хранения и если скрипты swfobject.js и gears_init.js не доступны, то соответствующие им технологии хранения вычеркиваются из списка анализируемых.
  1. <html>
  2. <head>
  3.   <script type='text/javascript' src='swfobject.js'></script>
  4.   <script type='text/javascript' src='gears_init.js'></script>
  5.   <script type='text/javascript' src='persist.js'></script>
  6. </head>
  7.   <body onLoad=”initDb()”>
  8.     Some content
  9.   </body>
  10. </html>
Конечно, вызывать функцию инициализации базы данных persistjs внутри обработчика события onLoad не правильно, и лучше всего было бы вызвать функцию initDb при наступлении события onDomReady. Т.к., наверняка, по ходу создания веб-приложения вы будете использовать какую-нибудь javascript-библиотечку, например, jquery или Yahoo UI. То задача привязки к событию “дерево DOM загружено” не составит для вас сложности. Вот, к примеру, как будет выглядеть такой код для YUI:
  1. YAHOO.util.Event.onDOMReady(initDb);
Или для jquery:
  1. $(document).ready(initDB);
В любом случае, выполнять инициализацию persistjs можно только, когда html-код страницы был загружен т.к. это необходимо для таких стратегий хранения данных как ‘flash’ и ‘ie’. Теперь я приведу код функции initDB:
  1. var storage = null;
  2.  
  3. function initDB(){
  4.   storage = new Persist.Store('storage_1', { swf_path: 'assets/persist.swf' });
  5.   alert (‘храним данные с помощью ’+Persist.type); 
  6. }
Первым параметром при создании объекта Persist.Store служит имя хранилища. Вы можете создать неограниченное количество хранилищ, но на имена каждого из них накладывается ограничение: имя должно начинаться с латинской буквы и не содержать спец. символов. Т.к. за persistjs скрывается целых восемь стратегий хранения данных, то для некоторых из них необходимо указывать специальные настройки. Почти единственной полезной настройкой является параметр swf_path – путь к flash-ролику, с помощью которого persistjs будет сохранять информацию, если стратегии 'gears', 'localstorage', 'whatwg_db', 'globalstorage' не подошли. Сам файл flash-ролика приложен к дистрибутиву persistjs и занимает всего лишь половинку. Параметры domain, expires и path нужны для “последней” стратегии cookie и задают домен, для которого будут доступны сохраненные данные, затем идет срок хранения данных и каталог на сервере (только файлы, размещенные в рамках данного каталога, будут иметь доступ к сохраненной информации). К сожалению, но вопрос наличия для других технологий хранения данных способа указать время жизни информации, наличие средств “расшарить информацию” между несколькими поддоменами сети, остался без положительного ответа. Также нет положительного ответа и для вопроса: можно ли информацию, сохраненную на веб-странице с помощью браузера “A” увидеть, если открыть ту же страничку в браузере “B”?. Единственным исключением здесь является методика хранения данных с помощью flash¬ролика. В этом случае вы можете сохранить данные на веб-странице, например, в firefox, а затем открыть ту же страницу в opera и увидеть сохранные ранее данные.

Вернемся назад к описанию шагов выполняемых persistjs после вызова “new Persist.Store”. Здесь persistjs создаст хранилище (тип доступных хранилищ был определен еще раньше, сразу после завершения загрузки файла persist.js). Когда я запустил пример в браузере chrome, то появилось диалоговое окно (см. рис. 1 и 2),





в котором у меня спросили можно ли разрешить сайту работать с gears. Если ответить на вопрос положительно, то persistjs создаст простенькую табличку из двух полей “ключ” и его “значение”. Если инициализировать persistjs в браузере opera 10, то на страницу будет внедрен flash-ролик. Браузер ie 8 и mozilla firefox 3 будут хранить данные с помощью domStorages (на рис. 4



показано то, как можно в настройках IE включать и отключать domStorages). Если вы приверженец safari то у вас данные будут храниться в базе данных whatwg_db (устройство таблички для хранения информации точно такое, как и в случае с google gears). На рис. 3



я показал то, как выглядит окно настроек safari, где вы можете увидеть список всех баз данных, которые были зарегистрированы на вашем компьютере; там же вы можете изменять размер пространства для каждой из баз данных. Небольшим недостатком persistjs является отсутствие возможности выполнить проверку того, какие технологии хранения данных доступны, без того, чтобы создавать хранилище. C другой стороны, вы можете воспользоваться следующим несложным тестом:
  1. alert ('localStorage = ' + window.localStorage);
  2. alert ('sessionStorage = ' + window.sessionStorage);
  3. alert ('safari whatwg_db = ' + window.openDatabase);
  4. alert ('globalStorage = ' + window.globalStorage);
  5. alert ('IE userData = ' + window.ActiveXObject);
  6. alert ('gears = ' + (window.google && window.google.gears));
Еще одним недостатком persistjs является то, что список и последовательность, в которой persistjs перебирает альтернативные методики хранения данных нельзя менять. Фактически, если вы решили повысить приоритет domStorages и поставить его перед gears-базой данных, то вам придется править код самой библиотеки persist.js (хотя разобраться в 10 килобайтах грамотно написанного кода совсем не сложно). Еще одна недоработка в persistjs будет понятна, если рассмотреть следующий сценарий. Предположим, что в браузере есть формальная поддержка google gears и в этом случае браузер спросит у клиента разрешения на сохранение данных. Но если клиент ответит отказом, то persistjs не продолжит перебор других методик сохранения данных и окажется в неопределенном положении. Продолжая тему недоработок в persistjs можно отметить потенциальную проблему “гонок потоков” при работе с flash-хранилищем. Дело в том, как только вы вызвали метод “new Persist.Store”, persistjs внедряет на html-страничку flash ролик, функции которого в последствии и вызываются всякий раз, когда нужно сохранять или загружать информацию. Но хотя swf-файл занимает всего половинку килобайта, есть маленькая вероятность того, что вы вызовете метод чтения или сохранения данных в хранилище еще до того, как flash-ролик был загружен. И это не говоря о том, что flash-трафик мог быть элементарно “срезан” корпоративным proxy-сервером; и вы никогда об этом не узнаете. Еще одним недостатком работы с flash-хранилищем является некорректная обработка ситуации, когда информация, которую вы хотите сохранить, не умещается в 100 килобайтах. 100 кб – это ограничение по-умолчанию на размер flash-хранилища. В практике, если вам не хватает выделенного места, то всегда можно попросить пользователя выделить еще немножко пространства. Чтобы отчетливее понимать о чем я говорю, советую открыть пример исходного кода файла PersistStore.as и посмотрите на код функции “set” (сохранение информации в хранилище). Технически, когда вы внутри flash-ролика вызываете метод flush для сохранения изменений, то flash-player проверит не будет ли текущий лимит места для хранения sharedobject превышен. И если это так, то метод flush вернет как значение специальную строку “pending”, а на экране, на фоне flash-ролика, появится всплывающее окошко с настройками flash, где вам предложат увеличить место, отведенное для хранения данных (см. рис. 5).



В последствии вы можете просмотреть список flash-хранилищ, которые были созданы различными сайтами у вас на компьютере, с помощью размещенного на сайте adobe “Диспетчера параметров adobe flash” (http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager07.html). Интересен тот факт, что flash player покажет окошко с запросом на увеличение размера хранилища только в том случае, если размер ролика менее чем 215 на 138 пикселей. Если flash-ролик спрятан не за счет нулевого размера, а за счет позиционирования за границы экрана – то диалоговое окно вы также не увидите. Как вывод: недостатков в реализации “моста” между persistjs и flash хватает. И если вы предполагаете, что основная аудитория вашего сайта – это посетители, у которых в браузере включен flash, а наличие альтернативных методик хранения данных таких gears или domStorages маловероятно. То лучше всего отказаться от использования persistjs и познакомиться с проектом dojo (http://dojotoolkit.org/). Dojo – огромная по возможностям (и соответственно, по размеру) javascript библиотека, предлагающая и средства для создания сложных интерфейсов пользователя и общения с сервером (ajax). Есть в составе dojo и средства для организации хранения данных внутри flash-контейнера, а для того, чтобы “совсем маленький” модуль flash storage не потянул за собой как зависимости сто тысяч javascript-файлов, мы можем собрать версию dojo под свои потребности. Как это сделать описано в dojo-книге по адресу http://www.dojotoolkit.org/book/dojo-book-0-9/part-4-meta-dojo/package-system-and-custom-builds Если инструкция покажется сложной, и вы не захотите вникать в подробности работы dojo только ради одной функции, то вы можете воспользоваться наработками команды http://fullajax.ru/. Они взяли из dojo только необходимый функционал для работы с flash-хранилищем и оформили это в виде отдельной библиотеки SRAX.Storage.

Возвращаясь к примеру работы с persistjs: после того как вы создали объект storage, у вас в наличии пять функций: load, save, get, set, delete. Первые две из них используются только в том редком случае, если клиент пользуется браузером IE 6 или 7, и в его наличии только технология ‘ie’ (подробный рассказ о том, как внутри устроена технология userData behavior и ее альтернативы можно найти по адресу http://javascript.ru/tutorial/storage). А вот пример работы функций set, get и delete:
  1. // сохраняем информацию
  2. storage.set('userName', 'Ronald');
  3.  
  4. // загружаем ее
  5. storage.get('userName, function(if_ok, value) {
  6.   if (if_ok) 
  7.      alert ('userName = ' + value);
  8. });
  9.  
  10. // и удаляем
  11. storage.remove('userName, function(if_ok, old_value) {
  12.   if (if_ok) 
  13.      alert ('value was deleted = ' + value);
  14.   else 
  15.     alert ('old value cannot be found');
  16. });
С использованием функции “set” вопросов не возникает т.к. она принимает всего два параметра: имя переменной и значение, которое нужно сохранить. А вот пример работы с функциями “get” и “delete” сложнее. Дело в том, что некоторые из технологий хранения данных предполагают работу в асинхронном режиме. Т.е. вызов метода “загрузить информацию” не сразу же возвращает результат, а начинает выполнять свою работу параллельно с основным потоком команд приложения. А для того, чтобы сигнализировать о завершении работы вызывает специальную “callback” или функцию обратного вызова или обработчик события “операция завершена”. Так в примере callback-функция для операции чтения (get) принимает два параметра: первый из них – is_ok – обозначает признак успешности операции, а второй параметр хранит собственно загруженную из хранилища информацию. В случае операции delete, callback-функция также принимает первым параметром признак того смог ли persistjs найти запись подлежащую удалению и если это так, то второй параметр функции хранит старое значение удаляемой переменной.

Сегодня я планировал завершить рассказ о совместном использовании persistjs и taffydb, но увлекся и не успел перейти к рассказу о том, как можно сохранять не простые пары “ключ и значение”, но сложные объекты с помощью taffydb. А может это и к лучшему, т.к. в следующий раз я смогу полностью посвятить статью не только taffydb, но и ее “подружке ” jlinq.