Skip to content

Latest commit

 

History

History
289 lines (224 loc) · 9.94 KB

code-standard-jquery-widgets.md

File metadata and controls

289 lines (224 loc) · 9.94 KB
group subgroup title landing-page menu_title menu_order functional_areas
coding-standards
01_Coding standards
jQuery widget coding standard
Coding standards
jQuery widget coding standard
7
Standards

In the Magento system, all jQuery UI widgets and interactions are built on a simple, reusable base—the jQuery UI Widget Factory{:target="_blank"}.

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{:target="_blank"}.

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{:target="_blank"} to interpret the "must," "must not," "required," "shall," "shall not," "should," "should not," "recommended," "may," and "optional" keywords.

Naming conventions

  • Widget names must consist of one or more non-abbreviated English word and in camelcase format.

    (function($) {
        $.widget('mage.accordion', $.ui.accordion, {
            // ... My custom code ...
        });
  • Widget names should be verbose enough to fully describe their purpose and behavior.

    // Declaration of the frontend.advancedEventTrigger widget
    (function($) {
        "use strict";
    
        $.widget('mage.advancedEventTrigger', $.ui.button, {
            // ... My custom code ...
        });
    }) (jQuery);

Instantiation and resources

  • Additional JavaScript files used as a resources must be dynamically loaded using the $.mage.components() method and must not be included in the <head> block.

  • Use the $.mage.components() method to load additional JavaScript resource files not 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 the benefits of $.mage.extend() and $.mage.components().
    • Using data-mage-init minimizes the inline JavaScript code footprint.
    • You can modify widget initialization parameters.
    // Widget initialization using the data-mage-init attribute
    <form data-mage-init="{form:[], validation:{ignore:':hidden'}}"></form>
    
    // Widget initialization using the mage plug-in
    (function($) {
        $('selector').mage('dialog', {
            close: function(e) {
                $(this).dialog('destroy');
            }
        });
    })(jQuery);
  • You can declare callback methods inline JavaScript but not methods and widgets.

    // 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
    (function($) {
        $.mage
            .extend('dialog', 'dialog',
                '<?php echo $this->getViewFileUrl('Enterprise_\*Module\*::page/js/dialog.js') ?>')
    })(jQuery);

Development standards

  • Widgets should comply with the single responsibility principle{:target="_blank"}.

    Widgets should not have responsibilities not related to the entity described by the widget.

    // Widget "dialog" that is responsible
    // only for opening content in an interactive overlay.
    $.widget('mage.dialog', {
        // Code logic
    });
    
    // Widget "validation" that is responsible
    // only for validating the form fields.
    $.widget('mage.validation', $.ui.sortable, {
        // Code logic
    });
    
    $('selector')
        .mage('dialog')
            .find('form')
                .mage('validation');
  • Widget properties that modify the widget's behavior must be located in the widget's options to make them configurable and reusable.

    //Declaration of the backend.dialog widget
    $.widget('mage.dialog', {
        options: {
            modal: false,
            autoOpen: true,
            // Additional widget options
        },
        // Additional widget properties
    });
    
    // Initializing
    $('selector').mage('dialog', {
        modal: true,
        autoOpen: false
    });
  • Widget communications must be handled by jQuery events

    <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);
            }
        }
    });
  • You must use DOM event bubbling{:target="_blank"} to perform one-way communication between a child widget and its parent widget.

  • Widgets must comply with the Law of Demeter{:target="_blank"} principle.

    Do not instantiate a widget or call a widget's methods inside another widget.

  • Make widgets abstract enough so that they can be used anywhere in Magento.

    For example, the mage.dropdown widget is applicable in many other scenarios, unlike mage.topShoppingCart.

  • Place abstract, share-able widgets under the <install dir>/pub/lib/<your company> directory so non-Magento applications can access them.

    For example:

    /pub
      /lib
      /magento
        dropdown.js
        validation.js
        dialog.js
    
  • Place Magento-specific widgets under the <install dir>/app/code/<namespace>/<module-name>/view/<area-name>/js directory.

    For example:

    /app
      /code
        /Mage
          /DesignEditor
            /view
              /frontend
                /js
                  vde-block.js
                  vde-container.js
    

Architecture

  • Use an underscore prefix to declare private widget methods.

    Properties without an underscore prefix are accessible using the jQuery Widget factory public API.

    // 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);
          }
    });
  • Start a widget's element selection with this.element

  • Widgets must not interact with DOM elements selected using this.element.parent(), this.element('selector'), or this.element.closest('selector').

    This reduces the number of widget conflicts because widgets interact only with their child elements.

  • Widget options should have default values.

    Use a null value if there is no default value for an option.

  • Pass as widget options all DOM selectors used by that widget.

  • Use the _setOption method to process required, immediate state changes.

  • Use the public widget API to call widget methods to allow chaining widget methods.

    // Call the 'open' method on the menu widget using the public widgets API
    $('selector')
    .menu('open')
    .addClass('ui-state-active');
  • Handle widget initialization 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 and prevents multiple instantiations of the same element.

    The _create() method is called only once for each widget instance and _init() is called each time the widget is called without arguments.

  • When a widget is destroyed, the attached element should be left exactly like it was before attachment.

    Common tasks for this include:

    • Removing or adding any CSS classes the widget added/removed to the element.
    • Detaching any elements the widget added to the DOM.
    • Destroying any widgets that the widget applied to other elements.
  • Bind event handlers using the _bind() method to make it easy to find what events the widget reacts on.

  • Bind events using the on() method.

    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.