Komponent siatki Magento nie sortuje się poprawnie

16

Skonfigurowałem komponent siatki w Magento - i sortowanie wydaje się zepsute. Gdzie mogę to debugować na poziomie javascript i / lub czy ktoś inny ma pojęcie, dlaczego tak się dzieje?

Jeśli raz posortuję siatkę, zostanie wysłane żądanie ajax i wszystko posortuje się poprawnie.

wprowadź opis zdjęcia tutaj

Jednak drugi sort, bez żądania ajax, renderuje siatkę z tymi samymi identyfikatorami.

wprowadź opis zdjęcia tutaj

To zachowanie nie powtarza się na siatkach rdzenia Magento, więc jestem prawie pewien, że to właśnie robię. Po prostu nie znam systemu komponentu interfejsu użytkownika wystarczająco dobrze, aby wiedzieć, od czego zacząć debugowanie.

Alan Storm
źródło

Odpowiedzi:

21

W porządku, nie mogę jeszcze udawać, że rozumiem dlaczego, ale problemem był dataargument mojego dataProviderargumentu.

<!-- ... -->
<argument name="dataProvider" xsi:type="configurableObject">
    <!-- ... --->
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="update_url" xsi:type="url" path="mui/index/render"/>
        </item>
    </argument>
    <!-- ... -->
</argument>
<!-- ... -->

Kiedy porównałem to z kilkoma podstawowymi siatkami, w dataargumencie brakowało storageConfigwęzła z indexFieldpodwęzłem z kluczem podstawowym mojego modelu.

<argument name="data" xsi:type="array">
    <item name="config" xsi:type="array">
        <item name="update_url" xsi:type="url" path="mui/index/render"/>
        <item name="storageConfig" xsi:type="array">
            <item name="indexField" xsi:type="string">pulsestorm_commercebug_log_id</item>
        </item>                    

    </item>                          
</argument>

Kiedy dodałem te węzły, funkcja sortowania została przywrócona.

Alan Storm
źródło
Właśnie natknąłem się na ten sam problem. Wyobrażam sobie, że spada on lub ładuje wartości z pamięci według indeksu wierszy, a nie identyfikatora wiersza danych, chociaż nie ma sensu, dlaczego dane są duplikowane. Dziękuję za Twoją odpowiedź.
LM_Fielding 18.07.17
7

TL; DR

To naprawdę interesujący problem.

Oto jak zrozumiałem system, ale może nie mam 100% racji.

Jak widać kliknięcie kolumny nagłówka generuje żądanie AJAX na następującą trasę: /admin_key/mui/index/renderz następującymi parametrami:

  • filtry [symbol zastępczy]
  • isAjax
  • przestrzeń nazw
  • stronicowanie [bieżący]
  • stronicowanie [rozmiar strony]
  • Szukaj
  • sortowanie [kierunek]
  • sortowanie [pole]

Ostatni to pole, na którym sortujesz siatkę.

Ta trasa jest domyślnie zadeklarowana w app/code/Magento/Ui/view/base/ui_component/etc/definition.xml:

<insertListing class="Magento\Ui\Component\Container">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="component" xsi:type="string">Magento_Ui/js/form/components/insert-listing</item>
            <item name="update_url" xsi:type="url" path="mui/index/render"/>
            <item name="render_url" xsi:type="url" path="mui/index/render"/>
            <item name="autoRender" xsi:type="boolean">false</item>
            <item name="dataLinks" xsi:type="array">
                <item name="imports" xsi:type="boolean">true</item>
                <item name="exports" xsi:type="boolean">false</item>
            </item>
            <item name="realTimeLink" xsi:type="boolean">true</item>
        </item>
    </argument>
</insertListing>

Ale na liście ui_component XML jest również zadeklarowany:

        <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">page_id</item>
                </item>
            </item>
        </argument>

Ta trasa jest obsługiwana na app/code/Magento/Ui/Controller/Adminhtml/Index/Render.phppodstawie parametru przestrzeni nazw (który zwykle jest nazwą komponentu interfejsu użytkownika)

public function execute()
{
    if ($this->_request->getParam('namespace') === null) {
        $this->_redirect('admin/noroute');
        return;
    }

    $component = $this->factory->create($this->_request->getParam('namespace'));
    $this->prepareComponent($component);
    $this->_response->appendBody((string) $component->render());
}

