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 © 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 …
Best Answer
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:But this needed to be:
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.