Creating a custom widget in Magento 2
It’s not common that you need a custom widget in Magento 2. They offer a fairly wide array of well-made widgets with easy-to-use customization. However, sometimes you need to change the functionality of a widget or increase the selection of customization options, and in order to do this, you need to create your own. In this article, we will be looking at how to create a widget that extends the functionality of a core widget. We will be extending the functionality of the “Catalog Products List” widget so that we can change how the products are ordered.
Create the extension
Before we can create a widget, we need somewhere to put it. For this, we need a vendor folder in appcode
and a module folder in the vendor folder. For the sake of this article, I will be using appcodeClassyLlamaSortedProductsWidget
. In the module folder we need two files: registration.php
and etcmodule.xml
.
registration.php
<?php
MagentoFrameworkComponentComponentRegistrar::register(
MagentoFrameworkComponentComponentRegistrar::MODULE,
'ClassyLlama_SortedProductsWidget',
__DIR__
);
etcmodule.xml
<?xml version="1.0"?>
<config xmlns_xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi_noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="ClassyLlama_SortedProductsWidget" setup_version="1.0.0" >
<sequence>
<module name="Magento_CatalogWidget"/>
</sequence>
</module>
</config>
As this is somewhat of an advanced tutorial, I assume that you know why these files need to be present and will not be going in-depth with their code. The only thing I will mention is the <sequence>
tag in etcmodule.xml
, which is basically telling Magento to load Magento_CatalogWidget
before our extension.
Customization
Layout
Now that we’ve created the extension, we can start building the widget. In order to create the layout of the widget, we must create etcwidget.xml
.
etcmodule.xml
<?xml version="1.0" encoding="UTF-8"?>
<widgets xmlns_xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi_noNamespaceSchemaLocation="urn:magento:module:Magento_Widget:etc/widget.xsd">
<widget id="classyllama_products_list" class="ClassyLlamaSortedProductsWidgetBlockProductProductsList"
placeholder_image="ClassyLlama_SortedProductsWidget::images/classyllama_widget_block.png">
<label translate="true">Sorted Products List</label>
<description>Sorted Catalog Products List</description>
<parameters>
<parameter name="title" xsi_type="text" required="false" visible="true">
<label translate="true">Title</label>
</parameter>
<parameter name="products_sort_by" xsi_type="select" visible="true"
source_model="ClassyLlamaSortedProductsWidgetModelSortBy">
<label translate="true">Sort Products By</label>
</parameter>
<parameter name="products_sort_order" xsi_type="select" visible="true"
source_model="ClassyLlamaSortedProductsWidgetModelSortOrder">
<label translate="true">Sort Products Order</label></parameter>
<parameter name="show_pager" xsi_type="select" visible="true"
source_model="MagentoConfigModelConfigSourceYesno">
<label translate="true">Display Page Control</label>
</parameter>
<parameter name="products_per_page" xsi_type="text" required="true" visible="true">
<label translate="true">Number of Products per Page</label>
<depends>
<parameter name="show_pager" value="1" />
</depends>
<value>5</value>
</parameter>
<parameter name="products_count" xsi_type="text" required="true" visible="true">
<label translate="true">Number of Products to Display</label>
<value>10</value>
</parameter>
<parameter name="template" xsi_type="select" required="true" visible="true">
<label translate="true">Template</label>
<options>
<option name="default" value="Magento_CatalogWidget::product/widget/content/grid.phtml" selected="true">
<label translate="true">Products Grid Template</label>
</option>
</options>
</parameter>
<parameter name="cache_lifetime" xsi_type="text" visible="true">
<label translate="true">Cache Lifetime (Seconds)</label>
<description translate="true">86400 by default, if not set. To refresh instantly, clear the Blocks HTML Output cache.</description>
</parameter>
<parameter name="condition" xsi_type="conditions" visible="true" required="true" sort_order="10"
class="MagentoCatalogWidgetBlockProductWidgetConditions">
<label translate="true">Conditions</label>
</parameter>
</parameters>
</widget>
</widgets>
You can see that we create a <widget>
inside a <widgets>
tag. The <widget>
tag contains parameters for our widget configuration. The id
for our widget is a specific name that you want to use for your widget. It can be anything, but for this tutorial we called it classyllama_products_list
. The class
is a block that we will create soon; it controls how the widget actually functions (we will be extending the functionality of the MagentoCatalogWidgetBlockProductProductsList
block). The placeholder_image
is just an image that shows in the WYSIWYG editor of the backend of Magento. It’s not necessary but adds a nice touch when viewing the WYSIWYG.
Inside the widget, you can see that we’ve listed parameters, which are the inputs that we need from the WYSIWYG editor. Each of these parameters having a name
, type
, required
, and visible
option. The name
should be logical, as you will need to reference it in the widget block. Since we are extending the base Magento block, we have used the same names as the ones in the core product list widget. The type
is simply what type of input you want to use in the backend when configuring the widget. visible
controls the option’s visibility in the form when configuring. Inside each parameter tag, there’s a <label>
tag that sets a label for the option. You’ll also notice that some parameters have a <depends>
tag. This states that the given parameter will only appear when the parameter specified in the depends tag has the value that is set in this tag. Some parameters also have a <value>
tag that sets the default value for the given parameter. Finally, the parameters that have their type as select
(or anything that requires predetermined options) need one of two things. They need either a source_model
defined like our products_sort_by
and products_sort_order
parameters or an <options>
tag like our template
parameter.
Creating the Block
Now that we understand the widget.xml
file, we can create our ‘ProductsList.php’ file. This file should be located at BlockProductProductsList.php
.
BlockProductProductsList.php
<?php
namespace ClassyLlamaSortedProductsWidgetBlockProduct;
class ProductsList extends MagentoCatalogWidgetBlockProductProductsList
{
const DEFAULT_SORT_BY = 'id';
const DEFAULT_SORT_ORDER = 'asc';
public function createCollection()
{
$collection = $this->productCollectionFactory->create();
$collection->setVisibility($this->catalogProductVisibility->getVisibleInCatalogIds());
$collection = $this->_addProductAttributesAndPrices($collection)
->addStoreFilter()
->setPageSize($this->getPageSize())
->setCurPage($this->getRequest()->getParam($this->getData('page_var_name'), 1))
->setOrder($this->getSortBy(), $this->getSortOrder());
$conditions = $this->getConditions();
$conditions->collectValidatedAttributes($collection);
$this->sqlBuilder->attachConditionToCollection($collection, $conditions);
return $collection;
}
public function getSortBy()
{
if (!$this->hasData('products_sort_by')) {
$this->setData('products_sort_by', self::DEFAULT_SORT_BY);
}
return $this->getData('products_sort_by');
}
public function getSortOrder()
{
if (!$this->hasData('products_sort_order')) {
$this->setData('products_sort_order', self::DEFAULT_SORT_ORDER);
}
return $this->getData('products_sort_order');
}
}
As I mentioned earlier, we are extending the MagentoCatalogWidgetBlockProductProductsList.php
file, so we don’t need to rewrite any methods that are already written for us. However, we did rewrite the createCollection()
function and added two new functions. The createCollection()
function didn’t change much, we just set the order of the collection using setOrder()
. This function takes two parameters: a string containing what you want to sort by, and a string containing the order in which you want to sort (ascending or descending).
If you look at one of our new functions, we can see what exactly they’re doing.
public function getSortBy()
{
if (!$this->hasData('products_sort_by')) {
$this->setData('products_sort_by', self::DEFAULT_SORT_BY);
}
return $this->getData('products_sort_by');
}
The first thing this method does is check whether the user has set the widget parameter with the name products_sort_by
. If not, then it sets products_sort_by
to the default value that we defined earlier in the code. Finally, it returns the data that is associated with products_sort_by
.
Creating the Models
Now that we’ve created our widget’s layout and its block, we can create our models. These models are the files that are assigned as source_model
for our widget parameters in the layout. These files are simple, their only job being to return an array of options. You will need to create two model files for this tutorial: ModelSortBy.php
and ModelSortOrder.php
.
ModelSortBy.php
<?php
namespace ClassyLlamaSortedProductsWidgetModel;
class SortBy implements MagentoFrameworkOptionArrayInterface
{
public function toOptionArray()
{
return [
['value' => 'id', 'label' => __('Product ID')],
['value' => 'name', 'label' => __('Name')],
['value' => 'price', 'label' => __('Price')]
];
}
}
ModelSortOrder.php
<?php
namespace ClassyLlamaSortedProductsWidgetModel;
class SortOrder implements MagentoFrameworkOptionArrayInterface
{
public function toOptionArray()
{
return [
['value' => 'asc', 'label' => __('Ascending')],
['value' => 'desc', 'label' => __('Descending')]
];
}
}
These files only have one method each: toOptionArray()
. This returns an array of the possible options for your widget parameter. Each option has a value
, which is what the value of an option is, and a label
, which is what the user sees when viewing the options.
Testing
Now that we’ve created our widget, we can test it and make sure everything works as it should. We’re just going to add our widget to the homepage, so navigate through the backend to the WYSIWYG for the homepage and add a widget. As you can see we’ve successfully selected our widget type and set our options.
Now that we’ve created and configured our extension, let’s go to the homepage and see if it works!
And it does! Congratulations, you’ve just created a custom widget in Magento 2!