The Javascript handlers used in Elementor’s core widgets are written in the form of ES6 classes. All core widget classes extend a JS Widget Handler base class: elementorModules.frontend.handlers.Base

The base class acts as a kind of “abstract” class, defining several basic methods that are used to enable targeting and manipulation of the UI instance of the widget.

Handler Class Structure

A basic JS widget handler class skeleton looks like this:

class WidgetHandlerClass extends elementorModules.frontend.handlers.Base {
   getDefaultSettings() {}

   getDefaultElements() {}

   bindEvents() {}
}

Let’s break it down:

Get Default Settings

The getDefaultSettings() method is used to add any custom settings to be used in the widget’s JS handler. A common use of this method is wiring up jQuery selectors to target the widget’s HTML. For example:

getDefaultSettings() {
    return {
        selectors: {
            button: '.buttonClassName',
            content: '.contentClassName',
        },
    };
}

The settings objects returned by getDefaultSettings can be accessed by calling this.getSettings( string ), and passing in a string with the object’s key (in this example, to access the selectors object above, you would call this.getSettings( 'selectors' ).

Get Default Elements

The getDefaultElements() method is used to create jQuery objects of the HTML elements targeted by the JS handler. These jQuery objects are then appended to the class instance object as properties of its elements property.

This screenshot from the Chrome Developer Console shows an instance of a widget handler class called ‘ContentToggleButton’ (represented inside the class by this keyword), with two targeted elements labeled $button and $content.

Example:

getDefaultElements() {
    const selectors = this.getSettings( 'selectors' );
    return {
        $button: this.$element.find( selectors.button ),
        $content: this.$element.find( selectors.content ),
    };
}

this.$element refers to the current widget instance being handled by the handler. By looking for our selectors under $element, we make sure our Javascript code will only affect the specific widget instance the user is interacting with (in case there are more than one instance of the widget on our page).

Bind Events

The bindEvents() method is used to add event listeners for widget-related events. For example, if you want to make an element in your widget clickable, you would add a ‘click’ event listener to the element. For example:

bindEvents() {
    this.elements.$button.on( 'click', callback );
}

The callback function you include in the event listener will be invoked when the element represented by the property $button is clicked. It is considered a best practice to write the callback function as a class method, then call the function in the event listener, like so:

bindEvents() {
    this.elements.$firstSelector.on( 'click', () => this.onFirstSelectorClick() );
}

onFirstSelectorClick( event ) {
    event.preventDefault();

    // DO STUFF HERE

}

Registering the Widget Handler with Elementor

In order to wire your JS handler to your widget and make sure the script runs at the proper time in Elementor’s loading sequence, it is necessary to register the handler with Elementor.

The registering action must be done once the main frontend instance has been initiated. This is done by listening to the elementor/frontend/init event, and adding the registration action within the listener’s callback. 

Registering our WidgetHandlerClass from the example above would look like this:

jQuery( window ).on( 'elementor/frontend/init', () => {
   const addHandler = ( $element ) => {
       elementorFrontend.elementsHandler.addHandler( WidgetHandlerClass, {
           $element,
       } );
   };

   elementorFrontend.hooks.addAction( 'frontend/element_ready/your-widget-name.default', addHandler );
} );

Let’s break this example down:

Example JS Handler File

The following example assumes it’s corresponding widget has two HTML elements with different class names (.firstSelectorClass, .secondSelectorClass). 

This example handler makes the element with class firstSelectorClass clickable, and makes the element with class secondSelectorClass hidden when the first one is clicked.

class WidgetHandlerClass extends elementorModules.frontend.handlers.Base {
    getDefaultSettings() {
        return {
            selectors: {
                firstSelector: '.firstSelectorClass',
                secondSelector: '.secondSelectorClass',
            },
        };
    }

    getDefaultElements() {
        const selectors = this.getSettings( 'selectors' );
        return {
            $firstSelector: this.$element.find( selectors.firstSelector ),
            $secondSelector: this.$element.find( selectors.secondSelector ),
        };
    }

    bindEvents() {
        this.elements.$firstSelector.on( 'click', this.onFirstSelectorClick.bind( this ) );
    }

    onFirstSelectorClick( event ) {
        event.preventDefault();

        this.elements.$secondSelector.show();
   }
}

jQuery( window ).on( 'elementor/frontend/init', () => {
   const addHandler = ( $element ) => {
       elementorFrontend.elementsHandler.addHandler( WidgetHandlerClass, {
           $element,
       } );
   };

   elementorFrontend.hooks.addAction( 'frontend/element_ready/your-widget-name.default', addHandler );
} );

For wider browser support, it is recommended to transpile your class into ES5 syntax (using a tool such as Babel.js) before deploying it in production.

Enqueuing the Handler Script

Like in the case of any other WordPress plugin, the JS script has to be enqueued in the plugin’s PHP. In order to make sure the script is only loaded if the widget is used, the script should be registered within the widget’s (PHP) class. 

A best practice is to register the script in the class constructor function, then return it in the class’ get_script_depends() method, which handles enqueuing:

<?php
class Widget_Class_Name extends Widget_Base {

   public function __construct($data = [], $args = null) {
      parent::__construct($data, $args);

      wp_register_script( 'script-handle', 'path/to/file.js', [ 'elementor-frontend' ], '1.0.0', true );
   }

  public function get_script_depends() {
     return [ 'script-handle' ];
  }

Let’s break it down:

If your plugin has custom CSS files, you can enqueue them in the exact same manner, register the style in the widget’s constructor and use the get_style_depends()method to tell elementor to enqueue it for you. For example:

<?php
class Widget_Class_Name extends Widget_Base {

   public function __construct($data = [], $args = null) {
      parent::__construct($data, $args);

      wp_register_script( 'script-handle', 'path/to/file.js', [ 'elementor-frontend' ], '1.0.0', true );
      wp_register_style( 'style-handle', 'path/to/file.CSS');
   }

  public function get_style_depends() {
     return [ 'style-handle' ];
  }

You can read more on wp_register_style & wp_register_script for better understanding how they work.