Extend Elementor like a Pro: Creating a Custom Control

Creating a Custom Control Featured Image

Every Elementor’s widget is composed of different input types called a control. A control is used to construct the interface of a widget. Using controls allows us to customize and set our favorite settings for a widget and see a live preview of it.

Before diving headfirst into this article, we suggest that you check out the following articles.

Elementor control

In default, Elementor has several control types like Text, Color, Dimension, Select, Icon, Media, Typography and so on that meet most common usages. But sometimes we need to create a custom control to realize our needs.

Directory Structure of a Control

Normally, each control contains three files as PHP, JavaScript, and CSS:

  • control.php
  • js/control.js
  • css/control.css

PHP File

The PHP file generates HTML and constructs UI of control in Panel. To create the PHP file, we need to extend the Base_Control abstract class.

The Base_Control provides several methods.

  • get_type: retrieve the type of control.
  • content_template: render the HTML output in Panel.
  • enqueue: use to add CSS and JavaScript files to control.
  • get_default_settings: retrieve the default setting of control on initialisation.
  • get_default_value: retrieve the default value of control.
  • get_value: determine how the control returns its value.
  • get_style_value: interact with the CSS rule that passed with Selector argument.

Elementor allows for five base classes to extend Base_Control for different purposes. We can extend the following classes to create new controls. For more info, you can refer to this page about Elementor controls.

  • Base_UI_Control: creates a UI control that is visible in the panel and doesn’t return any value. For example, Heading control.
  • Base_Data_Control: a base class that returns a single value. For example, Number Control.
  • Control_Base_Multiple: a base class that returns multiple values. For example, Media control.
  • Control_Base_Units: a base class that returns unit based values. For example, Dimension control.
  • Group_Control_Base: a base class for creating group control. For example, Typography control.

JavaScript File

The JavaScript file is used for interacting between UI and live preview and manipulate events that are related to control.

The Elementor uses the Backbone.js library for JS manipulation. For more information about Backbone.js, you can check out backbonejs.org and https://www.javatpoint.com/backbonejs-tutorial

Also in Elementor, there is an exclusive JS file for each PHP file that interacts with them. For instance, the above base PHP classes have JS files such as:

  • base.js
  • base-data.js
  • base-multiple.js
  • base-units.js

To create a new control JS file, we need to extend one of the above JS files. You can find the source of the JS file at GitHub.

Note: For some reason, Elementor merges all JS files in the editor.js file. We can extend the above JS file from by using the elementor.modules.controls object.

CSS File

CSS allows us to style the UI of a control. In the following sections, we’ll go into detail about the actual usage of CSS.

Creating a Custom Control

Now that we have gained a basic understanding of an Elementor control, we are ready to create a custom control called MultiUnit, which is similar to Elementor’s Dimension control.

In the Dimension control, we can only set the same unit (px, em,…) for the top, right, bottom and left values, but in MultiUnit control, we can set a different unit for each value.

At the end of this article, we’re going to create the following control.

Default Values Return Values (Array)
[
‘top’ => ”,
‘right’ => ”,
‘bottom’ => ”,
‘left’ => ”,
‘top_unit’=>’px’,
‘right_unit’ =>’px’,
‘left_unit’ =>’px’,
‘bottom_unit’ =>’px’,
‘isLinked’ => true,
];
[
‘top’ => ”, //int
‘right’ => ”, //int
‘bottom’ => ”, //int
‘left’ => ”, //int
‘top_unit’=>”, //string
‘right_unit’ =>” //string
‘left_unit’ =>”, //string
‘bottom_unit’ =>”, //string
‘isLinked’ => ”, //bool
];

Directory Structure of a Custom Control

  • index.php
  • multi-unit.php
  • css/MultiUnit.css
  • js/MultiUnit.js

You can download the full source code from Github.

Creating the PHP File

To create a custom control, we need to have a unique type. For our control, we’ll choose spicy-multi-unit-control.

Our control extends Control_Base_Multiple and overrides essential functions, as well as creates custom functions to meet our needs.

In this section, we’ll go into detail of each function one-by-one:

1. The below function sets the control type.

public function get_type() {
     return 'spicy-multi-unit-control';
 }

2. This function returns the default values of the control.

public function get_default_value() {
     return [
         'top' => '',
         'right' => '',
         'bottom' => '',
         'left' => '',
         'top_unit'=>'px',
         'right_unit' =>'px',
         'left_unit' =>'px',
         'bottom_unit' =>'px',
         'isLinked' => true,
     ];
 }

3. The below function returns the default settings of the control.

protected function get_default_settings() {
   return [
       'size_units' => [ 'px' ],
       'label_block' => true,
       'range' => [
           'px' => [
               'min' => '',
               'max' => 100,
               'step' => 1,
           ],
           'em' => [
               'min' => 0.1,
               'max' => 10,
               'step' => 0.1,
               ],
           'rem' => [
               'min' => 0.1,
               'max' => 10,
               'step' => 0.1,
           ],
           '%' => [
               'min' => 0,
               'max' => 100,
               'step' => 1,
           ],
           'deg' => [
               'min' => 0,
               'max' => 360,
               'step' => 1,
               ],
           'vh' => [
               'min' => 0,
               'max' => 100,
               'step' => 1,
           ],
           'vw' => [
               'min' => 0,
               'max' => 100,
               'step' => 1,
           ],
       ],
   ];
}

4. This function is used for adding JavaScript and CSS files to the control:

public function enqueue() {
     wp_enqueue_script( 'spicy_multi_unit',plugins_url( '/js/MultiUnit.js', FILE ) );
     wp_enqueue_style( 'spicy_multi_unit' , plugins_url( '/css/MultiUnit.css', FILE ) );
 }

5. The below function renders a button that is used to sync all units together.

Creating a Custom Control 5
private function print_link_unit_template(){
    ?>
    <# if ( data.size_units && data.size_units.length > 1 ) { #>
                                                    Sync Unit                                    <# _.each( data.size_units, function( unit ) { #>                {{{unit}}}            <#});#>                    
     <#}#>
    <?php
 }

6. We’ll use the below function to create a unit for each input:

Creating a Custom Control 6
private function print_units_template($arg){
    ?>
    <# if ( data.size_units && data.size_units.length > 1 ) { #>
                    <# .each( data.size_units, function( unit ) {            if(unit=='px' && data.range.px.step < 1){                data.range.px.step=1;            }            #>                        {{{ unit }}}            <# } ); #>        
    <# } #>
    <?php
 } 

7. Finally, the essential function for creating the UI:

public function content_template() {        
    $dimensions = [
        'top' => __( 'Top', 'Spicy-extension' ),
        'right' => __( 'Right', 'Spicy-extension' ),
        'bottom' => __( 'Bottom', 'Spicy-extension' ),
        'left' => __( 'Left', 'Spicy-extension' ),
    ];
 ?>
   <div class="elementor-control-field">
    <label class="elementor-control-title">{{{ data.label }}}</label>
    <div class="spicy-control-input-wrapper">
     <div class="units-wrapper">
       <div>   
        <ul>
          <?php
            foreach ( $dimensions as $dimension_key => $dimension_title ) :
               echo '<li>';
          ?>
          <# if ( -1 !== _.indexOf( allowed_dimensions, '<?php echo $dimension_key; ?>' ) ) { #>
             <?php                      $this->print_units_template($dimension_key );
             ?>  
          <# } #>
                 <?php           
                   echo '</li>';
                   endforeach;
                 ?>
         </ul>
    </div>  
   </div>
            
   <div>
     <ul class="spicy-control-dimensions">
       <?php
         foreach ( $dimensions as $dimension_key => $dimension_title ) :
            $control_uid = $this->get_control_uid( $dimension_key );
       ?>
         <li class="spicy-control-multiunit">
         <#
          var unit=data.controlValue[<?php echo "'". $dimension_key.'_unit'."'"; ?>];
         #>
          <input id="<?php echo $control_uid; ?>" type="number"  data-name="{{data.name}}-<?php echo esc_attr( $dimension_key ); ?>" min="{{ data.range[unit].min}}" max="{{ data.range[unit].max}}" step="{{ data.range[unit].step}}" data-setting="<?php echo esc_attr( $dimension_key ); ?>"
    placeholder="<#
                   if ( _.isObject( data.placeholder ) ) {
                    if ( ! _.isUndefined( data.placeholder.<?php echo $dimension_key; ?> ) ) {
                       print( data.placeholder.<?php echo $dimension_key; ?> );
                       }
                      } else {
                              print( data.placeholder );
                             } 
                     #>"
                                
   <label for="<?php echo esc_attr( $control_uid ); ?>" class="spicy-control-multiunit-label"><?php echo $dimension_title; ?></label>
         </li>
       <?php endforeach; ?>
         <li>                    
          <div style="display: flex;">
           <button class="spicy-link-dimensions tooltip-target" data-tooltip="<?php echo esc_attr__( 'Link values together', 'Spicy-extension' ); ?>">
             <span id="spisy-{{data.name}}" class="spicy-linked">
               <i class="fa fa-link" aria-hidden="true"></i>
               <span class="elementor-screen-only"><?php echo __( 'Link values together', 'Spicy-extension' ); ?></span>
             </span>
             <span id="spisy-{{data.name}}" class="spicy-unlinked">
               <i class="fa fa-chain-broken" aria-hidden="true"></i>
               <span class="elementor-screen-only"><?php echo __( 'Unlinked values', 'Spicy-extension' ); ?></span>
             </span>
            </button>
            <?php       
             $this->print_link_unit_template();  
            ?>
           </li>
          </ul>
         </div>
        </div>
        </div>
        <# if ( data.description ) { #>
        <div class="elementor-control-field-description">{{{ data.description }}}</div>
        <# } #>
        <?php
    }

