Cómo renderizar HTML con AJAX en Magento 2

12

Intento encontrar la mejor manera de representar HTML a través de AJAX en Magento 2.

Forma 1: uso del controlador sin diseño

Archivo Foo/Bar/Controller/Popin/Content.php

<?php

namespace Foo\Bar\Controller\Popin;

use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;

/**
 * Class Content
 */
class Content extends Action
{

    /**
     * Content constructor.
     *
     * @param Context $context
     */
    public function __construct(
        Context $context
    ) {
        parent::__construct($context);
    }

    /**
     *
     */
    public function execute()
    {
        /** @var \Magento\Framework\View\Layout $layout */
        $layout = $this->_view->getLayout();

        /** @var \Foo\Bar\Block\Popin\Content $block */
        $block = $layout->createBlock(\Foo\Bar\Block\Popin\Content::class);
        $block->setTemplate('Foo_Bar::popin/content.phtml');

        $this->getResponse()->setBody($block->toHtml());
    }
}   

Forma 2: uso del controlador con diseño personalizado

Archivo Foo/Bar/Controller/Popin/Content.php

<?php

namespace Foo\Bar\Controller\Popin;

use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;

/**
 * Class Content
 */
class Content extends Action
{

    /**
     * Content constructor.
     *
     * @param Context $context
     */
    public function __construct(
        Context $context
    ) {
        parent::__construct($context);
    }

    /**
     *
     */
    public function execute()
    {
        $this->_view->loadLayout();
        $this->_view->renderLayout();
    }
}    

Archivo Foo/Bar/view/frontend/page_layout/ajax-empty.xml

<?xml version="1.0"?>

<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_layout.xsd">
    <container name="root"/>
</layout>

Archivo Foo/Bar/view/frontend/layout/foo_bar_popin_content.xml

<?xml version="1.0"?>

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="ajax-empty" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="root">
            <block class="Foo\Bar\Block\Popin\Content" name="foo_bar_popin_content" template="Foo_Bar::popin/content.phtml" cacheable="false"/>
        </referenceContainer>
    </body>
</page>

En mi opinión, la mejor práctica parece ser el camino 2 porque separa la lógica del controlador.
Pero el problema con Way 2 es que se generan <body>y <head>con CSS/ JS, por lo que no es un HTML completamente limpio con solo mi plantilla de bloque.

  • ¿Estoy usando el diseño personalizado de forma incorrecta?
  • ¿El Camino 1 se considera una buena práctica?
  • ¿Hay alguna otra forma de hacer eso?
Matthéo Geoffray
fuente

Respuestas:

18

También iría por el camino 2 y, de hecho, en realidad puede renderizar HTML "puro" a través de AJAX sin la cabeza, el cuerpo, el CSS y así sucesivamente.

El truco es:

  • dígale a su controlador que instale una respuesta que sea de tipo en \Magento\Framework\View\Result\Layoutlugar de\Magento\Framework\View\Result\Page
  • utilizar un archivo de diseño de XML con un nodo raíz que es <layout...>...</layout>en lugar de<page...>...</page>

Aquí hay una implementación muy simple.

El controlador

<?php    
namespace Namespace\Module\Controller\Index;

use Magento\Framework\App\Action\Action;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\ResultFactory;

class Index extends Action
{
    /**
     * Dispatch request
     *
     * @return \Magento\Framework\Controller\ResultInterface|ResponseInterface
     * @throws \Magento\Framework\Exception\NotFoundException
     */
    public function execute()
    {
        return $this->resultFactory->create(ResultFactory::TYPE_LAYOUT);
    }
}

El diseño

<?xml version="1.0"?>
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd">
    <container name="root">
        <block class="Namespace\Module\Block\Some\Block" name="namespace_module.some_block" />
    </container>
</layout>

Ejemplo en Github

Vea este módulo de ejemplo: https://github.com/herveguetin/Herve_AjaxLayout_M2

Este módulo genera esto:

ingrese la descripción de la imagen aquí

Hervé Guétin
fuente
¿Qué sucede si quiero cargar el diseño completo (XML con pocos contenedores, bloques, etc.)? crear -> toHtml, y enviar por json a ajax?
mattkrupnik el
5

Fuera de la caja, Magento no utiliza ninguno de esos métodos para representar HTML a través de AJAX.

Por lo que he visto, cada vez que hay que hacer algo así, JSON se utiliza para transportar el resultado.

Ejemplo de Magento/Checkout/Controller/Cart/Add:

$this->getResponse()->representJson(
    $this->_objectManager->get('Magento\Framework\Json\Helper\Data')->jsonEncode($result)
);

