In the Magento system, all jQuery UI widgets and interactions are built on a simple, reusable base—the jQuery UI Widget Factory. The factory provides a flexible base for building complex, stateful plug-ins with a consistent API. It is designed not only for plug-ins that are part of jQuery UI, but for general usage by developers who want to create object-oriented components without reinventing common infrastructure.
For more information, see the jQuery Widget API documentation.
This standard is mandatory for Magento core developers and recommended for third-party extension developers. Some parts of Magento code might not comply with the standard, but we are working to gradually improve this.
Use RFC 2119 to interpret the "must," "must not," "required," "shall," "shall not," "should," "should not," "recommended," "may," and "optional" keywords.
Widget names must consist of one or more non-abbreviated English words
Correct |
Incorrect |
(function($) {
$.widget('mage.accordion', $.ui.accordion, {
// ... My custom code ...
});
}) (jQuery);
|
(function($) {
$.widget('mage.ak123', $.ui.accordion, {
// ... My custom code ...
});
}) (jQuery);
|
Widget names must be formatted in camelCase
Correct |
Incorrect |
(function($) {
$.widget('mage.vdeHistoryToolbar', {
// ... My custom code ...
});
}) (jQuery);
|
(function($) {
$.widget('mage.vde_historyToolbar', {
// ... My custom code ...
});
}) (jQuery);
|
Verbosity is generally encouraged
Widget names should be as verbose as needed to fully describe their purpose and behavior.
Correct |
Incorrect |
// Declaration of the frontend.advancedEventTrigger widget
(function($) {
"use strict";
$.widget('mage.advancedEventTrigger', $.ui.button, {
// ... My custom code ...
});
}) (jQuery);
|
// Declaration of the ui.button widget
(function($) {
"use strict";
$.widget('ui.button', $.ui.button, {
// ... My custom code ...
});
}) (jQuery);
|
Additional JavaScript files used as resources by a widget
Additional JavaScript files used as resources must be dynamically loaded using the $.mage.components()
method and must not be included in the <head>
block.
You must use $.mage.extend()
to extend an existing set of widget resources
You must instantiate widgets using the data-mage-init
attribute
You can use the .mage()
plug-in to instantiate widgets that use callback methods.
Benefits:
- You leverage benefits of
$.mage.extend()
and $.mage.components()
- Using
data-mage-init
minimizes inline JavaScript code footprint.
- You can modify widget initialization parameters.
Correct |
Incorrect |
// Widget initialization using the data-mage-init attribute
<form data-mage-init="{form:[], validation:{ignore:':hidden'}}"></form>
// Widget initialization using the mage plug-in
<script type="text/javascript">
(function($) {
$('selector').mage('dialog', {
close: function(e) {
$(this).dialog('destroy');
}
});
})(jQuery);
</script>
|
// Widget initialization without using the mage plug-in
<script type="text/javascript">
(function($) {
$('[data-role="form"]')
.form()
.validation({
ignore: ':hidden'
});
})(jQuery);
</script>
|
Methods and widgets must not be declared using inline JavaScript
You can declare callback methods inline.
Correct |
Incorrect |
// Widget initialization and configuration
$('selector').mage('dialog', {
close: function(e) {
$(this).dialog('destroy');
}
});
// Widget initialization and binding event handlers
$('selector').mage('dialog').on('dialogclose', {
$(this).dialog('destroy');
});
// Extension for widget in a JavaScript file
$.widget('mage.dialog', $.ui.dialog, {
close: function() {
this.destroy();
}
});
// Extension of widget resources
<script type="text/javascript">
(function($) {
$.mage
.extend('dialog', 'dialog',
'<?php echo $this->getViewFileUrl('Enterprise_\*Module\*::page/js/dialog.js') ?>')
})(jQuery);
|
// Initialization
$('selector').dialog();
$('selector')
.find('.ui-dialog-titlebar-close')
.on('click', function() {
$('selector').dialog('destroy');
});
|
The responsibilities which are not related to the entity described by the widget should be moved to another widget.
Correct |
Incorrect |
// Widget "dialog" that is responsible
// only for opening content in an interactive overlay.
$.widget('mage.dialog', {
/* ... */
});
// Widget "validation" that is responsible
// only for validating the form fields.
$.widget('mage.validation', $.ui.sortable, {
/* ... */
});
$('selector')
.mage('dialog')
.find('form')
.mage('validation');
|
// Widget named 'dialog' that is
// responsible for opening content in
// an interactive overlay and
// validating the form fields.
$.widget('mage.dialog', {
/* ... */
_validateForm: function() {
/* code which validates the form */
}
});
$('selector').mage('dialog')
|
All widget properties that can be used to modify widget's behavior must be located in widget's options.
Benefit: Widgets become configurable and reusable.
Correct |
Incorrect |
//Declaration of the
// backend.dialog widget
$.widget('mage.dialog', {
options: {
modal: false,
autoOpen: true,
/* ... */
},
/* ... */
});
// Initializing
$('selector').mage('dialog', {
modal: true,
autoOpen: false
});
|
// Declaration of the
// backend.modalDialog and backend.nonModalDialog
// widgets
$.widget('mage.modalDialog', {
/* ... */
});
$.widget('mage.nonModalDialog', {
/* ... */
});
// Initialization
$('selector').mage('modalDialog');
$('selector').mage('nonModalDialog');
|
Widget communications must be handled by jQuery events
Correct |
Incorrect |
// HTML structure
<body>
...
<button data-mage-init="{button: {event: 'save', target:'[data-role=edit-form]'}}" />
...
<form data-role="edit-form">
...
</form>
...
</body>
// Declaration of the mage.form widget
$.widget("mage.form," {
/* ... */
_create: function() {
this._bind();
},
_bind: function() {
this._on({
save: this._submit
})
}
_submit: function(e, data) {
this._rollback();
if (false !== this._beforeSubmit(e.type, data)) {
this.element.trigger('submit', e);
}
}
});
|
// HTML structure
<body>
...
<button data-mage-init="{formButton: {}}" />
...
<form data-role="edit-form">
...
</form>
...
</body>
// Declaration of the mage.button widget
$.widget('mage.formButton', $.ui.button, {
/* ... */
_create: function() {
this._bind();
this._super();
},
_bind: function() {
this._on({
click: function() {
$('[data-role=edit-form]').form('submit');
}
});
}
});
// Declaration of the mage.form widget
$.widget("mage.form," {
/* ... */
_create: function() {
this._bind();
}
submit: function(data) {
this._rollback();
if (false !== this._beforeSubmit(e.type, data)) {
this.element.trigger('submit', e);
}
}
});
|
You must use DOM event bubbling to perform one-way communication between a child widget and its parent widget
Widgets must comply with the Law of Demeter principle
About the Law of Demeter principle. We recommended against instantiating a widget or calling a widget's methods inside another widget.
We recommend you make widgets abstract enough so that they can be used anywhere in your Magento system
Example: Unlike the mage.topShoppingCart
widget, the mage.dropdown widget
can be used in many other scenarios.
Abstract widgets which can be used shared with non-Magento applications
Place all such widgets under the /pub/lib/[your company]/[author] directory.
Correct |
Incorrect |
/pub
/lib
/magento
dropdown.js
validation.js
dialog.js
|
/pub
/lib
/magento
vde-block.js
vde-container.js
|
Magento product specific widgets
You must locate all of these under the /app/code/<namespace>/<ModuleName>/view/<areaname>/js directory.
Correct |
Incorrect |
/app
/code
/Mage
/DesignEditor
/view
/frontend
/js
vde-block.js
vde-container.js
|
/pub
/lib
/magento
vde-block.js
vde-container.js
|
Use the underscore prefix only to declare private widget methods
Widget properties names should not start with underscore because those properties would not be accessible using the jQuery Widget Factory public API.
Correct |
Incorrect |
// Declaration of the backend.accordion widget
$.widget('mage.accordion', {
/* ... */
_create: function() {
this.header = this.element.find(this.options.header);
this.icon = $(this.options.icon).prependTo(this.header);
}
});
|
// Declaration of the backend.accordion widget
$.widget('mage.accordion', {
/* ... */
_create: function() {
this._header = this.element.find(this.options.header);
this._icon = $(this.options.icon).prependTo(this._header);
}
});
|
A widget's element selection should start with this.element
Widgets must not interact with certain DOM elements
Widgets must not interact with DOM elements that can be selected with this.element.parent()
, this.element.parents('selector')
, or this.element.closest('selector')
.
Benefit: Reduced number of widget conflicts because widgets interact only with their child elements.
All widget options should have default values
If there is no default value for an option by design, a null
value must be used.
All DOM selectors used by a widget must be passed to the widget as options
If an immediate state change is required, the change must be processed by the _setOption
method
To call widget methods, you must use the public widget API
Benefit: The public widget API enables using chaining for widget methods.
Correct |
Incorrect |
// Call the 'open' method on the menu widget using the public widgets API
$('selector')
.menu('open')
.addClass('ui-state-active');
|
// Call the 'open' method on the
// menu widget without using the public
// widgets API
var menuInstance = $('selector').data('menu');
menuInstance.open();
menuInstance.element.addClass('ui-state-active');
|
Widget initialization
Initializing a widget must be handled only if there is a logical action to perform on successive calls to the widget with no arguments.
The widget factory automatically fires the _create()
and _init()
methods during initialization, in that order. The widget factory prevents multiple instantiations on the same element, which is why _create()
is called only once for each widget instance, whereas _init()
is called each time the widget is called without arguments.
When a widget is destroyed, the element should be left exactly like it was before the widget was attached to it
Common tasks include:
- Removing or adding of any CSS classes your widget added/removed from the element.
- Detaching any elements your widget added to the DOM.
- Destroying any widgets that your widget applied to other elements.
Example:
All event handlers must be bound by the _bind()
method
Benefit: All widget event handlers are bound in one place (by the _bind
method), which makes it easy to find what events the widget reacts on.
You must use the _on() method to bind events
Benefits:
- Delegation is supported using selectors in the event names; for example,
click .foo
- Maintains proper this context inside the handlers, so it is not necessary to use the
$.proxy()
method.
- Event handlers are automatically namespaced and cleaned up on destruction.
Related topics
Find us on