El componente de cuadrícula de Magento no se ordena correctamente

16

He configurado un componente de cuadrícula en Magento, y el comportamiento de clasificación parece roto. ¿Dónde puedo depurar esto en el nivel de JavaScript, y / o alguien más tiene alguna idea de por qué esto podría estar sucediendo?

Si clasifico la cuadrícula una vez, se realiza una solicitud ajax y todo se ordena correctamente.

ingrese la descripción de la imagen aquí

Sin embargo, el segundo tipo, sin una solicitud ajax, representa la cuadrícula con los mismos ID.

ingrese la descripción de la imagen aquí

El comportamiento no se repite en las cuadrículas centrales de Magento, así que estoy bastante seguro de que esto es algo que estoy haciendo. Simplemente no conozco el sistema de componentes ui lo suficientemente bien como para saber dónde comenzar a depurar esto.

Alan Storm
fuente

Respuestas:

21

Muy bien, no puedo pretender entender por qué todavía, pero el problema fue el dataargumento de mi dataProviderargumento.

<!-- ... -->
<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>
<!-- ... -->

Cuando comparé esto con algunas de las cuadrículas centrales, al dataargumento le faltaba un storageConfignodo, con un indexFieldsubnodo con la clave principal de mi modelo.

<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>

Cuando agregué estos nodos, se restableció la funcionalidad de clasificación.

Alan Storm
fuente
Me encontré con el mismo problema, imagino que está retrocediendo o cargando valores del almacenamiento por el índice de fila en lugar de la identificación de la fila de datos, aunque no tiene sentido por qué los datos están duplicados. Gracias por la respuesta.
LM_Fielding
7

TL; DR

Este es un problema interesante de hecho.

Así es como entendí el sistema, pero es posible que no tenga el 100% de razón.

Como ha visto, al hacer clic en la columna del encabezado se genera una solicitud AJAX a la siguiente ruta: /admin_key/mui/index/rendercon los siguientes parámetros:

  • filtros [marcador de posición]
  • isAjax
  • espacio de nombres
  • paginación [actual]
  • paginación [pageSize]
  • buscar
  • clasificación [dirección]
  • clasificación [campo]

El último es el campo en el que está ordenando su cuadrícula.

Esta ruta se declara por defecto en 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>

Pero en un listado ui_component XML también se declara:

        <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>

Esta ruta se maneja en app/code/Magento/Ui/Controller/Adminhtml/Index/Render.phpfunción del parámetro del espacio de nombres (que normalmente es el nombre de su componente de IU)

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());
}

Donde el prepareComponentmétodo es recursivo en los componentes secundarios:

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

Cuando se prepara el componente de columna, la clasificación de columnas se maneja de la siguiente manera 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();
}

Donde el applySorting()método se basa en el parámetro de clasificación y simplemente agrega el orden al proveedor de datos:

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'])
        );
    }
}

Una vez que cada componente está preparado, la clase de acción representa el componente (nuevamente de forma recursiva) para la respuesta:

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

Creo que esos son los pasos importantes de PHP de lo que sucede durante la clasificación.

Ahora para el JS, las URL de renderización y actualización (declaradas definition.xmlanteriormente) se asignan al elemento en 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 }'
        }
    }

Todavía en este archivo, hay un requestDatamétodo que se utiliza para recuperar los datos 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);
    }

Puede ver que se llama a este método cuando se llama al render()método:

        $.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);
        });

Una vez hecho esto, se llama a un método de devolución de llamada para aplicar los datos. Es onRender():

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

Creo que ahí es donde se aplica el nuevo contenido.

Raphael en Digital Pianism
fuente
Code Columbo ...
LM_Fielding