2

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?

3
  • Hi, did you find a solution for this? Jun 11, 2019 at 8:56
  • Unfortunately I didn't, and went with the incredibly clunky approach of having the user enter the SKU of the product they want to use.
    – Geat
    Jun 11, 2019 at 16:47
  • Yep, it's a bit of a pain to get this working. I worked my own solution, see answer below. Jun 12, 2019 at 7:44

3 Answers 3

6

For large product catalog use this component with specified searchOptions and searchUrl:

<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 name="searchOptions" xsi:type="boolean">true</item>
            <item name="searchUrl" xsi:type="url" path="catalog/product/search"/>
            <item name="filterPlaceholder" xsi:type="string" translate="true">Product Name or SKU</item>
            <item name="emptyOptionsHtml" xsi:type="string" translate="true">Start typing to find products</item>
            <item name="missingValuePlaceholder" xsi:type="string" translate="true">Product with ID: %s doesn\'t exist</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>
</field>
1
  • this is the correct answer. Apr 11, 2022 at 12:22
4

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

3
  • 3
    Note that this solution isn't good for stored with massive product catalogs. It freezes the UI. It's apparently possible to use AJAX for lookup by suppying the searchUrl parameter to the ui-select but I haven't found any examples of how this can be done.
    – micwallace
    Jan 23, 2020 at 6:08
  • @micwallace thanks for this. I haven't had this issue yet, one project I did this on had just over 8,000 products. Not massive by any means, but a decent size. Jan 23, 2020 at 13:34
  • I need product name instead of sku in select element, I tried $product->getName(), but shows blank Apr 15, 2020 at 8:23
0

Sagar Parikh

For get product name add this:

$collection->addAttributeToSelect('name');

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.