Magento 2.3 – UI Component Admin Grid Stuck Loading

admingridmagento2.3uicomponent

This is the first CRUD module (called Sinapsis_Store, for code understanding) I am trying to write in Magento2. I have followed different ways for it, going step by step, trying to understand every new piece of code in module. This project is Magento 2.3.0 CE

I was able to get module Grid work using Blocks approach (very similar to Magento1). Next step was the Edit form, for that I followed UI Components approach & got it working in a short time, so I came back to the Grid part, but surely I am missing something (or lot of things). The grid page loads with no errors, but the page is stuck "Loading..."

This is my component for Edit form (very simple, just with one single field right now: name)

app/code/Sinapsis/Store/view/adminhtml/ui_component/store_form.xml

<?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">store_form.store_form_data_source</item>
            <item name="deps" xsi:type="string">store_form.store_form_data_source</item>
        </item>
        <item name="label" xsi:type="string" translate="true">Store Information</item>
        <item name="config" xsi:type="array">
            <item name="dataScope" xsi:type="string">data</item>
            <item name="namespace" xsi:type="string">store_form</item>
        </item>
        <item name="template" xsi:type="string">templates/form/collapsible</item>
        <item name="buttons" xsi:type="array">
            <item name="back" xsi:type="string">Sinapsis\Store\Block\Adminhtml\Edit\BackButton</item>
            <item name="delete" xsi:type="string">Sinapsis\Store\Block\Adminhtml\Edit\DeleteButton</item>
            <item name="save" xsi:type="string">Sinapsis\Store\Block\Adminhtml\Edit\SaveButton</item>
            <item name="save_and_continue" xsi:type="string">Sinapsis\Store\Block\Adminhtml\Edit\SaveAndContinueButton</item>
        </item>
    </argument>
    <dataSource name="store_form_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Sinapsis\Store\Model\DataProvider</argument>
            <argument name="name" xsi:type="string">store_form_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">store_id</argument>
            <argument name="requestFieldName" xsi:type="string">store_id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="submit_url" path="*/*/save" xsi:type="url"/>
                </item>
            </argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            </item>
        </argument>
    </dataSource>
    <fieldset name="store_details">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="collapsible" xsi:type="boolean">false</item>
                <item name="label" xsi:type="string" translate="true">Store Details</item>
                <item name="sortOrder" xsi:type="number">20</item>
            </item>
        </argument>
        <field name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Store Name</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">store</item>
                    <item name="dataScope" xsi:type="string">name</item>
                </item>
            </argument>
        </field>
    </fieldset>
</form>

The dataProvider class for this is very simple

app/code/Sinapsis/Store/Model/DataProvider.php

<?php
namespace Sinapsis\Store\Model;
use Sinapsis\Store\Model\ResourceModel\Store\CollectionFactory;

class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
{

    protected $_loadedData;

    /**
     * @param string $name
     * @param string $primaryFieldName
     * @param string $requestFieldName
     * @param CollectionFactory $employeeCollectionFactory
     * @param array $meta
     * @param array $data
     */
    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        CollectionFactory $collectionFactory,
        array $meta = [],
        array $data = []
    ) {
        $this->collection = $collectionFactory->create();
        parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
    }

    public function getData()
    {
        if (isset($this->_loadedData)) {
            return $this->_loadedData;
        }
        $items = $this->collection->getItems();
        foreach ($items as $store) {
            $this->_loadedData[$store->getId()] = $store->getData();
        }
        return $this->_loadedData;
    }
}

And here is the last try for Grid component… I have tried several ways for the dataProvider class, but none of them worked

app/code/Sinapsis/Store/view/adminhtml/ui_component/store_grid.xml

