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

How to Replace Inline-Javascript with a Component

Embedding Functionality in a Page

The easiest way to bind an interactive functionality with the particular markup is to write an inline JavaScript fragment:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<select id="my-select">
    <option value="foo">Foo</option>
    <option value="bar">Bar</option>
</select>
<script type="text/javascript">
    require(['jquery', 'jquery.select2'], function ($) {
        $('#my-select').select2({
            placeholder: 'Select one ...',
            allowClear: true
        });
    });
</script>

Inline scripts are often larger than in the example above and may also make use of inline Twig code. It is impossible to reuse this code, extend it or test it, and it is also hard to maintain.

Furthermore, the lifecycle of its instances is not defined and it is not specified how the function will be destructed properly when the control is not used anymore. Instead, one has to rely on jQuery to properly clean the memory. Most of the time, jQuery does this fine. However, there is no guarantee that jQuery would always handle this task successfully without the help from developers.

The solution for the problems explained above is a Page Component.

A Page Component

A Page Component is a controller that is used to implement certain parts of the page functionality. It is responsible for the following things:

  • creating related views, collections, models and even sub-components
  • handling environment events
  • disposing obsolete instances

See also

You can find more information about Page Components in the Frontend Architecture section and in the Page Component documentation.

Creating a Page Component

A Page Component is a JavaScript object based on the BaseComponent. The inline JavaScript from the introduction will be replaced by the new Select2Component. Start with creating the select2-component.js file that lives in the Resources/public/js/app/components directory of your bundle.

 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
// src/Acme/DemoBundle/Resources/public/js/app/components/select2-component.js
define(function (require) {
    'use strict';

    var Select2Component,
        BaseComponent = require('oroui/js/app/components/base/component');
    require('jquery.select2');

    Select2Component = BaseComponent.extend({
        /**
         * Initializes Select2 component
         *
         * @param {Object} options
         */
        initialize: function (options) {
            // _sourceElement refers to the HTMLElement
            // that contains the component declaration
            this.$elem = options._sourceElement;
            delete options._sourceElement;
            this.$elem.select2(options);
            Select2Component.__super__.initialize.call(this, options);
        },

        /**
         * Disposes the component
         */
        dispose: function () {
            if (this.disposed) {
                // component is already removed
                return;
            }
            this.$elem.select2('destroy');
            delete this.$elem;
            Select2Component.__super__.dispose.call(this);
        }
    });

    return Select2Component;
});

This code can be tested, extended and reused. What is even more important is that the component provides two methods initialize() and dispose() which restrict the existence of the select2 instance. Thus, it defines its own lifecycle and therefore minimizes the risk of memory leaks.

Declaring a Page Component in HTML

Next, the HTML code of the related template has to be modified to tell the parent View (or other parent ComponentContainer) which HTML elements are related to the Select2Component component:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{% set options = {
    placeholder: 'Select one ...',
    allowClear: true
} %}

{# assign the component module name and initialization options to HTML #}
<select
    data-page-component-module="acmedemo/js/app/components/select2-component"
    data-page-component-options="{{ options|json_encode }}">
    <option value="foo">Foo</option>
    <option value="bar">Bar</option>
</select>

The parent ComponentContainer uses two attributes to resolve the Component module associated with an HTML element when the initPageComponents method is executed:

data-page-component-module
The name of the module
data-page-component-options
A JSON encoded string containing module configuration options

Using the View Component

The code is now reusable. Though it can be improved by separating the business logic from the view layer. Therefore, replace the Select2Component with the Select2View class in the file named select2-view.js that lives in the Resources/public/js/app/views directory of your bundle and that extends the BaseView class:

 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
// src/Acme/DemoBundle/Resources/public/js/app/views/select2-view.js
define(function (require) {
    'use strict';

    var Select2View,
        BaseView = require('oroui/js/app/views/base/view');
    require('jquery.select2');

    Select2View = BaseView.extend({
        autoRender: true,

        /**
         * Renders a select2 view
         */
        render: function () {
            this.$el.select2(this.options);
            return Select2View.__super__.render.call(this);
        },

        /**
         * Disposes the view
         */
        dispose: function () {
            if (this.disposed) {
                // the view is already removed
                return;
            }
            this.$el.select2('destroy');
            Select2View.__super__.dispose.call(this);
        }
    });

    return Select2View;
});

This looks pretty much like the initially created Select2Component except that you don’t have to deal with retrieving the associated HTML element and that you don’t have to parse the options. This is done for you by the ViewComponent.

However, you still need to tell the component to instantiate your Select2View. For this purpose OroPlatform is shipped with the ViewComponent that instantiates views for HTML elements. To make use of the ViewComponent, replace the value of data-page-component-module attribute with the oroui/js/app/components/view-component and use the view option to point to your new Select2View:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{% set options = {
    view: 'acmedemo/js/app/views/select2-view',
    placeholder: 'Select one ...',
    allowClear: true
} %}

{# assign the component module name and initialization options to the HTML #}
<select
    data-page-component-module="oroui/js/app/components/view-component"
    data-page-component-options="{{ options|json_encode }}">
    <option value="foo">Foo</option>
    <option value="bar">Bar</option>
</select>

The ViewComponent loads the required module, fetches the view and the _sourceElement from the options and instantiates the View instance. This View instance is attached to the component instance. Once the component gets disposed, it automatically invokes the dispose() methods of all attached instances (if the dispose() method was defined for the instance).

Please note that as we instantiate the view in the module load callback, we deal with asynchronous process. Therefore, the component is not ready for use right after the initialization method has finished its work. We need to inform the super controller that this is async initialization. To do so, we first call this._deferredInit() that creates a promise object, and once the initialization is over, we invoke this._resolveDeferredInit() that resolves this promise. This way the super controller gets informed that the component is initialized.

Configure RequireJS

Finally, you need to make your new classes known to RequireJS:

1
2
3
4
5
6
7
# src/Acme/DemoBundle/Resources/config/requirejs.yml
config:
    paths:
        # for the Select2View class
        'acmedemo/js/app/views/select2-view': 'bundles/acmeui/js/app/views/select2-view.js'
        # for the Select2Component class
        'acmedemo/js/app/components/select2-component': 'bundles/acmeui/js/app/components/select2-component.js'

Whether you have created your own component or a view (that is instantiated by the ViewComponent), you’ll have to add the module name into RequireJS configuration, so that it can trace this module and include it into the build file.

Note

To see your component in action, you need to do several more things:

  • Clear the Symfony application cache to update the cache and the included RequireJS config:

    $ php bin/console cache:clear
    
  • Reinstall your assets if you don’t deploy them via symlinks:

    $ php bin/console oro:assets:install
    
  • In production mode, you also have to rebuild the JavaScript code:

    $ php bin/console oro:requirejs:build
    
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