Скорая Компьютерная Помощь г. Калуга

Полный спектр компьютерных услуг!

Здравствуйте, гость ( Вход | Регистрация )

> Внимание!

  • Вся информация, расположенная в данном и других разделах форума получена из открытых источников (интернет-ресурсы, средства массовой информации, печатные издания и т.п.) и/или добавлена самими пользователями. Администрация форума предоставляет его участникам площадку для общения / размещения файлов / статей и т.п. и не несет ответственности за содержание сообщений, а также за возможное нарушение авторских, смежных и каких-либо иных прав, которое может повлечь за собой информация, содержащаяся в сообщениях.
Ремонт компьютеров в калуге Рекламное место сдается
 
Ответить в эту темуОткрыть новую тему
> Библиотека ExtJS/Sencha / Пишем MVC приложение на Ext JS 4 с возможностью офлайн работы
Decker
сообщение 12.2.2012, 22:26
Сообщение #1


Администратор
*****

Группа: Главные администраторы
Сообщений: 14349
Регистрация: 12.10.2007
Из: Twilight Zone
Пользователь №: 1



До недавнего времени при необходимости дать пользователю возможность работать офлайн, то есть без активного подключения к Интернет, приходилось разрабатывать толстые клиенты. При таком подходе пользователь вводит данные в приложение, которые сохраняются локально, затем, воткнув шнурок сети, жмет магическую кнопку Синхронизировать и, довольный собой, идет пить чай.



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



Сегодня этот вопрос решается элегантно — с помощью HTML5 с его локальным хранилищем (local storage), Ext JS 4 с возможностью прозрачно работать с этим хранилищем, и HTML5 кэшем приложений (Application Cache). Совокупность этих технологий позволяет реализовать следующую схему: при наличии сети статичные файлы (HTML/CSS/JS код и картинки) загружаются с сайта и мы работаем с серверной централизованной базой данных, при отсутствии сети статика загружается из Application Cache и мы работаем с локальным хранилищем, которое сохраняется в серверную БД при появлении доступа к Интернет. При этом без активного подключения по URL адресу страницы браузер отображает не ошибку доступа к сети, а функциональную систему, работающую с локальным хранилищем. Пояснения и рабочий пример (да не упадет мой vds под хаброэффектом) — под катом. Статья получилась немаленькая, но, надеюсь, весьма содержательная.




HTML5


Если Вы знакомы с HTML 5 – смело пропускайте эту главу, если нет – здесь вы найдете краткое описание используемых технологий.



Application Cache



Application Cache – кэш приложения, позволяет сохранять локально статичные файлы и использовать их без подключения к сети. Список файлов к кэшированию находится в файле-манифесте, адрес которого указывается в тэге html, например:



Mime-type файла-манифеста должен быть установлен в text/cache-manifest. Для веб сервера Apache, например, добавьте в конфигурационный файл:



AddType text/cache-manifest .appcache

Для Java добавьте в web.xml:



<mime-mapping>
<extension>appcache</extension>
<mime-type>text/cache-manifest</mime-type>
</mime-mapping>

Пример простейшего файла-манифеста:



CACHE MANIFEST
index.html
stylesheet.css
images/logo.png
scripts/main.js

Первая строка (CACHE MANIFEST) обязательна. Если Вы хотите добавить ресурсы, которые всегда требуют наличия сети – добавьте их после строки NETWORK:



CACHE MANIFEST
index.html

NETWORK:
login.php

Поближе познакомиться с форматом манифест-файлов можно здесь. Обновиться Application Cache может тремя способами: его может принудительно удалить пользователь в браузере, кэш обновится при обновлении файла-манифеста, и, наконец, кэш можно принудительно обновить из JavaScript.

Поддержка браузеров: Chrome 4+, Firefox 4+, Safari 4+, Opera 11+, IE 10, iOS 5+, Android 3+.



Local storage



Локальное хранилище в HTML5 позволяет сохранять данные локально, рекомендуемое ограничение на размер – 5 Mb, однако, в ряде браузеров может быть увеличено. Данные не пропадают после закрытия страницы или браузера.



Хранилище одно на домен, то есть одни и те же данные доступны с разных страниц Вашего сайта. Более того, Вы можете отслеживать изменения данных в хранилище со всех открытых одновременно страниц. Например, одна из страниц может вызвать изменение хранилища, которое приведет к изменениям на другой странице, открытой в соседней вкладке. Круто, не правда ли?



Работать с локальным хранилищем просто – это всего лишь key-value структура:



