ExtJS Application Architecture: Zend Framework Approach

    In addition to oddy articles about ExtJS-based RIA application architecture, I want to offer my own alternative approach to this problem. It consists in using a ZendFramework-like xFrame framework written in JavaScript. Under the cut - a description of the key elements of the system, links to a demo application and source code.

    The history of xFrame began with a single order. The client wished to implement a financial viewing system on his website. reporting. He was already familiar with ExtJS (the site admin panel is implemented on it), he liked the library and he wanted to display reports in the form of ExtJS Grid. Additional requirements for the system were the delimitation of access rights to view reports for different groups of users. Thus, I was faced with the task of implementing an RIA application with authentication and distribution of access to resources.

    The admin panel of the site supported such features, but it was made in the form of WebDesktop, which was not suitable for the client. Therefore, I started the development of the system from scratch. At that time, I was reading K. Zervas’s book “Web 2.0: Creating Applications in PHP” and was inspired by the spirit of the Zend Framework described in it. And I wanted to create exactly something similar to ZF. And I did it. Basic features were implemented within two days, debugging took a couple of days. Later, after the implementation of the reporting system itself, in my free time I brought my framework to a state in which it can be presented and decided to submit it to the public.

    I have not yet come up with the final name for the framework, as long as I call it the "code" name xFrame (eXtjs FRAMEwork). xFrame implements:

    • file system organization and class naming accepted in ZF (the class name repeats the path to it in the FS; slashes are replaced by dots)
    • standard namespaces for custom system components. For controllers - Application.controllers, for components - Application.components, for models - Application.models.
    • ZF-accepted concept of separation of library code and application code. XFrame code is placed in one folder (e.g. js / lib / xframe), and application code is placed in another folder (e.g. js / xframe-app)
    • model-view-controller pattern
    • standard ZF reference system (http: /// controller / action / param1 / value1 / param2 / value2 / ....)
    • URI-fragment addressing (http: /// # token), which allows you to save application state in links, while maintaining the convenience of an AJAX application that works without reloading the page
    • Model-view-controller architecture


    Models in xFrame are implemented based on Ext.data.Record. The class (constructor) of the model is created using the Ext.data.Record.create ([]) method. If you need to encapsulate additional methods into the model, Ext.override () is used :
    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.

    Also, for each model, the application automatically creates two “static” (called from the constructor, not from the instance) methods createStore (add_cfg) (creates a config object for the data store with model fields) and createOne (hash) (creates an instance of the model and loads into fields from a hash object).

    Controllers are implemented by analogy with ZF. To create a controller with, you need to create the class <controller_name> Controller , to create an action - the method <action_name> Action . Three parameters are passed to the action handler: a hash with the parameters passed to the action, a link to the application object, a link to the ViewPort object.
    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.


    The presentation functionality is implemented entirely on ExtJS components. When the application is initialized, a viewport is created, which is rendered at the specified location on the page. The output area changes when changing controllers / actions. All other content remains unchanged. Before calling the action method, the output area about the components is cleared, the action itself can add the necessary components to it, after which the application redraws the viewport.

    At the structural level in the system, the following key components are distinguished:

    Class Application - the basis of the application. In this class, all other components of the system are encapsulated.
    • App.acl - link to Application.Acl (permission system)
    • App.router - link to Application.Router (custom routing)
    • App.auth - link to Application.Identity (user data)
    • App.sessionManager - link to the session manager
    • App.viewport - link to the output area


    Commonly used methods:

    • request - executes an AJAX request
    • log - displays a variable to the console
    • redirect - redirection to a given token (with a change in URI)
    • forward - redirection to the given token (without changing the URI)


    When creating an application, you must inherit this class and predefine the necessary initialization methods:

    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.


    The Application.Acl class is a permission system. It’s similar in principle to Zend_Acl: it allows you to define a set of resources, roles and permissions for each role to access each resource.

    Commonly used methods:
    • addRole (new Application.Acl.Role (rolename [, baseRole])) - adds a role. If a base role is specified when creating a role, the role will overlay all its authority
    • addResource (new Application.Acl.Resource (resourcename)) - adds a resource. Sets of controllers and actions are also resources, but they are added automatically
    • allow / deny (role, resource) - allows / denies access of the role role to the resource resource. In the case, if one of the parameters (or both are not specified), the permissions are set globally. allow () - gives default access to everything for everyone, allow (null, resource) - gives access to resource for everyone, allow (role) - gives access to everything for role
    • setErrorRedirect (role, resource, {controller: 'error_controller', action: 'error_action'}) - indicates the action that is called if the role role does not have permission to access the resource resource


    Usage example:
    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 - managed routing. Allows you to create "beautiful" links instead of / controller / action / params by default.

    Methods:
    • addRoute (new Application.Router.Route (name, path, params)) - adds a new routing rule. The Application.Router.Route class is similar to Zend_Controller_Router_Route_Regex - placeholders for parameters are written in the path, and regular expressions and constant parameters are specified in the parameters
    • route (params, route) - generates a link using the route rule and params parameters


    Examples:

    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 - user data. The class is not fully implemented. In the demo application, it stores session data in cookies using Ext.state.Manager. When login, authentication does not occur, the object sets the isLogged property to true and saves the username and user role. In the future, it is planned to make a full analogue of Zend_Auth with various adapters for authentication verification.

    By this link you can see the demo application. It implements viewing the blog feed, logging in to the system, and accessing the “closed” section. The source code is available for download here ( alternative link , without the ExtJS distribution kit). Works with ExtJS 3.1, supports all modern browsers (IE8, Opera10, FF3 +, Safari 4, Chrome).

    Any suggestions / wishes / ideas / comments / corrections are accepted. In general, I want to make a full-fledged open-source framework for RIA applications out of the framework. If among you, dear readers of Habr, there are those who are interested, as well as there is a desire and free time, we are welcome to the project team.
    UPD Moved to the ExtJS Library

    Also popular now: