Reputation: 518
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?
Upvotes: 17
Views: 17448
Reputation: 3151
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 ?>
Upvotes: 6
Reputation: 1379
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');
}
});
}
}
Upvotes: 0
Reputation: 11
The best is to use JsonModel which returns nice json and disable layout&view for you.
public function ajaxCallAction()
{
return new JsonModel(
[
'success' => true
]
);
}
Upvotes: 0
Reputation: 1
public function myAjaxAction()
{
....
// View - stuff that you returning usually in a case of non-ajax requests
View->setTerminal(true);
return View;
}
Upvotes: -2
Reputation: 6202
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
Upvotes: 4
Reputation: 1273
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');
Upvotes: 0
Reputation: 2251
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.
Upvotes: 0
Reputation: 16455
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
));
}
Upvotes: 8