Como renderizar HTML com AJAX no Magento 2

12

Eu tento encontrar a melhor maneira de renderizar HTML através do AJAX no Magento 2.

Caminho 1: Usando o Controller sem layout

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

Caminho 2: Usando o Controller com layout personalizado

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

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

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

Na IMO, a melhor prática parece ser o Caminho 2 porque separa a lógica do Controlador.
Mas o problema com o Way 2 é que o <body>e <head>com CSS/ JSsão gerados, portanto, não é um HTML totalmente limpo, apenas com o meu modelo de bloco.

  • estou usando o layout personalizado da maneira errada?
  • O Caminho 1 é considerado uma boa prática?
  • Existem outras maneiras de fazer isso?
Matthéo Geoffray
fonte

Respostas:

18

Eu também seguiria o caminho 2 e, de fato, você pode renderizar HTML "puro" via AJAX sem a cabeça, o corpo, o css e assim por diante.

O truque é:

  • diga ao seu controlador para instanciar uma resposta do tipo em \Magento\Framework\View\Result\Layoutvez de\Magento\Framework\View\Result\Page
  • usar um arquivo XML layout com um nó raiz que é <layout...>...</layout>, em vez de<page...>...</page>

Aqui está uma implementação muito simples.

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

O layout

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

Exemplo no Github

Veja este módulo de exemplo: https://github.com/herveguetin/Herve_AjaxLayout_M2

Este módulo gera isso:

insira a descrição da imagem aqui

Hervé Guétin
fonte
E se eu quiser carregar o layout inteiro (XML com poucos contêineres, bloco etc.)? criar -> toHtml e enviar via json para o ajax?
mattkrupnik
5

Pronto para uso, o Magento não usa nenhum desses métodos para renderizar HTML via AJAX.

Pelo que vi, sempre que isso precisa ser feito, o JSON é usado para transportar o resultado.

Exemplo do Magento/Checkout/Controller/Cart/Add:

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

Então o Magento 2 usa um novo mecanismo chamado seções, para manipular os dados no frontend e atualizar os blocos específicos que precisam ser atualizados. Você pode aprender mais sobre as seções nesta seção de perguntas e respostas: /magento//a/ 143381/2380

EDIT sobre a segunda parte da minha resposta: como afirma Max no comentário, as seções são usadas apenas com dados específicos do cliente e o uso dessa funcionalidade em vez de todas as chamadas AJAX não é a solução certa.

Raphael na Digital Pianism
fonte
Sim, eu também uso o JSON para transportar o resultado, mas simplifico minhas classes para o objetivo da pergunta;) Mas não conheço esse recurso de seção, parece ser o caminho certo para fazer o que quero. Vou dar uma olhada nisso. Esperarei se houver outra resposta, caso contrário validarei sua resposta. Obrigado !
Matthéo Geoffray
2
Concordo em usar a resposta Json em vez de dados html brutos. Mas a segunda parte da sua resposta não está totalmente correta. Observe que seções de clientes usando apenas dados específicos do cliente e usando essa funcionalidade em vez de todas as chamadas do Ajax não são uma boa ideia.
Max
2
@ Matthéo sim, eu entendi :) Meu comentário foi dirigido a Raphael por corrigir a resposta, porque a segunda parte da resposta pode ser mal interpretada por outros usuários.
Max
1
@MaxStsepantsevich graças para detectar isso, eu editei a minha resposta para refletir o que você disse
Raphael em Digital pianismo
1
Eu adicionei uma resposta usando seus feedbacks. Obrigado a vocês dois por sua ajuda.
Matthéo Geoffray
3

No meu exemplo, não posso usar sectionsporque não é customer datae não é depois de uma ação PUT/ POST, mas usando a Raphael at Digital Pianismresposta, descobri como o Magento renderiza seções.

Se usarmos o exemplo de cartseção, use o método \Magento\Customer\CustomerData\SectionPool::getSectionDataByNamespara recuperar dados de seções. Isso nos leva a \Magento\Checkout\CustomerData\Cart::getSectionDatauma única matriz contendo áreas da seção, incluindo$this->layout->createBlock('Magento\Catalog\Block\ShortcutButtons')->toHtml()

Dependendo disso, aqui está a classe final Controller:

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