Introducing WunderWP: Preset Styles for Elementor and More

Earlier last week we introduced WunderWP on ProductHunt. A new and free tool for Elementor that takes away a long-lasting pain shouldered by Elementor users. Let’s talk a little more about why this product was ideated and what problem it solves. Why on earth were preset styles for Elementor needed in the first place?

What is WunderWP?

WunderWP applies preset styling for Elementor widgets and beautifies your content in seconds. It helps you save big time by using its readymade styles to build your content and get your website up and going.

What problem preset styling for Elementor solves

Everyone who has used Elementor widgets knows that they are not ready to use right away! They won’t look nice until you put a good amount of time and energy stylizing and shaping them into something you want for your website. If you want to go about doing this, there are simply too many options and settings to deal with, and you need to make sure that what you customize looks professional and visually appealing.

All of this is time-consuming – and not to mention boring! WunderWP saves you that time by bringing on hours of work put by skilled designers and Elementor experts at Artbees to beautify your raw Elementor widgets and let you focus on the more important aspects of your project.

How WunderWP solves the problem

Regardless of which theme you use, once you install WunderWP, preset styles will appear in your element settings under the Content tab.

Preset Styles for Elementor Choose Style

Try different styles by clicking on their thumbnails and see which one looks better on the element and get something that more closely matches your preference. Voila! The ugly-looking default widget instantly turns into something beautiful.

Preset Styles for Elementor Before and After

New styles from the WunderWP Cloud

WunderWP adds new styles for different elements over time and, as long as you have the plugin installed, your library automatically updates itself with the latest preset styles for Elementor.

Compatible with everything Elementor

WunderWP is free and compatible with all Elementor-based themes, Elementor Free and Elementor Pro. It is also fully compatible with Jupiter X and the Raven library. Jupiter X users can simply download it from the WordPress repository.

250+ preset styles?

At the moment, WunderWP provides the following number of styles:

  • 105 element styles for Elementor Free
  • 139 element styles for Elementor Pro
  • 36 element styles for Jupiter X Raven

…and new styles are being added over time!

Is it possible to save/reuse my own styles?

At the moment, it’s only possible to apply WunderWP’s preset styles but this sounds like a tempting feature! If you’d like to be able to add your own styles and reuse it again for other elements, please do let us know if you want to see that in WunderWP!

What’s next

WunderWP was ideated to automate styling and to create something like readymade website templates but for individual widgets. This helped us produce website templates for Jupiter X at a more rapid pace and then we thought that maybe it could be of use as an independent plugin for Jupiter X users and maybe all Elementor users.

WunderWP is doing its job at the moment, and we’ll keep adding beautiful styles to it over time but are there other directions it can go in? Yes, there are! There could be preset styles for Elementor that could be applied globally on the website. Or, for example, there could be automation and readymade content for other content types!

It’s up to how our community reacts and which features they’d like to see next! We’re eagerly waiting by to hear your feedback before we decide the next move 😉 You can follow WunderWP on social media to stay current with the latest styles and features added to it.

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.

Extend Elementor like a pro: Creating a new extension in Elementor

Creating a New Extension Featured Image

Elementor is a powerful drag and drop page builder that allows us to create pages conveniently. One of Elementor’s greatest features is extendibility. With this feature, we can build custom widgets/controls via creating a new extension in Elementor.

This extension implements object-oriented programming in which a main class and extra classes for smaller parts like custom Elementor Widgets or any other components are used.

Plugin Structure

The main plugin should have basic information about the extensions, to check basic requirements and to load the required files to activate the plugin functionality. In the following sections, we’ll take a deep dive into each part of the plugin.

Defining Variable

Variables are used to store information to be referenced and manipulated in a computer program.

In the main class, we must define three constants like:

  • VERSION: store the version of our plugin.
  • MINIMUM_ELEMENTOR_VERSION: store the essential version of Elementor.
  • MINIMUM_PHP_VERSION: store the PHP version used in the plugin.
const VERSION;
const MINIMUM_ELEMENTOR_VERSION;
const MINIMUM_PHP_VERSION;

Single Instance

