Magento – “Product chooser” field for UI Component form

fieldformsmagento2uicomponent

I've searched everywhere but I'm unable to find a field that can be inserted into a UI Component form that allows the user to select a product.

It can be achieved easily for categories with the following code:

<field name="pick_a_category">
  <argument name="data" xsi:type="array">
    <item name="options" xsi:type="object">Magento\Catalog\Ui\Component\Product\Form\Categories\Options</item>
      <item name="config" xsi:type="array">
      ...
      </item>
    </item>
  </argument>
</field>

Is there an equivalent approach for products?

Best Answer

I was working on this myself, and came up with the following workaround.

I've reused a portion of the parent category selector on the category edit form: Magento_Catalog/view/adminhtml/ui_component/new_category_form.xml (field "parent").

In your ui_component XML file:

<field name="products" component="Magento_Ui/js/form/element/ui-select" sortOrder="10" formElement="select">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="filterOptions" xsi:type="boolean">true</item>
            <item name="multiple" xsi:type="boolean">true</item>
            <item name="showCheckbox" xsi:type="boolean">true</item>
            <item name="disableLabel" xsi:type="boolean">true</item>
            <item name="levelsVisibility" xsi:type="number">1</item>
        </item>
    </argument>
    <settings>
        <required>false</required>
        <validation>
            <rule name="required-entry" xsi:type="boolean">false</rule>
        </validation>
        <elementTmpl>ui/grid/filters/elements/ui-select</elementTmpl>
        <label translate="true">Products</label>
        <dataScope>data.products</dataScope>
        <componentType>field</componentType>
        <listens>
            <link name="${ $.namespace }.${ $.namespace }:responseData">setParsed</link>
        </listens>
    </settings>
    <formElements>
        <select>
            <settings>
                <options class="Vendor\Module\Model\Source\Products"/>
            </settings>
        </select>
    </formElements>
</field>

In your Vendor\Module\Model\Source\Products class:

namespace Vendor\Module\Model\Source;

class Products implements \Magento\Framework\Option\ArrayInterface {

    protected $_productCollectionFactory;

    public function __construct(
        \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory
    ) {
        $this->_productCollectionFactory = $productCollectionFactory;
    }

    public function toOptionArray()
    {
        $collection = $this->_productCollectionFactory->create();

        $options = [];

        foreach ($collection as $product) {
            $options[] = ['label' => $product->getSku(), 'value' => $product->getId()];
        }

        return $options;
    }

}

In your Save controller, check the post object for the presence of the parameter:

$data = $this->getRequest()->getPostValue();
if($data['data']['products']) {
    $data['products'] = implode(',', $data['data']['products']);
}

Lastly, in your DataProvider, ensure the entity's current data is loaded on page load:

public function getData()
{
    if (isset($this->loadedData)) {
        return $this->loadedData;
    }
    $items = $this->collection->getItems();
    foreach ($items as $model) {

        $this->loadedData[$model->getId()] = $model->getData();
        $this->loadedData[$model->getId()]["data"]["products"] = ["123","456"];

    }
    // ..... REST OF method
}

The ["123","456"] array is where you stipulate the product ID's of course. In my case, my products column in the entity's table is already a comma separated string (check the Save controller above where I do this). I can simply explode this into an array. I load the entity by the ID and grab whatever the current value is, such as below:

$entity = $this->_entityFactory->create()->load($model->getId());
$entityProds = $entity->getProducts();
$entityProdArr = explode(",",$entityProds);

I can then assign that as the products data variable:

$this->loadedData[$model->getId()]["data"]["products"] = $entityProdArr;

Here's how it looks and works. NB: the search works based on the label we stipulated in the Vendor\Module\Model\Source\Products toOptionArray method:

enter image description here