localStorage.setItem('name', 'Hello World!');
localStorage.getItem('name');
localStorage.removeItem('name');

Поддерживаемые браузеры: Chrome 5+, Firefox 3.6+, Opera 10+, Safari 4+, IE 8+.



<a name="localStorageExtJS4">
Локальное хранилище в Ext JS 4


Четвертый Ext JS позволяет работать с локальным хранилищем прозрачно, предоставляя для него отдельный прокси. Таким образом, Вы просто изменяете тип proxy с ajax на localstorage – и все данные Ext JS хранилища (store) загружаются не с сервера, а из локального хранилища браузера.




Погнали?


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



Структура файлов показана на скриншоте, для нас фактически важны файлы index.html, /app.js и каталог /app/.



Четвертая версия Ext JS предлагает использовать для разработки интерфейса модель MVC, будем следовать ей:

  • /app/model/ — директория моделей. Model в данном случае — описание полей хранилищ, то есть структура данных (аналог Record в третьей версии фреймворка) и, при необходимости, описание прокси. Прокси описывает способ получения данных — Ajax, JSON-P, local storage и т.д. Прокси может быть привязан к модели или хранилищу, использующему эту модель
  • /app/view/ — директория отображений. View — визуальные элементы (виджеты)
  • /app/controller/ — директория контроллеров. Controller — логика отображения, создание экземпляров моделей и т.д., то есть все, что связывает модели, хранилища и виджеты


Приложение будет состоять из двух виджетов — окна (window) с табицей (grid) внутри. Таблица в зависимости от наличия подключения к Интернет будет использовать серверное или локальное хранилище.



Итак, все, конечно, начинается с HTML:



<!-- Обратите внимание на указание файла-манифеста Application Cache -->
<html manifest="UsersApp.appcache">
<head>
<link rel="stylesheet" type="text/css" href="ext-4.0.7-gpl/resources/css/ext-all.css" />
<link rel="stylesheet" type="text/css" href="style.css" />
<script src="ext-4.0.7-gpl/ext-dev.js" type="text/javascript" charset=utf-8quot;utf-8"></script>
<script src="app.js" type="text/javascript" charset=utf-8quot;utf-8"></script>
</head>
<body style="padding: 25px;"><div id="console"><h2>Тестовое приложение</h2></div></body>
</html>



Здесь нет ничего особенного — указывается файл-манифест с указанием необходимых к кэшированию ресурсов, подключаются стили фреймворка и собственные стили, загружается ядро Ext JS и входная точка приложения app.js. В Ext JS 4 реализован механизм динамической загрузки, позволяющей на лету подключать необходимые JS файлы — таким образом, непосредственно в html прописывается только один JS файл самого приложения app.js.



Входной JavaScript файл прост:



// включаем динамическую загрузку JS файлов
Ext.Loader.setConfig({
enabled: true,
disableCaching: false,
paths: {UsersApp: 'app', Ext: 'ext-4.0.7-gpl/src'}
});

// указываем зависимости, которые необходимо предварительно загрузить
Ext.require(["UsersApp.view.win"]);

Ext.application({
name: 'UsersApp',
launch: function(){
Ext.create("UsersApp.view.win").show();
},
controllers: ["Main"]
});



Конструкция Ext.require() предназначена для указания зависимостей, то есть объектов, которые необходимо загрузить предварительно — до запуска приложения вызовом метода launch(). Вообще говоря, если такие зависимости не указать — при настроенном загрузчике Ext.Loader они загрузятся автоматически в процессе выполнения, но это может несколько снизить скорость работы и вообще не является кошерным, Ext.Loader в процессе такой неоптимальной загрузки выдаст сообщение в JS консоль браузера о целесообразности использования Ext.require().



Обратите внимание, что фактически имена объектов соответствуют пути, в которых хранятся эти объекты. Например, объект UsersApp.store.storeLocal хранится в директории /app/store/storeLocal.js, в то время как сопоставление имени приложения UsersApp имени физической директории app задано в настройках загрузчика Ext.Loader.



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



Итак, наше приложение создает виджет UsersApp.view.win и использует контроллер Main. Контроллер всегда вызывается до вызова метода launch(), он выполняет все необходимые подготовительные работы по связыванию компонентов системы.



Код окна UsersApp.view.win прост (здесь и далее приведены основные конфигурационные параметры, визуальные конфиги типа высоты-ширины и прочие маловажные моменты можно посмотреть в исходниках по ссылке в конце статьи):



