Jak renderować HTML za pomocą AJAX w Magento 2

12

Staram się znaleźć najlepszy sposób renderowania HTML przez AJAX w Magento 2.

Sposób 1: Korzystanie z kontrolera bez układu

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

Sposób 2: Używanie kontrolera z niestandardowym układem

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

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

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

IMO najlepszą praktyką wydaje się być Way 2, ponieważ oddziela logikę od kontrolera.
Ale problem z Drogą 2 jest to, że <body>i <head>z CSS/ JSsą generowane więc to nie jest pełny HTML czyszczone tylko mojego szablonu bloku w nim.

  • czy używam niestandardowego układu w niewłaściwy sposób?
  • Czy Way 1 jest uważane za dobrą praktykę?
  • Czy są na to inne sposoby?
Matthéo Geoffray
źródło

Odpowiedzi:

18

Chciałbym również przejść 2 i rzeczywiście możesz renderować „czysty” HTML przez AJAX bez głowy, ciała, css i tak dalej.

Sztuką jest:

  • powiedz kontrolerowi, aby podał odpowiedź typu, \Magento\Framework\View\Result\Layouta nie typu\Magento\Framework\View\Result\Page
  • użyj pliku XML układu z węzłem głównym, który jest <layout...>...</layout>zamiast<page...>...</page>

Oto bardzo prosta implementacja.

Kontroler

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

Układ

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

Przykład na Github

Zobacz przykładowy moduł: https://github.com/herveguetin/Herve_AjaxLayout_M2

Ten moduł generuje to:

wprowadź opis zdjęcia tutaj

Hervé Guétin
źródło
Co jeśli chcę załadować cały układ (XML z kilkoma kontenerami, blokami itp.)? utwórz -> toHtml i wyślij przez json do ajax?
mattkrupnik
5

Po wyjęciu z pudełka Magento nie używa żadnej z tych metod do renderowania HTML przez AJAX.

Z tego, co widziałem, za każdym razem, gdy trzeba coś takiego zrobić, JSON służy do przenoszenia wyniku.

Przykład z Magento/Checkout/Controller/Cart/Add:

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

Następnie Magento 2 korzysta z nowego mechanizmu zwanego sekcjami, aby obsłużyć dane w interfejsie użytkownika i zaktualizować określone bloki, które wymagają aktualizacji. Więcej informacji na temat sekcji można znaleźć w tym pytaniu: /magento//a/ 143381/2380

EDYCJA dotycząca drugiej części mojej odpowiedzi: jak stwierdził Max w komentarzu, sekcje są używane tylko z danymi specyficznymi dla klienta i używanie tej funkcji zamiast każdego wywołania AJAX nie jest właściwym rozwiązaniem.

Raphael at Digital Pianism
źródło
Tak, używam również JSON do przenoszenia wyniku, ale upraszczam moje klasy do celów pytania;) Ale nie znam tej funkcji sekcji, wydaje się, że jest to właściwy sposób na robienie tego, co chcę. Spojrzę na to. Poczekam, czy będzie jakakolwiek inna odpowiedź, w przeciwnym razie zweryfikuję twoją odpowiedź. Dzięki !
Matthéo Geoffray
2
Zgadzam się na użycie odpowiedzi Jsona zamiast surowych danych HTML. Ale druga część twojej odpowiedzi nie jest w pełni poprawna. Zauważ, że sekcje klientów wykorzystujące tylko dane specyficzne dla klienta i korzystające z tej funkcji zamiast każdego wywołania Ajax nie są dobrym pomysłem.
Max
2
@ Matthéo tak, zrozumiałem :) Mój komentarz skierowany do Rafaela w celu poprawienia odpowiedzi, ponieważ druga część odpowiedzi może być źle zrozumiana przez innych użytkowników.
Max
1
@MaxStsepantsevich dziękuję za zauważenie tego, zredagowałem moją odpowiedź, aby odzwierciedlić to, co powiedziałeś
Raphael w Digital Pianism
1
Dodałem odpowiedź za pomocą twoich opinii. Dziękuję wam za pomoc.
Matthéo Geoffray
3

W moim przykładzie nie mogę używać, sectionsponieważ tak nie jest customer datai nie jest po akcji PUT/, POSTale używając Raphael at Digital Pianismodpowiedzi zorientowałem się, jak Magento renderuje sekcje.

Jeśli weźmiemy przykład cartsekcji, użyj metody \Magento\Customer\CustomerData\SectionPool::getSectionDataByNamesdo pobrania danych z sekcji. To prowadzi nas do \Magento\Checkout\CustomerData\Cart::getSectionDatajednej tablicy zawierającej obszary tej sekcji, w tym$this->layout->createBlock('Magento\Catalog\Block\ShortcutButtons')->toHtml()

W zależności od tego tutaj jest ostateczna klasa 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
źródło