Adding viewModels to widgets

Or a journey through too many XML Schema files

We wanted to add the possibility to add a viewModel to a widget. We thought about it. There are two possibilities:

  1. Add it to an instance, e.g. one added in the backend or one declared via {{widget ...}}
  2. Add it to a widget type (e.g. by adding it to the widget.xml)

Widget Instance

The problems we had in mind and wanted to solve were like having a category or product list, and we wanted to move the loading of the products and/or category to a viewModel instead of adding all the business logic to the widget block. But especially with the curly brace syntac {{widget ...}} it is hard to add a parameter for the view model. And let’s be honest. In (almost?) all cases, we need the same viewModel to load data, products, etc. And adding the parameters, for the products or category already works fine.
So instead of the widget instance, we decided to add it directly to widget.xml

Widget Type

So the way to go was clear:
Add the possibility to have an <arguments> node in the widget.xml which gets copied in the merged layout. The tasks looked easy and I thought I could get it done in two days on the FireGento Hackathon in Bremen this weekend. But as you might guess, when I’m writing a blog post I don’t have much code to share :-)

Implementation and problems

When a widget is saved, Magento2 already creates layout xml for the widget block. This happens in \Magento\Widget\Model\Widget\Instance::generateLayoutUpdateXml.
So what we need to do?

  1. Add our <arguments> to widget.xml
    2.Make it accessable in \Magento\Widget\Model\Widget\Instance
  2. Add it to the generated layout.xml

Add it to widget.xml

It took a while to understand how everything works and I think I understand it now, which is the reason I stopped working on the task.
The widget.xml is validated agains Magento/Widget/etc/widget_file.xsd. So we need to change the widget_file.xsd to allow <arguments>. It took me a few hours to understand, that I need to edit widget_file.xsd instead of the widget.xsd, but after the penny dropped it went well. Unfortunately the XML Schema defines its own boolean, number, string, etc. therefore we have to rename all the types. Problem understood and fixed.

Open a widget

(I’m not 100% sure about this, I tried too much and didn’t test it deeply)
When you open a widget in the backend, the xml is validated against the widget.xsd, so we have to change this too. The structure is nearly the same, so one can copy the changes from widget_file.xsd.

We now can change a widget.xml and add the arguments, that is great! That looks like that:

<widgets ...>
    <widget ...>
        <arguments>
            <argument name="string" xsi:type="stringArgument">String</argument>
        </arguments>
    </widget>
</widgets>

To make the argument available during the \Magento\Widget\Model\Widget\Instance save, we have to fix the PHP-Validation of the XML and add our new node in \Magento\Widget\Model\Config\Converter::convert:

switch ($widgetSubNode->nodeName) {
		// ...
    case 'arguments':
        $widgetArray['arguments'] = $source->saveXML($widgetSubNode);
        break;
		// ...

Now we have finally the arguments in our widget instance and can add it to the \Magento\Widget\Model\Widget\Instance::generateLayoutUpdateXml!

$xml .= '<block class="' . $this->getType() . '" name="' . $hash . '"' . $template . '>';

	// NEW CODE
	if(isset($this->_widgetConfigXml['arguments'])) {
		$xml .= $this->_widgetConfigXml['arguments'];
	}

Cool! It should work now.

Add a widget to the homepage, hit F5 and... BAM.

1 exception(s):
Exception #0 (Magento\Framework\Config\Dom\ValidationException): Element 'argument', attribute '{http://www.w3.org/2001/XMLSchema-instance}type': The QName value 'stringArgument' of the xsi:type attribute does not resolve to a type definition.
Line: 689

Element 'argument': The type definition is abstract.
Line: 689

The layout which is generated by the method is added to the global layout (what else?! :D) and again validated, this time against lib/internal/Magento/Framework/View/Layout/etc/layout_merged.xsd.

Unfortunately due to our renaming it doesn’t work as expected.

What we need is to rewrite the types to the layout types. I started the blogpost without a plan how to continue, but now I see a way, we str_replace the Argument in the name.

Horrible idea, but would work.

If one day someone wants to add viewModels again to widgets, this blogpost might help you. Good luck with it!