The singleton pattern is used to restrict the instantiation of a class to a single object, which can be useful when only one object is required across the system.

When creating a new extension in Elementor, we can only have one instance of the main class. To do this, we use a singleton pattern. In this pattern, we must define a private static variable and public static function.

private static $_instance = null;

	public static function instance() {
            //check $_instance is null or not
		if ( is_null( self::$_instance ) ) {
			self::$_instance = new self();
		}
		return self::$_instance;

	}

Constructor

The Constructor or magic function is a special type of function that is automatically executed after a class is created or instantiated. Usually, the constructor starts with two underscore characters.

In the main class, the role of the constructor is to load localization functionality and initiate the plugin.

public function __construct() {

		add_action( 'init', [ $this, 'i18n' ] );
		add_action( 'plugins_loaded', [ $this, 'init' ] );

	}
public function i18n() {
		load_plugin_textdomain( 'our-plugin-name' );
	}
public function init() {
		// Plugin logic here...
	}

Check if Elementor is Installed

As the plugin extends Elementor functionality, you should first check whether Elementor is installed and activated before the main class loads.

If Elementor is activated, the main class will load. If it’s not activated, an admin notice will be displayed and the functionality won’t continue loading. This check must be done in the initialization stage of the main class.

public function init() {

		// Check if Elementor installed and activated
		if ( ! did_action( 'elementor/loaded' ) ) {
			add_action( 'admin_notices', [ $this, 'admin_notice_missing_main_plugin' ] );
			return;
		}

	}

	public function admin_notice_missing_main_plugin() {

		if ( isset( $_GET['activate'] ) ) unset( $_GET['activate'] );

		$message = sprintf(
			/* translators: 1: Our plugin name 2: Elementor */
			esc_html__( '"%1$s" requires "%2$s" to be installed and activated.', 'our-plugin-name' ),
			'<strong>' . esc_html__( 'Elementor our-plugin-name', 'our-plugin-name' ) . '</strong>',
			'<strong>' . esc_html__( 'Elementor', 'our-plugin-name' ) . '</strong>'
		);

		printf( '<div class="notice notice-warning is-dismissible"><p>%1$s</p></div>', $message );

	}

Check the Version of Elementor

After checking whether or not Elementor is installed, we must check the Elementor version for backward compatibility with older Elementor versions. If the defined minimum version of our plugin is not compatible with the installed version of Elementor, then the admin message will be displayed, and functionality will not be able to load.
This check is done in the initialisation stage of the main class.

const MINIMUM_ELEMENTOR_VERSION = '2.5.11';

	public function init() {

		// Check for required Elementor version
		if ( ! version_compare( ELEMENTOR_VERSION, self::MINIMUM_ELEMENTOR_VERSION, '>=' ) ) {
			add_action( 'admin_notices', [ $this, 'admin_notice_minimum_elementor_version' ] );
			return;
		}

	}

	public function admin_notice_minimum_elementor_version() {

		if ( isset( $_GET['activate'] ) ) unset( $_GET['activate'] );

		$message = sprintf(
			/* translators: 1: Our plugin name 2: Elementor 3: Required Elementor version */
			esc_html__( '"%1$s" requires "%2$s" version %3$s or greater.', 'our-plugin-name' ),
			'<strong>' . esc_html__( 'Elementor our-plugin-name', 'our-plugin-name' ) . '</strong>',
			'<strong>' . esc_html__( 'Elementor', 'our-plugin-name' ) . '</strong>',
			 self::MINIMUM_ELEMENTOR_VERSION
		);

		printf( '<div class="notice notice-warning is-dismissible"><p>%1$s</p></div>', $message );

	}

Check for the PHP Version

Finally, we must check our extension’s minimum PHP version, which . must be newer than the PHP version of the Elementor plugin. If there’s an older version, then the admin message will be displayed, and the functionality won’t load.