Ext.define('UsersApp.view.win', {
extend: 'Ext.Window',
requires: ['UsersApp.view.grid'],
itemId: 'usersWindow',
layout: 'fit',
items: [
{ xtype: 'NamesGridPanel', itemId: 'NamesGrid' }
]
});



Здесь мы определяем класс UsersApp.view.win, расширяющий класс стандартного окна Ext.Window, и требующий для себя загрузки UsersApp.view.grid. Код таблицы:



Ext.define('UsersApp.view.grid', {
extend: 'Ext.grid.Panel',
alias: 'widget.NamesGridPanel',
requires: ['Ext.grid.plugin.CellEditing', 'Ext.form.field.*'],
itemId: 'usersGrid',
// конструктор таблицы - будет вызван при создании экземпляра
initComponent : function() {
// хранилище для таблицы будет установлено в контроллере

// устанавливаем возможность редактирования таблицы, для
// добавляем плагин CellEditing
this.cellEditing = Ext.create('Ext.grid.plugin.CellEditing', {
clicksToEdit: 2
});
this.plugins = this.cellEditing;

this.columns = this.columnsGet();
this.tbar = this.tbarGet();
// и не забываем вызывать родительский конструктор
this.callParent();
},

tbarGet: function(){
return[
{
text: 'Добавить',
iconCls: 'add',
handler: this._onUserAddClick
},
{
text: 'Удалить',
iconCls: 'delete',
handler: this._onUserDelClick
}
]
},

columnsGet: function(){
return [
{
text: 'Имя',
field: 'textfield',
dataIndex: 'firstName'
},
{
text : 'Фамилия',
field: 'textfield',
dataIndex: 'secondName'
}
]
},

_onUserAddClick: function(button){
// код метода добавления новых записей
},

_onUserDelClick: function(button){
// код метода удаления выделенной записи
}
})




Здесь ничего нового — создается класс таблицы, производится настройка колонок (columns), добавляется Toolbar с кнопками добавления/удаления записей (реализация этих методов скрыта для лучшей читаемости кода). Обратите внимание, что к таблице пока не привязано хранилище, это будет сделано в контроллере.



Пришло время создать два хранилища — серверное и локальное. Оба эти хранилища будут иметь одну модель (так как они содержат фактически данные одной структуры), но разные прокси. Модель описывается классом UsersApp.model.Names:



Ext.define('UsersApp.model.Names', {
fields: [{name: 'id', type: 'int', useNull: true}, {name: 'firstName'}, {name: 'secondName'}],
extend: 'Ext.data.Model',

// имя и фамилия не могут быть пустыми, запретим это
validations: [{
type: 'length',
field: 'firstName',
min: 1
},{
type: 'length',
field: 'secondName',
min: 1
}
]
});




Модель состоит из трех полей — идентификатора человека, его имени и фамилии. Для идентификатора указан целочисленный тип и использован параметр useNull, устанавливающий значение в null, если оно не может быть распознано как целочисленное (в противном случае оно будет приравнено к 0). Также для модели указываются валидаторы — имя и фамилия человека должны быть не короче 1 символа.



Создаем хранилище с серверной загрузкой данных:



Ext.define('UsersApp.store.store', {
extend: 'Ext.data.Store',

requires : ['UsersApp.model.Names', 'Ext.data.proxy.Ajax'],
model: 'UsersApp.model.Names',

proxy: {
type: 'ajax',
api: {
read: 'crud.php?act=read',
update: 'crud.php?act=update',
create: 'crud.php?act=create',
destroy: 'crud.php?act=delete'
},

reader: {
type: 'json',
root: 'names',
idProperty: 'id'
},
writer: {
type: 'json',
writeAllFields: false,
root: 'names'
}
}
});



Итак, модель с серверной загрузкой использует созданную модель, описывающую структуру данных, и Ajax прокси с настроенным «читателем» reader и «писателем» writer для чтения и записи данных соответственно. Параметр api указывает URL адреса, по которым будет обращаться Ext JS для операций чтения, обновления, добавления и удаления данных.



Код локального хранилища:



Ext.define('UsersApp.store.storeLocal', {
extend: 'Ext.data.Store',
requires : ['UsersApp.model.Names', 'Ext.data.proxy.LocalStorage'],
model: "UsersApp.model.Names",

proxy: {
type: 'localstorage',
id : 'Names'
}
});




