3

As the title suggests, I'm trying to add a listing component for a custom model on the catalog/product/edit page. But I'm currently stuck, although I have the feeling I'm almost there!

The problem is that I see the headers of the grid, but the message 'We couldn't find any records' remains and the spinner is not going away (giving me the impression that I missed some configuration somewhere).

My setup:

I've added a modifier to the catalog product form modifier pool using etc/adminhtml/di.xml like so:

<!--
    We add something to the Catalog Product pool like so:
-->
<virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool"
             type="Magento\Ui\DataProvider\Modifier\Pool">
    <arguments>
        <argument name="modifiers" xsi:type="array">
            <!--
                Add a UI component
            -->
            <item name="attachments" xsi:type="array">
                <item name="class" xsi:type="string">Vendor\ProductAttachment\Ui\DataProvider\Product\Form\Modifier\ProductAttachment</item>
                <item name="sortOrder" xsi:type="number">115</item>
            </item>
        </argument>
    </arguments>
</virtualType>

(it's a product attachment module)

The is ProductAttachment.php:modifyMeta():

/**
 * Modify the metadata
 *
 * @param array $meta
 * @return array
 */
public function modifyMeta(array $meta)
{
    $meta = array_replace_recursive(
        $meta,
        [
            // Key = group name
            'product_attachment' => [
                // Array with children:
                'children' => [
                    // Include button that opens grid in new modal:
                    'product_attachment' => [
                        // Set the children:
                        'children' => [
                            // The button:
                            'button_set' => [
                                'arguments' => [
                                    'data' => [
                                        'config' => [
                                            'formElement' => 'container',
                                            'componentType' => 'container',
                                            'label' => false,
                                            'content' => __('Manage Product Attachments'),
                                            'template' => 'ui/form/components/complex'
                                        ]
                                    ]
                                ],
                                'children' => [
                                    'button_product_attachment' => [
                                        'arguments' => [
                                            'data' => [
                                                'config' => [
                                                    'formElement' => 'container',
                                                    'componentType' => 'container',
                                                    'component' => 'Magento_Ui/js/form/components/button',
                                                    'actions' => [
                                                        [
                                                            'targetName' => 'product_form.product_form.product_attachment.product_attachment.modal',
                                                            'actionName' => 'openModal'
                                                        ],
                                                        [
                                                            'targetName' => 'product_form.product_form.product_attachment.product_attachment.modal.listing',
                                                            'actionName' => 'render'
                                                        ]
                                                    ],
                                                    'title' => __('Show Product Attachments'),
                                                    'provider' => null
                                                ]
                                            ]
                                        ]
                                    ]
                                ]
                            ],
                            // The modal:
                            'modal' => [
                                'arguments' => [
                                    'data' => [
                                        'config' => [
                                            'componentType' => 'modal',
                                            'dataScope' => '',
                                            'options' => [
                                                'title' => __('Manage Product Attachments Modal'),
                                                'buttons' => [
                                                    // Cancel button:
                                                    [
                                                        'text' => __('Cancel'),
                                                        'actions' => [
                                                            'closeModal'
                                                        ]
                                                    ],
                                                    // Add button:
                                                    [
                                                        'text' => __('Link Attachments to Product'),
                                                        'class' => 'action-primary',
                                                        'actions' => [
                                                            // Save and close:
                                                            [
                                                                'targetName' => 'index = product_attachment_listing',
                                                                'actionName' => 'save'
                                                            ],
                                                            'closeModal'
                                                        ]
                                                    ]
                                                ]
                                            ]
                                        ]
                                    ]
                                ],
                                // This is where the grid / listing is rendered:
                                'children' => [
                                    'listing' => [
                                        'arguments' => [
                                            'data' => [
                                                'config' => [
                                                    'componentType' => 'insertListing',
                                                    'dataScope' => 'product_attachment_listing',
                                                    'ns' => 'product_attachment_listing',       // This is the namespace
                                                    'render_url' => $this->urlBuilder->getUrl('mui/index/render', []), // Explain
                                                ]
                                            ]
                                        ]
                                    ]
                                ]
                            ]
                        ],
                        // Configuration:
                        'arguments' => [
                            'data' => [
                                'config' => [
                                    'additionalClasses' => 'admin__fieldset-section',
                                    'label' => __('Manage Product Attachments'),
                                    'collapsible' => false,
                                    'componentType' => 'fieldset'
                                ]
                            ]
                        ]
                    ],
                ],
                // Arguments, information etc:
                'arguments' => [
                    'data' => [
                        'config' => [
                            'label' => __('Product Attachments'),
                            'collapsible' => true,
                            'componentType' => 'fieldset',
                            'dataScope' => ''
                        ]
                    ]
                ]
            ]
        ]
    );

    return $meta;
}

The above code works, since it adds an extra row below the product form, nicely with a button that when I click it opens a modal.

As you might have noticed, I point to product_attachment_listing. So let's take a look at that:

<?xml version="1.0" encoding="utf-8"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="context" xsi:type="configurableObject">
        <argument name="class" xsi:type="string">Magento\Framework\View\Element\UiComponent\Context</argument>
        <argument name="namespace" xsi:type="string">product_attachment_listing</argument>
    </argument>
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="provider" xsi:type="string">product_attachment_listing.product_attachment_listing_data_source</item>
            </item>
            <item name="deps" xsi:type="string">product_attachment_listing.product_attachment_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">product_attachment_columns</item>
    </argument>
    <!--
        Set the datasource:
    -->
    <dataSource name="product_attachment_listing_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider</argument>
            <argument name="name" xsi:type="string">product_attachment_listing_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
                    <item name="update_url" xsi:type="url" path="mui/index/render"/>
                    <item name="storageConfig" xsi:type="array">
                        <item name="indexField" xsi:type="string">id</item>
                    </item>
                </item>
            </argument>
        </argument>
    </dataSource>
    <!--
        The columns:
    -->
    <columns name="product_attachment_columns">
        <column name="id">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string" translate="true">ID</item>
                </item>
            </argument>
        </column>
        <column name="filename">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string" translate="true">File Name</item>
                </item>
            </argument>
        </column>
    </columns>
</listing>

The data source is defined in etc/di.xml:

<!--
    Add Product Attachments collection is a data provider:
-->
<type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
    <arguments>
        <argument name="collections" xsi:type="array">
            <item name="product_attachment_listing_data_source" xsi:type="string">Vendor\ProductAttachment\Model\ResourceModel\Attachment\Grid\Collection</item>
        </argument>
    </arguments>
</type>
<!--
    Add parameters for the constructor of \Vendor\ProductAttachment\Model\ResourceModel\Attachment\Grid\Collection
-->
<type name="Vendor\ProductAttachment\Model\ResourceModel\Attachment\Grid\Collection">
    <arguments>
        <argument name="mainTable" xsi:type="string">productattachment</argument>
        <argument name="eventPrefix" xsi:type="string">productattachment_block_grid_collection</argument>
        <argument name="eventObject" xsi:type="string">productattachment_grid_collection</argument>
        <argument name="resourceModel" xsi:type="string">Vendor\ProductAttachment\Model\ResourceModel\Attachment</argument>
    </arguments>
</type>

And to show you the Grid Collection, it's pretty straight-forward:

namespace Vendor\ProductAttachment\Model\ResourceModel\Attachment\Grid;

use Magento\Framework\Api\Search\AggregationInterface;
use Magento\Framework\Api\Search\SearchResultInterface;
use Vendor\ProductAttachment\Model\ResourceModel\Attachment\Collection as AttachmentCollection;
use Magento\Framework\Api\SearchResultsInterface;

/**
 * Class Collection
 * This is the collection that is used in grids
 * @package Vendor\ProductAttachment\Model\ResourceModel\Attachment\Grid
 */
class Collection extends AttachmentCollection implements SearchResultInterface
{
    /**
     * Collection constructor.
     * @param \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory
     * @param \Psr\Log\LoggerInterface $logger
     * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy
     * @param \Magento\Framework\Event\ManagerInterface $eventManager
     * @param \Magento\Framework\DB\Adapter\AdapterInterface $mainTable
     * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb $eventPrefix
     * @param $eventObject
     * @param $resourceModel
     * @param string $model
     * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection
     * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb|null $resource
     */
    public function __construct(
        \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory,
        \Psr\Log\LoggerInterface $logger,
        \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy,
        \Magento\Framework\Event\ManagerInterface $eventManager,
        // The following 4 parameters are set in etc/di.xml
        $mainTable,
        $eventPrefix,
        $eventObject,
        $resourceModel,
        $model = 'Magento\Framework\View\Element\UiComponent\DataProvider\Document',
        \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
        \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null
    ) {
        parent::__construct(
            $entityFactory,
            $logger,
            $fetchStrategy,
            $eventManager,
            $connection,
            $resource
        );

        $this->_eventPrefix = $eventPrefix;
        $this->_eventObject = $eventObject;
        $this->_init($model, $resourceModel);
        $this->setMainTable($mainTable);
    }

    /**
     * @var AggregationInterface
     */
    protected $aggregations;

    // The following methods are sort of stubs:

    /**
     * @return AggregationInterface
     */
    public function getAggregations()
    {
        return $this->aggregations;
    }

    /**
     * @param AggregationInterface $aggregations
     */
    public function setAggregations($aggregations)
    {
        $this->aggregations = $aggregations;
    }

    /**
     * @return null
     */
    public function getSearchCriteria()
    {
        return null;
    }

    /**
     * @param array|null $items
     * @return $this
     */
    public function setItems(array $items = null)
    {
        return $this;
    }

    /**
     * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
     * @return $this
     */
    public function setSearchCriteria(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria)
    {
        return $this;
    }

    /**
     * @return int
     */
    public function getTotalCount()
    {
        return $this->getSize();
    }

    /**
     * @param int $totalCount
     * @return $this
     */
    public function setTotalCount($totalCount)
    {
        return $this;
    }
}

I think I've now shown you most of the code that I use to create a product listing in a modal on a product edit-page. Like I said, the modal pops out, and I see that a grid is loaded / generated (because I can see the headers for ID and filename), but then it just seems to stop working. The spinner never leaves and the grid is never populated.

I also don't have any JS errors in my console.

For what's it worth: when the modal is opened, a XHR-request is done to http://www.vendor.com/admin/mui/index/render/?namespace=product_attachment_listing&isAjax=true

The response of that request is as follows:

<!--
/**
 * Copyright &copy; 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<div class="admin__data-grid-outer-wrap" data-bind="scope: 'product_attachment_listing.product_attachment_listing'">
    <div data-role="spinner"
         data-component="product_attachment_listing.product_attachment_listing.product_attachment_columns"
         class="admin__data-grid-loading-mask">
        <div class="spinner">
            <span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>
        </div>
    </div>
    <!-- ko template: getTemplate() --><!-- /ko -->
    <script type="text/x-magento-init">{
        "*": {
            "Magento_Ui/js/core/app": {
                "types": {
                    "dataSource": [],
                    "text": {
                        "component": "Magento_Ui\/js\/form\/element\/text",
                        "extends": "product_attachment_listing"
                    },
                    "column.text": {
                        "component": "Magento_Ui\/js\/form\/element\/text",
                        "extends": "product_attachment_listing"
                    },
                    "columns": {
                        "extends": "product_attachment_listing"
                    },
                    "listing": {
                        "config": {
                            "provider": "product_attachment_listing.product_attachment_listing_data_source"
                        },
                        "deps": "product_attachment_listing.product_attachment_listing_data_source",
                        "extends": "product_attachment_listing"
                    },
                    "html_content": {
                        "component": "Magento_Ui\/js\/form\/components\/html",
                        "extends": "product_attachment_listing"
                    }
                },
                "components": {
                    "product_attachment_listing": {
                        "children": {
                            "product_attachment_listing": {
                                "type": "product_attachment_listing",
                                "name": "product_attachment_listing",
                                "children": {
                                    "product_attachment_columns": {
                                        "type": "columns",
                                        "name": "product_attachment_columns",
                                        "children": {
                                            "id": {
                                                "type": "column.text",
                                                "name": "id",
                                                "config": {
                                                    "component": "Magento_Ui\/js\/grid\/columns\/column",
                                                    "componentType": "column",
                                                    "dataType": "text",
                                                    "label": "ID"
                                                }
                                            },
                                            "filename": {
                                                "type": "column.text",
                                                "name": "filename",
                                                "config": {
                                                    "component": "Magento_Ui\/js\/grid\/columns\/column",
                                                    "componentType": "column",
                                                    "dataType": "text",
                                                    "label": "File Name"
                                                }
                                            }
                                        },
                                        "config": {
                                            "component": "Magento_Ui\/js\/grid\/listing",
                                            "componentType": "columns",
                                            "storageConfig": {
                                                "provider": "ns = ${ $.ns }, index = bookmarks",
                                                "namespace": "current"
                                            },
                                            "childDefaults": {
                                                "storageConfig": {
                                                    "provider": "ns = ${ $.ns }, index = bookmarks",
                                                    "root": "columns.${ $.index }",
                                                    "namespace": "current.${ $.storageConfig.root }"
                                                }
                                            }
                                        }
                                    }
                                },
                                "config": {
                                    "component": "uiComponent"
                                }
                            },
                            "product_attachment_listing_data_source": {
                                "type": "dataSource",
                                "name": "product_attachment_listing_data_source",
                                "dataScope": "product_attachment_listing",
                                "config": {
                                    "data": {
                                        "items": [
                                            {
                                                "id_field_name": "id",
                                                "id": "1",
                                                "filename": "\/attachments\/2\/0\/2015-10-20t06-48_transaction_747982541978651-1643286.pdf",
                                                "product_id": "26",
                                                "orig_data": null
                                            }
                                        ],
                                        "totalRecords": 1
                                    },
                                    "component": "Magento_Ui\/js\/grid\/provider",
                                    "update_url": "http:\/\/www.vendor.com\/admin\/mui\/index\/render\/",
                                    "storageConfig": {
                                        "indexField": "id"
                                    },
                                    "params": {
                                        "namespace": "product_attachment_listing"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }</script>
</div>

If anyone could help me with this I would be really thankful. I've been struggling with this for 2-3 days now ...

3

Why is it that always when I post a question on Stack Exchange, I find out the answer myself a couple of moments later? Is it some sort of digital rubber ducking? Anyway...

My mistake was in product_attachment_listing.xml. I stated:

<argument name="data" xsi:type="array">
    <item name="js_config" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="provider" xsi:type="string">product_attachment_listing.product_attachment_listing_data_source</item>
        </item>
        <item name="deps" xsi:type="string">product_attachment_listing.product_attachment_listing_data_source</item>
    </item>
    <item name="spinner" xsi:type="string">product_attachment_columns</item>
</argument>

But this needed to be:

<argument name="data" xsi:type="array">
    <item name="js_config" xsi:type="array">
        <item name="provider" xsi:type="string">product_attachment_listing.product_attachment_listing_data_source</item>
        <item name="deps" xsi:type="string">product_attachment_listing.product_attachment_listing_data_source</item>
    </item>
    <item name="spinner" xsi:type="string">product_attachment_columns</item>
</argument>

I guess I made the mistake because I followed the official documentation (http://devdocs.magento.com/guides/v2.1/ui-components/ui-listing-grid.html) :-/

Hope this saves somebody some days smashing their head on their keyboard.

  • 2
    The reason why you get the answer yourself a moment later is because in order to make your question understandable to others you are forced to review/understand your work. It is pretty much the same when we ask a colleague to help us with a task, the colleague comes, sit down, we explain the task, the problem and immediately after we see the solution. – diazwatson Jul 31 '18 at 17:38
  • 1
    Btw I'm going to review your post as soon as I get home and hope it saves me more days of smashing my head against the keyboard :))) – diazwatson Jul 31 '18 at 17:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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