title: Elementor Custom Widget from Scratch
body: <p>In a project I have to create a carousel in Elementor which should look like the one below:</p>
  
  <p><strong>carousel design</strong></p>
  
  <p><a href="https://i.stack.imgur.com/7zLNa.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/7zLNa.jpg" alt="enter image description here"></a></p>
  
  <p>Using the widget from Elementor I couldn't create a carousel like this so I tried to create my own carousel using Easy-Responsive-jQuery-Carousel:</p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;!DOCTYPE html&gt;
  &lt;html&gt;
  &lt;head&gt;
   &lt;meta charset="utf-8"&gt;
   &lt;meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"&gt;
   &lt;title&gt;jQuery FilmRoll  Examples&lt;/title&gt;
   &lt;meta name="description" content=""&gt;
   &lt;meta name="viewport" content="width=device-width"&gt;
   &lt;link rel="stylesheet" href="https://www.jqueryscript.net/demo/Easy-Responsive-jQuery-Carousel-Slider-Plugin-FilmRoll/css/bootstrap.min.css"&gt;
   &lt;link rel="stylesheet" href="https://www.jqueryscript.net/demo/Easy-Responsive-jQuery-Carousel-Slider-Plugin-FilmRoll/css/bootstrap-responsive.min.css"&gt;
   &lt;style type="text/css"&gt;
    body {
      padding-top: 0;
      padding-bottom: 40px;
  }
  .hero-unit {
    text-align: center;
    border-radius: 0;
    margin: 150px 0 0 0;
    background: #1e5799; /* Old browsers */
    background: -moz-linear-gradient(top,  #1e5799 0%, #338cd6 59%, #ffffff 100%); /* FF3.6+ */
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#1e5799), color-stop(59%,#338cd6), color-stop(100%,#ffffff)); /* Chrome,Safari4+ */
    background: -webkit-linear-gradient(top,  #1e5799 0%,#338cd6 59%,#ffffff 100%); /* Chrome10+,Safari5.1+ */
    background: -o-linear-gradient(top,  #1e5799 0%,#338cd6 59%,#ffffff 100%); /* Opera 11.10+ */
    background: -ms-linear-gradient(top,  #1e5799 0%,#338cd6 59%,#ffffff 100%); /* IE10+ */
    background: linear-gradient(to bottom,  #1e5799 0%,#338cd6 59%,#ffffff 100%); /* W3C */
    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#1e5799', endColorstr='#ffffff',GradientType=0 ); /* IE6-9 */
  }
  .hero-unit h1, .hero-unit p.white { color: white; }
  .hero-unit h1 img { vertical-align: bottom; }
  .hero-unit p { margin-top: 30px; }
  .film_roll_wrapper img {
    border: 10px solid white;
    box-shadow: 7px 7px 15px #777;
    margin-left: 5px;
    margin-right: 5px;
    transition: all 1s ease;
  }
  .film_roll_wrapper .active img {
    border: 10px solid yellow;
    height: 600px;
  }
  .film_roll_container {
    position: relative;
  }
  .film_roll_child.active {
      padding-top: 70px;
  }
  
  @media (max-width: 979px) {
    body {
      padding-top: 0px;
    }
    .navbar-fixed-top {
      margin-bottom: 0;
    }
  }
  
  @media (max-width: 767px) {
    .hero-unit {
      margin-left: -20px;
      margin-right: -20px;
    }
  }
   &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
      &lt;div id='film_roll_1'&gt;
    &lt;div&gt;&lt;a href='#'&gt;&lt;img src="https://placehold.it/800x530"&gt;&lt;/a&gt;&lt;/div&gt;
    &lt;div&gt;&lt;a href='#'&gt;&lt;img src="https://placehold.it/800x530"&gt;&lt;/a&gt;&lt;/div&gt;
    &lt;div&gt;&lt;a href='#'&gt;&lt;img src="https://placehold.it/800x530"&gt;&lt;/a&gt;&lt;/div&gt;
    &lt;div&gt;&lt;a href='#'&gt;&lt;img src="https://placehold.it/800x530"&gt;&lt;/a&gt;&lt;/div&gt;
    &lt;div&gt;&lt;a href='#'&gt;&lt;img src="https://placehold.it/800x530"&gt;&lt;/a&gt;&lt;/div&gt;
   &lt;/div&gt;
  
   &lt;script src="https://code.jquery.com/jquery-latest.min.js"&gt;&lt;/script&gt;
   &lt;script src="https://www.jqueryscript.net/demo/Easy-Responsive-jQuery-Carousel-Slider-Plugin-FilmRoll/js/bootstrap.min.js"&gt;&lt;/script&gt; 
   &lt;!--&lt;script src="https://www.jqueryscript.net/demo/Easy-Responsive-jQuery-Carousel-Slider-Plugin-FilmRoll/js/jquery.film_roll.js"&gt;&lt;/script&gt;--&gt; 
   &lt;script type="text/javascript"&gt;
  (function() {
    var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  
    this.FilmRoll = (function() {
      function FilmRoll(options) {
        this.options = options != null ? options : {};
        this.rotateRight = __bind(this.rotateRight, this);
        this.rotateLeft = __bind(this.rotateLeft, this);
        this.moveRight = __bind(this.moveRight, this);
        this.moveLeft = __bind(this.moveLeft, this);
        this.clearScroll = __bind(this.clearScroll, this);
        this.configureScroll = __bind(this.configureScroll, this);
        this.configureWidths = __bind(this.configureWidths, this);
        this.configureHover = __bind(this.configureHover, this);
        if (this.options.container) {
          this.div = jQuery(this.options.container);
          if (this.div.length) {
            this.configure();
          }
        }
      }
  
      FilmRoll.prototype.configure = function() {
        var first_child, shuttle_width,
          _this = this;
        this.children = this.div.children();
        this.children.wrapAll('&lt;div class="film_roll_wrapper"&gt;&lt;/div&gt;');
        this.children.wrapAll('&lt;div class="film_roll_shuttle"&gt;&lt;/div&gt;');
        this.wrapper = this.div.find('.film_roll_wrapper');
        this.shuttle = this.div.find('.film_roll_shuttle');
        this.rotation = [];
        shuttle_width = this.options.shuttle_width ? parseInt(this.options.shuttle_width, 10) : 10000;
        this.shuttle.width(shuttle_width);
        this.height = this.options.height ? parseInt(this.options.height, 10) : 0;
        this.wrapper.height(this.height);
        this.shuttle.height(this.height);
        if (!(this.options.no_css === true || document.film_roll_styles_added)) {
          jQuery("&lt;style type='text/css'&gt;        .film_roll_wrapper {display: block; text-align: center; float: none; position: relative; top: auto; right: auto; bottom: auto; left: auto; z-index: auto; width: 100%; margin: 0 !important; padding: 0 !important; overflow: hidden; width: 100%}        .film_roll_shuttle {text-align: left; float: none; position: absolute; top: 0; left:0; right: auto; bottom: auto; margin: 0 !important; padding: 0 !important; z-index: auto}        .film_roll_prev, .film_roll_next {position:absolute; top:48%; left:15px; width:40px; height:40px; margin:-20px 0 0 0; padding:0; font-size:60px; font-weight:100; line-height:30px; color:white; text-align: center; background: #222; border: 3px solid white; border-radius:23px; opacity:0.5}        .film_roll_prev:hover, .film_roll_next:hover {color:white; text-decoration:none; opacity:0.9}        .film_roll_next {left:auto; right:15px}        .film_roll_pager {text-align:center}        .film_roll_pager a {width:5px; height:5px; border:2px solid #333; border-radius:5px; display:inline-block; margin:0 5px 0 0; transition: all 1s ease}        .film_roll_pager a:hover {background: #666}        .film_roll_pager a.active {background: #333;}        .film_roll_pager span {display:none} .film_roll_child .active{padding-top: 70px!important;}     &lt;/style&gt;").appendTo('head');
          document.film_roll_styles_added = true;
        }
        if (this.options.pager !== false) {
          this.pager = jQuery('&lt;div class="film_roll_pager"&gt;');
          this.div.append(this.pager);
          this.children.each(function(i, e) {
            var link;
            link = jQuery("&lt;a href='#' data-id='" + e.id + "'&gt;&lt;span&gt;" + (i + 1) + "&lt;/span&gt;&lt;/a&gt;");
            _this.pager.append(link);
            return link.click(function() {
              var direction, rotation_index;
              _this.index = i;
              rotation_index = jQuery.inArray(_this.children[i], _this.rotation);
              direction = rotation_index &lt; (_this.children.length / 2) ? 'right' : 'left';
              _this.moveToIndex(_this.index, direction, true);
              return false;
            });
          });
        }
        this.pager_links = this.div.find('.film_roll_pager a');
        this.mouse_catcher = jQuery('&lt;div style="position:absolute; top:0; left: 0; height: 100%; width: 100%;" class="film_roll_mouse_catcher"&gt;&lt;/div&gt;');
        this.mouse_catcher.appendTo(this.wrapper).mousemove(function(event) {
          _this.clearScroll();
          return _this.mouse_catcher.remove();
        });
        first_child = null;
        this.children.each(function(i, e) {
          var $el;
          $el = jQuery(e);
          $el.attr('style', 'position:relative; display:inline-block; vertical-align:middle');
          $el.attr('data-film-roll-child-id', i);
          $el.addClass("film_roll_child");
          return _this.rotation.push(e);
        });
        if (this.options.prev &amp;&amp; this.options.next) {
          this.prev = jQuery(this.options.prev);
          this.next = jQuery(this.options.next);
        } else {
          this.wrapper.append('&lt;a class="film_roll_prev" href="#"&gt;&amp;lsaquo;&lt;/a&gt;');
          this.wrapper.append('&lt;a class="film_roll_next" href="#"&gt;&amp;rsaquo;&lt;/a&gt;');
          this.prev = this.div.find('.film_roll_prev');
          this.next = this.div.find('.film_roll_next');
        }
        this.prev.click(function() {
          _this.clearScroll();
          return _this.moveRight();
        });
        this.next.click(function() {
          _this.clearScroll();
          return _this.moveLeft();
        });
        this.index = this.options.start_index || 0;
        this.interval = this.options.interval || 4000;
        this.animation = this.options.animation || this.interval / 4;
        jQuery(window).resize(function() {
          return _this.resize();
        });
        jQuery(window).load(function() {
          _this.configureWidths();
          _this.moveToIndex(_this.index, 'right', true);
          if (_this.options.scroll !== false) {
            _this.configureScroll();
            return _this.configureHover();
          }
        });
        this.div.trigger(jQuery.Event("film_roll:dom_ready"));
        return this;
      };
  
      FilmRoll.prototype.configureHover = function() {
        this.div.hover(this.clearScroll, this.configureScroll);
        if (this.options.prev &amp;&amp; this.options.next) {
          this.prev.hover(this.clearScroll, this.configureScroll);
          return this.next.hover(this.clearScroll, this.configureScroll);
        }
      };
  
      FilmRoll.prototype.configureWidths = function() {
        var max_el_height,
          _this = this;
        this.div.trigger(jQuery.Event("film_roll:before_loaded"));
        this.width = max_el_height = 0;
        this.children.each(function(i, e) {
          var $el, el_height;
          $el = jQuery(e);
          _this.width += $el.outerWidth(true);
          el_height = $el.outerHeight(true);
          if (el_height &gt; max_el_height) {
            return max_el_height = el_height;
          }
        });
        if (!this.options.height) {
          this.height = max_el_height;
        }
        this.wrapper.height(this.height);
        this.shuttle.height(this.height);
        this.real_width = this.width;
        this.shuttle.width(this.real_width * 2);
        return this;
      };
  
      FilmRoll.prototype.configureScroll = function() {
        var _this = this;
        if (this.scrolled !== true) {
          this.timer = setInterval(function() {
            return _this.moveLeft();
          }, this.interval);
          this.scrolled = true;
        }
        return this;
      };
  
      FilmRoll.prototype.clearScroll = function() {
        if (this.scrolled !== false) {
          clearInterval(this.timer);
          this.scrolled = false;
        }
        return this;
      };
  
      FilmRoll.prototype.marginLeft = function(rotation_index, offset) {
        var child, i, margin, _i, _len, _ref;
        if (offset == null) {
          offset = 0;
        }
        margin = 0;
        _ref = this.rotation;
        for (i = _i = 0, _len = _ref.length; _i &lt; _len; i = ++_i) {
          child = _ref[i];
          if (i &lt; rotation_index &amp;&amp; i &gt;= offset) {
            margin += jQuery(child).outerWidth(true);
          }
        }
        return margin;
      };
  
      FilmRoll.prototype.marginRight = function(rotation_index, offset) {
        var child, i, margin, _i, _len, _ref;
        if (offset == null) {
          offset = 0;
        }
        offset = this.rotation.length - offset - 1;
        margin = 0;
        _ref = this.rotation;
        for (i = _i = 0, _len = _ref.length; _i &lt; _len; i = ++_i) {
          child = _ref[i];
          if (i &gt; rotation_index &amp;&amp; i &lt;= offset) {
            margin += jQuery(child).outerWidth(true);
          }
        }
        return margin;
      };
  
      FilmRoll.prototype.moveLeft = function() {
        this.index = (this.index + 1) % this.children.length;
        this.moveToIndex(this.index, 'left', true);
        return false;
      };
  
      FilmRoll.prototype.moveRight = function() {
        this.index -= 1;
        if (this.index &lt; 0) {
          this.index = this.children.length - 1;
        }
        this.moveToIndex(this.index, 'right', true);
        return false;
      };
  
      FilmRoll.prototype.moveToIndex = function(index, direction, animate) {
        var child, new_left_margin, rotation_index, visible_margin, wrapper_width,
          _this = this;
        if (animate == null) {
          animate = true;
        }
        child = this.children[index];
        rotation_index = jQuery.inArray(child, this.rotation);
        this.children.removeClass('active');
        jQuery(child).addClass('active').trigger(jQuery.Event("film_roll:activate"));
        this.pager_links.removeClass('active');
        jQuery(this.pager_links[index]).addClass('active');
        wrapper_width = this.wrapper.width();
        if (wrapper_width &lt; this.real_width) {
          visible_margin = (wrapper_width - jQuery(child).outerWidth(true)) / 2;
          if (direction === 'right') {
            while (rotation_index === 0 || this.marginLeft(rotation_index) &lt; visible_margin) {
              this.rotateRight();
              rotation_index = jQuery.inArray(child, this.rotation);
            }
          } else {
            while (rotation_index === this.children.length - 1 || this.marginRight(rotation_index) &lt; visible_margin) {
              this.rotateLeft();
              rotation_index = jQuery.inArray(child, this.rotation);
            }
          }
          new_left_margin = -1 * (this.marginLeft(rotation_index) - visible_margin);
          if (animate) {
            this.shuttle.stop().animate({
              'left': new_left_margin
            }, this.animation, 'swing', function() {
              return _this.div.trigger(jQuery.Event("film_roll:moved"));
            });
          } else {
            this.shuttle.css('left', new_left_margin);
            this.div.trigger(jQuery.Event("film_roll:moved"));
          }
        } else {
          this.shuttle.css('left', (wrapper_width - this.width) / 2);
        }
        return this;
      };
  
      FilmRoll.prototype.resize = function() {
        var _this = this;
        this.clearScroll();
        clearTimeout(this.resize_timer);
        this.resize_timer = setTimeout(function() {
          _this.configureScroll();
          _this.moveToIndex(_this.index, 'left');
          return _this.div.trigger(jQuery.Event("film_roll:resized"));
        }, 200);
        return this;
      };
  
      FilmRoll.prototype.rotateLeft = function() {
        var _css_left, _first_child, _shuttle_left;
        _css_left = this.shuttle.css('left');
        _shuttle_left = _css_left ? parseInt(_css_left, 10) : 0;
        _first_child = this.rotation.shift();
        this.rotation.push(_first_child);
        this.shuttle.css('left', _shuttle_left + jQuery(_first_child).outerWidth(true));
        return this.shuttle.append(this.shuttle.children().first().detach());
      };
  
      FilmRoll.prototype.rotateRight = function() {
        var _css_left, _last_child, _shuttle_left;
        _css_left = this.shuttle.css('left');
        _shuttle_left = _css_left ? parseInt(_css_left, 10) : 0;
        _last_child = this.rotation.pop();
        this.rotation.unshift(_last_child);
        this.shuttle.css('left', _shuttle_left - jQuery(_last_child).outerWidth(true));
        return this.shuttle.prepend(this.shuttle.children().last().detach());
      };
  
      return FilmRoll;
  
    })();
  
  }).call(this);
   &lt;/script&gt;
  
   &lt;script type="text/javascript"&gt;
  (function() {
    jQuery(function() {
      this.film_rolls || (this.film_rolls = []);
      this.film_rolls['film_roll_1'] = new FilmRoll({
        container: '#film_roll_1',
        height: 750
      });
      //return true;
    });
  
  }).call(this);
   &lt;/script&gt;
  &lt;/body&gt;
  &lt;/html&gt;</code></pre>
  </div>
  </div>
  </p>
  
  <p>The customer wants the carousel to be modified not from Custom FIeld which was easier to do and directly from the Elementor.So I tried to create my own widget for that.
  after a tutorial on youtube and using information from the official documentation from Elementor 
  Website and I created the following files.
  The filles are situated in boostrap4-child in a folder call custom_widget_elementor after I created this php files in boostrap4-child theme in a folder call custom_widget_elementor.</p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-html lang-html prettyprint-override"><code>//my-widgets.php
  &lt;?php
  class My_Elementor_Widgets {
  
   protected static $instance = null;
  
   public static function get_instance() {
    if ( ! isset( static::$instance ) ) {
     static::$instance = new static;
    }
  
    return static::$instance;
   }
  
   protected function __construct() {
    require_once('widget1.php');
    add_action( 'elementor/widgets/widgets_registered', [ $this, 'register_widgets' ] );
   }
  
   public function register_widgets() {
    \Elementor\Plugin::instance()-&gt;widgets_manager-&gt;register_widget_type( new \Elementor\My_Widget_1() );
   }
  
  }
  
  add_action( 'init', 'my_elementor_init' );
  function my_elementor_init() {
   My_Elementor_Widgets::get_instance();
  }
  ?&gt;</code></pre>
  </div>
  </div>
  </p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-html lang-html prettyprint-override"><code>//widget1.php
  &lt;?php
  namespace Elementor;
  
  class My_Widget_1 extends Widget_Base {
   
   public function get_name() {
    return 'title-subtitle';
   }
   
   public function get_title() {
    return 'title &amp; sub-title';
   }
   
   public function get_icon() {
    return 'fa fa-font';
   }
   
   public function get_categories() {
    return [ 'basic' ];
   }
   
   protected function _register_controls() {
  
    $this-&gt;start_controls_section(
     'section_title',
     [
      'label' =&gt; __( 'Content', 'elementor' ),
     ]
    );
    
    $this-&gt;add_control(
     'title',
     [
      'label' =&gt; __( 'Title', 'elementor' ),
      'label_block' =&gt; true,
      'type' =&gt; Controls_Manager::TEXT,
      'placeholder' =&gt; __( 'Enter your title', 'elementor' ),
     ]
    );
  
    $this-&gt;add_control(
     'subtitle',
     [
      'label' =&gt; __( 'Sub-title', 'elementor' ),
      'label_block' =&gt; true,
      'type' =&gt; Controls_Manager::TEXT,
                  'placeholder' =&gt; __( 'Enter your sub-title', 'elementor' ),
     ]
    );
  
    $this-&gt;add_control(
     'link',
     [
      'label' =&gt; __( 'Link', 'elementor' ),
      'type' =&gt; Controls_Manager::URL,
      'placeholder' =&gt; __( 'https://your-link.com', 'elementor' ),
      'default' =&gt; [
       'url' =&gt; '',
      ]
     ]
    );
  
    $this-&gt;end_controls_section();
   }
   
   protected function render() {
  
          $settings = $this-&gt;get_settings_for_display();
          $url = $settings['link']['url'];
    echo  "&lt;a href='$url'&gt;&lt;div class='title'&gt;$settings[title]&lt;/div&gt; &lt;div class='subtitle'&gt;$settings[subtitle]&lt;/div&gt;&lt;/a&gt;";
     
  
   }
   
   protected function _content_template() {
  
      }
   
   
  }
  ?&gt;</code></pre>
  </div>
  </div>
  </p>
  
  <p>In child theme in function.php as I see in exemple i write this.</p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-html lang-html prettyprint-override"><code>//Function.php
  require_once('elementor_custom_catousel/widget1.php');</code></pre>
  </div>
  </div>
  </p>
  
  <p>But this does not work, although I have tried as many examples from the official documentation.that is, when I save, nothing appears in the basic categories in the elementor if i would solve this problem to build a carousel i just need to make html template. </p>