const MINIMUM_PHP_VERSION = '7.0';

	public function init() {

		// Check for required PHP version
		if ( version_compare( PHP_VERSION, self::MINIMUM_PHP_VERSION, '<' ) ) {
			add_action( 'admin_notices', [ $this, 'admin_notice_minimum_php_version' ] );
			return;
		}

	}

	public function admin_notice_minimum_php_version() {

		if ( isset( $_GET['activate'] ) ) unset( $_GET['activate'] );

		$message = sprintf(
			/* translators: 1: Our plugin name 2: PHP 3: Required PHP version */
			esc_html__( '"%1$s" requires "%2$s" version %3$s or greater.', 'our-plugin-name' ),
			'<strong>' . esc_html__( 'Elementor our-plugin-name', 'extension-name' ) . '</strong>',
			'<strong>' . esc_html__( 'PHP', 'our-plugin-name' ) . '</strong>',
			 self::MINIMUM_PHP_VERSION
		);

		printf( '<div class="notice notice-warning is-dismissible"><p>%1$s</p></div>', $message );

	}

Including Essential Files to Correctly Create a New Extension

After completing all checks, the extension must load essential files like widgets and controls in order for it to run correctly.

public function init() {

		// Add Plugin actions
		add_action( 'elementor/widgets/widgets_registered', [ $this, 'init_widgets' ] );
		add_action( 'elementor/controls/controls_registered', [ $this, 'init_controls' ] );
	}

	public function init_widgets() {

		// Include Widget files
		require_once( __DIR__ . '/widgets/our-plugin-name-widget.php' );

		// Register widget
		\Elementor\Plugin::instance()->widgets_manager->register_widget_type( new \Elementor_extension_Widget() );

	}

	public function init_controls() {

		// Include Control files
		require_once( __DIR__ . '/controls/our-plugin-name-control.php' );

		// Register control
		\Elementor\Plugin::$instance->controls_manager->register_control( 'control-type-', new \extension_Control() );

	}

Workflow Diagram

Create a New Extension - Workflow Diagram

In the end, if we want to create our extension named Spicy, we must have the following code in its entirety.

