layout | group | subgroup | title | menu_title | menu_order | github_link | redirect_from |
---|---|---|---|---|---|---|---|
default |
coding-standards |
Coding standards |
jQuery widget coding standard |
jQuery widget coding standard |
4 |
coding-standards/code-standard-jquery-widgets.md |
/guides/v1.0/coding-standards/code-standard-jquery-widgets.html |
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.
Correct | Incorrect |
---|---|
(function($) { $.widget('mage.accordion', $.ui.accordion, { // ... My custom code ... }); }) (jQuery); |
(function($) { $.widget('mage.ak123', $.ui.accordion, { // ... My custom code ... }); }) (jQuery); |
Correct | Incorrect |
---|---|
(function($) { $.widget('mage.vdeHistoryToolbar', { // ... My custom code ... }); }) (jQuery); |
(function($) { $.widget('mage.vde_historyToolbar', { // ... My custom code ... }); }) (jQuery); |
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"; |
// Declaration of the ui.button widget (function($) { "use strict"; |
Additional JavaScript files used as resources must be dynamically loaded using the $.mage.components()
method and must not be included in the <head>
block.
Correct | Incorrect |
---|---|
<script src="https://gist.github.com/xcomSteveJohnson/9a700d4f220ce06e3bc7.js"></script> | <script src="https://gist.github.com/xcomSteveJohnson/10019eab65d68b85cbb3.js"></script> |
Correct | Incorrect |
---|---|
<script src="https://gist.github.com/xcomSteveJohnson/05f55b091bdf0dbd31ba.js"></script> | <script src="https://gist.github.com/xcomSteveJohnson/5ebd41ec54beae23b178.js"></script> |
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> |
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); </script> |
// Initialization $('selector').dialog(); $('selector') .find('.ui-dialog-titlebar-close') .on('click', function() { $('selector').dialog('destroy'); }); |
Widgets should comply with the single responsibility principle.
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'); |
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
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.
Correct | Incorrect |
---|---|
<script src="https://gist.github.com/xcomSteveJohnson/efeb0b23c92e8a14797b.js"></script> | <script src="https://gist.github.com/xcomSteveJohnson/ddc67dacfb8eedc0a134.js"></script> |
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 |
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 |
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); } }); |
Correct | Incorrect |
---|---|
<script src="https://gist.github.com/xcomSteveJohnson/1230ea3e189d56ec2e1c.js"></script> | <script src="https://gist.github.com/xcomSteveJohnson/8ab943e0c335550cd901.js"></script> |
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.
Correct | Incorrect |
---|---|
<script src="https://gist.github.com/xcomSteveJohnson/5420a2d569a307a444f3.js"></script> | <script src="https://gist.github.com/xcomSteveJohnson/dd58dcb56b7a969e3b5c.js"></script> |
If there is no default value for an option by design, a null
value must be used.
Correct | Incorrect |
---|---|
<script src="https://gist.github.com/xcomSteveJohnson/1b12fde4f36739febb30.js"></script> | <script src="https://gist.github.com/xcomSteveJohnson/c970d7568346411fda59.js"></script> |
Correct | Incorrect |
---|---|
<script src="https://gist.github.com/xcomSteveJohnson/8912e16944ae3c18aca8.js"></script> | <script src="https://gist.github.com/xcomSteveJohnson/10634888ebd33b97ccc0.js"></script> |
Correct | Incorrect |
---|---|
<script src="https://gist.github.com/xcomSteveJohnson/80da4a8a2b45009c409b.js"></script> | <script src="https://gist.github.com/xcomSteveJohnson/1bda6a1f11ef807f3b47.js"></script> |
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'); |
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.
Correct | Incorrect |
---|---|
<script src="https://gist.github.com/xcomSteveJohnson/50214ee1333ae3729333.js"></script> | <script src="https://gist.github.com/xcomSteveJohnson/12649600c73015db3f21.js"></script> |
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:
<script src="https://gist.github.com/xcomSteveJohnson/b9e1fb5a78fe88e510db.js"></script>
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.
Correct | Incorrect |
---|---|
<script src="https://gist.github.com/xcomSteveJohnson/8375d8ac3a4f807e71f5.js"></script> | <script src="https://gist.github.com/xcomSteveJohnson/8b512431bd78b1bcb25d.js"></script> |
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.
Correct | Incorrect |
---|---|
<script src="https://gist.github.com/xcomSteveJohnson/e9f8dc5c56e84722aa88.js"></script> | <script src="https://gist.github.com/xcomSteveJohnson/7631a95d0476652b9d38.js"></script> |