Luego, Magento 2 usa un nuevo mecanismo llamado secciones, para manejar los datos en la interfaz y actualizar los bloques específicos que deben actualizarse, puede obtener más información sobre las secciones en estas preguntas y respuestas: /magento//a/ 143381/2380

EDITAR con respecto a la segunda parte de mi respuesta: como dijo Max en el comentario, las secciones solo se usan con datos específicos del cliente y usar esta funcionalidad en lugar de cada llamada AJAX no es la solución correcta.

Raphael en Digital Pianism
fuente
Sí, también uso JSON para transportar el resultado, pero simplifico mis clases para el propósito de la pregunta;) Pero no estoy al tanto de esa característica de la sección, parece ser la forma correcta de hacer lo que quiero. Le echaré un vistazo. Esperaré si hay alguna otra respuesta; de lo contrario, validaré tu respuesta. Gracias !
Matthéo Geoffray
2
Estoy de acuerdo con el uso de la respuesta Json en lugar de datos html sin procesar. Pero la segunda parte de su respuesta no es completamente correcta. Tenga en cuenta que las secciones de clientes que usan solo datos específicos del cliente y usan esta funcionalidad en lugar de todas las llamadas de Ajax no es una buena idea.
Max
2
@ Matthéo sí, lo entendí :) Mi comentario se dirigió a Raphael para corregir la respuesta, porque otros usuarios pueden malinterpretar la segunda parte de la respuesta.
Max
1
@MaxStsepantsevich gracias por ver eso, he editado mi respuesta para reflejar lo que has dicho
Raphael en Digital Pianism
1
Agregué una respuesta usando sus comentarios. Gracias a los dos por su ayuda.
Matthéo Geoffray
3

En mi ejemplo, no puedo usar sectionsporque no lo es customer datay no es después de una PUT/ POSTacción, pero usando la Raphael at Digital Pianismrespuesta descubrí cómo Magento renderiza las secciones.

Si tomamos el ejemplo de la cartsección, use el método \Magento\Customer\CustomerData\SectionPool::getSectionDataByNamespara recuperar datos de las secciones. Esto nos lleva a \Magento\Checkout\CustomerData\Cart::getSectionDatauna única matriz que contiene áreas de la sección, incluyendo$this->layout->createBlock('Magento\Catalog\Block\ShortcutButtons')->toHtml()

Dependiendo de eso, aquí está la clase de controlador final:

<?php

namespace Foo\Bar\Controller\Popin;

use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Data\Form\FormKey\Validator;
use Psr\Log\LoggerInterface;

/**
 * Class Content
 */
class Content extends Action
{

    /**
     * @var LoggerInterface $logger
     */
    private $logger;
    /**
     * @var Validator $formKeyValidator
     */
    private $formKeyValidator;
    /**
     * @var JsonFactory $resultJsonFactory
     */
    private $resultJsonFactory;

    /**
     * Content constructor.
     *
     * @param Context $context
     * @param LoggerInterface $logger
     * @param Validator $formKeyValidator
     * @param JsonFactory $resultJsonFactory
     */
    public function __construct(
        Context $context,
        LoggerInterface $logger,
        Validator $formKeyValidator,
        JsonFactory $resultJsonFactory
    ) {
        $this->logger            = $logger;
        $this->formKeyValidator  = $formKeyValidator;
        $this->resultJsonFactory = $resultJsonFactory;
        parent::__construct($context);
    }

    /**
     *
     */
    public function execute()
    {
        if (!$this->formKeyValidator->validate($this->getRequest())) {
            return $this->resultRedirectFactory->create()->setPath('checkout/cart/');
        }

        /** @var \Magento\Framework\Controller\Result\Json $resultJson */
        $resultJson = $this->resultJsonFactory->create();

        try {
            /** @var \Magento\Framework\View\Layout $layout */
            $layout = $this->_view->getLayout();
            /** @var \Foo\Bar\Block\Popin\Content $block */
            $block = $layout->createBlock(\Foo\Bar\Block\Popin\Content::class);
            /** @var array $response */
            $response = [
                'content' => $block->toHtml(),
            ];
        } catch (\Exception $exception) {
            $resultJson->setStatusHeader(
                \Zend\Http\Response::STATUS_CODE_400,
                \Zend\Http\AbstractMessage::VERSION_11,
                'Bad Request'
            );
            /** @var array $response */
            $response = [
                'message' => __('An error occurred')
            ];
            $this->logger->critical($exception);
        }

        return $resultJson->setData($response);
    }
}
Matthéo Geoffray
fuente