Come eseguire il rendering HTML con AJAX in Magento 2


12

Cerco di trovare il modo migliore per eseguire il rendering HTML tramite AJAX in Magento 2.

Modo 1: utilizzo del controller senza layout

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

Modo 2: utilizzo del controller con layout personalizzato

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

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

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

La best practice IMO sembra essere la Way 2 perché separa la logica dal controller.
Ma il problema con il Way 2 è che il <body>e <head>con CSS/ JSsono generati, quindi non è un HTML pulito e completo con solo il mio modello di blocco.

  • sto usando il layout personalizzato nel modo sbagliato?
  • Il modo 1 è considerato una buona pratica?
  • Ci sono altri modi per farlo?

Risposte:


18

Vorrei anche andare come 2 e, in effetti, puoi effettivamente rendere HTML "puro" tramite AJAX senza head, body, css e così via.

Il trucco è:

  • di 'al tuo controller di istanziare una risposta che è di tipo \Magento\Framework\View\Result\Layoutpiuttosto che\Magento\Framework\View\Result\Page
  • utilizzare un file XML di layout con un nodo principale <layout...>...</layout>anziché<page...>...</page>

Ecco un'implementazione molto semplice.

Il controller

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

Lo schema

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

Esempio su Github

Vedi questo modulo di esempio: https://github.com/herveguetin/Herve_AjaxLayout_M2

Questo modulo genera questo:

inserisci qui la descrizione dell'immagine


Cosa succede se desidero caricare l'intero layout (XML con pochi contenitori, blocchi, ecc.)? creare -> toHtml e inviare tramite json a ajax?
Mattkrupnik,

5

Immediatamente, Magento non utilizza nessuno di questi metodi per eseguire il rendering HTML tramite AJAX.

Da quello che ho visto, ogni volta che una cosa del genere deve essere fatta, JSON viene utilizzato per trasportare il risultato.

Esempio tratto da Magento/Checkout/Controller/Cart/Add:

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

Quindi Magento 2 utilizza un nuovo meccanismo chiamato sezioni, per gestire i dati sul frontend e aggiornare i blocchi specifici che devono essere aggiornati, puoi saperne di più sulle sezioni in questo Q&A: /magento//a/ 143381/2380

EDIT per quanto riguarda la seconda parte della mia risposta: come affermato da Max nel commento, le sezioni vengono utilizzate solo con dati specifici del cliente e l'utilizzo di questa funzionalità invece di ogni chiamata AJAX non è la soluzione giusta.


Sì, utilizzo anche JSON per trasportare il risultato ma semplifico le mie classi ai fini della domanda;) Ma non sono a conoscenza di quella funzione di sezione, sembra essere il modo giusto di fare quello che voglio. Ci darò un'occhiata. Aspetterò se ci sono altre risposte, altrimenti convaliderò la tua risposta. Grazie !
Matthéo Geoffray,

2
Sono d'accordo con l'utilizzo della risposta Json invece dei dati HTML non elaborati. Ma la seconda parte della tua risposta non è completamente corretta. Si noti che le sezioni dei clienti che utilizzano solo dati specifici del cliente e che utilizzano questa funzionalità anziché ogni chiamata Ajax non sono una buona idea.
Max

2
@Matteo sì, ho capito :) Il mio commento rivolto a Raffaello per aver corretto la risposta, perché la seconda parte della risposta può essere fraintesa da altri utenti.
Max

1
@MaxStsepantsevich grazie per averlo individuato, ho modificato la mia risposta per riflettere ciò che hai detto
Raffaello al Pianismo digitale

1
Ho aggiunto una risposta usando i tuoi feedback. Grazie a voi due per il vostro aiuto.
Matthéo Geoffray,

3

Nel mio esempio non posso usare sectionsperché non lo è customer datae non è dopo un'azione PUT/ POSTma usando la Raphael at Digital Pianismrisposta ho capito come Magento rende le sezioni.

Se prendiamo l'esempio della cartsezione, utilizza il metodo \Magento\Customer\CustomerData\SectionPool::getSectionDataByNamesper recuperare i dati dalle sezioni. Questo ci porta a \Magento\Checkout\CustomerData\Cart::getSectionDataun singolo array contenente aree della sezione, incluso$this->layout->createBlock('Magento\Catalog\Block\ShortcutButtons')->toHtml()

A seconda di ciò, ecco la classe finale del 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);
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.