Comment rendre HTML avec AJAX dans Magento 2

12

J'essaie de trouver la meilleure façon de rendre le HTML via AJAX dans Magento 2.

Méthode 1: Utilisation du contrôleur sans mise en page

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

Méthode 2: utilisation du contrôleur avec une disposition personnalisée

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

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

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

OMI, la meilleure pratique semble être la voie 2 car elle sépare la logique du contrôleur.
Mais le problème avec Way 2 est que le <body>et <head>avec CSS/ JSsont générés, donc ce n'est pas un HTML entièrement nettoyé avec seulement mon modèle de bloc.

  • est-ce que j'utilise la disposition personnalisée dans le mauvais sens?
  • La voie 1 est-elle considérée comme une bonne pratique?
  • Y a-t-il d'autres façons de le faire?
Matthéo Geoffray
la source

Réponses:

18

J'irais également dans le sens 2 et, en effet, vous pouvez réellement rendre le HTML "pur" via AJAX sans la tête, le corps, le CSS et ainsi de suite.

L'astuce consiste à:

  • dites à votre contrôleur d'instancier une réponse de type \Magento\Framework\View\Result\Layoutplutôt que\Magento\Framework\View\Result\Page
  • utiliser un fichier XML de mise en page avec un nœud racine qui est <layout...>...</layout>plutôt que<page...>...</page>

Voici une implémentation très simple.

Le controlle

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

La disposition

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

Exemple sur Github

Voir cet exemple de module: https://github.com/herveguetin/Herve_AjaxLayout_M2

Ce module génère ceci:

entrez la description de l'image ici

Hervé Guétin
la source
Que faire si je veux charger la mise en page entière (XML avec quelques conteneurs, bloc, etc.)? créer -> toHtml, et envoyer via json à ajax?
mattkrupnik
5

Hors de la boîte, Magento n'utilise aucune de ces méthodes pour rendre HTML via AJAX.

D'après ce que j'ai vu, chaque fois qu'une telle chose doit être faite, JSON est utilisé pour transporter le résultat.

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

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

Ensuite, Magento 2 utilise un nouveau mécanisme appelé sections, pour gérer les données sur le frontend et mettre à jour les blocs spécifiques qui doivent être mis à jour, vous pouvez en savoir plus sur les sections dans ce Q&A: /magento//a/ 143381/2380

EDITER concernant la deuxième partie de ma réponse: comme indiqué par Max dans le commentaire, les sections ne sont utilisées qu'avec des données spécifiques au client et utiliser cette fonctionnalité à la place de chaque appel AJAX n'est pas la bonne solution.

Raphael chez Digital Pianism
la source
Oui, j'utilise également JSON pour transporter le résultat mais je simplifie mes classes pour la question;) Mais je ne suis pas au courant de cette fonctionnalité de section, cela semble être la bonne façon de faire ce que je veux. Je vais y jeter un œil. J'attendrai s'il y a une autre réponse sinon je validerai votre réponse. Merci !
Matthéo Geoffray du
2
Je suis d'accord avec l'utilisation de la réponse Json au lieu des données html brutes. Mais la deuxième partie de votre réponse n'est pas entièrement correcte. Notez que les sections client n'utilisant que des données spécifiques au client et utilisant cette fonctionnalité à la place de chaque appel Ajax ne sont pas une bonne idée.
Max
2
@ Matthéo oui, j'ai compris :) Mon commentaire adressé à Raphael pour corriger la réponse, car la deuxième partie de la réponse peut être mal comprise par les autres utilisateurs.
Max
1
@MaxStsepantsevich merci d'avoir repéré cela, j'ai édité ma réponse pour refléter ce que vous avez dit
Raphael au Digital Pianism
1
J'ai ajouté une réponse en utilisant vos commentaires. Merci à vous deux pour votre aide.
Matthéo Geoffray le
3

Dans mon exemple, je ne peux pas l'utiliser sectionscar ce n'est pas le cas customer dataet ce n'est pas après une action PUT/ POSTmais en utilisant la Raphael at Digital Pianismréponse, j'ai compris comment Magento rend les sections.

Si nous prenons l'exemple de cartsection, il utilise la méthode \Magento\Customer\CustomerData\SectionPool::getSectionDataByNamespour récupérer les données des sections. Cela nous amène à \Magento\Checkout\CustomerData\Cart::getSectionDataun seul tableau contenant des zones de la section, y compris$this->layout->createBlock('Magento\Catalog\Block\ShortcutButtons')->toHtml()

En fonction de cela, voici la classe finale du contrôleur:

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