<?xml version="1.0" ?>
<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">store_grid</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">store_grid.store_grid_listing_data_source</item>
            </item>
            <item name="deps" xsi:type="string">store_grid.store_grid_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">store_grid_columns</item>
        <item name="buttons" xsi:type="array">
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" translate="true" xsi:type="string">Add new store</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">*/*/create</item>
            </item>
        </item>
    </argument>
    <dataSource name="store_grid_listing_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Sinapsis\Store\Model\DataProvider</argument>
            <argument name="name" xsi:type="string">store_grid_listing_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">store_id</argument>
            <argument name="requestFieldName" xsi:type="string">store_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">store_id</item>
                    </item>
                </item>
            </argument>
        </argument>
    </dataSource>

    <listingToolbar name="listing_top">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="sticky" xsi:type="boolean">true</item>
            </item>
        </argument>
        <bookmark name="bookmarks"/>
        <columnsControls name="columns_controls"/>
        <filters name="listing_filters"/>
        <paging name="listing_paging"/>
    </listingToolbar>
    <columns name="store_grid_columns">
        <selectionsColumn name="ids">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="indexField" xsi:type="string">store_id</item>
                </item>
            </argument>
        </selectionsColumn>
        <column name="store_id">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="label" translate="true" xsi:type="string">ID</item>
                </item>
            </argument>
        </column>
        <column name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="label" translate="true" xsi:type="string">Store Name</item>
                </item>
            </argument>
        </column>
    </columns>
</listing>

Reading this https://devdocs.magento.com/guides/v2.3/ui_comp_guide/concepts/ui_comp_data_source.html, and as you can see in the code my last try is using same class as the one I use in Edit form, as that class does extend \Magento\Ui\DataProvider\AbstractDataProvider so I added a getDataSourceData() method

To make the data available in javascript, add a getDataSourceData()
method to the UI component’s PHP class and return
$this->getContext()->getDataProvider()->getData(). This will output
the result of the data provider’s getData() method into the JSON that
is sent to the browser along with the rest of the UI component’s
configuration

But again, same result… grid is stuck "Loading..."

So, any tips? Maybe the problem is not in dataProvider, but in another piece of XML code in the component?

Best Answer

I finally solved this following Marius♦ advice

I created a module with same requirements with https://github.com/UltimateModuleCreator/umc & compared related files

XML format is not the same (this new version is kindly more human readable), so it can be hard to find the differences, but I paste the working app/code/Sinapsis/Store/view/adminhtml/ui_component/store_grid.xml

<?xml version="1.0"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">sinapsis_store_manager_grid.sinapsis_store_manager_grid_data_source</item>
        </item>
    </argument>
    <settings>
        <buttons>
            <button name="add">
                <url path="*/*/new"/>
                <class>primary</class>
                <label translate="true">Add New Store</label>
            </button>
        </buttons>
        <spinner>sinapsis_store_store_columns</spinner>
        <deps>
            <dep>sinapsis_store_manager_grid.sinapsis_store_manager_grid_data_source</dep>
        </deps>
    </settings>
    <dataSource name="sinapsis_store_manager_grid_data_source" component="Magento_Ui/js/grid/provider">
        <settings>
            <storageConfig>
                <param name="indexField" xsi:type="string">store_id</param>
            </storageConfig>
            <updateUrl path="mui/index/render"/>
        </settings>
        <dataProvider class="Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider" name="sinapsis_store_manager_grid_data_source">
            <settings>
                <requestFieldName>store_id</requestFieldName>
                <primaryFieldName>store_id</primaryFieldName>
            </settings>
        </dataProvider>
    </dataSource>
    <listingToolbar name="listing_top">
        <settings>
            <sticky>true</sticky>
        </settings>
        <bookmark name="bookmarks"/>
        <columnsControls name="columns_controls"/>
        <filterSearch name="fulltext"/>
        <filters name="listing_filters">
            <argument name="data" xsi:type="array">
                <item name="observers" xsi:type="array">
                    <item name="column" xsi:type="string">column</item>
                </item>
            </argument>
            <settings>
                <templates>
                    <filters>
                        <select>
                            <param name="template" xsi:type="string">ui/grid/filters/elements/ui-select</param>
                            <param name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</param>
                        </select>
                    </filters>
                </templates>
            </settings>
        </filters>
        <massaction name="listing_massaction">
            <action name="delete">
                <settings>
                    <confirm>
                        <message translate="true">Are you sure you wan't to delete selected Stores?</message>
                        <title translate="true">Delete Store</title>
                    </confirm>
                    <url path="sinapsis_store/store/massDelete"/>
                    <type>delete</type>
                    <label translate="true">Delete</label>
                </settings>
            </action>
            <action name="edit">
                <settings>
                    <callback>
                        <target>editSelected</target>
                        <provider>sinapsis_store_manager_grid.sinapsis_store_manager_grid.sinapsis_store_store_columns_editor</provider>
                    </callback>
                    <type>edit</type>
                    <label translate="true">Edit</label>
                </settings>
            </action>
        </massaction>
        <paging name="listing_paging"/>
    </listingToolbar>
    <columns name="sinapsis_store_store_columns">
        <settings>
            <editorConfig>
                <param name="clientConfig" xsi:type="array">
                    <item name="saveUrl" xsi:type="url" path="sinapsis_store/store/inlineEdit"/>
                    <item name="validateBeforeSave" xsi:type="boolean">false</item>
                </param>
                <param name="indexField" xsi:type="string">store_id</param>
                <param name="enabled" xsi:type="boolean">true</param>
                <param name="selectProvider" xsi:type="string">sinapsis_store_manager_grid.sinapsis_store_manager_grid.sinapsis_store_store_columns.ids</param>
            </editorConfig>
            <childDefaults>
                <param name="fieldAction" xsi:type="array">
                    <item name="provider" xsi:type="string">sinapsis_store_manager_grid.sinapsis_store_manager_grid.sinapsis_store_store_columns_editor</item>
                    <item name="target" xsi:type="string">startEdit</item>
                    <item name="params" xsi:type="array">
                        <item name="0" xsi:type="string">${ $.$data.rowIndex }</item>
                        <item name="1" xsi:type="boolean">true</item>
                    </item>
                </param>
            </childDefaults>
        </settings>
        <selectionsColumn name="ids">
            <settings>
                <indexField>store_id</indexField>
                <resizeEnabled>false</resizeEnabled>
                <resizeDefaultWidth>55</resizeDefaultWidth>
            </settings>
        </selectionsColumn>
        <column name="store_id">
            <settings>
                <filter>textRange</filter>
                <label translate="true">ID</label>
                <sorting>asc</sorting>
            </settings>
        </column>
        <column name="name">
            <settings>
                <filter>text</filter>
                <editor>
                    <validation>
                        <rule name="required-entry" xsi:type="boolean">true</rule>
                    </validation>
                    <editorType>text</editorType>
                </editor>
                <label translate="true">Store name</label>
            </settings>
        </column>
        <actionsColumn name="actions" class="Sinapsis\Store\Ui\Component\Listing\Column\StoreActions">
            <settings>
                <indexField>store_id</indexField>
                <resizeEnabled>false</resizeEnabled>
            </settings>
        </actionsColumn>
    </columns>