<?php
 /**
 * Plugin Name: Spicy Extension
 * Description: Custom Elementor extension.
 * Plugin URI:  https://spicy.test/
 * Version:     1.0.0
 * Author:      Spicy
 * Author URI:  https://spicy.test/
 * Text Domain: spicy-extension
 */

	if ( ! defined( 'ABSPATH' ) ) {
		exit; // Exit if accessed directly.
	}

	/**
	 * Main Spicy Extension Class
	 *
	 * The main class that initiates and runs the plugin.
	 *
	 * @since 1.0.0
	 */
	final class Spicy_Extension {

		/**
		 * Plugin Version
		 *
		 * @since 1.0.0
		 *
		 * @var string The plugin version.
		 */
		const VERSION = '1.0.0';

		/**
		 * Minimum Elementor Version
		 *
		 * @since 1.0.0
		 *
		 * @var string Minimum Elementor version required to run the plugin.
		 */
		const MINIMUM_ELEMENTOR_VERSION = '2.5.11';

		/**
		 * Minimum PHP Version
		 *
		 * @since 1.0.0
		 *
		 * @var string Minimum PHP version required to run the plugin.
		*/
		const MINIMUM_PHP_VERSION = '6.0';

		/**
		 * Instance
		 *
		 * @since 1.0.0
		 *
		 * @access private
		 * @static
		 *
		 * @var Spicy_Extension The single instance of the class.
		 */
		private static $_instance = null;

		/**
		 * Instance
		 *
		 * Ensures only one instance of the class is loaded or can be loaded.
		 *
		 * @since 1.0.0
		 *
		 * @access public
		 * @static
		 *
		 * @return Spicy_Extension An instance of the class.
		 */
		public static function instance() {

			if ( is_null( self::$_instance ) ) {
				self::$_instance = new self();
			}

		 	return self::$_instance;
		}

		/**
		 * Constructor
		 *
		 * @since 1.0.0
		 *
		 * @access public
		 */
		
		public function __construct() {	
			add_action( 'init', [ $this, 'i18n' ] );
			add_action( 'plugins_loaded', [ $this, 'init' ] );
		}

		/**
		 * Load Textdomain
		 *
		 * Load plugin localization files.
		 *
		 * Fired by `init` action hook.
		 *
		 * @since 1.0.0
		 *
		 * @access public
		 */
		public function i18n(){}

		/**
		 * Initialize the plugin
		 *
		 * Load the plugin only after Elementor (and other plugins) are loaded.
		 * Checks for basic plugin requirements, if one check fail don't continue,
		 * if all check have passed load the files required to run the plugin.
		 *
		 * Fired by `plugins_loaded` action hook.
		 *
		 * @since 1.0.0
		 *
		 * @access public
		 */
		public function init() {

			// Check if Elementor installed and activated
			if ( ! did_action( 'elementor/loaded' ) ) {
				add_action( 'admin_notices', [ $this, 'admin_notice_missing_main_plugin' ] );
				return;
			}

			// Check for required Elementor version			
			if ( ! version_compare( ELEMENTOR_VERSION, self::MINIMUM_ELEMENTOR_VERSION, '>=' ) ) {
				add_action( 'admin_notices', [ $this, 'admin_notice_minimum_elementor_version' ] );
				return;
			}

			// Check for required PHP version
			if ( version_compare( PHP_VERSION, self::MINIMUM_PHP_VERSION, '<' ) ) {
				add_action( 'admin_notices', [ $this, 'admin_notice_minimum_php_version' ] );
				return;
			}

			// Add Plugin actions
			add_action( 'elementor/widgets/widgets_registered', [ $this, 'init_widgets' ] );
			add_action( 'elementor/controls/controls_registered', [ $this, 'init_controls' ] );
			
			// Register Widget Styles
			add_action( 'elementor/frontend/after_enqueue_styles', [ $this, 'widget_styles' ] );

		}

		public function widget_styles() {
			//For Example
			//wp_enqueue_style( 'spicyPluginStylesheet', plugins_url( '/css/gallery.css', __FILE__ ) );
		}

		/**
		 * Admin notice
		 *
		 * Warning when the site doesn't have Elementor installed or activated.
		 *
		 * @since 1.0.0
		 *
		 * @access public
		 */
		public function admin_notice_missing_main_plugin() {

			if ( isset( $_GET['activate'] ) ) unset( $_GET['activate'] );

			$message = sprintf(
				/* translators: 1: Plugin name 2: Elementor */
				esc_html__( '"%1$s" requires "%2$s" to be installed and activated.', 'Spicy-extension' ),
				'<strong>' . esc_html__( 'Elementor Spicy Extension', 'Spicy-extension' ) . '</strong>',
				'<strong>' . esc_html__( 'Elementor', 'Spicy-extension' ) . '</strong>'
			);		

			printf( '<div class="notice notice-warning is-dismissible"><p>%1$s</p></div>', $message );

		}

		/**
		 * Admin notice
		 *
		 * Warning when the site doesn't have a minimum required Elementor version.
		 *
		 * @since 1.0.0
		 *
		 * @access public
		 */
		public function admin_notice_minimum_elementor_version() {

			if ( isset( $_GET['activate'] ) ) unset( $_GET['activate'] );

			$message = sprintf(
				/* translators: 1: Plugin name 2: Elementor 3: Required Elementor version */
				esc_html__( '"%1$s" requires "%2$s" version %3$s or greater.', 'Spicy-extension' ),
				'<strong>' . esc_html__( 'Elementor Spicy Extension', 'Spicy-extension' ) . '</strong>',
				'<strong>' . esc_html__( 'Elementor', 'Spicy-extension' ) . '</strong>',
				self::MINIMUM_ELEMENTOR_VERSION
			);

			printf( '<div class="notice notice-warning is-dismissible"><p>%1$s</p></div>', $message );

		}

		/**
		 * Admin notice
		 *
		 * Warning when the site doesn't have a minimum required PHP version.
		 *
		 * @since 1.0.0
		 *
		 * @access public
		 */
		public function admin_notice_minimum_php_version() {		

			if ( isset( $_GET['activate'] ) ) unset( $_GET['activate'] );

			$message = sprintf(
				/* translators: 1: Plugin name 2: PHP 3: Required PHP version */
				esc_html__( '"%1$s" requires "%2$s" version %3$s or greater.', 'Spicy-extension' ),
				'<strong>' . esc_html__( 'Elementor Spicy Extension', 'Spicy-extension' ) . '</strong>',
				'<strong>' . esc_html__( 'PHP', 'Spicy-extension' ) . '</strong>',
				self::MINIMUM_PHP_VERSION
			);

			printf( '<div class="notice notice-warning is-dismissible"><p>%1$s</p></div>', $message );

		}

		/**
		 * Init Widgets
		 *
		 * Include widgets files and register them
		 *
		 * @since 1.0.0
		 *
		 * @access public
		 */
		public function init_widgets() {
			 // For Example
			// Include Widget files
			//require_once( __DIR__ . '/widgets/gallery.php' );

			// Register widget
			//\Elementor\Plugin::instance()->widgets_manager->register_widget_type( new \spicy_oEmbed_Widget() );
			//\Elementor\Plugin::instance()->widgets_manager->register_widget_type( new \spicy_gallery_Widget() );

		}

		/*
		 * Init Controls
		 *
		 * Include controls files and register them
		 *
		 * @since 1.0.0				
		 *
		 * @access public
		*/
		public function init_controls() {
			//For example
			
			//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());

		}

    }

