So rendern Sie HTML mit AJAX in Magento 2

12

Ich versuche, den besten Weg zu finden, um HTML über AJAX in Magento 2 zu rendern.

Weg 1: Verwenden des Controllers ohne Layout

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

Weg 2: Verwenden des Controllers mit benutzerdefiniertem Layout

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

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

Datei 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 scheint die beste Vorgehensweise der Weg 2 zu sein, da er die Logik vom Controller trennt.
Das Problem mit Way 2 ist jedoch, dass das <body>und <head>mit CSS/ JSgeneriert werden, sodass es sich nicht um ein vollständig bereinigtes HTML handelt, in dem nur meine Blockvorlage enthalten ist.

  • verwende ich das benutzerdefinierte Layout falsch?
  • Wird der Weg 1 als gute Praxis angesehen?
  • Gibt es andere Möglichkeiten, das zu tun?
Matthéo Geoffray
quelle

Antworten:

18

Ich würde auch den Weg 2 gehen und tatsächlich können Sie "reines" HTML über AJAX ohne Kopf, Körper, CSS und so weiter rendern.

Der Trick ist:

  • Informieren Sie Ihren Controller eine Antwort auf instanziiert , die vom Typ ist , \Magento\Framework\View\Result\Layoutanstatt\Magento\Framework\View\Result\Page
  • Verwenden Sie eine XML-Layoutdatei mit einem Stammknoten <layout...>...</layout>anstelle von<page...>...</page>

Hier ist eine sehr einfache Implementierung.

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

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

Beispiel auf Github

Siehe dieses Beispielmodul: https://github.com/herveguetin/Herve_AjaxLayout_M2

Dieses Modul generiert Folgendes:

Geben Sie hier die Bildbeschreibung ein

Hervé Guétin
quelle
Was ist, wenn ich das gesamte Layout laden möchte (XML mit wenigen Containern, Blöcken usw.)? create -> toHtml und per json an ajax senden?
Mattkrupnik
5

Standardmäßig verwendet Magento keine dieser Methoden, um HTML über AJAX zu rendern.

Soweit ich gesehen habe, wird JSON jedes Mal, wenn so etwas getan werden muss, verwendet, um das Ergebnis zu transportieren.

Beispiel aus dem Magento/Checkout/Controller/Cart/Add:

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

Dann verwendet Magento 2 einen neuen Mechanismus namens Abschnitte, um die Daten im Frontend zu verarbeiten und die spezifischen Blöcke zu aktualisieren, die aktualisiert werden müssen. Weitere Informationen zu Abschnitten finden Sie in diesen Fragen und Antworten: /magento//a/ 143381/2380

BEARBEITEN Sie den zweiten Teil meiner Antwort: Wie von Max im Kommentar angegeben, werden Abschnitte nur mit kundenspezifischen Daten verwendet, und die Verwendung dieser Funktionalität anstelle jedes AJAX-Aufrufs ist nicht die richtige Lösung.

Raphael beim digitalen Pianismus
quelle
Ja, ich verwende auch JSON, um das Ergebnis zu transportieren, aber ich vereinfache meine Klassen für den Zweck der Frage;) Aber ich kenne diese Abschnittsfunktion nicht, es scheint der richtige Weg zu sein, um das zu tun, was ich will. Ich werde es mir ansehen. Ich werde warten, wenn es eine andere Antwort gibt, sonst werde ich Ihre Antwort bestätigen. Vielen Dank !
Matthéo Geoffray
2
Ich bin damit einverstanden, Json-Antwort anstelle von HTML-Rohdaten zu verwenden. Der zweite Teil Ihrer Antwort ist jedoch nicht vollständig korrekt. Beachten Sie, dass Kundenabschnitte, die nur für kundenspezifische Daten verwendet werden und diese Funktionalität anstelle jedes Ajax-Anrufs verwenden, keine gute Idee sind.
Max
2
@ Matthéo ja, ich habe verstanden :) Mein Kommentar an Raphael zur Korrektur der Antwort gerichtet, da der zweite Teil der Antwort von anderen Benutzern missverstanden werden kann.
Max
1
@ MaxStsepantsevich danke für das Erkennen, ich habe meine Antwort bearbeitet, um zu reflektieren, was Sie gesagt haben
Raphael bei Digital Pianism
1
Ich habe eine Antwort mit Ihren Rückmeldungen hinzugefügt. Vielen Dank für Ihre Hilfe.
Matthéo Geoffray
3

In meinem Beispiel kann ich nicht verwenden, sectionsweil es nicht ist customer dataund es nicht nach einer PUT/ POSTAktion ist, aber mit Raphael at Digital PianismAntwort habe ich herausgefunden, wie Magento Abschnitte rendert.

Wenn wir das Beispiel eines cartAbschnitts nehmen, verwenden wir die Methode \Magento\Customer\CustomerData\SectionPool::getSectionDataByNames, um Daten aus Abschnitten abzurufen. Dies führte uns zu \Magento\Checkout\CustomerData\Cart::getSectionDataeinem einzelnen Array, das Bereiche des Abschnitts enthält, einschließlich$this->layout->createBlock('Magento\Catalog\Block\ShortcutButtons')->toHtml()

Abhängig davon ist hier die endgültige Controller-Klasse:

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