Skip over navigation
Documentation
You are currently viewing documentation for version 3.0 which is not a long-term support release. The latest long-term support release is version 2.6

Frontend Architecture

Introduction

Client Side Architecture of OroPlatform is built over Chaplin (an architecture for JavaScript Web applications based on the Backbone.js library).

The Backbone provides little structure above simple routing, individual models, views and their binding. Chaplin addresses these limitations by providing a light-weight but flexible structure which leverages well-proven design patterns and best practises.

However, as we distribute functionality of some pages over multiple bundles (several bundles can extend a page with own functionalities), we had to extend Chaplin approach for our needs.

Technology Stack

Libraries used by OroPlatform on the client side:
  • RequireJS
  • jQuery + jQuery-UI
  • Bootstrap
  • Backbone + underscore
  • Chaplin

This is not the whole list, but only the main items that make the skeleton of the client.

Most of these libraries are placed in OroUIBundle (as the bundle which is responsible for the user interface). Each of these libraries is defined as a module in RequireJS config with short module_id, so there is no need to use the full path every time (e.g. the module_id is jquery instead of oroui/lib/jquery).

Naming Conventions

File structures and naming conventions use best practices of Backbone development adopted for Oro needs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
AcmeBundle/Resources/public
├── css
│   └── style.css
├── templates // frontend templates
│   ├── projects
│   │   ├── project-item.html
│   │   └── projects-list.html
│   └── users
│       ├── user-item.html
│       └── users-list.html
├── js
│   ├── app // code that fully supports Chaplin architecture
│   │   ├── components
│   │   │   ├── view-component.js
│   │   │   └── widget-component.js
│   │   ├── controllers // Chaplin controllers
│   │   │   └── page-controller.js
│   │   ├── models
│   │   │   ├── projects
│   │   │   │   ├── project-model.js
│   │   │   │   └── projects-collection.js
│   │   │   └── users
│   │   │       ├── user-model.js
│   │   │       └── users-collection.js
│   │   ├── modules
│   │   │   ├── layout-module.js
│   │   │   └── views-module.js
│   │   └── views
│   │       ├── projects
│   │       │   ├── project-item-view.js
│   │       │   └── projects-view.js
│   │       └── users
│   │           ├── user-item-view.js
│   │           └── users-view.js
│   │ // utility code or other kind of architectural solutions
│   ├── app.js
│   ├── tools.js
│   └── ...
└── lib // for the third party libraries
    ├── jquery
    │   └── jquery.min.js
    ├── backbone
    │   └── backbone.min.js
    └── underscore
        └── underscore.min.js

Summary

  • Modules that fully support Chaplin architecture are placed in app folder.
  • There are five folders inside the “app” directory, one for each of the modules with the following roles:
    • components – page components, described in the Page Component section
    • controllers – Chaplin controllers. Currently the only controller in application is PageController
    • models – folder for Chaplin (Backbone) models and collections; modules inside the folder may be grouped by their functionality
    • modules – app modules, described in the App Modules section
    • views – common folder for Chaplin views and collection views; files inside the folder are grouped by their functionality
  • each file name ends with a suffix that corresponds to its type (e.g. -view.js, -model.js, -component.js)
  • names of all the files and folders can contains only lowercase alphabetic symbols with minus (-) symbol as a word separator
  • outside the app folder there is a utility code or other kinds of architectural solutions (e.g. jQuery-UI widgets)

Application Lifecycle

Chaplin extends Backbone concept introducing missing parts (such as a controller) and providing a solid lifecycle for the application components:

../../../_images/chaplin-lifecycle.png

As a result, a controller and all of its models and views exist only between the navigation actions. Once the route is changed, the active controller gets disposed, as well as all of its nested views and related models. A new controller is created for the current route, and new views and models are created in the new active controller. This approach of limited lifecycle of application components solves memory leak issues. The rest of the components, such as the application itself, router, dispatcher, layout and composer (see the picture above) exist all through the navigation.

To cover our needs we had to extend this solution. In OroPlatform page content is defined with one bundle and might be extended with many other bundles. This way, there isn’t a single place where a client side controller can be defined. As a solution, we have a common controller for all pages (PageController) that handles route changes and numerous small controllers (PageComponent) defined in the HTML and dedicated to a certain feature implementation.

Page Layout View