Jeżeli prepareComponentmetoda jest rekurencyjna w elementach potomnych:

protected function prepareComponent(UiComponentInterface $component)
{
    foreach ($component->getChildComponents() as $child) {
        $this->prepareComponent($child);
    }
    $component->prepare();
}

Po przygotowaniu komponentu kolumny sortowanie kolumn jest obsługiwane przez app/code/Magento/Ui/Component/Listing/Columns/Column.php:

public function prepare()
{
    $this->addFieldToSelect();

    $dataType = $this->getData('config/dataType');
    if ($dataType) {
        $this->wrappedComponent = $this->uiComponentFactory->create(
            $this->getName(),
            $dataType,
            array_merge(['context' => $this->getContext()], (array) $this->getData())
        );
        $this->wrappedComponent->prepare();
        $wrappedComponentConfig = $this->getJsConfig($this->wrappedComponent);
        // Merge JS configuration with wrapped component configuration
        $jsConfig = array_replace_recursive($wrappedComponentConfig, $this->getJsConfig($this));
        $this->setData('js_config', $jsConfig);

        $this->setData(
            'config',
            array_replace_recursive(
                (array)$this->wrappedComponent->getData('config'),
                (array)$this->getData('config')
            )
        );
    }

    $this->applySorting();

    parent::prepare();
}

Jeśli applySorting()metoda oparta jest na parametrze sortowania i po prostu dodaje zamówienie do dostawcy danych:

protected function applySorting()
{
    $sorting = $this->getContext()->getRequestParam('sorting');
    $isSortable = $this->getData('config/sortable');
    if ($isSortable !== false
        && !empty($sorting['field'])
        && !empty($sorting['direction'])
        && $sorting['field'] === $this->getName()
    ) {
        $this->getContext()->getDataProvider()->addOrder(
            $this->getName(),
            strtoupper($sorting['direction'])
        );
    }
}

Po przygotowaniu każdego komponentu klasa akcji renderuje komponent (ponownie rekurencyjnie) dla odpowiedzi:

$this->_response->appendBody((string) $component->render());

Sądzę, że są to ważne kroki PHP w tym, co dzieje się podczas sortowania.

Teraz do JS, renderowane i aktualizowane adresy URL (zadeklarowane definition.xmlpowyżej) są przypisane do elementu w app/code/Magento/Ui/view/base/web/js/form/components/insert.js:

return Element.extend({
    defaults: {
        content: '',
        template: 'ui/form/insert',
        showSpinner: true,
        loading: false,
        autoRender: true,
        visible: true,
        contentSelector: '${$.name}',
        externalData: [],
        params: {
            namespace: '${ $.ns }'
        },
        renderSettings: {
            url: '${ $.render_url }',
            dataType: 'html'
        },
        updateSettings: {
            url: '${ $.update_url }',
            dataType: 'json'
        },
        imports: {},
        exports: {},
        listens: {},
        links: {
            value: '${ $.provider }:${ $.dataScope}'
        },
        modules: {
            externalSource: '${ $.externalProvider }'
        }
    }

W dalszym ciągu w tym pliku istnieje requestDatametoda używana do pobierania danych AJAX:

    requestData: function (params, ajaxSettings) {
        var query = utils.copy(params);

        ajaxSettings = _.extend({
            url: this['update_url'],
            method: 'GET',
            data: query,
            dataType: 'json'
        }, ajaxSettings);

        this.loading(true);

        return $.ajax(ajaxSettings);
    }

Możesz zobaczyć, że ta metoda jest wywoływana, gdy render()metoda jest wywoływana:

        $.async({
            component: this.name,
            ctx: '.' + this.contentSelector
        }, function (el) {
            self.contentEl = $(el);
            self.startRender = true;
            params = _.extend({}, self.params, params || {});
            request = self.requestData(params, self.renderSettings);
            request
                .done(self.onRender)
                .fail(self.onError);
        });

Po wykonaniu tej czynności wywoływana jest metoda wywołania zwrotnego w celu zastosowania danych. To jest onRender():

    onRender: function (data) {
        this.loading(false);
        this.set('content', data);
        this.isRendered = true;
        this.startRender = false;
    }

Sądzę, że właśnie tam zastosowano nową treść.

Raphael at Digital Pianism
źródło
Code Columbo ...
LM_Fielding 18.07.17