spicy_Extension::instance();

Also, you can download the entire code by clicking on this link.

Conclusion

In this article, we went into depth to illustrate the basic structure of the plugin that extends the functionality of Elementor. Also, we explained each essential part of the plugin code like Variable, Single Instance, Constructor, Checking steps and Include files. Finally, we included the entire code for the Spicy plugin.

We’d love to hear from you! Feel free to drop us a comment to share your experiences and ideas.

How to Add Filters to WooCommerce and Custom Posts

Add filters to WooCommerce and Custom Posts Featured Image

Just imagine how an actual customer experience looks like in a retail store. Most people don’t know what exactly they want in the first place. They like to see collections of items and then evaluate their options by narrowing down their selection. This is the ultimate experience.

Why do you need to add filters to WooCommerce and Custom Posts?

If you’re running an online store or using custom post types, you need to make sure that your users are able to easily find what product or post they are looking for. Having a search field on your website is a great help, but not nearly enough.

Just imagine how an actual customer experience looks like in a retail store. Most people don’t know what exactly they want in the first place. They like to see collections of items and then evaluate their options by narrowing down their selection. This is the ultimate experience.

Now, you should ask yourself as an online store runner. Does your website have this capability? Do you let your customers play with their options and narrow down their preferences?

Booking.com has a huge database but it is easily searchable by humans with the help of filters

Jupiter X is not only giving you an easy-to-setup and fully customizable platform to sell your products on, but it also offers comprehensive Woocommerce features to amplify your customer’s shopping experience even further.

Jupiter X comes bundled with a plugin called JetSmartFilters so you can add filters to WooCommerce and custom posts. This tiny and powerful plugin uses powerful AJAX filters so your customer can find the right WooCommerce products and custom posts they’re looking for.

Who needs to add filters to WooCommerce and Custom Posts?

It’s a must for pretty much anyone running an e-commerce website or custom post types.

  1. Any online store based on Woocommerce
  2. Listing websites such as real estate, car rentals, etc.
  3. Websites with custom post types

Features

Speed

JetSmartFilters is created based on AJAX technology, which allows the filters to work without refreshing the page, making them even faster. So, users can work with options and filters seamlessly without having to refresh or reload the page.

7 Smart Filter Variations

There are several filter types you can choose from based on your need. Everything is built on Elementor, so you can easily modify, customize, and add filters to WooCommerce and custom posts.

9 Filters and Pagination Widgets

Further improve your visitor’s experience with these versatile filters and pagination widgets:

Highly customizable WooCommerce and Custom Post Filters

JetSmartFilters is built upon the Elementor plugin, therefore it is extremely customizable, just like any other Elementor widget. You can easily drag and drop the filter widgets to the page and start customizing their elements until it matches your desired style and theme. With Jupiter X, it’s that easy to add filters to WooCommerce and custom posts!  

Official plugin website

Documentation