</listing>

This change required to create & modify some other files, of course, but answering the question, the key would be: trying to use the same class as DataProvider for both a grid & edit form UI components was a little barbarity. I paste here some part (as you'll see in the class, that required to create a new class for CollectionProviderInterface, etc etc...) of working code

The DataProvider referenced in above XML was now defined in app/code/Sinapsis/Store/etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
        <arguments>
            <argument name="collections" xsi:type="array">
                <item name="sinapsis_store_manager_grid_data_source" xsi:type="string">SinapsisStoreStoreGridCollection</item>
            </argument>
        </arguments>
    </type>
    <virtualType name="SinapsisStoreStoreGridCollection" type="Sinapsis\Store\Model\ResourceModel\Store\Collection">
        <arguments>
            <argument name="model" xsi:type="string">Magento\Framework\View\Element\UiComponent\DataProvider\Document</argument>
            <argument name="resourceModel" xsi:type="string">Sinapsis\Store\Model\ResourceModel\Store</argument>
            <argument name="idFieldName" xsi:type="string">store_id</argument>
            <argument name="eventPrefix" xsi:type="string">sinapsis_store_store_collection</argument>
            <argument name="eventObject" xsi:type="string">store_collection</argument>
        </arguments>
    </virtualType>
</config>

And the class... app/code/Sinapsis/Store/Model/CollectionProvider.php

<?php
namespace Sinapsis\Store\Model\Store;

use Sinapsis\Store\Ui\Provider\CollectionProviderInterface;
use Magento\Ui\Component\MassAction\Filter;
use Sinapsis\Store\Model\ResourceModel\Store\CollectionFactory;

class CollectionProvider implements CollectionProviderInterface
{
    /**
     * @var Filter
     */
    private $filter;
    /**
     * @var CollectionFactory
     */
    private $collectionFactory;

    /**
     * CollectionRetriever constructor.
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(
        Filter $filter,
        CollectionFactory $collectionFactory
    ) {
        $this->filter            = $filter;
        $this->collectionFactory = $collectionFactory;
    }

    /**
     * @return \Magento\Framework\Data\Collection\AbstractDb
     */
    public function getCollection()
    {
        return $this->filter->getCollection($this->collectionFactory->create());
    }
}

For other users trying to do the same, I would recommend directly creating the module with that tool, and then go step by step trying to get the purpose of relevant code / files

Related Topic