Creating the JavaScript File

Our JS file is named MultiUnit.js and extends the Elementor BaseMultiple module. This JS file contains a JavaScript class that has several functions, which are explained briefly below.

1. The below function defines the essential UI CSS selectors.

var ControlMultiUnitItemView=
    elementor.modules.controls.BaseMultiple.extend( {
    ui: function() {
     var ui = ControlBaseMultipleItemView.prototype.ui.apply( this, arguments );
         ui.controls = '.spicy-control-multiunit > input:enabled';
         ui.link = 'button.spicy-link-dimensions';
         ui.top_choices ='div.spicy-units-choices > input[type=radio][data-setting=top_unit]';   
         ui.right_choices = 'div.spicy-units-choices > input[type=radio][data-setting=right_unit]';
         ui.bottom_choices = 'div.spicy-units-choices > input[type=radio][data-setting=bottom_unit]';
         ui.left_choices = 'div.spicy-units-choices > input[type=radio][data-setting=left_unit]';
         ui.unitLink = '.spicy_link';
         ui.gear = '.spicy_link_first';
         ui.ranges_top = '.spicy-units-choices-label-top';
         ui.ranges_right = '.spicy-units-choices-label-right';
         ui.ranges_bottom = '.spicy-units-choices-label-bottom';
         ui.ranges_left = '.spicy-units-choices-label-left';
         ui.linked = '.spicy-linked';
         ui.unlinked = '.spicy-unlinked';
         return ui;
     }

2. This function defines the related event for each UI element.

events: function() {
  return _.extend( ControlBaseMultipleItemView.prototype.events.apply( this, arguments ), {
   'click @ui.ranges_top' : 'TopRangeToggle',
   'click @ui.ranges_right' : 'RightRangeToggle',
   'click @ui.ranges_bottom' : 'BottomRangeToggle',
    'click @ui.ranges_left' : 'LeftRangeToggle',
    'click @ui.link': 'onLinkDimensionsClicked',
    'change @ui.top_choices' : 'setTopInputRange',
    'change @ui.right_choices' : 'setRightInputRange',
    'change @ui.bottom_choices' : 'setBottomInputRange',
    'change @ui.left_choices' : 'setLeftInputRange',
    'click @ui.unitLink' : 'linkUnit',
    'click @ui.gear' : 'gearToggle',           
   } );
 }

3. This defines the essential variables.

defaultDimensionValue: 0,
 range_control:'',
 name_control:'',
 gear_check:true,
 range_all:true,
 Range_top:true,
 range_right:true,
 range_bottom:true,
 range_left:true,

4. This defines initial function for control initialization.

 initialize: function() {
 ControlBaseMultipleItemView.prototype.initialize.apply( this, arguments );
 this.model.set( 'allowed_dimensions', this.filterDimensions( this.model.get( 'allowed_dimensions' ) ) );
 this.range_control = this.model.get(['range']);
 this.name_control = this.model.get(['name']);
 }

5. We need a function to select the proper unit when it’s clicked. The below function is used for top_unit.

setTopInputRange : function( event ) {
  var val = $(event.target).val() ;
  var min = this.range_control[val].min;
  var max = this.range_control[val].max;
  var step = this.range_control[val].step;
        
  $('li.spicy-control-multiunit > input[type=number][data-name='+this.name_control+'-top]').attr({"min":min,"max":max,"step":step});
}

We’ve also declared similar functions for right_unit, bottom_unit, and left_unit that you can find in the source code.

6. We need other functions to show/hide units when clicked. The below function is used for the top_unit.

TopRangeToggle : function (event) {
  var cat = $(event.target).attr("data-cat");
  if(this.range_top==false){    $('.spicy-units-choices-label-top[data-cat='+cat+']').not(event.target).hide();
  this.range_top = true;
  }else if(this.range_top==true){
   $('.spicy-units-choices-label-top[data-cat='+cat+']').show();
   this.range_top=false;
 }   
 }

We also declared similar functions for right_unit, bottom_unit, and left_unit.

7. The function below syncs top_unit, right_unit, bottom_unit and left_unit together.

linkUnit : function(event) {
  var text=$(event.target).text();
  $('div.spicy-units-choices > input[type=radio][name=spicy-choose-'+this.name_control+'-top][value='+"'"+text+"'"+']').prop("checked", true).trigger('change');
  $('div.spicy-units-choices > input[type=radio][name=spicy-choose-'+this.name_control+'-right][value='+"'"+text+"'"+']').prop("checked", true).trigger('change');
  $('div.spicy-units-choices > input[type=radio][name=spicy-choose-'+this.name_control+'-bottom][value='+"'"+text+"'"+']').prop("checked", true).trigger('change');
  $('div.spicy-units-choices > input[type=radio][name=spicy-choose-'+this.name_control+'-left][value='+"'"+text+"'"+']').prop("checked", true).trigger('change');  
  $('div.spicy-units-choices > input[class='+this.name_control+']').not(':checked').next().hide();
  $('div.spicy-units-choices > input[class='+this.name_control+']:checked').next().show();

this.allRangeToggle(event);
 }

Finally, we have other functions that are similar to the JS file of the Dimension control and other custom functions that we haven’t explained. But you can find them in source code.

Creating the CSS File

To style the UI of control in the panel, we need CSS rules as seen below.

ul.spicy-control-dimensions {
 width: 100%;
}

.spicy-control-input-wrapper ul li {
 width: 20%;
 float: left;
}

div.spicy-units-choices > input {
 display: none;
}

div.spicy-units-choices > input:checked+label {
 color:red;
}
.spicy-control-multiunit > label {
 font-size: 11px;
}

div.spicy-units-choices > input:not(:checked)+label {
 display:none;
}

.spicy-link-dimensions {
 width:90%;
 height: 30px;
 border: 1px solid;
 border-color: black;
 background-color: #7a8791;
}

div.spicy-units-choices > label { 
 border-radius:50%;
 padding:3px;
 font-size:9px;
 background-color:#ebebeb;
 line-height: 16px;
}

.units-wrapper {
 height :29px;
 margin-top: 5px;
}

.units-wrapper ul li {
 margin-bottom: 0px;
 margin-top: 4px;
}

div.spicy-units-choices {
position: absolute;
}

.spicy_linkAllUnit {
 position: relative;
 height:30px;
 font-size: 14px;
 border-top-right-radius: 5px;
 border-bottom-right-radius: 5px;
 border: 1px solid;
 border-color: black;
 border-left: 0;
 background-color: #7a8791;
}

.spicy-control-multiunit > input {
 border-right : 0;
 border-radius: 0;
 border-color:rgb(58, 57, 57) !important;
 height:30px;
 padding-right: 0;
}

.spicy-control-dimensions >.spicy-control-multiunit:first-child input {
 border-top-left-radius: 5px;
 border-bottom-left-radius: 5px;
}

.spicy_link_first {
 display:block;
 border-radius:5px;
 padding:3px;
 font-size:13px;
 color: white;
}

.spicy_link {
 display:none;
 border-radius:50%;
 padding:3px;
 font-size: 12px;
 color: brown;
}

.spicy-unlinked {
 display: none;
}

.spicy_tooltip {
 width: max-content;
 background-color: #e6e9ec;
 text-align: center;
 border-radius: 6px;
 padding: 5px 0;
 position: absolute;
 z-index: 10;
 top: -1px;
 right: 100%;
}

.spicy-units-choices-label-top {
 position:relative;
 z-index: 5;
}

.spicy-units-choices-label-right {
 position:relative;
 z-index: 4;
}

.spicy-units-choices-label-bottom {
 position:relative;
 z-index: 3;
}

.spicy-units-choices-label-left {
 position:relative;
 z-index: 2;
}
.spicy_link_first:hover .spicy_link_tooltiptext {
 visibility: visible;
}
.spicy_linkAllUnit .spicy_link_tooltiptext {
 visibility: hidden;
 width: 88px;
 background-color: #373434;
 color: #fff;
 text-align: center;
 border-radius: 4px;
 padding: 5px;
 position: absolute;
 z-index: 1;
 right: 0;
 font-size: 9px;
 bottom: -26px;
}
.spicy_linkAllUnit .spicy_link_tooltiptext::after {
 content: " ";
 position: absolute;
 right: 5%;
 bottom: 94%;
 margin-left: -5px;
 border-width: 5px;
 border-style: solid;
 border-color: transparent transparent #373434 transparent;
}

How to Use the Custom Control

It’s time to put the control in action. Follow the steps below in order to use the control.

1. Register the custom control in our plugin using the following codes.

add_action( 'elementor/controls/controls_registered', [ $this,'init_controls']);

public function init_controls() {

  // Include Control files
  require_once( __DIR__ . '/controls/multi_unit.php' );

  // Register control
  \Elementor\Plugin::$instance->controls_manager->register_control( 'spicy-multi-unit-control', new Spicy_multi_unit());

}

2. Add the custom control to any widget. Now, it’s ready to use.

$this->add_responsive_control(
  'multiunit',
             [
              'label' => __( 'MultiUnit ', 'spicy' ),
              'type' => 'spicy-multi-unit-control',
              'size_units' => [ '%','em', 'px' ],
              'allowed_dimensions' =>'all',
              'selectors' => [
                '{{WRAPPER}} .elementor-widget-container' => 'padding: {{TOP}}{{TOP_UNIT}} {{RIGHT}}{{RIGHT_UNIT}} {{BOTTOM}}{{BOTTOM_UNIT}} {{LEFT}}{{LEFT_UNIT}};',
                             ],
            ]
 );

For more information, you can read this article.

Conclusion

In this article, we tried to explain how to create a custom control for Elementor. Also, we describe the essential files of control and the structure of them. Finally, we created a custom control named MultiUnit.

Feel free to share your experiences with us in the comments.

Extend Elementor like a pro: Creating a New Widget

Creating a New Widget Featured Image

Elementor is packed with various elements or widgets. Widgets allow us to build websites and display appropriate content. But sometimes we need specific functionality for our website. And to do this, we need to create a new widget or element.

What is a widget?

A widget or element is a section in the Graphical User Interface (GUI) that lets us display the information we want to showcase to users.

The Elementor widget is a combination of PHP, JavaScript, HTML and CSS codes that generate a custom output for us. We suggest visiting this link before reading this article.

Elementor Widgets

Each widget has custom controls like input, fields, buttons and more. With these controls, we can set our custom settings and see a live preview in the editor and render the final HTML in frontend.

Widget Structure

In order to create a new widget, we must extend the Widget_Base abstract class. This class has several methods that we first need to override.

<?php
class Elementor_Test_Widget extends \Elementor\Widget_Base {

    public function get_name() {}

    public function get_title() {}

    public function get_icon() {}

    public function get_categories() {}

    protected function _register_controls() {}

    protected function render() {}

    protected function _content_template() {}
}

Widget Name

With the get_name() method, we can set the name of a widget and return the name of the widget.

public function get_name() {
    return 'Widget Name';
}

Widget Title

With the get_title() method, we can set the label of the widget.

public function get_title() {
    return __( 'Widget Title', 'Plugin Name' );
}

Widget Icon

The get_icon() method set the icon for our widget that displayed in the panel.

public function get_icon() {
    return 'eicon-gallery-grid';
}

Widget Categories

The get_categories() method set the category that our widget must be placed on the panel’s categories. In default, there are several categories including the following:

  • Basic
  • Pro-elements
  • General
  • Woocommerce-elements
  • WordPress
  • And so on.
public function get_categories() {
    return [ 'basic' ];
}

We can also create custom categories with the elementor/elements/categories_registered action.

<?php
function create_custom_categories( $elements_manager ) {

    $elements_manager->add_category(
        'custom-category',
        [
         'title' => __( 'Custom Category', 'plugin-name' ),
         'icon' => 'fa fa-plug',
        ]
    );
}
add_action( 'elementor/elements/categories_registered', 'create_custom_categories' );

Widget Controls

With the _register_controls() method, we can set essential sections and controls for the widget.

There are several controls that we can use for widget settings, such as the following:

  1. UI Controls
    • Button
    • Divider
    • Heading
    • Raw Html
  2. Data Controls
    • Text
    • Select
    • Choose
    • Gallery
    • Slider
    • Typography
    • And so on.
  3. Multiple Controls
    • Url
    • Media
    • Image Dimensions
  4. Unit Controls
    • Slider
    • Dimensions
  5. Group Controls
    • Typography
    • Text Shadow
    • Box Shadow
    • Border
    • Background

For more information on Elementor controls, you can check out this link.

Widget Template

The render() method allows creating a frontend HTML code with the PHP.

The _content_template() method generates a live preview for the widget in the editor by using the Backbone JavaScript library.

How to add controls to our widget

In default, there are three tabs in the Elementor panel, which are the Content Tab, Style Tab, and Advanced Tab. To add controls to our widget, we should first create a section and assign it to one of the tabs. Then we can add our favorite controls to the section. As mentioned above, we must put our code in the _register_controls() method.

Creating a Section

To create a section, we need to set the following essential parameters:

  • Section Name
  • Section Settings(label ,tab)
$this->start_controls_section(
     'section_name',//set unique name

          //set setting of section
        [
         'label' => __( 'Section Label', 'plugin-name' ),
         'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
        ]
 );

  //THE CONTROLS COME HERE

$this->end_controls_section();

We must set the following parameters for each control as seen below:

  • Control Name
  • Control Setting(label,type)

Adding a normal control

Use the following control to add a normal control.

$this->add_control(
    'control_name',//set unique name for control

           //set essential settings
         [
        'label' => __( 'Control Label', 'plugin-name' ),

            //CONTROL_TYPE can be TEXT, SLIDER, ...
        'type' => \Elementor\Controls_Manager::CONTROL_TYPE,
         ]
    );

Adding a responsive control

This control type includes different settings value for desktop, tablet, and mobile devices.

$this->add_responsive_control(
     'control_name',
       [
        'label' => __( 'Control Name', 'plugin-name' ),
           //CONTROL_TYPE can be TEXT, SLIDER, …

         type' => \Elementor\Controls_Manager::CONTROL_TYPE,
           //SET FAVORITE RANGE LIKE PX, %, EM

         'range' => [
                 'px' => [
                        'min' => 0,
                        'max' => 100,
                       ],
                ],
            // SET FAVORITE DEVICES
          'devices' => [ 'desktop', 'tablet', 'mobile' ],
        'desktop_default' => [
                        'size' => 30,
                        'unit' => 'px',
                         ],
          'tablet_default' =>  [
                        'size' => 20,
                        'unit' => 'px',
                         ],
          'mobile_default' =>  [
                        'size' => 10,
                        'unit' => 'px',
                         ],
                
                ],
            ]
        );

Adding a group control

This control groups several normal controls in one control such as typography.

$this->add_group_control(
//Group_Control_Class can be       Group_Control_Typography,Group_Control_Border or other group controls

  Group_Control_Class::get_type(),
  [
   'name' => 'control_name',
   'label' => __( 'control Label', 'plugin-name' ),
  ]
);

Adding control tabs

Create a tab area to add various controls.

//start tabs area container
$this->start_controls_tabs();

  // create first tab area
  $this->start_controls_tab();

    $this->add_control();

      $this->end_controls_tab();

     // create second tab area
      $this->start_controls_tab();

    $this->add_control();

      $this->end_controls_tab();

$this->end_controls_tabs();
//end tabs area container

Adding control popovers

Create a popover window will allow you to display various controls.

$this->add_control(
   'control_name',
    [
     'label' => __( 'Control Label', 'plugin-name' ),
     'type' => \Elementor\Controls_Manager::POPOVER_TOGGLE,
     'label_off' => __( 'Default', 'plugin-name' ),
     'label_on' => __( 'Custom', 'plugin-name' ),
     'return_value' => 'yes',
    ]
  );

    $this->start_popover();

    $this->add_control();

    $this->add_control();

    $this->end_popover();

How to Display or Hide Sections and Controls in Widgets

At times, you might want to hide some sections or controls in your widget, but using if / else in the code is a hassle. Fortunately, now you can do this conveniently with the condition in the settings for sections or controls.

$this->start_controls_section(
  'my_section',
   [
    'label' => __( 'My Section', 'plugin-name' ),
    'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
    'condition' => [
                       'Name' => 'Value',
                      ]
      //name must be unique name of one control
      //Value must be the return value of control
   ]
);

Let’s say that we have one control named Choose_ Layout with two values first and second. After adding the code found below code to any section or control, our section or control will be displayed if the return value of the Choose_ Layout control is equal to first. And it will be hidden if the return value of Choose_ Layout control is equal to the second.

'condition' => [
                'Choose_ Layout' => 'first',
               ]

How to Change Widget Output Style Automatically from the Control Settings

One of Elementor’s amazing features is the ability to easily change the style of widget output with CSS code and selectors. For example, if we want to change the height of images in output, first we must assign a class attribute to our HTML tag. Then use this class with the selector in settings of control.

<img class="test-img">

$this->add_control(
          'test_height',
            [
             'label' => __( 'Height', 'Spicy-extension' ),
             'type' => \Elementor\Controls_Manager::SLIDER,
             'separator' => 'after',
             'show_label' => true,
             'selectors' => [
                '{{WRAPPER}} .test-img' => 'height: {{SIZE}}{{UNIT}};',
              ],
            ]
        );

How to Get Widget Settings

To create the final Html code in the render() method and live preview in the _content_template() we’ll need input the parameter from the widget controls like height, width, font-size and more. In Elementor, the input parameter is called Settings. We can arrange settings in render() using this method.

$widget_settings= $this->get_settings_for_display();
$widget_settings['control_name'];

For the _content_template() method the Elementor store setting in the settings variable.

settings.control_name

One important note that we must consider is the settings are saved in a multidimensional array. We can get an inner setting with the following code.

$widget_settings['control_name ']['option'];

settings.control_name.option 

Creating a Sample Widget

Below, we have illustrated how to build a gallery widget step-by-step so you can better understand how to cover the topics mentioned above and create an Elementor widget.

Our gallery widget is registered in the Spicy plugin and the root directory of the gallery widget is:

-Css
-gallery.css
-index.php
-widgets
-gallery.php
-index.php
-spicy.php
-index.php

Index.php file is an empty file that prevents a malicious hacker from directly accessing the plugin.
Spicy.php file is the main plugin file that we register our widget with.
Gallery.css file is a CSS file that sets the default style of a gallery widget.
Gallery.php is the main file that defines the functionality of a gallery widget.

Download the Source Code here.

Our gallery widget is based on the grid and has two layouts, which are classic and pro. Both layouts have the following controls:

  • Skin
  • Column gap
  • Row gap
  • Columns
  • Hover Animation
  • Box Shadow

The classic layout has a gallery control, and the image section contains the below controls:

  • Height
  • Image size behavior
  • Border type
  • Border radius

Also, the pro layout has an image repeater control with several sections such as the following:

  • Item section contains Border Type, Width, Color, and Border Radius controls.
  • Image section contains Height and Image Size behavior controls.
  • Text section contains Color, Typography, Alignment, and Spacing controls.
  • Avatar section contains Height, Width, Alignment, and Spacing controls.
  • Textarea section contains Color, Typography, Alignment, and Spacing controls.

Extending the Widget_Base Abstract Class

The main class of the gallery widget is similar to the code found below.

gallery.php
<?php
class Spicy_Gallery_Widget extends \Elementor\Widget_Base {
public function get_name() {
   return 'Gallery';
   }
public function get_title() {
    return __( 'Gallery', 'Spicy-extension' );
    }
public function get_icon() {
    return 'eicon-gallery-grid';
    }
public function get_categories() {
    return [ 'basic' ];
    }
protected function _register_controls() {

    //  start Content tab
        $this->start_controls_section(
            'content_section',
            [
             'label' => __( 'Content', 'Spicy-extension' ),
             'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
            ]
        );
        //add skin control
        $this->register_controls_skin();

        //add repeater for pro skin
        $this->register_controls_pro_repeater();

        //add gallery control
        $this->register_controls_Classic_gallery();

        //add column-gap slider
        $this->register_controls_column_gap();

        //add row-gap slider
        $this->register_controls_row_gap();


       
     
 //add columns number list
        $this->register_controls_column_number();

        $this->end_controls_section();
        // end content tab

        //start Style tab
        $this->register_controls_classic_style_image();

        //create pro item section
        $this->register_controls_pro_item();

      //create pro style section-image
        $this->register_controls_pro_style_image();

      //create pro style section-text
        $this->register_controls_pro_style_text();

      //create pro style section-avatar
        $this->register_controls_pro_style_avatar();

      //create pro style section-textarea
        $this->register_controls_pro_style_textarea();

      //Box Shadow
        $this->register_controls_pro_style_boxshadow();

        //hover animation
        $this->register_controls_pro_style_hoveranimation();
      //end style tab
    }

protected function render() {
        $settings = $this->get_settings_for_display();
        ?>
        <div class="spicy-gallery" width="100%">
        <?php
   
        if($settings['spicy_skin']=='classic'){
            $this->classic_render($settings);
        }else{
            $this->pro_render($settings);
  
                 }
        ?>
        </div>
        
        <?php
    }   

protected function _content_template() {
        ?>
        <div class="spicy-gallery">
        <#
         if(settings.spicy_skin=='classic'){
         _.each( settings.spicy_images, function( image ) {
        #>
        <div class="spicy-item elementor-animation-{{settings.spicyhover}}">
         <img  class="spicy-img" id="{{image.id}}" src="{{image.url}}"/>
        </div>
        <# }); } else{
         _.each( settings.spicy_image_list, function( image ) {
        #>
        <div class="spicy-item elementor-animation-{{settings.spicyhover}}">    
          <div class="spicy-pro-container">
            <img class="spicy-pro-img" src="{{image.spicy_pro_image.url}}" >
          </div>
          <p class="spicy-pro-text">{{image.spicy_pro_text}}</p>
          <div class="spicy-avatar-wrapper">
           <img class="spicy-avatar" src="{{image.spicy_pro_avatar.url}}" alt="Avatar">
          </div>
          <p class="spicy-pro-description">{{image.spicy_pro_description}}</p>
         </div>
        <# }); }#>
        </div>
     <?php
    }
?>

Creating Controls for a Gallery Widget

In the above code, you’ll see several functions in _register_controls() and render() functions.In fact, these methods assign sections and controls for the gallery widget. We can create these functions only for grouping codes and simplicity.

Common Controls

1. Skin control: This control’s type is Select with two options set as classic and pro. With this control, we have the ability to change the layout of the gallery widget.

protected function register_controls_skin(){
        $this->add_control(
               'spicy_skin',
               [
                'label' => __( 'Skin', 'spicy' ),
                'type' => \Elementor\Controls_Manager::SELECT,
                'options' => [
                'classic' => __( 'Classic', 'Spicy-extension' ),
                'pro' => __( 'Pro', 'spicy' ),
               ],
                'default' => 'classic',   
            ]
       );
}

2. Column Gap: This type of control is Slider, which allows us to change the space between columns of the gallery widget.

protected function register_controls_column_gap(){
        $this->add_responsive_control(
               'spicy_column_gap',
               [
                'label' => __( 'Column Gap', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::SLIDER,
                'size_units' => [ '%','em', 'px' ],
                'range' => [
                    'px' => [
                        'min' => 0,
                        'max' => 50,
                        'step' => 1,
                    ],
                    '%' => [
                        'min' => 0,
                        'max' => 10,
                        'step' => 0.1,
                    ],
                    'em' => [
                        'min' => 0,
                        'max' => 10,
                        'step' => 0.1,
                    ],
                ],
                'default' => [
                    'unit' => '%',
                    'size' => 0.5,
                ],
                'show_label' => true,
                'selectors' => [
                    '{{WRAPPER}} .spicy-gallery' => 'grid-column-gap: {{SIZE}}{{UNIT}};',
                ],
            ]
        );
    }

3. Row Gap: This type of control is Slider, which we can use to change the space between rows of the gallery widget.

protected function register_controls_row_gap(){
        $this->add_responsive_control(
               'spicy_row_gap',
               [
                'label' => __( 'row Gap', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::SLIDER,
                'size_units' => [ 'px','em','%' ],
                'range' => [
                    'px' => [
                        'min' => 0,
                        'max' => 50,
                        'step' => 1,
                    ],
                    '%' => [
                        'min' => 0,
                        'max' => 10,
                        'step' => 0.1,
                    ],
                    'em' => [
                        'min' => 0,
                        'max' => 10,
                        'step' => 0.1,
                    ],
                ],
                'default' => [
                    'unit' => 'px',
                    'size' => 5,
                ],
                'show_label' => true,
                'selectors' => [
                  '{{WRAPPER}} .spicy-gallery' => ' grid-row-gap: {{SIZE}}{{UNIT}};',
                ],
            ]
        );
    }

4. Columns: The type of control is Select, which lets us alter the number of columns of the gallery widget.

protected function register_controls_column_number(){
        $this->add_responsive_control(
               'spicy_columns',
               [
                'label' => __( 'Columns', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::SELECT,
                'default' => 3,
                'options' => ['1'=>1,'1'=>1,'2'=>2,'3'=>3,'4'=>4,'5'=>5,'6'=>6,'7'=>7,'8'=>8,'9'=>9,'10'=>10],
                'devices' => [ 'desktop', 'tablet', 'mobile' ],
                'desktop_default' => [
                    'size' => '',
                ],
                'tablet_default' => [
                    'size' => '',
                ],
                'mobile_default' => [
                    'size' => '',
                ],
                'selectors' => [
                  '{{WRAPPER}} .spicy-gallery' => 'grid-template-columns: repeat({{SIZE}},1fr);',
                ],
            ]
            
       );
    }

5. Box Shadow: This section is used to add a box shadow effect to gallery items.

protected function register_controls_style_boxshadow(){
        $this->start_controls_section(
               'spicy_box_shadow',
               [
                'label' => __( 'Box Shadow', 'Spicy-extension' ),
                'tab' => \Elementor\Controls_Manager::TAB_STYLE,
               ]
        );
        $this->add_group_control(
               Elementor\Group_Control_Box_Shadow::get_type(),
               [
                'name' => 'box_shadow',
                'label' => __( 'Box Shadow', 'Spicy-extension' ),
                'selector' => '{{WRAPPER}} .spicy-item',
               ]
        );
        $this->end_controls_section();
 }

6. Hover Animation: This section adds an animation effect to the gallery item.

protected function register_controls_style_hoveranimation(){
        $this->start_controls_section(
               'spicy_hover',
               [
                'label' => __( 'Hover Animation', 'Spicy-extension' ),
               'tab' => \Elementor\Controls_Manager::TAB_STYLE,
             ]
        );
        $this->add_control(
               'spicyhover',
               [
                'label' => __( 'Hover Animation', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::HOVER_ANIMATION,
                'default'=>'',
               ]
            
        );
        $this->end_controls_section();
    }

Classic Layout

The following controls or sections are only displayed in the classic layout.

1. Gallery Control: used to select multiple images for the gallery and displayed in the Content Tab.

protected function register_controls_Classic_gallery(){
        $this->add_control(
               'spicy_images',
              [
                'label' => __( 'Add Images', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::GALLERY,
                'separator' => 'default',
                'show_label' => false,
                'condition' => [
                                'spicy_skin' => 'classic',
                               ],
                'dynamic' => [
                              'active' => true,
                             ]
              ]
        );
 }

2. Image sections: This is displayed in the Style Tab to change the style of the image.

protected function register_controls_classic_style_image(){
        $this->start_controls_section(
               'style_section',
               [
                'label' => __( 'Image', 'Spicy-extension' ),
                'tab' => \Elementor\Controls_Manager::TAB_STYLE,
                'condition'=>[
                              'spicy_skin' => 'classic'
                             ]
               ]
        );
        //classic height image control
        $this->add_responsive_control(
               'spicy_height',
               [
                'label' => __( 'Height', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::SLIDER,
                'separator' => 'after',
                'size_units' => [ 'px','vw'],
                'range' => [
                            'px' => [
                                     'min' => 0,
                                     'max' => 500,
                                     'step' => 1,
                                    ],
                            'vw' => [
                                     'min' => 0,
                                     'max' => 100,
                                     'step' => 1,
                                    ],
                           ],
                'default' => [
                              'unit' => 'px',
                              'size' => 150,
                             ],
                'show_label' => true,
                'selectors' => [
                    '{{WRAPPER}} .spicy-img' => 'height: {{SIZE}}{{UNIT}};',
                ],
            ]
        );

        //image fitness classic
        $this->add_responsive_control(
               'spicy_image_fitness',
               [
                'label' => __( 'Image Size Behavior', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::SELECT,
                'options' => ['cover'=> __( 'Cover', 'Spicy-extension' ),'fill'=>__( 'Fit', 'Spicy-extension' ),'contain'=>__( 'Contain', 'Spicy-extension' ),'scale-down'=>__( 'Scale Down', 'Spicy-extension' ),'none'=>__( 'None', 'Spicy-extension' )],
                'devices' => [ 'desktop', 'tablet', 'mobile' ],
                'desktop_default' => [
                                      'val' => '',
                                     ],
                'tablet_default' => [
                                      'val' => '',
                                    ],
                'mobile_default' => [
                                     'val' => '',
                                    ],
                'default' => 'cover',
                'selectors' => [
                    '{{WRAPPER}} .spicy-img' => 'object-fit: {{val}};',
                 ],
               ]
            
            );
        //border classic    
        $this->add_group_control(
               Elementor\Group_Control_Border::get_type(),
               [
                'name' => 'spicy_border',
                'label' => __( 'Border', 'Spicy-extension' ),
                'selector' => '{{WRAPPER}} .spicy-img',
               ]
        );
        //border radius classic
        $this->add_responsive_control(
               'spicy_image_border_radius',
               [
                'label' => __( 'Border Radius', 'Spicy-extension' ),
                'type' => Elementor\Controls_Manager::DIMENSIONS,
                'size_units' => [ 'px', '%' ],
                'selectors' => [
                    '{{WRAPPER}} .spicy-img' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
                ],
            ]
        );
        $this->end_controls_section();
    }

Pro Layout

The following controls or sections are only displayed in the pro layout.

1. Repeater Section: This section has four controls, which are Image, Text, Avatar, and Textarea. It’s used for adding images individually and is displayed in the Content Tab.

protected function register_controls_pro_repeater(){
        $repeater = new \Elementor\Repeater();
        //add pro image control
        $repeater->add_control(
                   'spicy_pro_image',
                   [
                    'label' => __( 'Choose Image', 'Spicy-extension' ),
                    'type' => \Elementor\Controls_Manager::MEDIA,
                    'dynamic' => [
                                  'active' => true,
                                 ],
                    'default' => [
                     'url' => \Elementor\Utils::get_placeholder_image_src(),
                    ],
                  ]
        );
        //add pro text control
        $repeater->add_control(
                   'spicy_pro_text',
               [
                'label' => __( 'Text', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::TEXT,
                'label_block' => true,
                'placeholder' => __( 'Your Text', 'Spicy-extension' ),
                'default' => __( '', 'Spicy-extension' ),
                'dynamic' => [
                              'active' => true,
                             ],
               ]
        );
        //add avatar image control
        $repeater->add_control(
            'spicy_pro_avatar',
            [
                'label' => __( 'Choose Avatar', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::MEDIA,
                'dynamic' => [
                    'active' => true,
                ],
                'default' => [
                    'url' => \Elementor\Utils::get_placeholder_image_src(),
                ],
            ]
     );
        //pro textarea control
        $repeater->add_control(
               'spicy_pro_description',
               [
                'label' => __( 'Description', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::TEXTAREA,
                'rows' => 5,
                'default' => __( '', 'Spicy-extension' ),
                'placeholder' => __( 'Type your description here', 'spicy' ),
               ]
        );

        //add repeater control
        $this->add_control(
               'spicy_image_list',
               [
                'label' => __('Images','Spicy-extension'),
                'type' => \Elementor\Controls_Manager::REPEATER,
                'label_block' => true,
                'separator' => 'default',
                'fields' => $repeater->get_controls(),
                'title_field' => '{{{spicy_pro_text }}}',
                'condition' => [
                                'spicy_skin' => 'pro',
                            ],
             ]
        );
    }

2. Item Section: This is displayed in the Style Tab to change the border style of the gallery item.

protected function register_controls_pro_item(){
     $this->start_controls_section(
               'spicy_pro_item',
               [
                'label' => __( 'Item', 'Spicy-extension' ),
                'tab' => \Elementor\Controls_Manager::TAB_STYLE,
                'condition'=>[
                              'spicy_skin' => 'pro'
                             ]
              ]
        );
        // pro item border
        $this->add_group_control(
               \Elementor\Group_Control_Border::get_type(),
               [
                'name' => 'spicy_item_border',
                'label' => __( 'Border', 'Spicy-extension' ),
                'selector' => '{{WRAPPER}} .spicy-item',
               ]
        );  
        //pro item border radius
        $this->add_responsive_control(
               'spicy_pro_item_border_radius',
               [
                'label' => __( 'Border Radius', 'Spicy-extension' ),
                'type' => Elementor\Controls_Manager::DIMENSIONS,
                'size_units' => [ 'px', '%' ],
                'selectors' => [
                    '{{WRAPPER}} .spicy-item' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
                ],
               ]
        );
        $this->end_controls_section();
    }

3. Image Section: This is displayed in the Style Tab to change the image size.

protected function register_controls_pro_style_image(){
        $this->start_controls_section(
               'spicy_pro_style_image',
               [
                'label' => __( 'Image', 'Spicy-extension' ),
                'tab' => \Elementor\Controls_Manager::TAB_STYLE,
                'condition'=>[
                              'spicy_skin' => 'pro'
                             ]
               ]
        );
        //pro image size
        $this->add_responsive_control(
               'spicy_pro_image_height',
               [
                'label' => __( 'height', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::SLIDER,
                'size_units' => [ 'px','em','vh' ],
                'range' => [
                            'px' => [
                                     'min' => 0,
                                     'max' => 800,
                                     'step' => 1,
                                    ],
                            'vh' => [
                                     'min' => 0,
                                     'max' => 100,
                                     'step' => 1,
                                    ],
                            'em' => [
                                     'min' => 0,
                                     'max' => 10,
                                     'step' => 0.1,
                                    ],
                          ],
                'default' => [
                              'unit' => 'px',
                              'size' => 150,
                             ],
                'show_label' => true,
                'selectors' => [
                  '{{WRAPPER}} .spicy-pro-img' => ' height: {{SIZE}}{{UNIT}};',
                ],
            ]
        );
        //pro image fitness
        $this->add_responsive_control(
            'spicy_pro_image_fitness',
            [
                'label' => __( 'Image Size Behavior', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::SELECT,
                'options' => ['cover'=> __( 'Cover', 'Spicy-extension' ),'fill'=>__( 'Fill', 'Spicy-extension' ),'contain'=>__( 'Contain', 'Spicy-extension' ),'scale-down'=>__( 'Scale Down', 'Spicy-extension' ),'none'=>__( 'None', 'Spicy-extension' )],
                'devices' => [ 'desktop', 'tablet', 'mobile' ],
                'desktop_default' => [
                                      'val' => '',
                                     ],
                'tablet_default' => [
                                     'val' => '',
                                    ],
                'mobile_default' => [
                                     'val' => '',
                                    ],
                'default' => 'cover',
                'selectors' => [
                  '{{WRAPPER}} .spicy-pro-img' => 'object-fit: {{val}};',
                ],
            ]
            
      );
        $this->end_controls_section();

4. Text Section: This is displayed in the Style Tab to change the style of the text item.

protected function register_controls_pro_style_text(){
        $this->start_controls_section(
                'spicy_pro_style_text',
                [
                 'label' => __( 'Text', 'Spicy-extension' ),
                 'tab' => \Elementor\Controls_Manager::TAB_STYLE,
                 'condition'=>[
                    'spicy_skin' => 'pro'
                ]
            ]
        );

        //pro text color control
        $this->add_control(
               'spicy_pro_text_color',
               [
                'label' => __( 'Color', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::COLOR,
                'default' => '',
                'selectors' => [
                  '{{WRAPPER}} .spicy-pro-text' => 'color: {{VALUE}};',
                 ],
                'scheme' => [
                             'type' => \Elementor\Scheme_Color::get_type(),
                             'value' => \Elementor\Scheme_Color::COLOR_1,
                            ],
               ]
        );

        //pro text typography
        $this->add_group_control(
                \Elementor\Group_Control_Typography::get_type(),
                [
                 'name' => 'spicy_pro_text_typography',
                 'selector' => '{{WRAPPER}} .spicy-pro-text',
                 'scheme' => \Elementor\Scheme_Typography::TYPOGRAPHY_1,
                ]
        );

        //pro text alignment
        $this->add_responsive_control(
               'spicy_pro_text-_align',
               [
                'label' => __( 'Alignment', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::CHOOSE,
                'options' => [
                              'left' => [
                                          'title' => __( 'Left','Spicy-extension'),
                                          'icon' => 'fa fa-align-left',
                                        ],
                              'center' => [
                                            'title' => __( 'Center', 'Spicy-extension' ),
                                            'icon' => 'fa fa-align-center',
                                           ],
                              'right' => [
                                          'title' => __( 'Right', 'Spicy-extension' ),
                                          'icon' => 'fa fa-align-right',
                                         ],
                              'justify' => [
                                            'title' => __( 'Justified', 'Spicy-extension' ),
                                            'icon' => 'fa fa-align-justify',
                                           ],
                             ],
                'default' =>'',
                'selectors' => [
                  '{{WRAPPER}} .spicy-pro-text' => 'text-align: {{VALUE}};',
                ],
            ]
        );
        //pro text spacing control
        $this->add_responsive_control(
               'spicy_text_margin',
               [
                'label' => __( 'Spacing', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::DIMENSIONS,
                'size_units' => [ 'px', '%', 'em' ],
                'selectors' => [
                  '{{WRAPPER}} .spicy-pro-text' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
                ],
              ]
        );

        $this->end_controls_section();
    } 

5. Avatar Section: This is displayed in the Style Tab to change the style of the avatar.

protected function register_controls_pro_style_avatar(){
        $this->start_controls_section(
            'spicy_pro_style_avatar',
            [
                'label' => __( 'Avatar', 'Spicy-extension' ),
                'tab' => \Elementor\Controls_Manager::TAB_STYLE,
                'condition'=>[
                    'spicy_skin' => 'pro'
                ]
            ]
        );
        //pro avatar height
        $this->add_responsive_control(
            'spicy_avatar_height',
             [
                'label' => __( 'height', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::SLIDER,
                'size_units' => [ 'px','em','vh' ],
                'range' => [
                    'px' => [
                        'min' => 0,
                        'max' => 100,
                        'step' => 1,
                    ],
                    'vh' => [
                        'min' => 0,
                        'max' => 100,
                        'step' => 1,
                    ],
                    'em' => [
                        'min' => 0,
                        'max' => 10,
                        'step' => 0.1,
                    ],
                ],
                'default' => [
                    'unit' => 'px',
                    'size' => 50,
                ],
                'show_label' => true,
                'selectors' => [
                    '{{WRAPPER}} .spicy-avatar' => ' height: {{SIZE}}{{UNIT}};',
                ],
             ]
        );
        //pro avatar width
        $this->add_responsive_control(
            'spicy_avatar_width',
            [
                'label' => __( 'Width', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::SLIDER,
                'size_units' => [ 'px','em','vh' ],
                'range' => [
                    'px' => [
                        'min' => 0,
                        'max' => 100,
                        'step' => 1,
                    ],
                    'vh' => [
                        'min' => 0,
                        'max' => 100,
                        'step' => 1,
                    ],
                    'em' => [
                        'min' => 0,
                        'max' => 10,
                        'step' => 0.1,
                    ],
                ],
                'default' => [
                    'unit' => 'px',
                    'size' => 50,
                ],
                'show_label' => true,
                'selectors' => [
                    '{{WRAPPER}} .spicy-avatar' => ' width: {{SIZE}}{{UNIT}};',
                ],
            ]
        );
        //pro avatar align control
        $this->add_responsive_control(
            'spicy_avatar_align',
            [
                'label' => __( 'Alignment', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::CHOOSE,
                'options' => [
                    'left' => [
                        'title' => __( 'Left', 'Spicy-extension' ),
                        'icon' => 'eicon-h-align-left',
                    ],
                    'center' => [
                        'title' => __( 'Center', 'Spicy-extension' ),
                        'icon' => 'eicon-h-align-center',
                    ],
                    'right' => [
                        'title' => __( 'Right', 'Spicy-extension' ),
                        'icon' => 'eicon-h-align-right',
                    ],
                ],
                'selectors' => [
                    '{{WRAPPER}} .spicy-avatar-wrapper' => ' text-align: {{VALUE}};',
                ],
            ]
        );
        //pro avatar spacing control
        $this->add_responsive_control(
            'spicy_avatar_spacing',
            [
                'label' => __( 'Spacing', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::DIMENSIONS,
                'size_units' => [ 'px', '%', 'em' ],
                'selectors' => [
                    '{{WRAPPER}} .spicy-avatar' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
                ],
            ]
        );
        //pro border avatar
        $this->add_group_control(
            \Elementor\Group_Control_Border::get_type(),
            [
                'name' => 'spicy_avatar_border',
                'label' => __( 'Border', 'Spicy-extension' ),
                'selector' => '{{WRAPPER}} .spicy-avatar',
            ]
        );
        //pro border radius
        $this->add_responsive_control(
            'spicy_pro_border_radius',
            [
                'label' => __( 'Border Radius', 'Spicy-extension' ),
                'type' => Elementor\Controls_Manager::DIMENSIONS,
                'size_units' => [ 'px', '%' ],
                'selectors' => [
                    '{{WRAPPER}} .spicy-avatar' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
                ],
            ]
        );
        $this->end_controls_section();
    }

5. Textarea Section: This is displayed in the Style Tab to change the style of Textarea.

protected function register_controls_pro_style_textarea(){
        $this->start_controls_section(
               'spicy_pro_style_textarea',
               [
                'label' => __( 'Textarea', 'Spicy-extension' ),
                'tab' => \Elementor\Controls_Manager::TAB_STYLE,
                'condition'=>[
                  'spicy_skin' => 'pro'
                ]
              ]
        );
        //pro textarea color control
        $this->add_control(
               'spicy_pro_textarea-color',
               [
                'label' => __( 'Color', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::COLOR,
                'default' => '',
                'selectors' => [
                  '{{WRAPPER}} .spicy-pro-description' => 'color: {{VALUE}};',
                ],
                'scheme' => [
                  'type' => \Elementor\Scheme_Color::get_type(),
                  'value' => \Elementor\Scheme_Color::COLOR_1,
                ],
               ]
        );
        //pro textarea typography control
        $this->add_group_control(
                \Elementor\Group_Control_Typography::get_type(),
                [
                 'name' => 'spicy_pro_textarea_typography',
                 'selector' => '{{WRAPPER}} .spicy-pro-description',
                 'scheme' => \Elementor\Scheme_Typography::TYPOGRAPHY_1,
             ]
        );
        //pro textarea align control
        $this->add_responsive_control(
               'spicy_pro_textarea-align',
                [
                 'label' => __( 'Alignment', 'Spicy-extension' ),
                 'type' => \Elementor\Controls_Manager::CHOOSE,
                 'options' => [
                               'left' => [
                                          'title' => __( 'Left','Spicy-extension' ),
                                          'icon' => 'fa fa-align-left',
                                         ],
                               'center' =>[
                                           'title'=>__('Center','Spicy-extension' ),
                                           'icon' => 'fa fa-align-center',
                                          ],
                               'right' =>[
                                          'title' => __( 'Right','Spicy-extension'),
                                          'icon' => 'fa fa-align-right',
                                         ],
                               'justify'=>[
                                           'title' => __( 'Justified','Spicy-extension'),
                                           'icon' => 'fa fa-align-justify',
                                          ],
                              ],
                'default' =>'',
                'selectors' => [
                  '{{WRAPPER}} .spicy-pro-description' => 'text-align: {{VALUE}};',
                ],
            ]
        );
        //pro textarea spacing control
        $this->add_responsive_control(
               'spicy_textarea_spacing',
               [
                'label' => __( 'Spacing', 'Spicy-extension' ),
                'type' => \Elementor\Controls_Manager::DIMENSIONS,
                'size_units' => [ 'px', '%', 'em' ],
                'selectors' => [
                  '{{WRAPPER}} .spicy-pro-description' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
                ],
            ]
        );
        $this->end_controls_section();
    } 

Create Final Html Output

To render our gallery output in the frontend, we must create the render function as the following:

protected function render() {
        $settings = $this->get_settings_for_display();
        ?>
        <div class="spicy-gallery" width="100%">
        <?php
   
        if($settings['spicy_skin']=='classic'){
            $this->classic_render($settings);
        }
         else{
          $this->pro_render($settings);
         }
        ?>
        </div>        
        <?php
    }
protected function classic_render($settings){
        if($settings['spicy_images']){
            foreach ( $settings['spicy_images'] as $image ) {
            echo '<div class="spicy-item elementor-animation-'. $settings['spicyhover'] .' ">';
               echo '<img id="'. $image['id'].'" class="spicy-img" src="' . $image['url'] . '">';
               echo '</div>';
            }
      }  
    }
protected function pro_render($settings){
        if($settings['spicy_image_list']){
            foreach ( $settings['spicy_image_list'] as $image ) {
                    echo '<div class="spicy-item elementor-animation-'. $settings['spicyhover'] .' ">';
                    echo '<div class="spicy-pro-container">';   
                        echo '<img class="spicy-pro-img" src="'.$image['spicy_pro_image']['url'].'" >';     
                    echo '</div>';  
                    echo '<p class="spicy-pro-text">'.$image['spicy_pro_text'].'</p>';
                    echo'<div class="spicy-avatar-wrapper">';
                        echo '<img class="spicy-avatar" src="'.$image['spicy_pro_avatar']['url'].'" alt="Avatar">';
                    echo'</div>';
                    echo '<p class="spicy-pro-description">'.$image['spicy_pro_description'].'</p>';
                    
                    
                echo '</div>';
            }
        }
    }

Create Live Preview in Editor

To generate a live preview of the gallery output, we must create the _content_template() method by using Javascript:

protected function _content_template() {
       ?>
        <div class="spicy-gallery">
        <#
         if(settings.spicy_skin=='classic'){
         _.each( settings.spicy_images, function( image ) {
        #>
        <div class="spicy-item elementor-animation-{{settings.spicyhover}}">
         <img  class="spicy-img" id="{{image.id}}" src="{{image.url}}"/>
        </div>
        <# }); } else{
            _.each( settings.spicy_image_list, function( image ) {
        #>
            <div class="spicy-item elementor-animation-{{settings.spicyhover}}">    
             <div class="spicy-pro-container">
                <img class="spicy-pro-img" src="{{image.spicy_pro_image.url}}" >
           </div>
              <p class="spicy-pro-text">{{image.spicy_pro_text}}</p>
                <div class="spicy-avatar-wrapper">
                 <img class="spicy-avatar" src="{{image.spicy_pro_avatar.url}}" alt="Avatar">
                </div>
                <p class="spicy-pro-description">{{image.spicy_pro_description}}</p>
            </div>
        <# }); }#>
        </div>
      <?php
    }

How to Register CSS Style to Widget

After creating a gallery widget, we need to register essential CSS style to our widget. To do this, we need to first create a CSS file, then register it in Spicy.php.

Gallery.css
.spicy-gallery{
 display:grid;
 grid-template-rows: auto;
 }
.spicy-gallery .spicy-img{
 width: 100%;
 object-fit: cover;
 margin: 0 auto;
 }

.spicy-gallery .spicy-pro-container{
 width:100%; 
 }
.spicy-gallery .spicy-pro-text{
 text-align:center;
 margin-top:-70px;
 margin-bottom: 10px;
 }
.spicy-gallery .spicy-pro-description{
 width:100%;
 text-align: center;
 padding-left: 10px;
 padding-right: 10px;
        
 }
.spicy-gallery .spicy-pro-img{
 width: 100%;
 }
.spicy-gallery .spicy-avatar{
 display:inline-block;
 margin-left:10px;
 margin-bottom: 20px;
 border-radius: 12px;
 }
.spicy-gallery .spicy-avatar.wrapper{
 display: block;
 }

In order to register our CSS file, we need to add below code to Spicy.php

// Register Widget Styles
add_action( 'elementor/frontend/after_enqueue_styles', [ $this, 'widget_styles' ] );
public function widget_styles() {
 wp_enqueue_style( 'spicyPluginStylesheet', plugins_url( '/css/gallery.css', __FILE__ ) );
}

Classic layout output

Creating a New Widget Classic Layout Output

Pro layout output

Creating a New Widget Pro Layout Output

Conclusion

In this blog post, we described how to extend Elementor like a pro in detail and explained the structure of an Elementor widget. We also illustrated how to create a section and control, how to display/hide a section or control, how to change the widget style from controls, how to get settings and how to render the final output in frontend and Elementor editor.

Finally, we walked you through the steps of developing a gallery widget. Feel free to share your ideas and experiences in the comments section below.

How to Create Custom Forms with Jupiter X Form Builder

Create Custom Forms Featured

Creating custom forms in WordPress has always been a hassle. Jupiter X has begun a new era in the form building experience. Now, you can create custom forms visually. Typically, site visitors come across forms such as signup, sign in, register, subscription, contact or any other custom forms.

Forms allow site visitors the ability to send information such as usernames, passwords, comments, emails, among other things to the server.

Forms contain fields such as dropdowns, checkboxes, text boxes, input, as well as submit and radio buttons. Nowadays forms play an integral role, particularly in digital marketing and social networks.

Form Builder in Depth

If you want to build a beautiful and secure form with Jupiter X, then you can use the Form Builder, which allows you to create custom forms in a few minutes with minimum effort.

Content Tab

This tab contains four sections such as Form Fields, Submit Button, Settings and Feedback Messages. Let’s go through each of them one by one.

Form Fields

In this section, we can create fields for our forms. Form Fields have two options:

  • Form: for setting the name of the form.
  • Items: each field in the form.
Create Custom Forms Screenshot 1
Create Custom Forms Screenshot 2

Furthermore, each item has its options like:

  • Type
  • Label
  • Placeholder
  • Required
  • Column Width
  • And more.

One of the important options of each field is the Type option, which defines the role of the field. The Jupiter X Form Builder has eleven types for fields:

  • Text: for creating the text input.
  • Email: for creating the email input.
  • Textarea: for creating the textarea input.
  • Tel: for creating only the phone number input.
  • Number: for creating only the number input.
  • Date: for creating the date picker.
  • Time: for creating the time picker.
  • Checkbox: for creating a checkbox element.
  • Radio: for creating a radio button element.
  • Acceptance: for creating an agreement for conditions or a term of use button.
  • reCAPTCHA: for creating the human testing Google service.
Create Custom Forms Screenshot 3

Submit Button

After clicking on the Submit Button, the information contained in the form is sent to the server. The Submit Button section has three options:

  • Text: for changing the bottom text.
  • Icon: for setting an icon for the Submit Button.
  • Column Width: for changing the Submit Button container.
Create Custom Forms Screenshot 4

Settings

The Settings section has three options:

  • Label: Show/Hide status for fields label.
  • Required Mark: Show/Hide * after fields label for required fields.
  • Add Action: Set the action in the form.
Create Custom Forms Screenshot 5

The Jupiter X Form Builder allows the user to choose from seven action fields. If you choose one of them, the options section of each action appears at the bottom of the setting section to where you can modify it.

1. Email: This action email form allows users to fill in their email address such as admin. We can set To, Email Subject, From Email, From Name, Reply-To, Cc, Bcc.

Create Custom Forms Screenshot 6
Create Custom Forms Screenshot 7

2. MailChimp: This is used for a subscription form. In order to use this, we must first have an API Key from MailChimp to manage our subscriber list. You can learn more about how to get a MailChimp API by clicking on this link.

In the MailChimp section, set the API Key, choose the Audience, and finally set the field mapping. In default, MailChimp has six fields including Email, Address, Birthday, First Name, Last Name, and Phone Number.

Create Custom Forms Screenshot 8
Create Custom Forms Screenshot 9

3. Redirect: With this, we can redirect our page to another URL.

Create Custom Forms Screenshot 10
Create Custom Forms Screenshot 11

4. Slack: this action allows us to send information from our form to Slack. In order to do this, we must first sign up for Slack, enable the incoming webhook, then set our information in the Slack setting section. For more info on how to do this, you can head over to this link.

Create Custom Forms Screenshot 12
Create Custom Forms Screenshot 13

5. HubSpot: One feature that Hubspot provides is allowing users to create a contact form and then to publish these forms on the internet. This allows us to directly submit our form data to the Hubspot form.

First, signup to HubSpot then go to Marketing > Forms and create a new form. After that, design a form and get a Portal ID and Form ID to enter in the HubSpot action settings in the form.

Create Custom Forms Screenshot 16
Create Custom Forms Screenshot 17

6. Download: This allows site visitors to download a file from your website.

Create Custom Forms Screenshot 18
Create Custom Forms Screenshot 19
Create Custom Forms Screenshot 20

7. Webhook: this enables us to integrate our form with Zapier webhooks. Zapier webhooks allows us to complete several tasks such as sending an email, connecting to Facebook, Twitter, and Slack, as well as accessing more than 1,500 apps.

To activate this action, go to Zapier, generate a zap, and copy the Webhook URL and paste it into the Webhook action setting. To find out more about how to do this, visit this link.

Feedback Messages

After a page visitor submits a form, they will be able to see a message on their screen. The Jupiter X Form Builder has four types of messages, including Successful Message, Error Message, Required Message and Subscriber Already Exists Message. In the Feedback Messages section, you can alter the messages tailored to fit your needs.

Create Custom Forms Screenshot 22

Style Tab

This tab contains six settings including General, Label, Field, Checkbox, Radio, Button. Let’s walk through each setting one by one.

General

In the general section, we have two options:

  • Column Spacing: this allows you to change the space between fields that are arranged horizontally.
  • Row Spacing: this enables you to adjust the space between fields that are arranged vertically.

Label

In the label section, we can set the below style for the label:

  • Color
  • Font family
  • Font size
  • Font weight
  • Transform
  • Style
  • Decoration
  • Line-height
  • Letter spacing
  • Space between the label and field.

Field

In the field section, we can set the below style in the normal and focused situation.

  • Background color
  • Border Type
  • Border Radius
  • Box Shadow

Also, we can set Color, Typography, and Padding of the placeholder and value.

Checkbox

In the section, we can set below style in the normal and checked situation.

  • Size
  • Color
  • Typography
  • Spacing between
  • Spacing
  • Background color
  • Border style
  • Box-shadow

Radio

In the section, we can set below style in the normal and checked situation.

  • Size
  • Color
  • Typography
  • Spacing between
  • Spacing
  • Background color
  • Border style
  • Box-shadow

Button

In this section, we can adjust the style for a button with the following settings:

  • Height
  • Color
  • Typography
  • Spacing
  • Alignment
  • Background Type
  • Icon color
  • Border style
  • Box-shadow

Creating a Custom Contact Form

Contact forms are a useful type of form commonly used on websites. Visitors to your page can fill out contact forms to send messages, ideas, questions or any other information to the site owner.

In creating this form, you have the option to include Email, Redirect, Slack and Webhook actions. At the end of this section, we will create a Contact Us form like in the image below:

1. Go to Elementor > Add New.

2. Create a Section that is named Contact Us.

Create Custom Forms Screenshot 24

3. Add a new section with one column and set the following:

  • This image as a background image.
  • In the Content tab, Content Width: 680px
  • In the Advanced tab, Padding top and Padding bottom: 100px

4. Add a Heading.

  • In the Content tab, set Title to Contact Us and Alignment to Center.
  • In the Style tab, set Color to #FFFFFF
  • In the Advanced tab, set Padding Bottom to 16px.

5. Add a Form element from Raven Elements, then set the name to contact.

6. Create a First Name Field.

Create Custom Forms Screenshot 25

7. Create a Last Name field.

Create Custom Forms Screenshot 26

8. Create an Email Field.

Create Custom Forms Screenshot 27

9. Create a Phone Number field.

Create Custom Forms Screenshot 28

10. Create a Message field.

Create Custom Forms Screenshot 29

11. In the Style tab, set:

  • Background Color to rgba(255,255,255,0.91)
  • Placeholder Color to #000000

Also, set the other settings as shown in the images below.

Create Custom Forms Screenshot 30
Create Custom Forms Screenshot 31

12. Go to Content tab > Submit Button section.

Create Custom Forms Screenshot 32

13. Go to Style Tab > Button Section. Set Background Color to #6073e0

Create Custom Forms Screenshot 33

14. Go to Content tab > Setting section. Set your preferred actions.

Create Custom Forms Screenshot 34

15. Change the settings like in the images below in the sections of each action.

Create Custom Forms Screenshot 35
Create Custom Forms Screenshot 36

 

In the end, when submitting the form, the data will be sent to my@example.com, the page will be redirected to www.artbees.net, the form data will be sent to our Slack channel and a new email will be sent to the Gmail account that we set up in our zap in Zapier.

Creating a Subscription Form

Subscription forms are popular on websites as it enables the site owner to send news and deals to their subscriber list.

In this section, we’ll go through and explain the MailChimp, Download and Hubspot actions in detail. Also at the end, we’ll create the subscription form shown in the following image.

1. Go to Elementor > Add New. Create a new section and call it “subscribe.”

2. Add a New Section with two columns then set:

  • Background Image to this image.
  • In the Layout tab, set content width to 800.
  • In the Advanced tab, set Padding to 100px.

3. Edit the left column. In the Advanced tab, set:

  • Padding-top to 19 %.
  • Padding-Right to 30%.

4. Add a Heading element in the left column the set following settings.

  • Title: Subscribe to our Feeds
  • Alignment: center
  • Text color : #FFFFFF
  • Size: 26

5. Add a Spacer element in the left column.

6. Add a Social Icons element in the left column with the following settings.

  • Shape : Circle
  • Alignment : Center
  • In style tab: Primary Color to rgba(255,255,255,0.54), Secondary Color to rgba(0,0,0,0.68), Size to 17, Spacing to 18.

7. Edit the Right column:

  1. In the Style tab, set background color to rgba(255,255,255,0.8).
  2. In the Advanced tab, set the following:
  • Padding-top to 10%.
  • Padding-Right to 5%.
  • Padding-left to 5%.
  • Padding-bottom to 4%.

8. Edit the Right column:

Add a Form Element. In the Content tab set Form to Subscribe.

9. Create the First Name and Last Name fields like in the images below:

 

10. Create the Email and Phone fields as seen in the following images:

11. Create a Birthday field like the images below:

Create Custom Forms Screenshot 46

Important Note: In your MailChimp account, the type of birthday field must be Date.

12. Change the Submit button and Settings sections like in the image below:

Create Custom Forms Screenshot 49
Create Custom Forms Screenshot 49

13. In the MailChimp section, set our API Key and create five fields in Field Mapping with the following values:

Field Mapping

MailChimp FieldForm Field
Phone NumberPhone
BirthdayBirthday
First NameFirst Name
Last NameLast Name
EmailEmail

14. In the Download section, set your URL for download.

15. In the Hubspot section, set the settings as seen in the following image:

Field Mapping

HubspotForm Field
firstnameFirst Name
lastnameLast Name
emailEmail

16. Edit the Form > Style Tab, set the following:

  • Column Spacing: 15
  • Row Spacing: 25

Finally, after submitting a form the download will begin, the data in the form will be sent to Hubspot, and users will be subscribed to MailChimp.

Securing the Form

There’s no doubt that security is one of the most vital factors when it comes to forms. The Jupiter X Form Builder has been built on top of standard security practices.

Frontend Validation

Frontend validation (HTML5 validation) is used on the client-side to ensure that the data is correct before sending. The data entered into a form needs to be in the right format and certain fields need to be filled in order to effectively send the submitted form. For example, if we enter a number in the email field, an error occurs and the data is not sent.

Backend validation

Backend validation runs on the server after the data from the form is sent to the server. Backend is more important than frontend validation because hackers can disable frontend validation and send malicious data to the server.

reCAPTCHA

reCAPTCHA is a free Google service that protects websites from spam and abuse. With this service, we can ensure that the data is sent by a human, not a robot.

To add this to our form, we must go to reCAPTCHA and get the Site Key and Secret Key, and then insert them into Elementor > Setting > Raven.

Then, create a field with reCAPTCHA type to use it.

Logging the Sent Emails

Being able to log and view the sent emails in WordPress admin is very helpful both for development purposes and in tracking the emails.

There are some plugins that allow us to log the emails.

1. Email Log

Email Log is a WordPress plugin that allows you to easily log and view all emails sent from WordPress.

The features of Email Log include the following:

  • See all sent emails.
  • Filtered or sorted based on the date, email, subject, etc.
  • Delete selected emails or bulk emails.
  • Resend emails.
  • Forward emails.
  • Export email logs.

2. WP Mail Logging

WP Mail Logging logs each email sent by WordPress. This can be useful if you don’t want to lose such mail content. It can also be useful for debugging purposes while developing.

Features

  • A complete list of sent emails.
  • Error status from mail implementation is logged.
  • Zero-configuration – just install and enjoy.
  • Log rotation – decide which emails you want to keep.
  • DevOP: IP of server sent the mail.

Conclusion

In this article, we took a deep dive into how you can create custom forms with the Jupiter X Form Builder. This post provided step-by-step instructions on how to create two types of forms: a contact form and a subscription form. Through looking at both of these examples, we also covered the Email, Redirect, Slack, MailChimp and Download actions that are found in them.

We took this post a step further and explained the significance of security and addressed frontend and backend validations, as well as reCAPTCHA. This post further examined two plugins that will allow you to log the emails that have been sent to your server from your site’s forms.

Feel free to share your ideas and experiences with us in the comments!