<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Elementor Widget &#8211; sajdoko::</title>
	<atom:link href="https://sajdoko.com/blog/tag/elementor-widget/feed/" rel="self" type="application/rss+xml" />
	<link>https://sajdoko.com</link>
	<description></description>
	<lastBuildDate>Thu, 10 Sep 2026 20:40:49 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.2</generator>

<image>
	<url>https://sajdoko.com/wp-content/uploads/2024/08/cropped-cropped-black-32x32.png</url>
	<title>Elementor Widget &#8211; sajdoko::</title>
	<link>https://sajdoko.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Elementor Widget Part 4: WooCommerce Product Integration</title>
		<link>https://sajdoko.com/blog/elementor-widget-part-4-woocommerce-product-integration/</link>
					<comments>https://sajdoko.com/blog/elementor-widget-part-4-woocommerce-product-integration/#respond</comments>
		
		<dc:creator><![CDATA[sajdoko]]></dc:creator>
		<pubDate>Thu, 13 Nov 2025 19:50:07 +0000</pubDate>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Elementor]]></category>
		<category><![CDATA[WooCommerce]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[Elementor Widget]]></category>
		<guid isPermaLink="false">https://sajdoko.al/blog/elementor-widget-part-4-woocommerce-product-integration/</guid>

					<description><![CDATA[We’ll query products, display their images and prices, and build a basic grid layout. # Learning&#8230;]]></description>
										<content:encoded><![CDATA[<h2 id="introduction" tabindex="-1"><a class="header-anchor" href="#introduction">#</a> Introduction</h2>
<p>This part connects the widget to WooCommerce products. We’ll query products, display their images and prices, and build a basic grid layout.</p>
<h2 id="learning-objectives" tabindex="-1"><a class="header-anchor" href="#learning-objectives">#</a> Learning Objectives</h2>
<p>By the end of this tutorial, you will:</p>
<ul>
<li>Query WooCommerce products using WP_Query</li>
<li>Build query arguments based on widget settings</li>
<li>Access product data using WooCommerce API</li>
<li>Display product titles, prices, and images</li>
<li>Work with product taxonomies</li>
<li>Create a basic product grid layout</li>
<li>Handle products with no results</li>
</ul>
<h2 id="step-1%3A-understanding-wp_query-for-products" tabindex="-1"><a class="header-anchor" href="#step-1%3A-understanding-wp_query-for-products">#</a> Step 1: Understanding WP_Query for Products</h2>
<p>WooCommerce products are a custom post type called <code>product</code>. We use WordPress’s <code>WP_Query</code> to retrieve them.</p>
<h3 id="basic-product-query-structure" tabindex="-1"><a class="header-anchor" href="#basic-product-query-structure">#</a> Basic Product Query Structure</h3>
<pre tabindex="0" class="hljs"><code class="language-php">
$args = [
    'post_type' =&gt; 'product',
    'posts_per_page' =&gt; 9,
    'post_status' =&gt; 'publish',
];

$query = new WP_Query( $args );
</code></pre>
<h3 id="common-query-parameters" tabindex="-1"><a class="header-anchor" href="#common-query-parameters">#</a> Common Query Parameters</h3>
<ul>
<li><strong>post_type</strong>: Must be ‘product’ for WooCommerce products</li>
<li><strong>posts_per_page</strong>: Number of products to retrieve</li>
<li><strong>orderby</strong>: How to sort (date, title, price, etc.)</li>
<li><strong>order</strong>: ASC or DESC</li>
<li><strong>tax_query</strong>: Filter by categories, tags, or attributes</li>
<li><strong>post_status</strong>: Usually ‘publish’ for public products</li>
</ul>
<h2 id="step-2%3A-update-the-render()-method" tabindex="-1"><a class="header-anchor" href="#step-2%3A-update-the-render()-method">#</a> Step 2: Update the render() Method</h2>
<p>Replace the placeholder <code>render()</code> method in <code>collection-products-widget.php</code> with a proper product query:</p>
<pre tabindex="0" class="hljs"><code class="language-php">
&lt;?php
protected function render() {
    $settings = $this-&gt;get_settings_for_display();

    // Build query arguments
    $args = [
        'post_type' =&gt; 'product',
        'posts_per_page' =&gt; $settings['products_per_page'],
        'orderby' =&gt; $settings['orderby'],
        'order' =&gt; $settings['order'],
        'post_status' =&gt; 'publish',
    ];

    // Add category filter if specified
    if ( ! empty( $settings['category'] ) ) {
        $args['tax_query'] = [
            [
                'taxonomy' =&gt; 'product_cat',
                'field' =&gt; 'term_id',
                'terms' =&gt; $settings['category'],
            ],
        ];
    }

    // Execute query
    $query = new WP_Query( $args );

    // Check if products found
    if ( $query-&gt;have_posts() ) {
        ?&gt;
        &lt;div class="collection-products-wrapper"&gt;
            &lt;div class="collection-products-grid"&gt;
                &lt;?php
                while ( $query-&gt;have_posts() ) {
                    $query-&gt;the_post();
                    global $product;

                    // We'll add product display code here
                    ?&gt;
                    &lt;div class="collection-product-item"&gt;
                        &lt;h3&gt;&lt;?php the_title(); ?&gt;&lt;/h3&gt;
                    &lt;/div&gt;
                    &lt;?php
                }
                ?&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;?php
        wp_reset_postdata();
    } else {
        echo '&lt;p&gt;' . __( 'No products found.', 'hello-biz-child' ) . '&lt;/p&gt;';
    }
}
</code></pre>
<p><strong>Code Explanation:</strong></p>
<ul>
<li><strong>Build Arguments</strong>: Create array based on widget settings</li>
<li><strong>Tax Query</strong>: Filter by categories if user selected any</li>
<li><strong>WP_Query</strong>: Execute the query</li>
<li><strong>The Loop</strong>: Iterate through results with <code>have_posts()</code> and <code>the_post()</code></li>
<li><strong>global $product</strong>: Access WooCommerce product object</li>
<li><strong>wp_reset_postdata()</strong>: Reset query data after loop (important!)</li>
</ul>
<h2 id="step-3%3A-accessing-product-data" tabindex="-1"><a class="header-anchor" href="#step-3%3A-accessing-product-data">#</a> Step 3: Accessing Product Data</h2>
<p>WooCommerce provides rich product data through the <code>$product</code> object. Let’s explore common methods:</p>
<h3 id="common-product-methods" tabindex="-1"><a class="header-anchor" href="#common-product-methods">#</a> Common Product Methods</h3>
<pre tabindex="0" class="hljs"><code class="language-php">
global $product;

// Basic info
$product-&gt;get_id();                    // Product ID
$product-&gt;get_name();                  // Product name
$product-&gt;get_title();                 // Product title
$product-&gt;get_permalink();             // Product URL

// Pricing
$product-&gt;get_price();                 // Current price
$product-&gt;get_regular_price();         // Regular price
$product-&gt;get_sale_price();            // Sale price
$product-&gt;get_price_html();            // Formatted price HTML

// Images
$product-&gt;get_image();                 // Featured image HTML
$product-&gt;get_image_id();              // Featured image ID
get_the_post_thumbnail_url( $id );     // Image URL

// Product types
$product-&gt;is_type( 'simple' );         // Is simple product
$product-&gt;is_type( 'variable' );       // Is variable product
$product-&gt;is_on_sale();                // Is on sale

// Variations
$product-&gt;get_variation_attributes();  // For variable products
</code></pre>
<h2 id="step-4%3A-display-product-information" tabindex="-1"><a class="header-anchor" href="#step-4%3A-display-product-information">#</a> Step 4: Display Product Information</h2>
<p>Update the product loop to display complete product information:</p>
<pre tabindex="0" class="hljs"><code class="language-php">
&lt;?php
while ( $query-&gt;have_posts() ) {
    $query-&gt;the_post();
    global $product;

    $product_id = get_the_ID();
    ?&gt;
    &lt;div class="collection-product-item"&gt;
        &lt;a href="&lt;?php echo esc_url( get_permalink() ); ?&gt;" class="collection-product-link"&gt;
            &lt;div class="collection-product-image"&gt;
                &lt;?php
                $image_url = get_the_post_thumbnail_url( $product_id, 'full' );
                if ( $image_url ) :
                ?&gt;
                    &lt;img src="&lt;?php echo esc_url( $image_url ); ?&gt;"
                         alt="&lt;?php echo esc_attr( get_the_title() ); ?&gt;"&gt;
                &lt;?php endif; ?&gt;
            &lt;/div&gt;
        &lt;/a&gt;
        
        &lt;div class="collection-product-info"&gt;
            &lt;div class="collection-product-info-left"&gt;
                &lt;h3 class="collection-product-title"&gt;
                    &lt;a href="&lt;?php echo esc_url( get_permalink() ); ?&gt;"&gt;
                        &lt;?php the_title(); ?&gt;
                    &lt;/a&gt;
                &lt;/h3&gt;
            &lt;/div&gt;
            
            &lt;div class="collection-product-price"&gt;
                &lt;?php echo $product-&gt;get_price_html(); ?&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;?php
}
</code></pre>
<p><strong>Code Explanation:</strong></p>
<ul>
<li><strong>Product Link</strong>: Wraps image in link to product page</li>
<li><strong>Product Image</strong>: Displays featured image</li>
<li><strong>esc_url()</strong>: Sanitizes URLs</li>
<li><strong>esc_attr()</strong>: Sanitizes attributes</li>
<li><strong>get_price_html()</strong>: Displays formatted price with currency</li>
</ul>
<h2 id="step-5%3A-add-basic-grid-css" tabindex="-1"><a class="header-anchor" href="#step-5%3A-add-basic-grid-css">#</a> Step 5: Add Basic Grid CSS</h2>
<p>Create <code>wp-content/themes/hello-biz-child/css/collection.css</code>:</p>
<pre tabindex="0" class="hljs"><code class="language-css">
/* Collection Products Grid */
.collection-products-wrapper {
    width: 100%;
    max-width: 100%;
}

.collection-products-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
    width: 100%;
}

.collection-product-item {
    position: relative;
}

.collection-product-link {
    display: block;
    text-decoration: none;
    color: inherit;
}

.collection-product-image {
    position: relative;
    width: 100%;
    padding-bottom: 100%; /* Square aspect ratio */
    overflow: hidden;
    background: #f5f5f5;
}

.collection-product-image img {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.collection-product-info {
    padding: 15px 0;
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 15px;
}

.collection-product-info-left {
    flex: 1;
}

.collection-product-title {
    margin: 0;
    font-size: 14px;
    font-weight: 400;
    line-height: 1.4;
}

.collection-product-title a {
    color: #333;
    text-decoration: none;
}

.collection-product-title a:hover {
    color: #000;
}

.collection-product-price {
    font-size: 14px;
    color: #333;
    white-space: nowrap;
}

/* Responsive */
@media (max-width: 768px) {
    .collection-products-grid {
        grid-template-columns: 1fr;
    }
}
</code></pre>
<h2 id="step-6%3A-enqueue-the-css-file" tabindex="-1"><a class="header-anchor" href="#step-6%3A-enqueue-the-css-file">#</a> Step 6: Enqueue the CSS File</h2>
<p>Add this function to <code>collection-products-register.php</code>:</p>
<pre tabindex="0" class="hljs"><code class="language-php">
/**
 * Load widget styles.
 *
 * @return void
 */
function collection_products_widget_styles() {
    wp_enqueue_style(
        'collection-products-widget-style',
        get_stylesheet_directory_uri() . '/css/collection.css',
        [],
        HELLO_BIZ_CHILD_VERSION
    );
}
add_action( 'wp_enqueue_scripts', 'collection_products_widget_styles', 20 );
</code></pre>
<h2 id="step-7%3A-test-product-display" tabindex="-1"><a class="header-anchor" href="#step-7%3A-test-product-display">#</a> Step 7: Test Product Display</h2>
<ol>
<li>Edit your test page in Elementor</li>
<li>The widget should now display products in a grid</li>
<li>Try different settings:
<ul>
<li>Change products per page</li>
<li>Change order by</li>
<li>Select specific categories</li>
</ul>
</li>
<li>View the page on the frontend to see the live result</li>
</ol>
<p><img fetchpriority="high" decoding="async" class="alignnone size-full wp-image-996" src="/wp-content/uploads/2025/11/part4-products-list.webp" alt="" width="1920" height="893" /><br />
<em>WooCommerce products displayed in the admin</em></p>
<h2 id="step-8%3A-handle-special-cases" tabindex="-1"><a class="header-anchor" href="#step-8%3A-handle-special-cases">#</a> Step 8: Handle Special Cases</h2>
<h3 id="handle-products-without-images" tabindex="-1"><a class="header-anchor" href="#handle-products-without-images">#</a> Handle Products Without Images</h3>
<p>Update the image display code:</p>
<pre tabindex="0" class="hljs"><code class="language-php">
&lt;div class="collection-product-image"&gt;
    &lt;?php
    $image_url = get_the_post_thumbnail_url( $product_id, 'full' );
    if ( $image_url ) :
    ?&gt;
        &lt;img src="&lt;?php echo esc_url( $image_url ); ?&gt;"
             alt="&lt;?php echo esc_attr( get_the_title() ); ?&gt;"&gt;
    &lt;?php else : ?&gt;
        &lt;div class="no-image-placeholder"&gt;
            &lt;span&gt;&lt;?php _e( 'No Image', 'hello-biz-child' ); ?&gt;&lt;/span&gt;
        &lt;/div&gt;
    &lt;?php endif; ?&gt;
&lt;/div&gt;
</code></pre>
<p>Add to CSS:</p>
<pre tabindex="0" class="hljs"><code class="language-css">
.no-image-placeholder {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #f0f0f0;
    color: #999;
}
</code></pre>
<h3 id="handle-variable-products" tabindex="-1"><a class="header-anchor" href="#handle-variable-products">#</a> Handle Variable Products</h3>
<p>Check if product is variable and show starting price:</p>
<pre tabindex="0" class="hljs"><code class="language-php">
&lt;div class="collection-product-price"&gt;
    &lt;?php
    if ( $product-&gt;is_type( 'variable' ) ) {
        echo __( 'From ', 'hello-biz-child' );
    }
    echo $product-&gt;get_price_html();
    ?&gt;
&lt;/div&gt;
</code></pre>
<h2 id="complete-render()-method" tabindex="-1"><a class="header-anchor" href="#complete-render()-method">#</a> Complete render() Method</h2>
<p>Here’s the complete <code>render()</code> method with all improvements:</p>
<pre tabindex="0" class="hljs"><code class="language-php">
&lt;?php
protected function render() {
    $settings = $this-&gt;get_settings_for_display();

    $args = [
        'post_type' =&gt; 'product',
        'posts_per_page' =&gt; $settings['products_per_page'],
        'orderby' =&gt; $settings['orderby'],
        'order' =&gt; $settings['order'],
        'post_status' =&gt; 'publish',
    ];

    // Add category filter
    if ( ! empty( $settings['category'] ) ) {
        $args['tax_query'] = [
            [
                'taxonomy' =&gt; 'product_cat',
                'field' =&gt; 'term_id',
                'terms' =&gt; $settings['category'],
            ],
        ];
    }

    $query = new WP_Query( $args );

    if ( $query-&gt;have_posts() ) {
        ?&gt;
        &lt;div class="collection-products-wrapper"&gt;
            &lt;div class="collection-products-grid"&gt;
                &lt;?php
                while ( $query-&gt;have_posts() ) {
                    $query-&gt;the_post();
                    global $product;

                    $product_id = get_the_ID();
                    ?&gt;
                    &lt;div class="collection-product-item"&gt;
                        &lt;a href="&lt;?php echo esc_url( get_permalink() ); ?&gt;" class="collection-product-link"&gt;
                            &lt;div class="collection-product-image"&gt;
                                &lt;?php
                                $image_url = get_the_post_thumbnail_url( $product_id, 'full' );
                                if ( $image_url ) :
                                ?&gt;
                                    &lt;img src="&lt;?php echo esc_url( $image_url ); ?&gt;"
                                         alt="&lt;?php echo esc_attr( get_the_title() ); ?&gt;"&gt;
                                &lt;?php else : ?&gt;
                                    &lt;div class="no-image-placeholder"&gt;
                                        &lt;span&gt;&lt;?php _e( 'No Image', 'hello-biz-child' ); ?&gt;&lt;/span&gt;
                                    &lt;/div&gt;
                                &lt;?php endif; ?&gt;
                            &lt;/div&gt;
                        &lt;/a&gt;
                        
                        &lt;div class="collection-product-info"&gt;
                            &lt;div class="collection-product-info-left"&gt;
                                &lt;h3 class="collection-product-title"&gt;
                                    &lt;a href="&lt;?php echo esc_url( get_permalink() ); ?&gt;"&gt;
                                        &lt;?php the_title(); ?&gt;
                                    &lt;/a&gt;
                                &lt;/h3&gt;
                            &lt;/div&gt;
                            
                            &lt;div class="collection-product-price"&gt;
                                &lt;?php echo $product-&gt;get_price_html(); ?&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                    &lt;?php
                }
                ?&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;?php
        wp_reset_postdata();
    } else {
        echo '&lt;p&gt;' . __( 'No products found.', 'hello-biz-child' ) . '&lt;/p&gt;';
    }
}
</code></pre>
<h2 id="common-issues-and-solutions" tabindex="-1"><a class="header-anchor" href="#common-issues-and-solutions">#</a> Common Issues and Solutions</h2>
<h3 id="issue-1%3A-products-not-showing" tabindex="-1"><a class="header-anchor" href="#issue-1%3A-products-not-showing">#</a> Issue 1: Products Not Showing</h3>
<p><strong>Solutions:</strong></p>
<ul>
<li>Verify you have published products in WooCommerce</li>
<li>Check that product visibility is set to “Catalog and search”</li>
<li>Ensure products are in the selected categories</li>
<li>Check <code>post_status</code> is ‘publish’</li>
</ul>
<h3 id="issue-2%3A-wrong-products-showing" tabindex="-1"><a class="header-anchor" href="#issue-2%3A-wrong-products-showing">#</a> Issue 2: Wrong Products Showing</h3>
<p><strong>Solutions:</strong></p>
<ul>
<li>Verify <code>orderby</code> and <code>order</code> settings</li>
<li>Check category filter is working correctly</li>
<li>Ensure no other plugins are filtering the query</li>
</ul>
<h3 id="issue-3%3A-images-not-displaying" tabindex="-1"><a class="header-anchor" href="#issue-3%3A-images-not-displaying">#</a> Issue 3: Images Not Displaying</h3>
<p><strong>Solutions:</strong></p>
<ul>
<li>Verify products have featured images set</li>
<li>Check image URLs in browser console</li>
<li>Ensure file permissions are correct</li>
<li>Try regenerating thumbnails</li>
</ul>
<h3 id="issue-4%3A-prices-not-showing-correctly" tabindex="-1"><a class="header-anchor" href="#issue-4%3A-prices-not-showing-correctly">#</a> Issue 4: Prices Not Showing Correctly</h3>
<p><strong>Solutions:</strong></p>
<ul>
<li>Ensure WooCommerce currency settings are configured</li>
<li>Verify products have prices set</li>
<li>Check for theme conflicts with price display</li>
</ul>
<h2 id="summary" tabindex="-1"><a class="header-anchor" href="#summary">#</a> Summary</h2>
<p>You’ve successfully integrated WooCommerce products into your widget:</p>
<ul>
<li>Query products with WP_Query</li>
<li>Access product data via WooCommerce API</li>
<li>Display product images, titles, and prices</li>
<li>Handle products without images</li>
<li>Create responsive grid layout</li>
<li>Added basic styling</li>
</ul>
<h2 id="what%E2%80%99s-next%3F" tabindex="-1"><a class="header-anchor" href="#what%E2%80%99s-next%3F">#</a> What’s Next?</h2>
<p>In Part 5: Implementing Custom Product Meta Fields, we’ll:</p>
<ul>
<li>Add custom collection image field to products</li>
<li>Integrate WordPress media library uploader</li>
<li>Add product orientation field</li>
<li>Save and retrieve custom meta data</li>
<li>Update frontend to display custom images</li>
</ul>
<hr />
<p><strong>Previous:</strong> <a href="/blog/elementor-widget-part-3-building-the-basic-widget-structure/">← Part 3: Building the Basic Widget Structure</a><br />
<strong>Next:</strong> Part 5: Implementing Custom Product Meta Fields →</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sajdoko.com/blog/elementor-widget-part-4-woocommerce-product-integration/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Elementor Widget Part 3: Building the Basic Widget Structure</title>
		<link>https://sajdoko.com/blog/elementor-widget-part-3-building-the-basic-widget-structure/</link>
					<comments>https://sajdoko.com/blog/elementor-widget-part-3-building-the-basic-widget-structure/#respond</comments>
		
		<dc:creator><![CDATA[sajdoko]]></dc:creator>
		<pubDate>Tue, 11 Nov 2025 10:37:34 +0000</pubDate>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Elementor]]></category>
		<category><![CDATA[WooCommerce]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[Elementor Widget]]></category>
		<guid isPermaLink="false">https://sajdoko.al/blog/elementor-widget-part-3-building-the-basic-widget-structure/</guid>

					<description><![CDATA[Introduction We’ll now create the Collection Products widget files, add its controls and register it with Elementor. # Learning Objectives By the end of this&#8230;]]></description>
										<content:encoded><![CDATA[<h2 id="introduction" tabindex="-1"><a class="header-anchor" href="#introduction">#</a> Introduction</h2>
<p>We’ll now create the Collection Products widget files, add its controls and register it with Elementor.</p>
<h2 id="learning-objectives" tabindex="-1"><a class="header-anchor" href="#learning-objectives">#</a> Learning Objectives</h2>
<p>By the end of this tutorial, you will:</p>
<ul>
<li>Create the widget PHP files in the correct structure</li>
<li>Implement a complete widget class with all required methods</li>
<li>Register the widget with Elementor</li>
<li>Add basic query controls (products per page, order, category)</li>
<li>Add display controls (show/hide filters and sorting)</li>
<li>Test the widget in the Elementor editor</li>
<li>Understand how to organize widget files</li>
</ul>
<h2 id="step-1%3A-create-widget-files" tabindex="-1"><a class="header-anchor" href="#step-1%3A-create-widget-files">#</a> Step 1: Create Widget Files</h2>
<p>We’ll create two PHP files to keep our code organized:</p>
<ol>
<li><strong>collection-products-widget.php</strong> &#8211; The main widget class</li>
<li><strong>collection-products-register.php</strong> &#8211; Registration and helper functions</li>
</ol>
<h3 id="1.1-create-the-widget-class-file" tabindex="-1"><a class="header-anchor" href="#1.1-create-the-widget-class-file">#</a> 1.1 Create the Widget Class File</h3>
<p>Create a new file at <code>wp-content/themes/hello-biz-child/widgets/collection-products/collection-products-widget.php</code>:</p>
<pre tabindex="0"><code class="language-php">
&lt;?php
/**
 * Collection Products Widget for Elementor
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

class Collection_Products_Widget extends \Elementor\Widget_Base {

    /**
     * Get widget name.
     *
     * @return string Widget name.
     */
    public function get_name() {
        return 'collection_products';
    }

    /**
     * Get widget title.
     *
     * @return string Widget title.
     */
    public function get_title() {
        return __( 'Collection Products', 'hello-biz-child' );
    }

    /**
     * Get widget icon.
     *
     * @return string Widget icon.
     */
    public function get_icon() {
        return 'eicon-products';
    }

    /**
     * Get widget categories.
     *
     * @return array Widget categories.
     */
    public function get_categories() {
        return [ 'woocommerce-elements' ];
    }

    /**
     * Get widget keywords.
     *
     * @return array Widget keywords.
     */
    public function get_keywords() {
        return [ 'woocommerce', 'shop', 'store', 'products', 'collection' ];
    }

    /**
     * Register widget controls.
     */
    protected function register_controls() {
        // We'll add controls in the next steps
    }

    /**
     * Render widget output on the frontend.
     */
    protected function render() {
        echo '&lt;div class="collection-products-wrapper"&gt;';
        echo '&lt;p&gt;' . __( 'Collection Products Widget - Coming Soon!', 'hello-biz-child' ) . '&lt;/p&gt;';
        echo '&lt;/div&gt;';
    }
}
</code></pre>
<p><strong>Code Explanation:</strong></p>
<ul>
<li><strong>Security Check</strong>: <code>if ( ! defined( 'ABSPATH' ) )</code> prevents direct file access</li>
<li><strong>Class Declaration</strong>: Extends <code>\Elementor\Widget_Base</code></li>
<li><strong>get_name()</strong>: Returns unique identifier <code>collection_products</code></li>
<li><strong>get_title()</strong>: Returns display name for the widget panel</li>
<li><strong>get_icon()</strong>: Uses Elementor’s products icon</li>
<li><strong>get_categories()</strong>: Places widget in WooCommerce elements category</li>
<li><strong>get_keywords()</strong>: Helps users find the widget via search</li>
<li><strong>register_controls()</strong>: Empty for now, we’ll add controls next</li>
<li><strong>render()</strong>: Simple placeholder output</li>
</ul>
<h2 id="step-2%3A-create-registration-file" tabindex="-1"><a class="header-anchor" href="#step-2%3A-create-registration-file">#</a> Step 2: Create Registration File</h2>
<p>Create <code>wp-content/themes/hello-biz-child/widgets/collection-products/collection-products-register.php</code>:</p>
<pre tabindex="0"><code class="language-php">
&lt;?php
/**
 * Register Elementor Collection Products Widget
 */

/**
 * Register the widget with Elementor.
 *
 * @param \Elementor\Widgets_Manager $widgets_manager Elementor widgets manager.
 * @return void
 */
function register_collection_product_widget( $widgets_manager ) {
    require_once( __DIR__ . '/collection-products-widget.php' );
    $widgets_manager-&gt;register( new \Collection_Products_Widget() );
}
add_action( 'elementor/widgets/register', 'register_collection_product_widget' );
</code></pre>
<p><strong>Code Explanation:</strong></p>
<ul>
<li><strong>Function</strong>: <code>register_collection_product_widget()</code> handles registration</li>
<li><strong>require_once</strong>: Loads the widget class file</li>
<li><strong>register()</strong>: Registers a new instance of our widget</li>
<li><strong>Hook</strong>: <code>elementor/widgets/register</code> is fired when Elementor loads widgets</li>
</ul>
<h2 id="step-3%3A-include-registration-file-in-functions.php" tabindex="-1"><a class="header-anchor" href="#step-3%3A-include-registration-file-in-functions.php">#</a> Step 3: Include Registration File in functions.php</h2>
<p>Open <code>wp-content/themes/hello-biz-child/functions.php</code> and add at the bottom:</p>
<pre tabindex="0"><code class="language-php">
// Include collection products widget registration
require_once get_stylesheet_directory() . '/widgets/collection-products/collection-products-register.php';
</code></pre>
<p><strong>Complete functions.php should now look like:</strong></p>
<pre tabindex="0"><code class="language-php">
&lt;?php
/**
 * Theme functions and definitions.
 *
 * @package HelloBizChild
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

define( 'HELLO_BIZ_CHILD_VERSION', '1.0.0' );

/**
 * Load child theme scripts &amp; styles.
 *
 * @return void
 */
function hello_biz_child_scripts_styles() {

	wp_enqueue_style(
		'hello-biz-child-style',
		get_stylesheet_directory_uri() . '/style.css',
		[
			'theme',
		],
		HELLO_BIZ_CHILD_VERSION
	);
}

add_action( 'wp_enqueue_scripts', 'hello_biz_child_scripts_styles', 20 );

// Include collection products widget registration
require_once get_stylesheet_directory() . '/widgets/collection-products/collection-products-register.php';
</code></pre>
<h2 id="step-4%3A-test-basic-widget" tabindex="-1"><a class="header-anchor" href="#step-4%3A-test-basic-widget">#</a> Step 4: Test Basic Widget</h2>
<p>Before adding more features, let’s test that the widget loads correctly.</p>
<h3 id="4.1-clear-cache" tabindex="-1"><a class="header-anchor" href="#4.1-clear-cache">#</a> 4.1 Clear Cache</h3>
<ol>
<li>Clear any WordPress caching plugins</li>
<li>Clear browser cache</li>
<li>If using object caching, flush it</li>
</ol>
<h3 id="4.2-test-in-elementor" tabindex="-1"><a class="header-anchor" href="#4.2-test-in-elementor">#</a> 4.2 Test in Elementor</h3>
<ol>
<li>Go to <strong>Pages → Add New</strong></li>
<li>Give the page a title (e.g., “Widget Test”)</li>
<li>Click <strong>Edit with Elementor</strong></li>
<li>In the left panel, search for “Collection Products”</li>
<li>Drag the widget onto the page</li>
<li>You should see “Collection Products Widget &#8211; Coming Soon!”</li>
</ol>
<p><img decoding="async" class="alignnone size-full wp-image-981" src="/wp-content/uploads/2025/11/part3-elementor-editor.webp" alt="" width="1920" height="893" /><br />
<em>Collection Products widget in the Elementor editor</em></p>
<p><strong>Troubleshooting:</strong></p>
<p>If the widget doesn’t appear:</p>
<ul>
<li>Check for PHP errors (enable <code>WP_DEBUG</code> in wp-config.php)</li>
<li>Verify file paths are correct</li>
<li>Ensure WooCommerce is active</li>
<li>Check that all files are saved properly</li>
</ul>
<h2 id="step-5%3A-add-query-controls" tabindex="-1"><a class="header-anchor" href="#step-5%3A-add-query-controls">#</a> Step 5: Add Query Controls</h2>
<p>Now let’s add controls to configure product queries. Update the <code>register_controls()</code> method in <code>collection-products-widget.php</code>:</p>
<pre tabindex="0"><code class="language-php">
protected function register_controls() {

    // Query Section
    $this-&gt;start_controls_section(
        'section_query',
        [
            'label' =&gt; __( 'Query', 'hello-biz-child' ),
        ]
    );

    $this-&gt;add_control(
        'products_per_page',
        [
            'label' =&gt; __( 'Products Per Page', 'hello-biz-child' ),
            'type' =&gt; \Elementor\Controls_Manager::NUMBER,
            'default' =&gt; 9,
            'min' =&gt; 3,
            'step' =&gt; 3,
            'description' =&gt; __( 'Number of products to display', 'hello-biz-child' ),
        ]
    );

    $this-&gt;add_control(
        'orderby',
        [
            'label' =&gt; __( 'Order By', 'hello-biz-child' ),
            'type' =&gt; \Elementor\Controls_Manager::SELECT,
            'default' =&gt; 'date',
            'options' =&gt; [
                'date' =&gt; __( 'Date', 'hello-biz-child' ),
                'title' =&gt; __( 'Title', 'hello-biz-child' ),
                'price' =&gt; __( 'Price', 'hello-biz-child' ),
                'popularity' =&gt; __( 'Popularity', 'hello-biz-child' ),
                'rating' =&gt; __( 'Rating', 'hello-biz-child' ),
                'rand' =&gt; __( 'Random', 'hello-biz-child' ),
                'menu_order' =&gt; __( 'Menu Order', 'hello-biz-child' ),
            ],
        ]
    );

    $this-&gt;add_control(
        'order',
        [
            'label' =&gt; __( 'Order', 'hello-biz-child' ),
            'type' =&gt; \Elementor\Controls_Manager::SELECT,
            'default' =&gt; 'DESC',
            'options' =&gt; [
                'DESC' =&gt; __( 'Descending', 'hello-biz-child' ),
                'ASC' =&gt; __( 'Ascending', 'hello-biz-child' ),
            ],
        ]
    );

    $this-&gt;add_control(
        'category',
        [
            'label' =&gt; __( 'Category', 'hello-biz-child' ),
            'type' =&gt; \Elementor\Controls_Manager::SELECT2,
            'multiple' =&gt; true,
            'options' =&gt; $this-&gt;get_product_categories(),
            'label_block' =&gt; true,
            'description' =&gt; __( 'Select categories to filter products', 'hello-biz-child' ),
        ]
    );

    $this-&gt;add_control(
        'show_filters',
        [
            'label' =&gt; __( 'Show Filters', 'hello-biz-child' ),
            'type' =&gt; \Elementor\Controls_Manager::SWITCHER,
            'label_on' =&gt; __( 'Yes', 'hello-biz-child' ),
            'label_off' =&gt; __( 'No', 'hello-biz-child' ),
            'return_value' =&gt; 'yes',
            'default' =&gt; 'yes',
        ]
    );

    $this-&gt;add_control(
        'show_sorting',
        [
            'label' =&gt; __( 'Show Sorting', 'hello-biz-child' ),
            'type' =&gt; \Elementor\Controls_Manager::SWITCHER,
            'label_on' =&gt; __( 'Yes', 'hello-biz-child' ),
            'label_off' =&gt; __( 'No', 'hello-biz-child' ),
            'return_value' =&gt; 'yes',
            'default' =&gt; 'yes',
        ]
    );

    $this-&gt;end_controls_section();
}
</code></pre>
<p><strong>Code Explanation:</strong></p>
<ul>
<li><strong>Products Per Page</strong>: Number control with default of 9, minimum 3, step of 3</li>
<li><strong>Order By</strong>: Dropdown with common sorting options</li>
<li><strong>Order</strong>: ASC or DESC</li>
<li><strong>Category</strong>: Multi-select dropdown of product categories</li>
<li><strong>Show Filters</strong>: Toggle to show/hide filter panel</li>
<li><strong>Show Sorting</strong>: Toggle to show/hide sorting dropdown</li>
</ul>
<h2 id="step-6%3A-add-helper-method-for-categories" tabindex="-1"><a class="header-anchor" href="#step-6%3A-add-helper-method-for-categories">#</a> Step 6: Add Helper Method for Categories</h2>
<p>Add this helper method to the widget class (before or after <code>register_controls()</code>):</p>
<pre tabindex="0"><code class="language-php">
/**
 * Get product categories for control options.
 *
 * @return array Categories array.
 */
protected function get_product_categories() {
    $categories = get_terms( 'product_cat', [
        'hide_empty' =&gt; false,
    ] );

    $options = [];
    
    if ( ! empty( $categories ) &amp;&amp; ! is_wp_error( $categories ) ) {
        foreach ( $categories as $category ) {
            $options[ $category-&gt;term_id ] = $category-&gt;name;
        }
    }

    return $options;
}
</code></pre>
<p><strong>Code Explanation:</strong></p>
<ul>
<li><strong>get_terms()</strong>: Retrieves WooCommerce product categories</li>
<li><strong>hide_empty =&gt; false</strong>: Include categories with no products</li>
<li><strong>Returns</strong>: Array formatted for SELECT2 control</li>
</ul>
<h2 id="step-7%3A-add-style-controls" tabindex="-1"><a class="header-anchor" href="#step-7%3A-add-style-controls">#</a> Step 7: Add Style Controls</h2>
<p>After the query section, add a style section:</p>
<pre tabindex="0"><code class="language-php">
// Style Section
$this-&gt;start_controls_section(
    'section_style',
    [
        'label' =&gt; __( 'Style', 'hello-biz-child' ),
        'tab' =&gt; \Elementor\Controls_Manager::TAB_STYLE,
    ]
);

$this-&gt;add_control(
    'gap',
    [
        'label' =&gt; __( 'Gap', 'hello-biz-child' ),
        'type' =&gt; \Elementor\Controls_Manager::SLIDER,
        'size_units' =&gt; [ 'px' ],
        'range' =&gt; [
            'px' =&gt; [
                'min' =&gt; 0,
                'max' =&gt; 50,
                'step' =&gt; 1,
            ],
        ],
        'default' =&gt; [
            'unit' =&gt; 'px',
            'size' =&gt; 10,
        ],
        'selectors' =&gt; [
            '{{WRAPPER}} .collection-products-grid' =&gt; 'gap: {{SIZE}}{{UNIT}};',
        ],
    ]
);

$this-&gt;end_controls_section();
</code></pre>
<p><strong>Code Explanation:</strong></p>
<ul>
<li><strong>TAB_STYLE</strong>: Places this section in the Style tab</li>
<li><strong>SLIDER</strong>: Creates a range slider control</li>
<li><strong>selectors</strong>: Automatically applies CSS when slider changes</li>
<li><strong>{{WRAPPER}}</strong>: Replaced with unique widget selector</li>
<li><strong>{{SIZE}}{{UNIT}}</strong>: Replaced with selected value (e.g., “10px”)</li>
</ul>
<h2 id="step-8%3A-update-render-method" tabindex="-1"><a class="header-anchor" href="#step-8%3A-update-render-method">#</a> Step 8: Update Render Method</h2>
<p>Update the <code>render()</code> method to display settings:</p>
<pre tabindex="0"><code class="language-php">
protected function render() {
    $settings = $this-&gt;get_settings_for_display();
    ?&gt;
    &lt;div class="collection-products-wrapper"&gt;
        &lt;h3&gt;Collection Products Widget&lt;/h3&gt;
        &lt;p&gt;&lt;strong&gt;Settings:&lt;/strong&gt;&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;Products per page: &lt;?php echo esc_html( $settings['products_per_page'] ); ?&gt;&lt;/li&gt;
            &lt;li&gt;Order by: &lt;?php echo esc_html( $settings['orderby'] ); ?&gt;&lt;/li&gt;
            &lt;li&gt;Order: &lt;?php echo esc_html( $settings['order'] ); ?&gt;&lt;/li&gt;
            &lt;li&gt;Show filters: &lt;?php echo esc_html( $settings['show_filters'] ); ?&gt;&lt;/li&gt;
            &lt;li&gt;Show sorting: &lt;?php echo esc_html( $settings['show_sorting'] ); ?&gt;&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/div&gt;
    &lt;?php
}
</code></pre>
<p>This temporary render method helps us verify that settings are working correctly.</p>
<h2 id="step-9%3A-test-the-controls" tabindex="-1"><a class="header-anchor" href="#step-9%3A-test-the-controls">#</a> Step 9: Test the Controls</h2>
<ol>
<li>Refresh your Elementor editor page (or edit a new page)</li>
<li>Add/re-add the Collection Products widget</li>
<li>In the left panel, you should see:
<ul>
<li><strong>Content tab</strong> with Query section</li>
<li><strong>Style tab</strong> with Style section</li>
</ul>
</li>
<li>Try changing values:
<ul>
<li>Set “Products Per Page” to 12</li>
<li>Change “Order By” to “Price”</li>
<li>Toggle “Show Filters” off and on</li>
</ul>
</li>
<li>Verify the values appear in the preview</li>
</ol>
<h2 id="complete-widget-code-so-far" tabindex="-1"><a class="header-anchor" href="#complete-widget-code-so-far">#</a> Complete Widget Code So Far</h2>
<p>Here’s what your <code>collection-products-widget.php</code> should look like at this point:</p>
<pre tabindex="0"><code class="language-php">
&lt;?php
/**
 * Collection Products Widget for Elementor
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

class Collection_Products_Widget extends \Elementor\Widget_Base {

    public function get_name() {
        return 'collection_products';
    }

    public function get_title() {
        return __( 'Collection Products', 'hello-biz-child' );
    }

    public function get_icon() {
        return 'eicon-products';
    }

    public function get_categories() {
        return [ 'woocommerce-elements' ];
    }

    public function get_keywords() {
        return [ 'woocommerce', 'shop', 'store', 'products', 'collection' ];
    }

    protected function register_controls() {

        $this-&gt;start_controls_section(
            'section_query',
            [
                'label' =&gt; __( 'Query', 'hello-biz-child' ),
            ]
        );

        $this-&gt;add_control(
            'products_per_page',
            [
                'label' =&gt; __( 'Products Per Page', 'hello-biz-child' ),
                'type' =&gt; \Elementor\Controls_Manager::NUMBER,
                'default' =&gt; 9,
                'min' =&gt; 3,
                'step' =&gt; 3,
            ]
        );

        $this-&gt;add_control(
            'orderby',
            [
                'label' =&gt; __( 'Order By', 'hello-biz-child' ),
                'type' =&gt; \Elementor\Controls_Manager::SELECT,
                'default' =&gt; 'date',
                'options' =&gt; [
                    'date' =&gt; __( 'Date', 'hello-biz-child' ),
                    'title' =&gt; __( 'Title', 'hello-biz-child' ),
                    'price' =&gt; __( 'Price', 'hello-biz-child' ),
                    'popularity' =&gt; __( 'Popularity', 'hello-biz-child' ),
                    'rating' =&gt; __( 'Rating', 'hello-biz-child' ),
                    'rand' =&gt; __( 'Random', 'hello-biz-child' ),
                    'menu_order' =&gt; __( 'Menu Order', 'hello-biz-child' ),
                ],
            ]
        );

        $this-&gt;add_control(
            'order',
            [
                'label' =&gt; __( 'Order', 'hello-biz-child' ),
                'type' =&gt; \Elementor\Controls_Manager::SELECT,
                'default' =&gt; 'DESC',
                'options' =&gt; [
                    'DESC' =&gt; __( 'Descending', 'hello-biz-child' ),
                    'ASC' =&gt; __( 'Ascending', 'hello-biz-child' ),
                ],
            ]
        );

        $this-&gt;add_control(
            'category',
            [
                'label' =&gt; __( 'Category', 'hello-biz-child' ),
                'type' =&gt; \Elementor\Controls_Manager::SELECT2,
                'multiple' =&gt; true,
                'options' =&gt; $this-&gt;get_product_categories(),
                'label_block' =&gt; true,
            ]
        );

        $this-&gt;add_control(
            'show_filters',
            [
                'label' =&gt; __( 'Show Filters', 'hello-biz-child' ),
                'type' =&gt; \Elementor\Controls_Manager::SWITCHER,
                'label_on' =&gt; __( 'Yes', 'hello-biz-child' ),
                'label_off' =&gt; __( 'No', 'hello-biz-child' ),
                'return_value' =&gt; 'yes',
                'default' =&gt; 'yes',
            ]
        );

        $this-&gt;add_control(
            'show_sorting',
            [
                'label' =&gt; __( 'Show Sorting', 'hello-biz-child' ),
                'type' =&gt; \Elementor\Controls_Manager::SWITCHER,
                'label_on' =&gt; __( 'Yes', 'hello-biz-child' ),
                'label_off' =&gt; __( 'No', 'hello-biz-child' ),
                'return_value' =&gt; 'yes',
                'default' =&gt; 'yes',
            ]
        );

        $this-&gt;end_controls_section();

        // Style Section
        $this-&gt;start_controls_section(
            'section_style',
            [
                'label' =&gt; __( 'Style', 'hello-biz-child' ),
                'tab' =&gt; \Elementor\Controls_Manager::TAB_STYLE,
            ]
        );

        $this-&gt;add_control(
            'gap',
            [
                'label' =&gt; __( 'Gap', 'hello-biz-child' ),
                'type' =&gt; \Elementor\Controls_Manager::SLIDER,
                'size_units' =&gt; [ 'px' ],
                'range' =&gt; [
                    'px' =&gt; [
                        'min' =&gt; 0,
                        'max' =&gt; 50,
                        'step' =&gt; 1,
                    ],
                ],
                'default' =&gt; [
                    'unit' =&gt; 'px',
                    'size' =&gt; 10,
                ],
                'selectors' =&gt; [
                    '{{WRAPPER}} .collection-products-grid' =&gt; 'gap: {{SIZE}}{{UNIT}};',
                ],
            ]
        );

        $this-&gt;end_controls_section();
    }

    protected function get_product_categories() {
        $categories = get_terms( 'product_cat', [
            'hide_empty' =&gt; false,
        ] );

        $options = [];
        foreach ( $categories as $category ) {
            $options[ $category-&gt;term_id ] = $category-&gt;name;
        }

        return $options;
    }

    protected function render() {
        $settings = $this-&gt;get_settings_for_display();
        ?&gt;
        &lt;div class="collection-products-wrapper"&gt;
            &lt;h3&gt;Collection Products Widget&lt;/h3&gt;
            &lt;p&gt;<strong>Settings:</strong>&lt;/p&gt;
            &lt;ul&gt;
                &lt;li&gt;Products per page: &lt;?php echo esc_html( $settings['products_per_page'] ); ?&gt;&lt;/li&gt;
                &lt;li&gt;Order by: &lt;?php echo esc_html( $settings['orderby'] ); ?&gt;&lt;/li&gt;
                &lt;li&gt;Order: &lt;?php echo esc_html( $settings['order'] ); ?&gt;&lt;/li&gt;
                &lt;li&gt;Show filters: &lt;?php echo esc_html( $settings['show_filters'] ); ?&gt;&lt;/li&gt;
                &lt;li&gt;Show sorting: &lt;?php echo esc_html( $settings['show_sorting'] ); ?&gt;&lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
        &lt;?php
    }
}
</code></pre>
<h2 id="common-issues-and-solutions" tabindex="-1"><a class="header-anchor" href="#common-issues-and-solutions">#</a> Common Issues and Solutions</h2>
<h3 id="issue-1%3A-widget-not-appearing-in-panel" tabindex="-1"><a class="header-anchor" href="#issue-1%3A-widget-not-appearing-in-panel">#</a> Issue 1: Widget Not Appearing in Panel</h3>
<p><strong>Solutions:</strong></p>
<ul>
<li>Verify <code>get_categories()</code> returns a valid category</li>
<li>Check that WooCommerce is active (required for ‘woocommerce-elements’ category)</li>
<li>Clear Elementor cache: Elementor → Tools → Regenerate CSS</li>
<li>Check PHP error logs</li>
</ul>
<h3 id="issue-2%3A-controls-not-showing" tabindex="-1"><a class="header-anchor" href="#issue-2%3A-controls-not-showing">#</a> Issue 2: Controls Not Showing</h3>
<p><strong>Solutions:</strong></p>
<ul>
<li>Verify <code>register_controls()</code> is called correctly</li>
<li>Check for PHP syntax errors</li>
<li>Ensure <code>start_controls_section()</code> is matched with <code>end_controls_section()</code></li>
<li>Verify control IDs are unique</li>
</ul>
<h3 id="issue-3%3A-category-dropdown-empty" tabindex="-1"><a class="header-anchor" href="#issue-3%3A-category-dropdown-empty">#</a> Issue 3: Category Dropdown Empty</h3>
<p><strong>Solutions:</strong></p>
<ul>
<li>Create some product categories in WooCommerce</li>
<li>Check <code>get_product_categories()</code> method</li>
<li>Verify WooCommerce is installed and active</li>
</ul>
<h3 id="issue-4%3A-settings-not-updating-in-preview" tabindex="-1"><a class="header-anchor" href="#issue-4%3A-settings-not-updating-in-preview">#</a> Issue 4: Settings Not Updating in Preview</h3>
<p><strong>Solutions:</strong></p>
<ul>
<li>Refresh the Elementor editor</li>
<li>Check browser console for JavaScript errors</li>
<li>Verify <code>get_settings_for_display()</code> is used in <code>render()</code></li>
</ul>
<h2 id="summary" tabindex="-1"><a class="header-anchor" href="#summary">#</a> Summary</h2>
<p>The Collection Products widget now includes:</p>
<ul>
<li>Created widget class file</li>
<li>Created registration file</li>
<li>Registered widget with Elementor</li>
<li>Added query controls</li>
<li>Added style controls</li>
<li>Tested widget in Elementor editor</li>
</ul>
<p>Check that the widget appears in Elementor and that its settings update the preview.</p>
<h2 id="what%E2%80%99s-next%3F" tabindex="-1"><a class="header-anchor" href="#what%E2%80%99s-next%3F">#</a> What’s Next?</h2>
<p>In Part 4: WooCommerce Product Integration, we’ll:</p>
<ul>
<li>Query WooCommerce products using WP_Query</li>
<li>Access and display product data</li>
<li>Work with product categories and taxonomies</li>
<li>Display product prices and images</li>
<li>Handle product variations</li>
<li>Create the initial product grid layout</li>
</ul>
<hr />
<p><strong>Previous:</strong> <a href="/blog/elementor-widget-part-2-understanding-elementor-widget-architecture/">← Part 2: Understanding Elementor Widget Architecture</a><br />
<strong>Next:</strong> <a href="/blog/elementor-widget-part-4-woocommerce-product-integration/">Part 4: WooCommerce Product Integration →</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://sajdoko.com/blog/elementor-widget-part-3-building-the-basic-widget-structure/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Elementor Widget Part 2: Understanding Elementor Widget Architecture</title>
		<link>https://sajdoko.com/blog/elementor-widget-part-2-understanding-elementor-widget-architecture/</link>
					<comments>https://sajdoko.com/blog/elementor-widget-part-2-understanding-elementor-widget-architecture/#respond</comments>
		
		<dc:creator><![CDATA[sajdoko]]></dc:creator>
		<pubDate>Mon, 10 Nov 2025 10:25:51 +0000</pubDate>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Elementor]]></category>
		<category><![CDATA[WooCommerce]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[Elementor Widget]]></category>
		<guid isPermaLink="false">https://sajdoko.al/blog/elementor-widget-part-2-understanding-elementor-widget-architecture/</guid>

					<description><![CDATA[We’ll use these concepts to build the Collection Products widget in the&#8230;]]></description>
										<content:encoded><![CDATA[<h2 id="introduction" tabindex="-1"><a class="header-anchor" href="#introduction">#</a> Introduction</h2>
<p>This part explains the Elementor widget base class, required methods and lifecycle. We’ll use these concepts to build the Collection Products widget in the next part.</p>
<h2 id="learning-objectives" tabindex="-1"><a class="header-anchor" href="#learning-objectives">#</a> Learning Objectives</h2>
<p>By the end of this tutorial, you will understand:</p>
<ul>
<li style="list-style-type: none;">
<ul>
<li>The structure of an Elementor widget class</li>
<li>Required methods and their purposes</li>
<li>How widgets are registered with Elementor</li>
</ul>
</li>
</ul>
<ul>
<li>The widget lifecycle from registration to rendering</li>
<li>Widget controls and how they create the settings panel</li>
<li>How widgets render content on the frontend</li>
<li>Best practices for widget development</li>
</ul>
<h2 id="the-elementor-widget-base-class" tabindex="-1"><a class="header-anchor" href="#the-elementor-widget-base-class">#</a> The Elementor Widget Base Class</h2>
<p>All Elementor widgets extend the <code>\Elementor\Widget_Base</code> abstract class. This base class provides the foundation and structure that Elementor expects from every widget.</p>
<h3 id="key-concept%3A-object-oriented-programming" tabindex="-1"><a class="header-anchor" href="#key-concept%3A-object-oriented-programming">#</a> Key Concept: Object-Oriented Programming</h3>
<p>Elementor uses Object-Oriented Programming (OOP) principles. Your widget is a PHP class that extends (inherits from) the base widget class.</p>
<pre tabindex="0"><code class="language-php">
    class My_Custom_Widget extends \Elementor\Widget_Base {
      // Your widget code here
    }
</code></pre>
<h2 id="required-methods" tabindex="-1"><a class="header-anchor" href="#required-methods">#</a> Required Methods</h2>
<p>Every Elementor widget <strong>must</strong> implement certain methods. Let’s examine each one:</p>
<h3 id="1.-get_name()" tabindex="-1"><a class="header-anchor" href="#1.-get_name()">#</a> 1. get_name()</h3>
<p><strong>Purpose:</strong> Returns a unique identifier for your widget.</p>
<pre tabindex="0"><code class="language-php">
  public function get_name() {
    return 'collection_products';
  }
</code></pre>
<p><strong>Important Points:</strong></p>
<ul>
<li>Must be unique across all widgets</li>
<li>Use lowercase letters, numbers, and underscores only</li>
<li>No spaces or special characters</li>
<li>This name is used internally by Elementor</li>
</ul>
<h3 id="2.-get_title()" tabindex="-1"><a class="header-anchor" href="#2.-get_title()">#</a> 2. get_title()</h3>
<p><strong>Purpose:</strong> Returns the human-readable title shown in the widget panel.</p>
<pre tabindex="0"><code class="language-php">
  public function get_title() {
    return __( 'Collection Products', 'hello-biz-child' );
  }
</code></pre>
<p><strong>Important Points:</strong></p>
<ul>
<li>This is what users see in the Elementor editor</li>
<li>Use <code>__()</code> for translation support</li>
<li>Keep it clear and descriptive</li>
</ul>
<h3 id="3.-get_icon()" tabindex="-1"><a class="header-anchor" href="#3.-get_icon()">#</a> 3. get_icon()</h3>
<p><strong>Purpose:</strong> Returns the icon class that appears next to the widget name.</p>
<pre tabindex="0"><code class="language-php">
  public function get_icon() {
    return 'eicon-products';
  }
</code></pre>
<p><strong>Important Points:</strong></p>
<ul>
<li>Uses Elementor’s icon library</li>
<li>Format: <code>eicon-{icon-name}</code></li>
<li>Browse available icons in Elementor’s icon library</li>
<li>Common icons: <code>eicon-posts-grid</code>, <code>eicon-products</code>, <code>eicon-gallery-grid</code></li>
</ul>
<h3 id="4.-get_categories()" tabindex="-1"><a class="header-anchor" href="#4.-get_categories()">#</a> 4. get_categories()</h3>
<p><strong>Purpose:</strong> Defines which category panel the widget appears in.</p>
<pre tabindex="0"><code class="language-php">
  public function get_categories() {
    return [ 'woocommerce-elements' ];
  }
</code></pre>
<p><strong>Available Categories:</strong></p>
<ul>
<li><code>basic</code> &#8211; Basic widgets</li>
<li><code>general</code> &#8211; General elements</li>
<li><code>woocommerce-elements</code> &#8211; WooCommerce widgets (we’ll use this)</li>
<li><code>theme-elements</code> &#8211; Theme-specific widgets</li>
<li>You can also create custom categories</li>
</ul>
<h3 id="5.-register_controls()-(optional-but-essential)" tabindex="-1"><a class="header-anchor" href="#5.-register_controls()-(optional-but-essential)">#</a> 5. register_controls() (Optional but Essential)</h3>
<p><strong>Purpose:</strong> Defines the widget’s settings panel in the Elementor editor.</p>
<pre tabindex="0"><code class="language-php">
  protected function register_controls() {
    // Define control sections and controls here
  }
</code></pre>
<p><strong>Important Points:</strong></p>
<ul>
<li>This method is <code>protected</code> (not <code>public</code>)</li>
<li>Controls are settings users can modify in the Elementor panel</li>
<li>Controls are organized into sections</li>
<li>This is where you define all user-configurable options</li>
</ul>
<h3 id="6.-render()" tabindex="-1"><a class="header-anchor" href="#6.-render()">#</a> 6. render()</h3>
<p><strong>Purpose:</strong> Outputs the HTML for the frontend display.</p>
<pre tabindex="0"><code class="language-php">
  protected function render() {
    $settings = $this-&gt;get_settings_for_display();

    // Output your HTML here
    echo '</code></pre>
<div class="my-widget">&#8216;; echo &#8216;Hello from my widget!&#8217;; echo &#8216;</div>
<pre tabindex="0"><code class="language-php"></code></pre>
<p>&#8216;; }</p>
<pre tabindex="0"><code class="language-php"></code></pre>
<p><strong>Important Points:</strong></p>
<ul>
<li>This method is <code>protected</code></li>
<li>Use <code>$this-&gt;get_settings_for_display()</code> to access user settings</li>
<li>Output sanitized HTML</li>
<li>This code runs on the frontend when the page loads</li>
</ul>
<h2 id="widget-lifecycle" tabindex="-1"><a class="header-anchor" href="#widget-lifecycle">#</a> Widget Lifecycle</h2>
<p>Understanding when methods are called helps you structure your code effectively:</p>
<ol>
<li><strong>Widget Registration</strong>
<ul>
<li>WordPress loads</li>
<li>Elementor initializes</li>
<li>Your widget class is registered via <code>elementor/widgets/register</code> hook</li>
</ul>
</li>
<li><strong>Editor Load</strong>
<ul>
<li>User opens Elementor editor</li>
<li><code>get_name()</code>, <code>get_title()</code>, <code>get_icon()</code>, <code>get_categories()</code> are called</li>
<li>Widget appears in the panel</li>
</ul>
</li>
<li><strong>Widget Inserted</strong>
<ul>
<li>User drags widget onto page</li>
<li><code>register_controls()</code> is called</li>
<li>Control panel appears in the editor</li>
</ul>
</li>
<li><strong>Settings Changed</strong>
<ul>
<li>User modifies controls</li>
<li>Widget preview updates in editor</li>
<li><code>render()</code> is called for preview</li>
</ul>
</li>
<li><strong>Page Saved</strong>
<ul>
<li>Settings are saved to the database</li>
<li>Widget configuration is stored</li>
</ul>
</li>
<li><strong>Frontend Display</strong>
<ul>
<li>Visitor loads the page</li>
<li><code>render()</code> is called</li>
<li>HTML is output to the page</li>
</ul>
</li>
</ol>
<h2 id="understanding-controls" tabindex="-1"><a class="header-anchor" href="#understanding-controls">#</a> Understanding Controls</h2>
<p>Controls are the building blocks of your widget’s settings panel. Let’s break down how they work:</p>
<h3 id="control-sections" tabindex="-1"><a class="header-anchor" href="#control-sections">#</a> Control Sections</h3>
<p>Controls are organized into sections. A section is a collapsible panel in the editor.</p>
<pre tabindex="0"><code class="language-php">
  protected function register_controls() {
    // Start a new section
    $this-&gt;start_controls_section(
      'section_content', // Section ID (must be unique within widget)
      [
        'label' =&gt; __( 'Content', 'hello-biz-child' ), // Section title
      ]
    );

    // Add controls here

    // End the section
    $this-&gt;end_controls_section();
  }
</code></pre>
<h3 id="adding-controls" tabindex="-1"><a class="header-anchor" href="#adding-controls">#</a> Adding Controls</h3>
<p>Between <code>start_controls_section()</code> and <code>end_controls_section()</code>, you add individual controls:</p>
<pre tabindex="0"><code class="language-php">
  $this-&gt;add_control(
    'products_per_page', // Control ID (must be unique within widget)
    [
      'label' =&gt; __( 'Products Per Page', 'hello-biz-child' ),
      'type' =&gt; \Elementor\Controls_Manager::NUMBER,
      'default' =&gt; 9,
      'min' =&gt; 3,
    ]
  );
</code></pre>
<h3 id="common-control-types" tabindex="-1"><a class="header-anchor" href="#common-control-types">#</a> Common Control Types</h3>
<p><strong>NUMBER</strong> &#8211; Numeric input</p>
<pre tabindex="0" class="hljs"><code><span class="hljs-string">'type'</span> =&gt; <span class="hljs-title class_">\Elementor\Controls_Manager</span>::<span class="hljs-variable constant_">NUMBER</span>,
<span class="hljs-string">'default'</span> =&gt; <span class="hljs-number">9</span>,
<span class="hljs-string">'min'</span> =&gt; <span class="hljs-number">1</span>,
<span class="hljs-string">'max'</span> =&gt; <span class="hljs-number">100</span>,
<span class="hljs-string">'step'</span> =&gt; <span class="hljs-number">3</span>,
</code></pre>
<p><strong>SELECT</strong> &#8211; Dropdown menu</p>
<pre tabindex="0" class="hljs"><code><span class="hljs-string">'type'</span> =&gt; <span class="hljs-title class_">\Elementor\Controls_Manager</span>::<span class="hljs-variable constant_">SELECT</span>,
<span class="hljs-string">'default'</span> =&gt; <span class="hljs-string">'date'</span>,
<span class="hljs-string">'options'</span> =&gt; [
    <span class="hljs-string">'date'</span> =&gt; <span class="hljs-title function_ invoke__">__</span>( <span class="hljs-string">'Date'</span>, <span class="hljs-string">'hello-biz-child'</span> ),
    <span class="hljs-string">'title'</span> =&gt; <span class="hljs-title function_ invoke__">__</span>( <span class="hljs-string">'Title'</span>, <span class="hljs-string">'hello-biz-child'</span> ),
    <span class="hljs-string">'price'</span> =&gt; <span class="hljs-title function_ invoke__">__</span>( <span class="hljs-string">'Price'</span>, <span class="hljs-string">'hello-biz-child'</span> ),
],
</code></pre>
<p><strong>SELECT2</strong> &#8211; Multi-select dropdown</p>
<pre tabindex="0" class="hljs"><code><span class="hljs-string">'type'</span> =&gt; <span class="hljs-title class_">\Elementor\Controls_Manager</span>::<span class="hljs-variable constant_">SELECT2</span>,
<span class="hljs-string">'multiple'</span> =&gt; <span class="hljs-literal">true</span>,
<span class="hljs-string">'options'</span> =&gt; <span class="hljs-variable language_">$this</span>-&gt;<span class="hljs-title function_ invoke__">get_categories</span>(),
<span class="hljs-string">'label_block'</span> =&gt; <span class="hljs-literal">true</span>,
</code></pre>
<p><strong>SWITCHER</strong> &#8211; Toggle switch (yes/no)</p>
<pre tabindex="0" class="hljs"><code><span class="hljs-string">'type'</span> =&gt; <span class="hljs-title class_">\Elementor\Controls_Manager</span>::<span class="hljs-variable constant_">SWITCHER</span>,
<span class="hljs-string">'label_on'</span> =&gt; <span class="hljs-title function_ invoke__">__</span>( <span class="hljs-string">'Yes'</span>, <span class="hljs-string">'hello-biz-child'</span> ),
<span class="hljs-string">'label_off'</span> =&gt; <span class="hljs-title function_ invoke__">__</span>( <span class="hljs-string">'No'</span>, <span class="hljs-string">'hello-biz-child'</span> ),
<span class="hljs-string">'return_value'</span> =&gt; <span class="hljs-string">'yes'</span>,
<span class="hljs-string">'default'</span> =&gt; <span class="hljs-string">'yes'</span>,
</code></pre>
<p><strong>SLIDER</strong> &#8211; Range slider</p>
<pre tabindex="0" class="hljs"><code><span class="hljs-string">'type'</span> =&gt; <span class="hljs-title class_">\Elementor\Controls_Manager</span>::<span class="hljs-variable constant_">SLIDER</span>,
<span class="hljs-string">'size_units'</span> =&gt; [ <span class="hljs-string">'px'</span> ],
<span class="hljs-string">'range'</span> =&gt; [
    <span class="hljs-string">'px'</span> =&gt; [
        <span class="hljs-string">'min'</span> =&gt; <span class="hljs-number">0</span>,
        <span class="hljs-string">'max'</span> =&gt; <span class="hljs-number">50</span>,
        <span class="hljs-string">'step'</span> =&gt; <span class="hljs-number">1</span>,
    ],
],
<span class="hljs-string">'default'</span> =&gt; [
    <span class="hljs-string">'unit'</span> =&gt; <span class="hljs-string">'px'</span>,
    <span class="hljs-string">'size'</span> =&gt; <span class="hljs-number">10</span>,
],
</code></pre>
<p><strong>TEXT</strong> &#8211; Text input</p>
<pre tabindex="0" class="hljs"><code><span class="hljs-string">'type'</span> =&gt; <span class="hljs-title class_">\Elementor\Controls_Manager</span>::<span class="hljs-variable constant_">TEXT</span>,
<span class="hljs-string">'default'</span> =&gt; <span class="hljs-title function_ invoke__">__</span>( <span class="hljs-string">'Default text'</span>, <span class="hljs-string">'hello-biz-child'</span> ),
<span class="hljs-string">'placeholder'</span> =&gt; <span class="hljs-title function_ invoke__">__</span>( <span class="hljs-string">'Enter text'</span>, <span class="hljs-string">'hello-biz-child'</span> ),
</code></pre>
<h2 id="style-controls" tabindex="-1"><a class="header-anchor" href="#style-controls">#</a> Style Controls</h2>
<p>For styling options, use the <code>TAB_STYLE</code> tab:</p>
<pre tabindex="0"><code class="language-php">
  $this-&gt;start_controls_section(
    'section_style',
    [
      'label' =&gt; __( 'Style', 'hello-biz-child' ),
      'tab' =&gt; \Elementor\Controls_Manager::TAB_STYLE, // Style tab
    ]
  );
</code></pre>
<h3 id="using-selectors" tabindex="-1"><a class="header-anchor" href="#using-selectors">#</a> Using Selectors</h3>
<p>Selectors allow controls to directly affect CSS:</p>
<pre tabindex="0"><code class="language-php">
$this-&gt;add_control(
  'gap',
  [
    'label' =&gt; __( 'Gap', 'hello-biz-child' ),
    'type' =&gt; \Elementor\Controls_Manager::SLIDER,
    'selectors' =&gt; [
      '{{WRAPPER}} .my-grid' =&gt; 'gap: {{SIZE}}{{UNIT}};',
    ],
  ]
);
</code></pre>
<p><strong>Key Points:</strong></p>
<ul>
<li><code>{{WRAPPER}}</code> is replaced with the widget’s unique wrapper selector</li>
<li><code>{{SIZE}}</code> is replaced with the slider value</li>
<li><code>{{UNIT}}</code> is replaced with the selected unit (px, %, em, etc.)</li>
</ul>
<h2 id="accessing-settings-in-render()" tabindex="-1"><a class="header-anchor" href="#accessing-settings-in-render()">#</a> Accessing Settings in render()</h2>
<p>To use the settings in your widget:</p>
<pre tabindex="0"><code class="language-php">
  protected function render() {
      // Get settings
      $settings = $this-&gt;get_settings_for_display();
      
      // Access individual settings
      $products_per_page = $settings['products_per_page'];
      $orderby = $settings['orderby'];
      $show_filters = $settings['show_filters'];
      
      // Use in your code
      if ( $show_filters === 'yes' ) {
          // Display filters
      }
  }
</code></pre>
<h2 id="widget-registration" tabindex="-1"><a class="header-anchor" href="#widget-registration">#</a> Widget Registration</h2>
<p>To make your widget available in Elementor, you must register it:</p>
<pre tabindex="0"><code class="language-php">
function register_my_widget( $widgets_manager ) {
    require_once( __DIR__ . '/my-widget.php' );
    $widgets_manager-&gt;register( new \My_Custom_Widget() );
}
add_action( 'elementor/widgets/register', 'register_my_widget' );
</code></pre>
<p><strong>Important Points:</strong></p>
<ul>
<li>Hook into <code>elementor/widgets/register</code></li>
<li>Require/include your widget file</li>
<li>Instantiate and register your widget class</li>
</ul>
<h2 id="best-practices" tabindex="-1"><a class="header-anchor" href="#best-practices">#</a> Best Practices</h2>
<h3 id="1.-namespace-your-widget" tabindex="-1"><a class="header-anchor" href="#1.-namespace-your-widget">#</a> 1. Namespace Your Widget</h3>
<p>Avoid naming conflicts by using a unique class name:</p>
<pre tabindex="0" class="hljs"><code><span class="hljs-comment">// Good</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Collection_Products_Widget</span> <span class="hljs-keyword">extends</span> \<span class="hljs-title">Elementor</span>\<span class="hljs-title">Widget_Base</span> </span>{

<span class="hljs-comment">// Better (with namespace)</span>
<span class="hljs-keyword">namespace</span> <span class="hljs-title class_">MyTheme</span>\<span class="hljs-title class_">Widgets</span>;
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Collection_Products</span> <span class="hljs-keyword">extends</span> \<span class="hljs-title">Elementor</span>\<span class="hljs-title">Widget_Base</span> </span>{
</code></pre>
<h3 id="2.-translation-ready" tabindex="-1"><a class="header-anchor" href="#2.-translation-ready">#</a> 2. Translation Ready</h3>
<p>Always use translation functions:</p>
<pre tabindex="0" class="hljs"><code><span class="hljs-comment">// Good</span>
<span class="hljs-string">'label'</span> =&gt; <span class="hljs-title function_ invoke__">__</span>( <span class="hljs-string">'Products Per Page'</span>, <span class="hljs-string">'hello-biz-child'</span> ),

<span class="hljs-comment">// Bad</span>
<span class="hljs-string">'label'</span> =&gt; <span class="hljs-string">'Products Per Page'</span>,
</code></pre>
<h3 id="3.-sanitize-output" tabindex="-1"><a class="header-anchor" href="#3.-sanitize-output">#</a> 3. Sanitize Output</h3>
<p>Always escape output in <code>render()</code>:</p>
<pre tabindex="0" class="hljs"><code><span class="hljs-comment">// Good</span>
<span class="hljs-keyword">echo</span> <span class="hljs-string">'&lt;div class="'</span> . <span class="hljs-title function_ invoke__">esc_attr</span>( <span class="hljs-variable">$class</span> ) . <span class="hljs-string">'"&gt;'</span>;
<span class="hljs-keyword">echo</span> <span class="hljs-title function_ invoke__">esc_html</span>( <span class="hljs-variable">$title</span> );

<span class="hljs-comment">// Bad</span>
<span class="hljs-keyword">echo</span> <span class="hljs-string">'&lt;div class="'</span> . <span class="hljs-variable">$class</span> . <span class="hljs-string">'"&gt;'</span>;
<span class="hljs-keyword">echo</span> <span class="hljs-variable">$title</span>;
</code></pre>
<h3 id="4.-organize-controls" tabindex="-1"><a class="header-anchor" href="#4.-organize-controls">#</a> 4. Organize Controls</h3>
<p>Group related controls into sections:</p>
<pre tabindex="0" class="hljs"><code><span class="hljs-comment">// Query settings section</span>
<span class="hljs-variable language_">$this</span>-&gt;<span class="hljs-title function_ invoke__">start_controls_section</span>( <span class="hljs-string">'section_query'</span>, [...] );

<span class="hljs-comment">// Display settings section</span>
<span class="hljs-variable language_">$this</span>-&gt;<span class="hljs-title function_ invoke__">start_controls_section</span>( <span class="hljs-string">'section_display'</span>, [...] );

<span class="hljs-comment">// Style settings section  </span>
<span class="hljs-variable language_">$this</span>-&gt;<span class="hljs-title function_ invoke__">start_controls_section</span>( <span class="hljs-string">'section_style'</span>, [...] );
</code></pre>
<h3 id="5.-provide-defaults" tabindex="-1"><a class="header-anchor" href="#5.-provide-defaults">#</a> 5. Provide Defaults</h3>
<p>Always provide sensible default values:</p>
<pre tabindex="0" class="hljs"><code><span class="hljs-string">'default'</span> =&gt; <span class="hljs-number">9</span>,  <span class="hljs-comment">// Good default for products per page</span>
<span class="hljs-string">'default'</span> =&gt; <span class="hljs-string">'date'</span>,  <span class="hljs-comment">// Reasonable sort option</span>
</code></pre>
<h2 id="example%3A-simple-widget-structure" tabindex="-1"><a class="header-anchor" href="#example%3A-simple-widget-structure">#</a> Example: Simple Widget Structure</h2>
<p>Here’s a complete example of a minimal widget:</p>
<pre tabindex="0"><code class="language-php">
&lt;?php
class Simple_Widget extends \Elementor\Widget_Base {

    // Required: Unique widget name
    public function get_name() {
        return 'simple_widget';
    }

    // Required: Widget title in panel
    public function get_title() {
        return __( 'Simple Widget', 'hello-biz-child' );
    }

    // Required: Widget icon
    public function get_icon() {
        return 'eicon-posts-grid';
    }

    // Required: Widget category
    public function get_categories() {
        return [ 'general' ];
    }

    // Optional: Widget keywords for search
    public function get_keywords() {
        return [ 'simple', 'example' ];
    }

    // Define settings controls
    protected function register_controls() {
        
        // Content Section
        $this-&gt;start_controls_section(
            'content_section',
            [
                'label' =&gt; __( 'Content', 'hello-biz-child' ),
            ]
        );

        $this-&gt;add_control(
            'title',
            [
                'label' =&gt; __( 'Title', 'hello-biz-child' ),
                'type' =&gt; \Elementor\Controls_Manager::TEXT,
                'default' =&gt; __( 'Hello World', 'hello-biz-child' ),
            ]
        );

        $this-&gt;end_controls_section();
    }

    // Render widget output
    // Render widget output
    protected function render() {
        $settings = $this-&gt;get_settings_for_display();
        ?&gt;
&lt;div class="simple-widget"&gt;

&lt;h2&gt;&lt;?php echo esc_html( $settings['title'] ); ?&gt;&lt;/h2&gt;

&lt;/div&gt;
&lt;?php }
}
</code></pre>
<h2 id="understanding-the-data-flow" tabindex="-1"><a class="header-anchor" href="#understanding-the-data-flow">#</a> Understanding the Data Flow</h2>
<p>Let’s trace how data flows through a widget:</p>
<ol>
<li><strong>User Interaction</strong>: User sets “Products Per Page” to 12 in editor</li>
<li><strong>Setting Saved</strong>: Value stored as <code>['products_per_page' =&gt; 12]</code></li>
<li><strong>Render Called</strong>: <code>render()</code> method executes</li>
<li><strong>Settings Retrieved</strong>: <code>$settings = $this-&gt;get_settings_for_display()</code></li>
<li><strong>Value Accessed</strong>: <code>$settings['products_per_page']</code> returns 12</li>
<li><strong>Value Used</strong>: Used in WP_Query to limit products</li>
</ol>
<h2 id="common-mistakes-to-avoid" tabindex="-1"><a class="header-anchor" href="#common-mistakes-to-avoid">#</a> Common Mistakes to Avoid</h2>
<h3 id="mistake-1%3A-forgetting-required-methods" tabindex="-1"><a class="header-anchor" href="#mistake-1%3A-forgetting-required-methods">#</a> Mistake 1: Forgetting Required Methods</h3>
<pre tabindex="0" class="hljs"><code><span class="hljs-comment">// Bad - Missing required methods</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">My_Widget</span> <span class="hljs-keyword">extends</span> \<span class="hljs-title">Elementor</span>\<span class="hljs-title">Widget_Base</span> </span>{
    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">render</span>() </span>{
        <span class="hljs-keyword">echo</span> <span class="hljs-string">'Hello'</span>;
    }
}

<span class="hljs-comment">// Good - All required methods present</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">My_Widget</span> <span class="hljs-keyword">extends</span> \<span class="hljs-title">Elementor</span>\<span class="hljs-title">Widget_Base</span> </span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get_name</span>() </span>{ <span class="hljs-keyword">return</span> <span class="hljs-string">'my_widget'</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get_title</span>() </span>{ <span class="hljs-keyword">return</span> <span class="hljs-string">'My Widget'</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get_icon</span>() </span>{ <span class="hljs-keyword">return</span> <span class="hljs-string">'eicon-posts-grid'</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get_categories</span>() </span>{ <span class="hljs-keyword">return</span> [<span class="hljs-string">'general'</span>]; }
    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">render</span>() </span>{ <span class="hljs-keyword">echo</span> <span class="hljs-string">'Hello'</span>; }
}
</code></pre>
<h3 id="mistake-2%3A-not-escaping-output" tabindex="-1"><a class="header-anchor" href="#mistake-2%3A-not-escaping-output">#</a> Mistake 2: Not Escaping Output</h3>
<pre tabindex="0" class="hljs"><code><span class="hljs-comment">// Bad - No escaping</span>
<span class="hljs-keyword">echo</span> <span class="hljs-string">'&lt;div class="'</span> . <span class="hljs-variable">$class</span> . <span class="hljs-string">'"&gt;'</span> . <span class="hljs-variable">$content</span> . <span class="hljs-string">'&lt;/div&gt;'</span>;

<span class="hljs-comment">// Good - Properly escaped</span>
<span class="hljs-keyword">echo</span> <span class="hljs-string">'&lt;div class="'</span> . <span class="hljs-title function_ invoke__">esc_attr</span>( <span class="hljs-variable">$class</span> ) . <span class="hljs-string">'"&gt;'</span> . <span class="hljs-title function_ invoke__">esc_html</span>( <span class="hljs-variable">$content</span> ) . <span class="hljs-string">'&lt;/div&gt;'</span>;
</code></pre>
<h3 id="mistake-3%3A-using-echo-in-register_controls()" tabindex="-1"><a class="header-anchor" href="#mistake-3%3A-using-echo-in-register_controls()">#</a> Mistake 3: Using echo in register_controls()</h3>
<pre tabindex="0" class="hljs"><code><span class="hljs-comment">// Bad - Don't output in register_controls()</span>
<span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">register_controls</span>() </span>{
    <span class="hljs-keyword">echo</span> <span class="hljs-string">'Adding controls'</span>;  <span class="hljs-comment">// Wrong!</span>
    <span class="hljs-variable language_">$this</span>-&gt;<span class="hljs-title function_ invoke__">add_control</span>(...);
}

<span class="hljs-comment">// Good - Only define controls</span>
<span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">register_controls</span>() </span>{
    <span class="hljs-variable language_">$this</span>-&gt;<span class="hljs-title function_ invoke__">add_control</span>(...);
}
</code></pre>
<h2 id="testing-your-understanding" tabindex="-1"><a class="header-anchor" href="#testing-your-understanding">#</a> Testing Your Understanding</h2>
<p>To verify you understand these concepts, try answering:</p>
<ol>
<li>What happens if you don’t implement <code>get_name()</code>?</li>
<li>Which method outputs HTML to the frontend?</li>
<li>Where do you define the widget’s settings panel?</li>
<li>How do you access user settings in the <code>render()</code> method?</li>
<li>What’s the difference between <code>protected</code> and <code>public</code> methods?</li>
</ol>
<p><strong>Answers:</strong></p>
<ol>
<li>PHP will throw a fatal error &#8211; it’s a required abstract method</li>
<li>The <code>render()</code> method</li>
<li>In the <code>register_controls()</code> method</li>
<li>Using <code>$this-&gt;get_settings_for_display()</code></li>
<li><code>public</code> methods can be called from outside the class, <code>protected</code> only from within the class or child classes</li>
</ol>
<h2 id="summary" tabindex="-1"><a class="header-anchor" href="#summary">#</a> Summary</h2>
<p>You now understand:</p>
<ul>
<li>The Elementor widget base class structure</li>
<li>Required methods and their purposes</li>
<li>How to define widget controls</li>
<li>The widget lifecycle</li>
<li>How settings flow from editor to frontend</li>
<li>Best practices for widget development</li>
</ul>
<h2 id="what%E2%80%99s-next%3F" tabindex="-1"><a class="header-anchor" href="#what%E2%80%99s-next%3F">#</a> What’s Next?</h2>
<p>In Part 3: Building the Basic Widget Structure, we’ll apply this knowledge to create the foundation of our Collection Products widget. We’ll:</p>
<ul>
<li>Create the widget PHP files</li>
<li>Implement the required methods</li>
<li>Register the widget with Elementor</li>
<li>Add basic controls</li>
<li>Create a simple render method</li>
<li>Test the widget in Elementor</li>
</ul>
<h2 id="additional-resources" tabindex="-1"><a class="header-anchor" href="#additional-resources">#</a> Additional Resources</h2>
<ul>
<li><a href="https://developers.elementor.com/docs/widgets/">Elementor Developers &#8211; Widget Structure</a></li>
<li><a href="https://developers.elementor.com/docs/controls/">Elementor Controls Reference</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.php">PHP Object-Oriented Programming</a></li>
</ul>
<hr />
<p><strong>Previous:</strong> <a href="/blog/elementor-widget-part-1-setting-up-the-development-environment/">← Part 1: Setting Up the Development Environment</a><br />
<strong>Next:</strong> <a href="/blog/elementor-widget-part-3-building-the-basic-widget-structure/">Part 3: Building the Basic Widget Structure →</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://sajdoko.com/blog/elementor-widget-part-2-understanding-elementor-widget-architecture/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Elementor Widget Part 1: Setting Up the Development Environment</title>
		<link>https://sajdoko.com/blog/elementor-widget-part-1-setting-up-the-development-environment/</link>
					<comments>https://sajdoko.com/blog/elementor-widget-part-1-setting-up-the-development-environment/#respond</comments>
		
		<dc:creator><![CDATA[sajdoko]]></dc:creator>
		<pubDate>Sun, 09 Nov 2025 20:43:52 +0000</pubDate>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Elementor]]></category>
		<category><![CDATA[WooCommerce]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[Elementor Widget]]></category>
		<guid isPermaLink="false">https://sajdoko.al/blog/elementor-widget-part-1-setting-up-the-development-environment/</guid>

					<description><![CDATA[Introduction This first part sets up the local WordPress, WooCommerce and Elementor environment for the Collection Products widget. # Learning Objectives By the end of&#8230;]]></description>
										<content:encoded><![CDATA[<h2 id="introduction" tabindex="-1"><a class="header-anchor" href="#introduction">#</a> Introduction</h2>
<p>This first part sets up the local WordPress, WooCommerce and Elementor environment for the Collection Products widget.</p>
<h2 id="learning-objectives" tabindex="-1"><a class="header-anchor" href="#learning-objectives">#</a> Learning Objectives</h2>
<p>By the end of this tutorial, you will:</p>
<ul>
<li>Have a working WordPress installation</li>
<li>Install and configure WooCommerce</li>
<li>Install and activate Elementor</li>
<li>Set up Hello Biz theme with a child theme</li>
<li>Understand the child theme file structure</li>
<li>Create the basic directory structure for the widget</li>
</ul>
<h2 id="prerequisites" tabindex="-1"><a class="header-anchor" href="#prerequisites">#</a> Prerequisites</h2>
<ul>
<li>Local development environment (XAMPP, MAMP, Local by Flywheel, or similar)</li>
<li>Basic understanding of WordPress installation</li>
<li>Text editor or IDE (VS Code, PHPStorm, Sublime Text, etc.)</li>
<li>FTP/SFTP client (optional, for remote development)</li>
</ul>
<h2 id="step-1%3A-wordpress-installation" tabindex="-1"><a class="header-anchor" href="#step-1%3A-wordpress-installation">#</a> Step 1: WordPress Installation</h2>
<h3 id="1.1-install-wordpress" tabindex="-1"><a class="header-anchor" href="#1.1-install-wordpress">#</a> 1.1 Install WordPress</h3>
<p>If you don’t already have WordPress installed:</p>
<ol>
<li>Download the latest version of WordPress from <a href="https://wordpress.org/download/">wordpress.org</a></li>
<li>Create a new MySQL database for your WordPress installation</li>
<li>Extract WordPress files to your web server directory</li>
<li>Navigate to your site URL in a browser</li>
<li>Follow the WordPress installation wizard:
<ul>
<li>Enter database details</li>
<li>Create an admin account</li>
<li>Complete the installation</li>
</ul>
</li>
</ol>
<h3 id="1.2-verify-installation" tabindex="-1"><a class="header-anchor" href="#1.2-verify-installation">#</a> 1.2 Verify Installation</h3>
<p>After installation:</p>
<ol>
<li>Log in to your WordPress admin dashboard (usually <code>http://yoursite.com/wp-admin</code>)</li>
<li>Check that you can access the dashboard successfully</li>
<li>Navigate to Settings → Permalinks and set to “Post name” (recommended)</li>
</ol>
<p><img decoding="async" class="alignnone size-full wp-image-950" src="/wp-content/uploads/2025/11/part1-wordpress-dashboard.webp" alt="" width="929" height="865" /><br />
<em>The WordPress admin dashboard after successful installation</em></p>
<h2 id="step-2%3A-install-required-plugins" tabindex="-1"><a class="header-anchor" href="#step-2%3A-install-required-plugins">#</a> Step 2: Install Required Plugins</h2>
<h3 id="2.1-install-woocommerce" tabindex="-1"><a class="header-anchor" href="#2.1-install-woocommerce">#</a> 2.1 Install WooCommerce</h3>
<p>WooCommerce is the e-commerce foundation for our widget.</p>
<ol>
<li>Go to <strong>Plugins → Add New</strong></li>
<li>Search for “WooCommerce”</li>
<li>Click <strong>Install Now</strong> on the official WooCommerce plugin</li>
<li>After installation, click <strong>Activate</strong></li>
<li>Follow the WooCommerce setup wizard:
<ul>
<li>Choose your store location</li>
<li>Select industry and product types</li>
<li>Configure business details (you can skip this for development)</li>
<li>Select a theme (we’ll install Hello Biz later)</li>
<li>Complete the setup</li>
</ul>
</li>
</ol>
<h3 id="2.2-install-elementor" tabindex="-1"><a class="header-anchor" href="#2.2-install-elementor">#</a> 2.2 Install Elementor</h3>
<p>Elementor is the page builder we’ll extend with our custom widget.</p>
<ol>
<li>Go to <strong>Plugins → Add New</strong></li>
<li>Search for “Elementor”</li>
<li>Click <strong>Install Now</strong> on the official Elementor plugin</li>
<li>After installation, click <strong>Activate</strong></li>
<li>You may see a welcome screen &#8211; you can skip the introduction</li>
</ol>
<p><strong>Note:</strong> The free version of Elementor is sufficient for this tutorial. Elementor Pro is not required.</p>
<h3 id="2.3-verify-plugin-installation" tabindex="-1"><a class="header-anchor" href="#2.3-verify-plugin-installation">#</a> 2.3 Verify Plugin Installation</h3>
<p>Check that both plugins are active:</p>
<ol>
<li>Go to <strong>Plugins → Installed Plugins</strong></li>
<li>Verify both WooCommerce and Elementor show as “Active”</li>
<li>You should see new menu items for both plugins in the admin sidebar</li>
</ol>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-951" src="/wp-content/uploads/2025/11/part1-plugins-installed.webp" alt="" width="929" height="865" /><br />
<em>WooCommerce and Elementor plugins installed and active</em></p>
<h2 id="step-3%3A-install-hello-biz-theme" tabindex="-1"><a class="header-anchor" href="#step-3%3A-install-hello-biz-theme">#</a> Step 3: Install Hello Biz Theme</h2>
<h3 id="3.1-install-the-parent-theme" tabindex="-1"><a class="header-anchor" href="#3.1-install-the-parent-theme">#</a> 3.1 Install the Parent Theme</h3>
<p>Hello Biz is a lightweight theme optimized for Elementor.</p>
<ol>
<li>Go to <strong>Appearance → Themes</strong></li>
<li>Click <strong>Add New</strong></li>
<li>Search for “Hello Biz”</li>
<li>Click <strong>Install</strong> on the Hello Biz theme</li>
<li>After installation, click <strong>Activate</strong></li>
</ol>
<p><strong>Alternative:</strong> If Hello Biz is not available in the repository, you can use “Hello Elementor” as an alternative parent theme.</p>
<h2 id="step-4%3A-create-a-child-theme" tabindex="-1"><a class="header-anchor" href="#step-4%3A-create-a-child-theme">#</a> Step 4: Create a Child Theme</h2>
<p>Creating a child theme is essential &#8211; it allows you to modify and extend the parent theme without losing changes when the parent theme updates.<br />
We can download the Hello Biz theme from the Official Github repository: <a href="https://github.com/elementor/hello-theme-child" target="_blank" rel="noopener noreferrer">Hello Biz Child Theme</a>, or you can create it manually.</p>
<h3 id="4.1-create-child-theme-directory" tabindex="-1"><a class="header-anchor" href="#4.1-create-child-theme-directory">#</a> 4.1 Create Child Theme Directory</h3>
<ol>
<li>Navigate to <code>wp-content/themes/</code> on your WordPress installation</li>
<li>Create a new folder named <code>hello-biz-child</code></li>
</ol>
<h3 id="4.2-create-style.css" tabindex="-1"><a class="header-anchor" href="#4.2-create-style.css">#</a> 4.2 Create style.css</h3>
<p>Create a file named <code>style.css</code> in the <code>hello-biz-child</code> folder with the following content:</p>
<pre tabindex="0" class="hljs"><code class="language-php"><span class="hljs-comment">/*
Theme Name: Hello Biz Child
Description: Child theme for Hello Biz with Collection Products widget
Author: Your Name
Author URI: https://yourwebsite.com
Template: hello-biz
Version: 1.0.0
License: GNU General Public License v3 or later
License URI: https://www.gnu.org/licenses/gpl-3.0.html
Text Domain: hello-biz-child
*/</span>

<span class="hljs-comment">/* Add your custom styles here */</span>
</code></pre>
<p><strong>Important:</strong> The <code>Template:</code> line must match the directory name of your parent theme exactly.</p>
<h3 id="4.3-create-functions.php" tabindex="-1"><a class="header-anchor" href="#4.3-create-functions.php">#</a> 4.3 Create functions.php</h3>
<p>Create a file named <code>functions.php</code> in the <code>hello-biz-child</code> folder:</p>
<pre tabindex="0"><code class="language-php">
/**
 * Theme functions and definitions.
 *
 * For additional information on potential customization options,
 * read the developers' documentation:
 *
 * @package HelloBizChild
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

define( 'HELLO_BIZ_CHILD_VERSION', '1.0.0' );

/**
 * Load child theme scripts &amp; styles.
 *
 * @return void
 */
function hello_biz_child_scripts_styles() {

	wp_enqueue_style(
		'hello-biz-child-style',
		get_stylesheet_directory_uri() . '/style.css',
		[
			'theme', // This ensures parent theme styles load first
		],
		HELLO_BIZ_CHILD_VERSION
	);
}

add_action( 'wp_enqueue_scripts', 'hello_biz_child_scripts_styles', 20 );

// We'll add more code here in later tutorials
</code></pre>
<h3 id="4.4-activate-child-theme" tabindex="-1"><a class="header-anchor" href="#4.4-activate-child-theme">#</a> 4.4 Activate Child Theme</h3>
<ol>
<li>Go to <strong>Appearance → Themes</strong> in WordPress admin</li>
<li>You should see “Hello Biz Child” theme</li>
<li>Click <strong>Activate</strong> on the child theme</li>
<li>Your site should now be using the child theme</li>
</ol>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-952" src="/wp-content/uploads/2025/11/part1-theme-active.webp" alt="" width="929" height="865" /><br />
<em>Hello Biz Child theme activated in WordPress</em></p>
<h2 id="step-5%3A-create-widget-directory-structure" tabindex="-1"><a class="header-anchor" href="#step-5%3A-create-widget-directory-structure">#</a> Step 5: Create Widget Directory Structure</h2>
<p>Now let’s set up the directory structure for our Collection Products widget.</p>
<h3 id="5.1-create-directories" tabindex="-1"><a class="header-anchor" href="#5.1-create-directories">#</a> 5.1 Create Directories</h3>
<p>In your <code>hello-biz-child</code> folder, create the following directories:</p>
<pre tabindex="0" class="hljs"><code class="language-bash">hello-biz-child/
├── widgets/
│   └── collection-products/
├── js/
└── css/
</code></pre>
<p>You can create these via FTP, File Manager, or command line:</p>
<p><strong>Via Command Line:</strong></p>
<pre tabindex="0"><code class="language-bash">
cd wp-content/themes/hello-biz-child
mkdir -p widgets/collection-products
mkdir js
mkdir css
</code></pre>
<p><strong>Via File Manager or FTP:</strong><br />
Simply create each folder manually.</p>
<h3 id="5.2-verify-directory-structure" tabindex="-1"><a class="header-anchor" href="#5.2-verify-directory-structure">#</a> 5.2 Verify Directory Structure</h3>
<p>Your child theme should now have this structure:</p>
<pre tabindex="0"><code class="language-bash">
hello-biz-child/
├── functions.php
├── style.css
├── widgets/
│   └── collection-products/
├── js/
└── css/
</code></pre>
<h2 id="step-6%3A-add-sample-woocommerce-products-(optional-but-recommended)" tabindex="-1"><a class="header-anchor" href="#step-6%3A-add-sample-woocommerce-products-(optional-but-recommended)">#</a> Step 6: Add Sample WooCommerce Products (Optional but Recommended)</h2>
<p>To test our widget properly, let’s add some sample products.</p>
<h3 id="6.1-install-woocommerce-sample-data" tabindex="-1"><a class="header-anchor" href="#6.1-install-woocommerce-sample-data">#</a> 6.1 Install WooCommerce Sample Data</h3>
<ol>
<li>Go to <strong>WooCommerce → Status → Tools</strong></li>
<li>Click on “Create default WooCommerce pages” (if not already created)</li>
<li>For sample products, you can:
<ul>
<li>Install the “WooCommerce Sample Data” plugin, OR</li>
<li>Manually create a few products</li>
</ul>
</li>
</ol>
<h3 id="6.2-create-sample-products-manually" tabindex="-1"><a class="header-anchor" href="#6.2-create-sample-products-manually">#</a> 6.2 Create Sample Products Manually</h3>
<p>If creating manually:</p>
<ol>
<li>Go to <strong>Products → Add New</strong></li>
<li>Create at least 9 products (to test the grid layout)</li>
<li>For each product:
<ul>
<li>Add a title and description</li>
<li>Set a price</li>
<li>Add a featured image</li>
<li>Assign to a category</li>
<li>Publish the product</li>
</ul>
</li>
</ol>
<h3 id="6.3-add-product-variations-(for-color-swatches)" tabindex="-1"><a class="header-anchor" href="#6.3-add-product-variations-(for-color-swatches)">#</a> 6.3 Add Product Variations (for Color Swatches)</h3>
<p>For testing color swatches:</p>
<ol>
<li>Create a Variable Product</li>
<li>Add an attribute named “Color” (make it global attribute)</li>
<li>Add color terms (e.g., “Black”, “White”, “Blue”)</li>
<li>Create variations for each color</li>
<li>Save the product</li>
</ol>
<h2 id="step-7%3A-test-your-setup" tabindex="-1"><a class="header-anchor" href="#step-7%3A-test-your-setup">#</a> Step 7: Test Your Setup</h2>
<h3 id="7.1-verify-wordpress" tabindex="-1"><a class="header-anchor" href="#7.1-verify-wordpress">#</a> 7.1 Verify WordPress</h3>
<ol>
<li>Visit your site frontend</li>
<li>Confirm the site loads without errors</li>
<li>Check that the Hello Biz Child theme is active</li>
</ol>
<h3 id="7.2-verify-woocommerce" tabindex="-1"><a class="header-anchor" href="#7.2-verify-woocommerce">#</a> 7.2 Verify WooCommerce</h3>
<ol>
<li>Go to <strong>Products → All Products</strong></li>
<li>Verify your sample products are listed</li>
<li>Visit the Shop page on the frontend</li>
<li>Confirm products display correctly</li>
</ol>
<h3 id="7.3-verify-elementor" tabindex="-1"><a class="header-anchor" href="#7.3-verify-elementor">#</a> 7.3 Verify Elementor</h3>
<ol>
<li>Create a new page or edit an existing one</li>
<li>Click “Edit with Elementor”</li>
<li>Verify Elementor editor loads</li>
<li>Check the widgets panel on the left</li>
<li>You should see various Elementor widgets</li>
</ol>
<h2 id="common-issues-and-solutions" tabindex="-1"><a class="header-anchor" href="#common-issues-and-solutions">#</a> Common Issues and Solutions</h2>
<h3 id="issue-1%3A-child-theme-not-showing" tabindex="-1"><a class="header-anchor" href="#issue-1%3A-child-theme-not-showing">#</a> Issue 1: Child Theme Not Showing</h3>
<p><strong>Solution:</strong></p>
<ul>
<li>Check that <code>style.css</code> has the correct <code>Template:</code> header matching the parent theme folder name</li>
<li>Verify both <code>style.css</code> and <code>functions.php</code> exist in the child theme folder</li>
<li>Clear browser cache and WordPress cache</li>
</ul>
<h3 id="issue-2%3A-parent-theme-styles-not-loading" tabindex="-1"><a class="header-anchor" href="#issue-2%3A-parent-theme-styles-not-loading">#</a> Issue 2: Parent Theme Styles Not Loading</h3>
<p><strong>Solution:</strong></p>
<ul>
<li>Ensure the parent theme handle in <code>wp_enqueue_style()</code> is correct</li>
<li>Some themes use ‘theme-style’ or ‘hello-elementor’ &#8211; check your parent theme’s functions.php</li>
<li>Adjust the dependency array accordingly</li>
</ul>
<h3 id="issue-3%3A-woocommerce-not-showing-products" tabindex="-1"><a class="header-anchor" href="#issue-3%3A-woocommerce-not-showing-products">#</a> Issue 3: WooCommerce Not Showing Products</h3>
<p><strong>Solution:</strong></p>
<ul>
<li>Ensure products are published (not drafts)</li>
<li>Check permalink settings (Settings → Permalinks, click Save)</li>
<li>Verify WooCommerce pages were created (WooCommerce → Status)</li>
</ul>
<h3 id="issue-4%3A-elementor-editor-not-loading" tabindex="-1"><a class="header-anchor" href="#issue-4%3A-elementor-editor-not-loading">#</a> Issue 4: Elementor Editor Not Loading</h3>
<p><strong>Solution:</strong></p>
<ul>
<li>Deactivate all plugins except Elementor and WooCommerce</li>
<li>Switch to a default WordPress theme temporarily to isolate the issue</li>
<li>Check browser console for JavaScript errors</li>
<li>Clear WordPress cache and browser cache</li>
</ul>
<h2 id="testing-your-environment" tabindex="-1"><a class="header-anchor" href="#testing-your-environment">#</a> Testing Your Environment</h2>
<p>To ensure everything is set up correctly, perform these checks:</p>
<h3 id="checklist" tabindex="-1"><a class="header-anchor" href="#checklist">#</a> Checklist</h3>
<ul>
<li>[ ] WordPress is installed and accessible</li>
<li>[ ] You can log in to WordPress admin</li>
<li>[ ] WooCommerce is active and configured</li>
<li>[ ] Elementor is active</li>
<li>[ ] Hello Biz parent theme is installed</li>
<li>[ ] Hello Biz Child theme is active and working</li>
<li>[ ] Directory structure is created (widgets/, js/, css/)</li>
<li>[ ] Sample products are created in WooCommerce</li>
<li>[ ] You can edit pages with Elementor</li>
<li>[ ] WooCommerce shop page displays products</li>
</ul>
<h2 id="summary" tabindex="-1"><a class="header-anchor" href="#summary">#</a> Summary</h2>
<p>The development environment now includes:</p>
<ul>
<li>WordPress installed and configured</li>
<li>WooCommerce plugin activated with sample products</li>
<li>Elementor plugin activated</li>
<li>Hello Biz child theme created and activated</li>
<li>Directory structure ready for widget development</li>
</ul>
<h2 id="what%E2%80%99s-next%3F" tabindex="-1"><a class="header-anchor" href="#what%E2%80%99s-next%3F">#</a> What’s Next?</h2>
<p>In <a href="/blog/elementor-widget-part-2-understanding-elementor-widget-architecture/">Part 2: Understanding Elementor Widget Architecture</a>, we’ll examine the widget base class, required methods and lifecycle before building our widget.</p>
<h2 id="additional-resources" tabindex="-1"><a class="header-anchor" href="#additional-resources">#</a> Additional Resources</h2>
<ul>
<li><a href="https://developer.wordpress.org/themes/advanced-topics/child-themes/">WordPress Child Themes Documentation</a></li>
<li><a href="https://woocommerce.com/documentation/">WooCommerce Documentation</a></li>
<li><a href="https://developers.elementor.com/">Elementor Developers Documentation</a></li>
<li><a href="/blog/how-to-install-wordpress-on-localhost-using-xampp-step-by-step-guide/">Local WordPress Development</a></li>
</ul>
<hr />
<p><strong>Next:</strong> <a href="/blog/elementor-widget-part-2-understanding-elementor-widget-architecture/">Part 2: Understanding Elementor Widget Architecture →</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://sajdoko.com/blog/elementor-widget-part-1-setting-up-the-development-environment/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Building a Custom Elementor WooCommerce Collection Widget &#8211; Tutorial Series</title>
		<link>https://sajdoko.com/blog/building-a-custom-elementor-woocommerce-collection-widget-tutorial-series/</link>
					<comments>https://sajdoko.com/blog/building-a-custom-elementor-woocommerce-collection-widget-tutorial-series/#respond</comments>
		
		<dc:creator><![CDATA[sajdoko]]></dc:creator>
		<pubDate>Sun, 09 Nov 2025 20:21:54 +0000</pubDate>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Elementor]]></category>
		<category><![CDATA[WooCommerce]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[Elementor Widget]]></category>
		<guid isPermaLink="false">https://sajdoko.al/blog/building-a-custom-elementor-woocommerce-collection-widget-tutorial-series/</guid>

					<description><![CDATA[It displays products in a Pinterest-style grid with collection images, filters and sorting controls. #&#8230;]]></description>
										<content:encoded><![CDATA[<h2 id="introduction" tabindex="-1"><a class="header-anchor" href="#introduction">#</a> Introduction</h2>
<p>This series builds a Collection Products widget for WooCommerce. It displays products in a Pinterest-style grid with collection images, filters and sorting controls.</p>
<h2 id="what-you%E2%80%99ll-build" tabindex="-1"><a class="header-anchor" href="#what-you%E2%80%99ll-build">#</a> What You’ll Build</h2>
<p>By the end of this tutorial series, you’ll have created a fully functional Elementor widget that:</p>
<ul>
<li style="list-style-type: none;">
<ul>
<li>Displays WooCommerce products in a responsive 2-column grid layout</li>
</ul>
</li>
</ul>
<ul>
<li>Supports custom “collection” images separate from product featured images</li>
<li>Shows featured images on hover (for portrait products)</li>
<li>Includes client-side filtering by colors and sizes</li>
<li>Provides sorting options (price low to high, high to low)</li>
<li>Features a slide-in filter panel</li>
<li>Displays product color swatches</li>
<li>Adapts the layout to desktop, tablet and phone screens</li>
</ul>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-957" src="/wp-content/uploads/2025/11/elementor-widget.webp" alt="" width="1920" height="911" /><br />
<em>The Elementor WooCommerce Collection Widget</em></p>
<h2 id="prerequisites" tabindex="-1"><a class="header-anchor" href="#prerequisites">#</a> Prerequisites</h2>
<p>Before starting this tutorial series, you should have:</p>
<ul>
<li>Basic understanding of PHP (WordPress/WooCommerce development)</li>
<li>Familiarity with JavaScript and jQuery</li>
<li>Understanding of HTML and CSS</li>
<li>Basic knowledge of WordPress hooks and filters</li>
<li>WordPress installation with admin access</li>
<li>WooCommerce plugin installed and activated</li>
<li>Elementor plugin installed and activated</li>
<li>Hello Biz theme with Hello Biz Child theme set up</li>
</ul>
<h2 id="tutorial-series-structure" tabindex="-1"><a class="header-anchor" href="#tutorial-series-structure">#</a> Tutorial Series Structure</h2>
<h3 id="part-1%3A-setting-up-the-development-environment" tabindex="-1"><a href="/blog/elementor-widget-part-1-setting-up-the-development-environment/">Part 1: Setting Up the Development Environment</a></h3>
<p>Learn how to set up your WordPress development environment, install necessary plugins, and create the child theme structure for the widget.</p>
<h3 id="part-2%3A-understanding-elementor-widget-architecture" tabindex="-1"><a href="/blog/elementor-widget-part-2-understanding-elementor-widget-architecture/">Part 2: Understanding Elementor Widget Architecture</a></h3>
<p>Dive into the fundamentals of Elementor widget development, understanding the base class, required methods, and the widget lifecycle.</p>
<h3 id="part-3%3A-building-the-basic-widget-structure" tabindex="-1"><a href="/blog/elementor-widget-part-3-building-the-basic-widget-structure/">Part 3: Building the Basic Widget Structure</a></h3>
<p>Create the foundation of your widget by implementing the widget class, registering it with Elementor, and adding basic controls.</p>
<h3 id="part-4%3A-woocommerce-product-integration" tabindex="-1"><a href="/blog/elementor-widget-part-4-woocommerce-product-integration/">Part 4: WooCommerce Product Integration</a></h3>
<p>Learn how to query WooCommerce products, access product data, work with product taxonomies, and display product information.</p>
<h3 id="part-5%3A-implementing-custom-product-meta-fields" tabindex="-1">Part 5: Implementing Custom Product Meta Fields</h3>
<p>Add custom meta fields to WooCommerce products including a collection image uploader using the WordPress media library and product orientation settings.</p>
<h3 id="part-6%3A-creating-the-frontend-rendering" tabindex="-1">Part 6: Creating the Frontend Rendering</h3>
<p>Build the grid layout using CSS Grid, implement the product rendering logic, add color swatches, and create responsive designs.</p>
<h3 id="part-7%3A-adding-sorting-and-filtering-functionality" tabindex="-1">Part 7: Adding Sorting and Filtering Functionality</h3>
<p>Implement client-side JavaScript filtering and sorting, create the filter panel UI, and add touch-friendly interactions.</p>
<h2 id="key-concepts-covered" tabindex="-1"><a class="header-anchor" href="#key-concepts-covered">#</a> Key Concepts Covered</h2>
<p>Throughout this series, you’ll learn:</p>
<ol>
<li><strong>Elementor Widget Development</strong>
<ul>
<li>Extending <code>\Elementor\Widget_Base</code></li>
<li>Implementing required methods</li>
<li>Adding widget controls</li>
<li>Rendering frontend output</li>
</ul>
</li>
<li><strong>WooCommerce Integration</strong>
<ul>
<li>Querying products with <code>WP_Query</code></li>
<li>Working with product data</li>
<li>Handling product variations</li>
<li>Displaying product meta</li>
</ul>
</li>
<li><strong>WordPress Development Best Practices</strong>
<ul>
<li>Using WordPress hooks and filters</li>
<li>Sanitizing and escaping data</li>
<li>Enqueuing scripts and styles</li>
<li>Following WordPress coding standards</li>
</ul>
</li>
<li><strong>Modern Frontend Development</strong>
<ul>
<li>CSS Grid layouts</li>
<li>Responsive design with media queries</li>
<li>JavaScript DOM manipulation</li>
<li>Client-side filtering without AJAX</li>
</ul>
</li>
<li><strong>WordPress Media Library</strong>
<ul>
<li>Integrating <code>wp.media()</code> API</li>
<li>Creating custom media uploaders</li>
<li>Handling image selection</li>
</ul>
</li>
</ol>
<h2 id="file-structure" tabindex="-1"><a class="header-anchor" href="#file-structure">#</a> File Structure</h2>
<p>By the end of this series, your child theme will have the following structure:</p>
<pre tabindex="0" class="hljs"><code>wp-content/themes/hello-biz-child/
├── functions.php                                    # Main theme functions
├── style.css                                        # Child theme styles
├── widgets/
│   └── collection-products/
│       ├── collection-products-register.php         # Widget registration &amp; helpers
│       └── collection-products-widget.php           # Main widget class
├── js/
│   ├── collection-image-uploader.js                # Admin media uploader
│   └── collection-filters.js                       # Frontend filtering &amp; sorting
└── css/
    └── collection.css                              # Widget styles
</code></pre>
<h2 id="tutorial-format" tabindex="-1"><a class="header-anchor" href="#tutorial-format">#</a> Tutorial Format</h2>
<p>Each tutorial part includes:</p>
<ul>
<li><strong>Clear objectives</strong> &#8211; What you’ll accomplish in that part</li>
<li><strong>Step-by-step instructions</strong> &#8211; Detailed implementation steps</li>
<li><strong>Code snippets with comments</strong> &#8211; Well-documented code examples</li>
<li><strong>Explanations</strong> &#8211; Understanding of what the code does and why</li>
<li><strong>Common issues and solutions</strong> &#8211; Troubleshooting tips</li>
<li><strong>Testing instructions</strong> &#8211; How to verify your implementation</li>
<li><strong>Summary</strong> &#8211; Key takeaways and what’s next</li>
</ul>
<h2 id="additional-resources" tabindex="-1"><a class="header-anchor" href="#additional-resources">#</a> Additional Resources</h2>
<ul>
<li><a href="https://developers.elementor.com/">Elementor Developers Documentation</a></li>
<li><a href="https://woocommerce.com/documentation/plugins/woocommerce/woocommerce-codex/">WooCommerce Developer Documentation</a></li>
<li><a href="https://developer.wordpress.org/">WordPress Developer Resources</a></li>
<li><a href="https://developer.wordpress.org/coding-standards/">WordPress Coding Standards</a></li>
</ul>
<h2 id="getting-started" tabindex="-1"><a class="header-anchor" href="#getting-started">#</a> Getting Started</h2>
<p>Ready to begin? Start with <a href="/blog/elementor-widget-part-1-setting-up-the-development-environment/">Part 1: Setting Up the Development Environment</a>!</p>
<h2 id="support-and-feedback" tabindex="-1"><a class="header-anchor" href="#support-and-feedback">#</a> Support and Feedback</h2>
<p>If you encounter any issues or have questions while following this tutorial series, please:</p>
<ol>
<li>Review the troubleshooting sections in each part</li>
<li>Check the code examples in the repository</li>
<li>Consult the WordPress and Elementor documentation</li>
<li>Search for similar issues in WordPress/Elementor forums</li>
</ol>
<p>Happy coding! Let’s build an amazing WooCommerce widget together!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://sajdoko.com/blog/building-a-custom-elementor-woocommerce-collection-widget-tutorial-series/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
