« Про java swing - часть 4 | Обучающая машина mysql (обновления от 04.03.2008) » |
Разработка веб-страниц с помощью google gears. Часть 4
Я завершаю рассказ о разработке веб-приложений в стиле google gears и хранящих часть данных не на сервере в internet, а на компьютере клиента. Кроме сухой теории я не забываю и про практику: мы создаем приложение “записная книжка”. От всех прочих записных книжек она отличается возможностью хранить информацию и изменять ее без прямого подключения к интернету: загрузив базу записей на свой компьютер, вы правите заметки, а после восстановления соединения с internet они “пачкой” сохраняются на сервер.В прошлый раз я остановился на том, что был создан php-скрипт, отбирающий данные из sqlite-базы (размещенной на сервере) и формирующий json-поток данных. Эти сведения, в свою очередь, должны быть загружены в браузер клиента и отображены в виде html-таблицы. Но перед тем как я приведу пример кода, визуализирующего загруженную информацию, необходимо подготовить “окружение” для веб-приложения. Под “окружением” я понимаю набор вспомогательных функций и переменных, которые позволят писать меньше кода и, главное, позволят плавно переключать стиль работы приложения с gears-стиля на классический стиль (ведь не у всех пользователей пока установлен gears-плагин). И первым шагом в создании подобной “среды” будет написание кода, который определяет, а есть ли в данном конкретном браузере поддержка gears или нет? Как именно это сделать я рассказывал еще в первой статье серии. В том случае, если поддержки gears нет, то следует выполнить загрузку данных в таблицу из internet и на этом все. Действия, которые срабатывают при наличии gears, гораздо сложнее.
Прежде всего, необходимо провести анализ того, в первый ли раз (самый-самый первый раз) пользователь открыл веб-страницу с нашей записной книжкой. Если это так, то необходимо создать в локальном хранилище gears две таблицы. Одна из них будет хранить значения элементов записной книжки (notes), а вторая – конфигурационные переменные приложения. Зачем, скажите Вы, еще одна таблица, и какие такие конфигурационные переменные? Смотрите, gears приложение работает либо с подключением в internet, либо без. Когда клиент открывает браузер, то мы должны проанализировать то, какой режим был активным в прошлый раз. Если активен был режим “online”, то следует загрузить информацию из internet, а если режим “offline”, то из локального хранилища gears. Определение последнего активного режима не представляет сложности: сохранить одно из этих двух слов можно где угодно, например, в cookie, которые я так критиковал в первой статье этой серии. Но мы пойдем другим путем: по мере роста приложения, добавления к нему всяких удобностей и полезностей нам все равно придется делать механизм хранения пользовательских настроек (например, цветовую палитру оформления внешнего вида, количество одновременно отображаемых на странице заметок из записной книжки и т.д.). Так почему бы не создать сейчас в дополнение к таблице notes (содержание записной книжки), так же и таблицу config (хранилище всевозможных опций настройки и конфигурационных переменных)? Плюс на этом легко показать методы чтения и записи информации в gears таблицы.
var db = null;
var tab = null;
var glob_jsonnotes = null;
var ICON_OFFLINE = {'background-image' : 'url(disconnect24.png)'};
var ICON_ONLINE = {'background-image' : 'url(connect24.png)'};
var msg_Offline = 'Данные загружены из локального хранилища';
var msg_Online = 'Данные загружены из internet';
$(document).ready(init);
function init (){
tab = $('#rows')[0];
if (!window.google || !google.gears){
$('#hint_switch').html('google gears не доступен');
$('#hint_mode').html('google gears не доступен');
loadFromInet();
}
else{
setup ();
if (getConfig('mode') == 'offline'){
$('#hint_mode').html (msg_Offline);
$('#hint_switch').css (ICON_OFFLINE);
loadFromLocal();
}
else{
$('#hint_mode').html (msg_Online);
$('#hint_switch').css (ICON_ONLINE);
loadFromInet(); }
$('#hint_switch').click (doSwitchMode);
}
}
function setup (){
db = google.gears.factory.create('beta.database', '1.0');
db.open('notebook');
db.execute('CREATE TABLE IF NOT EXISTS notes
(id INTEGER PRIMARY KEY, category varchar(100),
dateof datetime, title varchar(100), comment TEXT)');
db.execute('CREATE TABLE IF NOT EXISTS config
(id INTEGER PRIMARY KEY, variable varchar(100), value TEXT)');
}
Для работы с конфигурационным переменными я создал три функции: setConfig, getConfig, hasConfig. Их назначение это, соответственно, установка нового значения для некоторой переменной, получение значения этой переменной и проверка того существует ли такая переменная или нет. Все эти функции работают с объектом db (он был создан в функции setup). Когда мы выполняем запрос с помощью функции execute, то в качестве параметра передается не только строка SQL-запроса, но массив переменных. Каждая из этих переменных будет подставлена внутрь SQL-запроса вместо символа “?” (при этом если переменные содержат спец. символы, то они будут экранированы). Если был выполнен запрос SELECT, то отобранная информация будет возвращена в виде объекта ResultSet (в примерах выше это переменная rs). Для перемещения по записям используйте метод next объекта ResultSet. А для проверки того, что ваш цикл перебора записей все еще не до конца перебираемой таблицы, используйте isValidRow (она вернет true в случае, если текущая запись содержит информацию из таблицы). Значения полей текущей записи можно получить с помощью функций field или fieldByName. Первая из них вернет значение поля на основании его порядкового номера (задается как аргумент вызова функции). Если же порядок следования не известен, то применяйте функцию fieldByName: она принимает в качестве параметра имя того поля, значение которого нужно вернуть. И последнее: не забывайте закрыть объект ResultSet после окончания работы с ним (экономьте ресурсы).
function hasConfig (v){
var rs = db.execute ('select 1 from config where variable = ?', [v]);
var rez = rs.isValidRow();
rs.close ();
return rez;
}
function getConfig (v){
var rs = db.execute ('select value from config where variable = ?', [v]);
var value = null;
if (rs.isValidRow())
value = rs.fieldByName ('value');
rs.close ();
return value;
}
function setConfig (k, v){
if (hasConfig(v))
db.execute ('UPDATE config set value = ? where variable = ?', [v, k]);
else
db.execute ('INSERT INTO config(variable, value) values (?,?)', [k, v]);
}
function loadFromLocal (){
var rs = db.execute ('select * from notes');
var data = [];
while (rs.isValidRow()){
data.push ({
id:rs.fieldByName('id'),
category:rs.fieldByName('category'),
dateof : rs.fieldByName('dateof'),
title : rs.fieldByName('title'),
comment:rs.fieldByName('comment')
});
rs.next ();
}
rs.close ();
fillTableFromJSON (data);
}
$records = json_decode ($_REQUEST['records']);
$conn = new PDO('sqlite:notebook.db3');
$conn->query ('DELETE FROM notes');
$stmt = $conn->prepare("INSERT INTO notes (id, category, dateof, title, comment)
values (:id,:category, :dateof,:title,:comment)");
for ($i = 0; $i < count($records); $i++){
$r = $records[$i];
$stmt->bindValue(':id', $r->id, PDO::PARAM_INT);
$stmt->bindValue(':category', urldecode($r->category), PDO::PARAM_STR);
$stmt->bindValue(':title', urldecode($r->title), PDO::PARAM_STR);
$stmt->bindValue(':dateof', $r->dateof, PDO::PARAM_STR);
$stmt->bindValue(':comment', urldecode($r->comment), PDO::PARAM_STR);
$stmt->execute();
}
die (json_encode (array ('status'=>'true')));
function toJSON (x){
if (x == null)
return null;
if(typeof x != "object")
return '"'+encodeURIComponent(x)+'"';
var s = [];
if (x.constructor == Array){
for (var i in x) s.push (toJSON(x[i]));
return "["+s.join (',')+"]";
}
else{
for (var i in x) s.push ('"'+i+'":'+toJSON(x[i]));
return "{"+s.join (',')+"}";
}
}
function saveToInet (){
$.each($('tr:eq(1)', tab), function(i, n){
n.doEdit();
});
$.ajax(
{
type: "POST",
cache: false,
url: "save_json.php",
dataType : 'json',
data : {
records : toJSON(glob_jsonnotes)
},
success : loadFromInet,
error : function (e) {
alert ('Не возможно сохранить данные на сервер')
}
}
);
}
function loadFromInet (){
$.ajax(
{
type: "POST",
cache: false,
url: "select_json.php",
dataType : 'json',
success: function (e) {
fillTableFromJSON (e);
if(db)
saveToLocal();
},
error : function (e) {
alert ('Не возможно загрузить данные из Internet.')
}
}
);
}
function saveToLocal (){
db.execute ('delete from notes').close();
for (var i = 0; i < glob_jsonnotes.length; i++){
db.execute ('insert into notes (id, category, dateof, title, comment)
values(?,?,?,?,?)', [
glob_jsonnotes[i].id,
glob_jsonnotes[i].category,
glob_jsonnotes[i].dateof,
glob_jsonnotes[i].title,
glob_jsonnotes[i].comment]
);
}
}
function fillTableFromJSON(notes){
lastSavedRow = null;
while (tab.rows.length > 1)
ab.deleteRow (1);
glob_jsonnotes = notes;
var oRow = null;
var oCell = null;
for (var i = 0; i < notes.length; i++){
var n = notes [i] ;
// создаем очередную строку
oRow = tab.insertRow(i + 1);
$(oRow).attr ({id:i, id2: n.id});
// в нее помещаем три ячейки
oCell = oRow.insertCell(0);
oCell.innerHTML = n.category;
oCell = oRow.insertCell(1);
oCell.innerHTML = n.dateof;
oCell = oRow.insertCell(2);
oCell.innerHTML = n.title;
oRow.doEdit = doEdit;
$(oRow).click (doEdit);
}
}
Демо
<iframe src="../contents/data/mediawiki/__special__/html/gears_demo_1/index.html" width="1000" height="900"> нет поддержки iframe </iframe>
Примеры исходных текстов
https://github.com/study-and-dev-site-attachments/all-in-one/tree/master/php/gears_demo_1
На этом все. Надеюсь, что свою основную цель: заинтересовать читателя новой идеологией разработки веб-приложений и показать как легко (ладно, все же довольно тяжело) создавать gears-приложения, я выполнил. Еще Вас могут заинтересовать механизмы взаимодействия между google gears и flash/flex. Так, способность хранить и обновлять по требованию не только табличную информацию, но и произвольные файлы, была бы полезна для разработчиков flash-основанных игр с большим объемом графического наполнения.
« Про java swing - часть 4 | Обучающая машина mysql (обновления от 04.03.2008) » |