Chaplin has introduced Chaplin.Layout which is the top-level application view. The view is initialized for the body element and stays in memory, even when the active controller is changed. We have extended this approach and created PageLayoutView. In addition to handling clicks on application-internal links, it collects form data and prepares navigation options for the AJAX POST request. It also implements the ComponentContainer interface and initializes the top level Page Component defined in the page’s HTML. This allows to create the so called global views. These views stay in the memory, as well as PageLayoutView, when active controller is changed.

Page Controller

The route module contains the only route mask that always leads to the PageController::index action point.

1
2
3
4
5
6
define(function () {
    'use strict';
    return [
        ['*pathname', 'page#index']
    ];
});

This way, the disposed and created controllers for each navigation action are instances of the same constructor, which exists in different life cycles of the application.

This PageController loads page content over PageModel and sends a series of system events to notify the environment that the page content has changed.

Note

The page update flow contains the following system events:
  • page:beforeChange
  • page:request
  • page:update
  • page:afterChange
../../../_images/page-controller.png

These events are handled by global views (views and components that exist throughout the navigation and are not deleted by the page change;, see Page Layout View for more information). One of them is PageContentView that listens to page:update and updates page content area with HTML from PageModel.

Page Component

As the functionality of a page depends on its content and this content is generated by multiple bundles, we cannot use a single controller to be responsible for it. We have introduced an alternative approach that allows to use multiple controllers, each of which is responsible for a certain functionality and is related to a certain part of the HTML.

Such controllers are called a Page Component. Functionally, a “Page Component” is similar to the “Controller” component in Chaplin, however it implements a different flow:

  • The “Controller” represents one screen of the application and is created when the page URL is changed
  • The “Page Component” represents a part of the page with a certain functionality and is created in the course of page processing, subject to the settings declared in the HTML.

Defining a Page Component

To define a PageComponent for a block, specify the following two data-attributes the HTML node:

  • data-page-component-module — the name of the module
  • data-page-component-options — a safe JSON-string
1
2
3
4
5
6
{% set options  = {
    metadata: metaData,
    data: data
} %}
<div data-page-component-module="mybundle/js/app/components/grid-component"
     data-page-component-options="{{ options|json_encode }}"></div>

How It Works

The PageController loads a page and therewith triggers the page:update event. Global views (see Page Layout View) handle the event and update its HTML content. After that, views invoke initLayout method. It performs series of actions on its element, one of the actions is initPageComponents. This method performs the following:

  • collects all the elements with proper data-attributes
  • loads defined modules of PageComponents
  • executes init method with the options received to initialize the PageComponents
  • resolves the initialization promise with the array of components after te initialization of all the components

The PageController collects all promises from page:update event handlers and once all of them are resolved, it triggers the page:afterChange event.

See also

For more details, see the Page Component documentation.

App Module

App Modules are atomic parts of the general application and they are responsible for the following:

  • register handlers in the mediator (see Chaplin.mediator)
  • subscribe to mediator events, and
  • perform all the preliminary actions before an instance of the application is created

App modules export nothing, they are callback functions executed before the application is started. They make the whole application modular and the functionality distributed among the bundles ready to work.

App Modules are declared in the requirejs.yml configuration file in the custom appmodules section:

1
2
3
config:
    appmodules:
        - oroui/js/app/modules/messenger-module

This way you can define the code to be executed at the application start for every bundle.

An example of using App Modules is provided in the section below.

Example

oroui/js/app/modules/messenger-module declares handlers of the messenger in the mediator.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
define(function(require) {
    'use strict';

    var mediator = require('oroui/js/mediator');
    var messenger = require('oroui/js/messenger');

    /**
     * Init messenger's handlers
     */
    mediator.setHandler('showMessage',
        messenger.notificationMessage, messenger);
    mediator.setHandler('showFlashMessage',
        messenger.notificationFlashMessage, messenger);
    /* ... */
});

This way we guarantee that all the necessary handlers are declared before they are used. The handlers can be executed by any component or view in the Chaplin lifecycle.

1
mediator.execute('showMessage', 'success', 'Record is saved');

See also

For more details, see Chaplin documentation and Client Side Architecture.

Browse maintained versions:3.01.122.02.32.6

You will be redirected to [title]. Would you like to continue?

Yes No
sso for www.magecore.comsso for oroinc.desso for oroinc.frsso for marketplace.orocommerce.comsso for marketplace.orocrm.com
Back to top