Архитектура ExtJS приложений: подход со стороны Zend Framework

Библиотека ExtJS/Sencha
В дополнение статей oddy про архитектуру RIA-приложений на основе ExtJS хочу предложить свой, альтернативный подход к данной проблеме. Он состоит в использовании ZendFramework-подобного каркаса xFrame, написанного на JavaScript. Под катом — описание ключевых элементов системы, ссылки на демо приложение и исходный код.

История 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():
  1. Application.models.News = Ext.data.Record.create([
  2. {name: 'Id', type:'int'},
  3. {name: 'Permalink', type:'string'},
  4. {name: 'Title', type:'string'},
  5. {name: 'Brief', type:'string'},
  6. {name: 'Text', type:'string'},
  7. {name: 'DateCreated', type:'date', dateFormat: 'Y-m-d'},
  8. ]);
  9. Ext.override(Application.models.News, {
  10. getLink : function () {
  11. return App.route({ permalink : this.get("Permalink") }, "news");
  12. },
  13. getDateCreated : function () {
  14. return this.get("DateCreated").format('m/d/Y');
  15. }
  16. });
* This source code was highlighted with Source Code Highlighter.

Так же, для каждой модели приложение автоматически создает два «статических» (вызываемых из конструктора, а не из экземпляра) метода createStore(add_cfg) (создает config-объект для хранилища данных с полями модели) и createOne(hash) (создает экзмепляр модели и загружает в поля из хэш-объекта).

Контроллеры реализированы по аналогии с ZF. Для создания контроллера с необходимо создать класс <имя_контроллера>Controller, для создания действия — метод <имя_действия>Action. В обработчик действия передаются три параметра: хэш с переданными в дейстия параметрами, ссылка на объект приложения, ссылка на объект ViewPort'a.
  1. Application.controllers.FrontController = Ext.extend(Application.controllers.Abstract, {
  2. .......................................................................................................................
  3. newsAction : function (params, app, panel) {
  4. panel.add({
  5. xtype: 'Application.components.NewsViewer',
  6. newsPermalink : params.permalink
  7. });
  8. },
* 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)


При создании приложения необходимо унаследовать данный класс и преопределить нужные методы инициализации:

  1. var App = new (Ext.extend(Application, {
  2. renderTo: 'app',
  3. autoRun : true ,
  4. initAcl : function () {
  5. this.constructor.superclass.initAcl.call(this);
  6. this.acl.addRole(new Application.Acl.Role(....................));
  7. ......................................................................
  8. },
  9. initRouter : function () {
  10. this.constructor.superclass.initRouter.call(this);
  11. this.router.addRoute(new Application.Router.Route(....................................}));
  12. }
  13. }));
* 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


Пример использования:
  1. initAcl : function () {
  2. this.constructor.superclass.initAcl.call(this);
  3. // добавляем роль guest
  4. this.acl.addRole(new Application.Acl.Role("guest"));
  5. // добавляем роль user которая наследует полномочия роли guest
  6. this.acl.addRole(new Application.Acl.Role("user", "guest"));
  7. // по умолчанию закрываем всем доступ на все
  8. this.acl.deny(null);
  9. this.acl.deny("guest");
  10. this.acl.deny("user");
  11. // разрешения для роли guest
  12. this.acl.allow("guest","front/index");
  13. this.acl.allow("guest","front/test");
  14. this.acl.allow("guest","front/news");
  15. this.acl.allow("guest","front/action1");
  16. this.acl.allow("guest","front/action2");
  17. this.acl.allow("guest","user/login");
  18. this.acl.allow("guest","user/noaccess");
  19. // роли user дорполнительно даем доступ к user/logout/ restricted/index и запрещаем логинится
  20. this.acl.allow("user","user/logout");
  21. this.acl.allow("user","restricted/index");
  22. this.acl.deny ("user","user/login");
  23. this.acl.setErrorRedirect(null, null, {controller:'user',action:'noaccess'});
  24. }
* 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


Примеры:

  1. .........................................................................................
  2. this.router.addRoute(new Application.Router.Route("news", "news/:permalink", { "controller" : 'front', "action" : 'news', "permalink" : "[\\w\\d\\-]+" }));
  3. .........................................................................................
  4. getLink : function () {
  5. return App.route({ id : this.get("Id"), permalink : this.get("Permalink") }, "news");
  6. }
  7. .........................................................................................
* 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»
Теги:ExtJSархитектура приложенийzend frameworkmvcriaajaxframeworkкаркасjavascript
Хабы: Библиотека ExtJS/Sencha
+19
3,3k 39
Комментарии 12

Похожие публикации

Node.js: серверный JavaScript
1 марта 202127 000 ₽Loftschool
Комплексное обучение JavaScript
1 марта 202127 000 ₽Loftschool
JavaScript Developer. Professional
29 марта 202172 500 ₽OTUS
Framework Laravel
29 апреля 202150 000 ₽OTUS

Лучшие публикации за сутки