17

An AJAX request to one of my controller actions currently returns the full page HTML.

I only want it to return the HTML (.phtml contents) for that particular action.

The following code poorly solves the problem by manually disabling the layout for the particular action:

    $viewModel = new ViewModel();
    $viewModel->setTerminal(true);
    return $viewModel;

How can I make my application automatically disable the layout when an AJAX request is detected? Do I need to write a custom strategy for this? Any advice on how to do this is much appreciated.

Additionally, I've tried the following code in my app Module.php - it is detecting AJAX correctly but the setTerminal() is not disabling the layout.

public function onBootstrap(EventInterface $e)
{
    $application = $e->getApplication();
    $application->getEventManager()->attach('route', array($this, 'setLayout'), 100);

    $this->setApplication($application);

    $this->initPhpSettings($e);
    $this->initSession($e);
    $this->initTranslator($e);
    $this->initAppDi($e);
}

public function setLayout(EventInterface $e)
{
    $request = $e->getRequest();
    $server  = $request->getServer();

    if ($request->isXmlHttpRequest()) {
        $view_model = $e->getViewModel();
        $view_model->setTerminal(true);
    }
}

Thoughts?

Daniel Sposito
  • 448
  • 1
  • 4
  • 13

8 Answers8

8

Indeed the best thing would be to write another Strategy. There is a JsonStrategy which can auto-detect the accept header to automatically return Json-Format, but as with Ajax-Calls for fullpages, there it's good that it doesn't automatically do things, because you MAY want to get a full page. Above mentioned solution you mentioned would be the quick way to go.

When going for full speed, you'd only have one additional line. It's a best practice to always return fully qualified ViewModels from within your controller. Like:

public function indexAction() 
{
    $request   = $this->getRequest();
    $viewModel = new ViewModel();
    $viewModel->setTemplate('module/controller/action');
    $viewModel->setTerminal($request->isXmlHttpRequest());

    return $viewModel->setVariables(array(
         //list of vars
    ));
}
Sam
  • 16,435
  • 6
  • 55
  • 89
  • Thanks, Sam - I've also updated my post to include an approach I've tried with the app's Module.php. Any thoughts on why setTerminal() isn't having an affect on the ViewModel? – Daniel Sposito Nov 01 '12 at 15:50
  • My guess is that onBootstrap no ViewModel() is present yet, an idea would be to inject a viewModel with terminal(true) into the controller and use this for output (am i making any sense?) – Sam Nov 01 '12 at 16:15
6

I think the problem is that you're calling setTerminal() on the view model $e->getViewModel() that is responsible for rendering the layout, not the action. You'll have to create a new view model, call setTerminal(true), and return it. I use a dedicated ajax controller so there's no need of determining whether the action is ajax or not:

use Zend\View\Model\ViewModel;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\Controller\AbstractActionController;

class AjaxController extends AbstractActionController
{
    protected $viewModel;

    public function onDispatch(MvcEvent $mvcEvent)
    {
        $this->viewModel = new ViewModel; // Don't use $mvcEvent->getViewModel()!
        $this->viewModel->setTemplate('ajax/response');
        $this->viewModel->setTerminal(true); // Layout won't be rendered

        return parent::onDispatch($mvcEvent);
    }

    public function someAjaxAction()
    {
        $this->viewModel->setVariable('response', 'success');

        return $this->viewModel;
    }
}

and in ajax/response.phtml simply the following:

<?= $this->response ?>
Blackcoat77
  • 1,574
  • 1
  • 21
  • 31
aimfeld
  • 2,931
  • 7
  • 32
  • 43
4

Here's the best solution (in my humble opinion). I've spent almost two days to figure it out. No one on the Internet posted about it so far I think.

public function onBootstrap(MvcEvent $e)
{
    $eventManager= $e->getApplication()->getEventManager();

    // The next two lines are from the Zend Skeleton Application found on git
    $moduleRouteListener = new ModuleRouteListener();
    $moduleRouteListener->attach($eventManager);

    // Hybrid view for ajax calls (disable layout for xmlHttpRequests)
    $eventManager->getSharedManager()->attach('Zend\Mvc\Controller\AbstractController', MvcEvent::EVENT_DISPATCH, function(MvcEvent $event){

        /**
         * @var Request $request
         */
        $request = $event->getRequest();
        $viewModel = $event->getResult();

        if($request->isXmlHttpRequest()) {
            $viewModel->setTerminal(true);
        }

        return $viewModel;
    }, -95);

}

I'm still not satisfied though. I would create a plugin as a listener and configure it via configuration file instead of onBootstrap method. But I'll let this for the next time =P

Keyne Viana
  • 6,194
  • 2
  • 24
  • 55
  • Bear in mind that the result of a controller may not be a `ViewModel` at all; even the request may not be a `Zend\Http\Request` so be careful when you write event listeners, always put in place controls on what you grab from the event, and remember that listeners affect the whole MVC application cycle, not just your module. – Stefano Torresi Apr 04 '14 at 14:29
  • How do i access this specific view model inside my controller? – somejkuser Nov 07 '17 at 07:34
0

I replied to this question and seems it maybe similar - Access ViewModel variables on dispatch event

Attach an event callback to the dispatch event trigger. Once this event triggers it should allow you to obtain the result of the action method by calling $e->getResult(). In the case of an action returning a ViewModel it should allow you to do the setTerminal() modification.

Community
  • 1
  • 1
DrBeza
  • 2,241
  • 1
  • 16
  • 18
0

aimfeld solution works for me, but in case some of you experiment issues with the location of the template, try to specify the module:

 $this->viewModel->setTemplate('application/ajax/response');
Conti
  • 1,153
  • 9
  • 12
0

The best is to use JsonModel which returns nice json and disable layout&view for you.

public function ajaxCallAction()
    {
        return new JsonModel(
            [
                'success' => true
            ]
        );
    }
tomekSzad
  • 11
  • 1
0

I had this problem before and here is a quikc trick to solved that.

First of all, create an empty layout in your layout folder module/YourModule/view/layout/empty.phtml

You should only echo the view content in this layout this way <?php echo $this->content; ?>

Now In your Module.php set the controller layout to layout/empty for ajax request

namespace YourModule;
use Zend\Mvc\MvcEvent;

class Module {
    public function onBootstrap(MvcEvent $e) {
        $sharedEvents = $e->getApplication()->getEventManager()->getSharedManager();
        $sharedEvents->attach(__NAMESPACE__, 'dispatch', function($e) {
            if ($e->getRequest()->isXmlHttpRequest()) {
                $controller = $e->getTarget();
                $controller->layout('layout/empty');
            }
        });
    }
}
BoCyrill
  • 1,219
  • 16
  • 17
-2
public function myAjaxAction() 
{
    ....
    // View - stuff that you returning usually in a case of non-ajax requests
    View->setTerminal(true);
    return View;
}
Rio
  • 1