В качестве прокси указываем localstorage — все данные будут загружаться из локального хранилища. В качестве id указываем уникальный идентификатор прокси, используемый для создания имен в key-value локальном хранилище.



Подведем итоги. У нас есть окно, содержащее в себе таблицу с настроенными колонками, но не подключенным хранилищем, есть два хранилища — серверное и локальное — с одной моделью. Нужно связать всё это добро в рабочее приложение! Этим займется контроллер:



Ext.define("UsersApp.controller.Main", {
extend: 'Ext.app.Controller',
requires: [
// утилита проверки наличия связи - "пингует" сервер
'UsersApp.Utils',
'UsersApp.store.storeLocal', 'UsersApp.store.store'
],

init: function(){
// метод getStore контроллера возвращает экземпляр хранилища,
// если он уже создан - или создаёт его
var storeLocal = this.getStore("storeLocal");
var store = this.getStore("store");
// вешаем обработчик на событие загрузки локального хранилища, он будет вызван
// сразу _после_ успешной загрузки
storeLocal.addListener('load', function(){
// локальное хранилище загружено - самое время
// проверить, есть ли связь с Интернет. UsersApp.Utils.ping принимает
// в качестве параметров callback функции
UsersApp.Utils.ping({
success: this._onPingSuccess, // Интернет есть
failure: this._onPingFailure // Интернета нет
}, this);
}, this);

// инициируем загрузку локальное хранилище
storeLocal.load();
},

_onPingSuccess: function(){
// сеть есть
var win = Ext.ComponentQuery.query('#usersWindow')[0];
var storeLocal = this.getStore('storeLocal');
var store = this.getStore('store');
var grid = win.getComponent('NamesGrid');

win.setTitle("Люди, онлайн")
// выясняем количество записей в локальном хранилище
localCnt = storeLocal.getCount();

// проверяем состояние локального хранилища,
// выясняя, необходима ли синхронизация
if (localCnt > 0){
// синхронизация нужна, добавляем записи
// по одной из локального хранилища
// в серверное
for (i = 0; i < localCnt; i++){
var localRecord = storeLocal.getAt(i);
var deletedId = localRecord.data.id;
delete localRecord.data.id;
store.add(localRecord.data);
localRecord.data.id = deletedId;
}
// сохраняем серверное хранилище
store.sync();
// очищаем локальное хранилище
for (i = 0; i < localCnt; i++){
storeLocal.removeAt(0);
}
}

store.load();
// подключаем к таблице серверное хранилище
grid.reconfigure(store);
grid.store.autoSync = true;
},
_onPingFailure: function(){
// сети нет, работаем с локальным хранилищем
var win = Ext.ComponentQuery.query('#usersWindow')[0];
var storeLocal = this.getStore('storeLocal');
var store = this.getStore('store');
var grid = win.getComponent('NamesGrid');

win.setTitle("Люди, офлайн")
// устанавливаем хранилище таблицы на локальное
grid.reconfigure(storeLocal);
grid.store.autoSync = true;
}
});




Много кода? Пройдем по порядку. В первую очередь для контроллера нужно указать нужные ему зависимости — в нашем случае это внутренние утилиты UsersApp.Utils и два наших хранилища. Метод init будет вызван при инициализации контроллера, то есть до запуска приложения, в нем должны быть выполнены все подготовительные действия. Мы создаем экземпляры хранилищ и загружаем локальное хранилище (оно работает вне зависимости от наличия доступа к сети), предварительно указав callback — после загрузки проверяем наличие сети вызовом метода UsersApp.Utils.ping. Функция ping посылает Ajax запрос к файлу на сервере, и в случае успеха вызывает callback функцию success, в противном случае вызывается failure.



Итак, если сеть есть, в серверное хранилище добавляются записи хранилища локального, после чего хранилище таблицы устанавливается на серверное. В случае отсутствия сети хранилище таблицы просто устанавливается на локальное.



Пример работы можно посмотреть здесь. Исходники (в качестве серверной части PHP, писал еще на Java — если кому надо, выложу) здесь.



PS. Ценителей с новым альбомом Руставели — и тёплых зимних программерских вечером Вам:)

Original source: habrahabr.ru (comments, light).

Читать дальше


--------------------

Вернуться в начало страницы
 
+Ответить с цитированием данного сообщения

Ответить в эту темуОткрыть новую тему
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 

Рекламное место сдается Рекламное место сдается
Текстовая версия Сейчас: 17.6.2025, 16:43
Рейтинг@Mail.ru
Яндекс.Метрика Яндекс цитирования