В дополнение статей oddy про архитектуру RIA-приложений на основе ExtJS хочу предложить свой, альтернативный подход к данной проблеме. Он состоит в использовании ZendFramework-подобного каркаса xFrame, написанного на JavaScript. Под катом — описание ключевых элементов системы, ссылки на демо приложение и исходный код.
История xFrame началась с выполнения одного заказа. Клиент пожелал реализовать на своем сайте систему просмотра фин. отчетности. Он был уже знаком с ExtJS (на ней реализована админка сайта), библиотека ему понравилась и он захотел отображать отчеты в виде ExtJS Grid'ов. Дополнительными требованиями к системе было разграничение прав доступа на просмотр отчетов разным группам пользователей. Таким образом, передо мною стояла задача реализации RIA-приложения с аутентификацией и распределением доступа к ресурсам.
Админ панель сайта поддерживала такие возможности, но выполнена она в виде WebDesktop'а, что клиенту не подходило. Поэтому разработку системы я начал с чистого листа. В то время я как раз читал книгу К. Зерваса «Web 2.0: создание приложений на PHP» и проникся духом описанного в ней Zend Framework. И мне захотелось создать именно что-нибудь похожее на ZF. И у меня получилось. Базовые возможности были реализованы в течении двух дней, еще пару дней заняла отладка. Позже, после реализации самой системы отчетов, я, в свободное от работы время довел свой каркас до состояния, в котором его можно презентовать и решил представить его на суд общественности.
Окончательного названия для каркаса я еще не придумал, пока именую его «кодовым» именем xFrame (eXtjs FRAMEwork). xFrame реализирует:
Модели в xFrame реализируются на основе Ext.data.Record. Класс (конструктор) модели создаются при помощи метода Ext.data.Record.create([]). Если необходимо инкапсулировать в модель дополнительные методы, используется Ext.override():
Так же, для каждой модели приложение автоматически создает два «статических» (вызываемых из конструктора, а не из экземпляра) метода createStore(add_cfg) (создает config-объект для хранилища данных с полями модели) и createOne(hash) (создает экзмепляр модели и загружает в поля из хэш-объекта).
Контроллеры реализированы по аналогии с ZF. Для создания контроллера с необходимо создать класс <имя_контроллера>Controller, для создания действия — метод <имя_действия>Action. В обработчик действия передаются три параметра: хэш с переданными в дейстия параметрами, ссылка на объект приложения, ссылка на объект ViewPort'a.
Функционал представления реализуеться полностью на компонентах ExtJS. При инициализации приложения создается область вывода (viewport) которая рендерится в указанное место на странице. Область вывода меняется при смене контроллеров/действий. Все остальное содержимое остается неизменным. Перед вызовом метода действия производится очистка области вывода о компонентов, само действие может добавить на нее необходимые компоненты, после чего приложение перерисует viewport.
На структурном уровне в системе выделяются следующие ключевые компоненты:
Класс Application — основа приложения. В этом классе инкапсулируются все остальные компоненты системы.
Часто используемые методы:
При создании приложения необходимо унаследовать данный класс и преопределить нужные методы инициализации:
Класс Application.Acl — система полномочий. По принципу действия похож на Zend_Acl: позволяет определить набор ресурсов, ролей и полномочия на доступ каждой роли к каждому ресурсу.
Часто используемые методы:
Пример использования:
Application.Router — управляемый роутинг. Позволяет создавать «красивые» ссылки вместо /controller/action/params по умолчанию.
Методы:
Примеры:
Application.Identity — данные пользователя. Класс не реализирован в полном объеме. В демо приложении он хранит данные сессии в cookies при помощи Ext.state.Manager. При логине собственно аутнентификации не происходит, объект устанавливает в true свойство isLogged и сохраняет имя пользователя и роль user. В будущем планируется сделать полный аналог Zend_Auth с различными adapter'ами для проверки аутентификации.
По этой ссылке Вы можете посмотреть демо-приложение. Оно реализирует просмотр ленты блога, вход в систему, доступ к «закрытому» разделу. Исходный код доступен на скачивание здесь (альтернативная ссылка, без дистрибутива ExtJS). Работает с ExtJS 3.1, поддерживаются все современные браузеры (IE8, Opera10, FF3+, Safari 4, Chrome).
Принимаются любые предложения/пожелания/идеи/замечания/исправления. Вообще у меня желание сделать из каркаса полноценный open-source фреймворк для RIA-приложений. Если среди Вас, уважаемые читатели Хабра, есть заинтересовавшиеся, а так же есть желание и свободное время — милости просим в команду проекта.
UPD. Перенесено в «Библиотека ExtJS»
История xFrame началась с выполнения одного заказа. Клиент пожелал реализовать на своем сайте систему просмотра фин. отчетности. Он был уже знаком с ExtJS (на ней реализована админка сайта), библиотека ему понравилась и он захотел отображать отчеты в виде ExtJS Grid'ов. Дополнительными требованиями к системе было разграничение прав доступа на просмотр отчетов разным группам пользователей. Таким образом, передо мною стояла задача реализации RIA-приложения с аутентификацией и распределением доступа к ресурсам.
Админ панель сайта поддерживала такие возможности, но выполнена она в виде WebDesktop'а, что клиенту не подходило. Поэтому разработку системы я начал с чистого листа. В то время я как раз читал книгу К. Зерваса «Web 2.0: создание приложений на PHP» и проникся духом описанного в ней Zend Framework. И мне захотелось создать именно что-нибудь похожее на ZF. И у меня получилось. Базовые возможности были реализованы в течении двух дней, еще пару дней заняла отладка. Позже, после реализации самой системы отчетов, я, в свободное от работы время довел свой каркас до состояния, в котором его можно презентовать и решил представить его на суд общественности.
Окончательного названия для каркаса я еще не придумал, пока именую его «кодовым» именем xFrame (eXtjs FRAMEwork). xFrame реализирует:
- принятую в ZF организацию файловой системы и именование классов (Имя класса повторяет путь к нему в ФС, слэши заменяются на точки)
- стандартные пространствах имен для пользовательских компонентов системы. Для контроллеров — Application.controllers, для компонентов — Application.components, для моделей — Application.models.
- принятую в ZF концепцию разделение библиотечного кода и кода приложения. Код xFrame помещается в одну папку (например js/lib/xframe), а код приложения — в другую (например js/xframe-app)
- паттерн model-view-controller
- стандартную систему ссылок ZF (http://<app_url>/controller/action/param1/value1/param2/value2/....)
- URI-fragment адресацию (http://<app_url>/#token), что позволяет сохранять состояния приложения в ссылках, сохраняя удобство AJAX-приложения, работающего без перезагрузки страницы
- архитектуру Model-view-controller
Модели в xFrame реализируются на основе Ext.data.Record. Класс (конструктор) модели создаются при помощи метода Ext.data.Record.create([]). Если необходимо инкапсулировать в модель дополнительные методы, используется Ext.override():
- Application.models.News = Ext.data.Record.create([
- {name: 'Id', type:'int'},
- {name: 'Permalink', type:'string'},
- {name: 'Title', type:'string'},
- {name: 'Brief', type:'string'},
- {name: 'Text', type:'string'},
- {name: 'DateCreated', type:'date', dateFormat: 'Y-m-d'},
- ]);
-
- Ext.override(Application.models.News, {
- getLink : function () {
- return App.route({ permalink : this.get("Permalink") }, "news");
- },
- getDateCreated : function () {
- return this.get("DateCreated").format('m/d/Y');
- }
- });
* This source code was highlighted with Source Code Highlighter.
Так же, для каждой модели приложение автоматически создает два «статических» (вызываемых из конструктора, а не из экземпляра) метода createStore(add_cfg) (создает config-объект для хранилища данных с полями модели) и createOne(hash) (создает экзмепляр модели и загружает в поля из хэш-объекта).
Контроллеры реализированы по аналогии с ZF. Для создания контроллера с необходимо создать класс <имя_контроллера>Controller, для создания действия — метод <имя_действия>Action. В обработчик действия передаются три параметра: хэш с переданными в дейстия параметрами, ссылка на объект приложения, ссылка на объект ViewPort'a.
- Application.controllers.FrontController = Ext.extend(Application.controllers.Abstract, {
- .......................................................................................................................
- newsAction : function (params, app, panel) {
- panel.add({
- xtype: 'Application.components.NewsViewer',
- newsPermalink : params.permalink
- });
- },
* This source code was highlighted with Source Code Highlighter.
Функционал представления реализуеться полностью на компонентах ExtJS. При инициализации приложения создается область вывода (viewport) которая рендерится в указанное место на странице. Область вывода меняется при смене контроллеров/действий. Все остальное содержимое остается неизменным. Перед вызовом метода действия производится очистка области вывода о компонентов, само действие может добавить на нее необходимые компоненты, после чего приложение перерисует viewport.
На структурном уровне в системе выделяются следующие ключевые компоненты:
Класс Application — основа приложения. В этом классе инкапсулируются все остальные компоненты системы.
- App.acl — ссылка на Application.Acl (система полномочий)
- App.router — ссылка на Application.Router (настраиваемый роутинг)
- App.auth — ссылка на Application.Identity (данные пользователя)
- App.sessionManager — ссылка на менеджер сессии
- App.viewport — ссылка на область вывода
Часто используемые методы:
- request — выполняет AJAX-запрос
- log — выводит переменную на консоль
- redirect — перенаправление на заданный токен (с изменением URI)
- forward — перенаправление на заданный токен (без изменениея URI)
При создании приложения необходимо унаследовать данный класс и преопределить нужные методы инициализации:
- var App = new (Ext.extend(Application, {
- renderTo: 'app',
- autoRun : true ,
- initAcl : function () {
- this.constructor.superclass.initAcl.call(this);
- this.acl.addRole(new Application.Acl.Role(....................));
- ......................................................................
- },
- initRouter : function () {
- this.constructor.superclass.initRouter.call(this);
- this.router.addRoute(new Application.Router.Route(....................................}));
- }
- }));
* This source code was highlighted with Source Code Highlighter.
Класс Application.Acl — система полномочий. По принципу действия похож на Zend_Acl: позволяет определить набор ресурсов, ролей и полномочия на доступ каждой роли к каждому ресурсу.
Часто используемые методы:
- addRole ( new Application.Acl.Role (rolename[, baseRole])) — добавляет роль. Если при создании роли указывается базовая роль, роль наслоедует все ее полномочия
- addResource ( new Application.Acl.Resource (resourcename) ) — добавляет ресурс. Наборы контроллеров и действий тоже являются ресурсами, но добавляются они автоматически
- allow / deny (role, resource) — разрешает/запрещает доступ роли role к ресурсу resource. В случе, если один из параметров (или оба не указаны) полномочия задаются глобально. allow() — дает доступ по умолчанию ко всему для всех, allow(null, resource) — дает доступ к resource для всех, allow(role) — дает доступ ко всему для role
- setErrorRedirect (role, resource, {controller:'error_controller', action:'error_action'}) — указывет действия, вызываемое в случае если у роли role нет полномочия на доступ к ресурсу resource
Пример использования:
- initAcl : function () {
- this.constructor.superclass.initAcl.call(this);
- // добавляем роль guest
- this.acl.addRole(new Application.Acl.Role("guest"));
- // добавляем роль user которая наследует полномочия роли guest
- this.acl.addRole(new Application.Acl.Role("user", "guest"));
- // по умолчанию закрываем всем доступ на все
- this.acl.deny(null);
- this.acl.deny("guest");
- this.acl.deny("user");
- // разрешения для роли guest
- this.acl.allow("guest","front/index");
- this.acl.allow("guest","front/test");
- this.acl.allow("guest","front/news");
- this.acl.allow("guest","front/action1");
- this.acl.allow("guest","front/action2");
- this.acl.allow("guest","user/login");
- this.acl.allow("guest","user/noaccess");
- // роли user дорполнительно даем доступ к user/logout/ restricted/index и запрещаем логинится
- this.acl.allow("user","user/logout");
- this.acl.allow("user","restricted/index");
- this.acl.deny ("user","user/login");
- this.acl.setErrorRedirect(null, null, {controller:'user',action:'noaccess'});
- }
* This source code was highlighted with Source Code Highlighter.
Application.Router — управляемый роутинг. Позволяет создавать «красивые» ссылки вместо /controller/action/params по умолчанию.
Методы:
- addRoute ( new Application.Router.Route(name, path, params) ) — добавляет новое правило маршрутизации. Класс Application.Router.Route похож на Zend_Controller_Router_Route_Regex — в пути прописываются плейсхолдеры для параметров, а в параметрах — регулярные выражения и праметры константы
- route (params, route) — генерирует ссылку с использованием правила route и параметров params
Примеры:
- .........................................................................................
- this.router.addRoute(new Application.Router.Route("news", "news/:permalink", { "controller" : 'front', "action" : 'news', "permalink" : "[\\w\\d\\-]+" }));
- .........................................................................................
- getLink : function () {
- return App.route({ id : this.get("Id"), permalink : this.get("Permalink") }, "news");
- }
- .........................................................................................
* This source code was highlighted with Source Code Highlighter.
Application.Identity — данные пользователя. Класс не реализирован в полном объеме. В демо приложении он хранит данные сессии в cookies при помощи Ext.state.Manager. При логине собственно аутнентификации не происходит, объект устанавливает в true свойство isLogged и сохраняет имя пользователя и роль user. В будущем планируется сделать полный аналог Zend_Auth с различными adapter'ами для проверки аутентификации.
По этой ссылке Вы можете посмотреть демо-приложение. Оно реализирует просмотр ленты блога, вход в систему, доступ к «закрытому» разделу. Исходный код доступен на скачивание здесь (альтернативная ссылка, без дистрибутива ExtJS). Работает с ExtJS 3.1, поддерживаются все современные браузеры (IE8, Opera10, FF3+, Safari 4, Chrome).
Принимаются любые предложения/пожелания/идеи/замечания/исправления. Вообще у меня желание сделать из каркаса полноценный open-source фреймворк для RIA-приложений. Если среди Вас, уважаемые читатели Хабра, есть заинтересовавшиеся, а так же есть желание и свободное время — милости просим в команду проекта.
UPD. Перенесено в «Библиотека ExtJS»