Important
You are browsing the documentation for version 3.1 of OroCommerce, OroCRM and OroPlatform, which is no longer maintained. Read version 5.1 (the latest LTS version) of the Oro documentation to get up-to-date information.
See our Release Process documentation for more information on the currently supported and upcoming releases.
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<select id="my-select">
2 <option value="foo">Foo</option>
3 <option value="bar">Bar</option>
4</select>
5<script type="text/javascript">
6 require(['jquery', 'jquery.select2'], function ($) {
7 $('#my-select').select2({
8 placeholder: 'Select one ...',
9 allowClear: true
10 });
11 });
12</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// src/Acme/DemoBundle/Resources/public/js/app/components/select2-component.js
2define(function (require) {
3 'use strict';
4
5 var Select2Component,
6 BaseComponent = require('oroui/js/app/components/base/component');
7 require('jquery.select2');
8
9 Select2Component = BaseComponent.extend({
10 /**
11 * Initializes Select2 component
12 *
13 * @param {Object} options
14 */
15 initialize: function (options) {
16 // _sourceElement refers to the HTMLElement
17 // that contains the component declaration
18 this.$elem = options._sourceElement;
19 delete options._sourceElement;
20 this.$elem.select2(options);
21 Select2Component.__super__.initialize.call(this, options);
22 },
23
24 /**
25 * Disposes the component
26 */
27 dispose: function () {
28 if (this.disposed) {
29 // component is already removed
30 return;
31 }
32 this.$elem.select2('destroy');
33 delete this.$elem;
34 Select2Component.__super__.dispose.call(this);
35 }
36 });
37
38 return Select2Component;
39});
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{% set options = {
2 placeholder: 'Select one ...',
3 allowClear: true
4} %}
5
6{# assign the component module name and initialization options to HTML #}
7<select
8 data-page-component-module="acmedemo/js/app/components/select2-component"
9 data-page-component-options="{{ options|json_encode }}">
10 <option value="foo">Foo</option>
11 <option value="bar">Bar</option>
12</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// src/Acme/DemoBundle/Resources/public/js/app/views/select2-view.js
2define(function (require) {
3 'use strict';
4
5 var Select2View,
6 BaseView = require('oroui/js/app/views/base/view');
7 require('jquery.select2');
8
9 Select2View = BaseView.extend({
10 autoRender: true,
11
12 /**
13 * Renders a select2 view
14 */
15 render: function () {
16 this.$el.select2(this.options);
17 return Select2View.__super__.render.call(this);
18 },
19
20 /**
21 * Disposes the view
22 */
23 dispose: function () {
24 if (this.disposed) {
25 // the view is already removed
26 return;
27 }
28 this.$el.select2('destroy');
29 Select2View.__super__.dispose.call(this);
30 }
31 });
32
33 return Select2View;
34});
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{% set options = {
2 view: 'acmedemo/js/app/views/select2-view',
3 placeholder: 'Select one ...',
4 allowClear: true
5} %}
6
7{# assign the component module name and initialization options to the HTML #}
8<select
9 data-page-component-module="oroui/js/app/components/view-component"
10 data-page-component-options="{{ options|json_encode }}">
11 <option value="foo">Foo</option>
12 <option value="bar">Bar</option>
13</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# src/Acme/DemoBundle/Resources/config/requirejs.yml
2config:
3 paths:
4 # for the Select2View class
5 'acmedemo/js/app/views/select2-view': 'bundles/acmeui/js/app/views/select2-view.js'
6 # for the Select2Component class
7 '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:
1$ php bin/console cache:clear
Reinstall your assets if you don’t deploy them via symlinks:
1$ php bin/console assets:install
In production mode, you also have to rebuild the JavaScript code:
1$ php bin/console oro:requirejs:build