To implement a new widget you need to make changes both in Magento and ScandiPWA:

  1. Create a widget and update Magento to process it as a <widget> tag with its attributes.
  2. Update ScandiPWA to associate the widget type with a valid component.

<aside> ⚠️ You may need to make additional changes to allow the widget component to access the attributes defined in the <widget> tag as props.

</aside>

How to implement a widget on Magento 2?

Normally, to implement widget rendering, you'd have to define a widget, its block class as well as the Magento template. However, for ScandiPWA, all widgets are rendered the same way – as a single <widget> element with various attributes.

Hence, you need to:

  1. Define the widget - This is done in the etc/widget.xml file:

    <widgets xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
    xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Widget:etc/widget.xsd">
        <widget class="<YOUR VENDOR>\\<YOUR MODULE>\\Block\\Widget\\<WIDGET NAME>" id="your_widget">
            <label>Your Widget</label>
            <description>A description of your widget</description>
            <parameters>
                <parameter name="foo" xsi:type="text" required="true" visible="true" sort_order="10">
                    <label>Foo Parameter</label>
                </parameter>
            </parameters>
        </widget>
    </widgets>
    

    Configuration options:

    widget defines a new widget, rendered using the specified block class.

    <aside> ➡️ In this tutorial, you do not need to create the block class. You only need to ensure that it is unique.

    </aside>

    <aside> ❗ To make the foo attribute available in the widget component in ScandiPWA, you need to make sure it’s allowed. (What can be accessed in the widget component?)

    </aside>

  2. Update GraphQL filter

    Normally, to implement widget rendering, you'd have to define a block class as well as a Magento template. However, for ScandiPWA, all widgets are rendered the same way – as a single <widget> element with various attributes.

    This rendering logic is already implemented for widgets in ScandiPWA. All you need to do is configure it to render your widget as well. You can do this by adding an argument to ScandiPWA\\CmsGraphQl\\Model\\Template\\VirtualFilter for your widget in the /etc/graphql/di.xml file:

    <?xml version="1.0"?>
    
    <config xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
            xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
        <virtualType name="ScandiPWA\\CmsGraphQl\\Model\\Template\\VirtualFilter" type="ScandiPWA\\CmsGraphQl\\Model\\Template\\Filter">
            <arguments>
                <argument name="availableFilters" xsi:type="array">
                    <item name="<TYPE OF WIDGET>" xsi:type="string"><YOURVENDOR>\\<YOURMODULE>\\Block\\Widget\\<WIDGETNAME></item>
                </argument>
            </arguments>
        </virtualType>
    </config>
    

    You only need to modify the item tag:

    This code is responsible for making the block you defined in the widget available when a GraphQL request is made. This block process a tag <widget> with the attributes defined in the widget.

    For example, a processed widget with the previous example would look like this:

    <widget type="<TYPE OF WIDGET>" foo="data">
    

<aside> ➡️ You could also create a block and template for the widget (how to create a block?). However, be sure to meet the requirements for the ScandiPWA widget component.

</aside>

ScandiPWA will only use widget components on pages of type CMS_PAGE. Therefore, widgets will not apply to product pages, category pages, etc. To confirm the page type, make a GraphQL query for the URL and send it to <your_host>/graphql(How to work with requests?).

query{
    urlResolver(url:"<YOUR URL>"){
        type
    }
}

An example of a query and its response:

query{
    urlResolver(url:"/"){
        type
    }
}
{
	"data": {
		"urlResolver": {
			"type": "CMS_PAGE"
		}
	}
}

How to implement a widget component in ScandiPWA?

To implement a widget component in ScandiPWA you need to determine what component you want to render and the widget type it must implement (how to create a component?).

For example, when the type of widget is Slider, the HomeSlider component is rendered by the WidgetFactory component(how to implement a widget in Magento?).

The recommended way of adding a widget component is through the use of a plugin to include the widget in the renderMap variable from the WidgetFactory component. (learn more about how to create a plugin)

For that you just need to create a file src/plugin/WidgetFactory.plugin.js with:


export const YourWidgetComponent = lazy(() => import(
    /* webpackMode: "lazy", webpackChunkName: "<EXTENSION NAME>" */
    '../route/YourWidgetComponent'
    ));

export const WIDGET_TYPE = "<YOUR WIDGET TYPE>";

const renderMap = (originalMember) => ({
   ...originalMember,
   [WIDGET_TYPE]: {
       component: YourWidgetComponent
   }
});

export default {
	"Component/WidgetFactory/Component": {
		'member-property': {
			renderMap
		}
	}
}

This will include your component in the renderMap variable of the WidgetFactory component, which is responsible for storing the components associated with a type.

<aside> ➡️ A widget is a component, remember to follow the best practices:

How to work with components?

Often, you'll need to request additional data for your widget:

How to work with requests?

</aside>

What can be accessed in the widget component?

By default, ScandiPWA does not allow you to access all attributes defined in the <widget> tag.

Here is a list of attributes that are allowed and available to be accessed:

In the example:

<widget type="test" foo="foo text" title="widget title"></widget>

In this example, you can only access the type and title attribute values in the widget component as props. To access other attributes, such as foo, you need to allow the WidgetFactory component to pass it as a prop to your custom widget component.

Generally, this is done by using a plugin to update the renderContent method of the WidgetFactory component.

WidgetFactory