Pull to refresh

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

Reading time8 min
Views3.7K
В дополнение статей 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.  
  10. Ext.override(Application.models.News, {
  11.   getLink : function () {
  12.     return App.route({ permalink : this.get("Permalink") }, "news");
  13.   },
  14.   getDateCreated : function () {
  15.     return this.get("DateCreated").format('m/d/Y');
  16.   }
  17. });
* 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»
Tags:
Hubs:
+19
Comments12

Articles

Change theme settings