i am new to zend and i could not find a way to implement Ajax in zend.
In general php, its pretty easy to make an ajax request and show the response in the desired part of our page. But coming to zend, i have no clue how to do it.
Suppose in my index controller's index.phtml file, i have a button, and when i click on it, i have to make an ajax request to a particular controller and action, and load the related controller action's view in my page.
But what i could not understand is how to specify the urls for the ajax request and how the routing works.
Currently, i made ajax request to load the view statically like this:
xmlhttp.open("GET","../application/views/scripts/register/register.phtml",true);
FYI, i am using regex routing in my application, so would it be better to use curl to route the requests?
First things first, you don't request the view directly. You need to request the specific controller action, eg
/register/register
Zend comes with a great action helper called AjaxContext. This helper lets you respond with a different view based on the type of request (XmlHttpRequest) and a format parameter, disabling any layouts normally present.
To set it up, place something like this in your controller's init() method
public function init()
{
$this->_helper->ajaxContext->addActionContext('register', 'html')
->initContext();
}
Then, add a view script with the ajax suffix, eg register/register.ajax.phtml.
Construct your AJAX GET request to include the format=html parameter, eg
xmlhttp.open('GET', '/register/register/format/html', true);
or
xmlhttp.open('GET', '/register/register?format=html', true);
What will be returned is the rendered contents of register.ajax.phtml, without any layouts.
Apart from what the other answers stated, there's also a URL view helper function that can be useful to call a specific action on a controller. So you could just use $this->url(array('controller' => 'your_controller', 'action' => 'your_action'), 'default', true); to get the link to "your_action" action on the "your_controller" controller (using the default route). You could also specify a specific route instead of 'default' if you have one defined.
So for your example you would use something like the following in your view phtml file (if you're using the default routing) :
xmlhttp.open("GET","<?php echo $this->url(array('controller' => 'register', 'action' => 'register'), 'default', true);?>");
For more information refer to Zend Framework - View Helpers.
You should never be requesting the view directly. That's just wrong. Request URI like this:
xmlhttp.open("GET","/register/register");
Which means "I am looking for default module, register controller and register action", or in other words RegisterController::registerAction().
It's the same as:
xmlhttp.open("GET","/default/register/register");
Which is the same, the default module can be omitted.
Zend Framework knows where to look for the view script (unless you are using some unusual directory structure).
You can just create a blank layout and use it for your ajax controller actions (or what Phil suggested, AjaxContent is probably better for this).
I'll write here an almost complete "how to" guide to implement AJAX calls in Zendframework 3.
We need:
A route declaration
A controller
Some javaScript (is out of skope, perhaps I'll show it in other place)
The route declaration:
<?php
// editing a module.config.php in your project
// typical lines:
namespace Ajax2l; // my ajax module name
use Zend\Router\Http\Segment;
// ...
'router' => [
// ... other routes
'ajax2lsajaxcall' => [
'type' => Segment::class,
'options' => [
'route' => '/ajax2l/ajaxrequest[/:action][/:garbage]',
// the :action wildcard is a place to have extra parameters
// the :garbage wildcard is a place to send random strings
// to ensure you break cache to get real process for
// all your calls, since every call will be different
'defaults' => [
'controller' => Controller\AjaxController::class,
'action' => 'ajaxrequest'
]
],
'may_terminate' => true,
'child_routes' => [
// other ajax routes if needed
]
],
// I only use one ajax route and one controller for all my sites' ajax
// calls because they fire small db actions and reflect them on small
// parts of my pages. So I think I do not need a route and a controller
// for each ajax call. Instead, I use a Library and some services to get
// sets of elementary components and to perform tasks.
The Controller
My Ajax2l module is located under "myzend_project/module" directory.
<?php
// Filename: /module/Ajax2l/src/Controller/AjaxController.php
namespace Ajax2l\Controller
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
// No more components are needed
class AjaxController extends AbstractActionController {
public function ajaxrequestAction(){
$request = $this->getRequest();
$content = $request->getContent();
$isHttpRequest = $this->ifIsValidHttpRequest();
// if you have a real ajax call you must disable the normal output
$viewmodel = new ViewModel();
$viewmodel->setTerminal($is_xmlhttprequest);
// If not an Ajax call (perhaps from untrusted eyes) serve a forgiven
// page message
if(!$isHttpRequest){
return $this->forgivenPage();
}
// Now prepare a response and responds
$requestParams = $this->preProcessRequest($content);
// call the logics to process your params
$output = $this->processRequestParams($requestParams);
// and send a response
$response = $this->getResponse();
$response->setContent($output);
return $response;
}
private function ifIsValidHttpRequest($content){
// I use a small token string to verify if posted data matches
// perhaps not the proper way but is fast and works
$token = 'my_secure_visitor_session_token_identifier';
return (strpos($content, $token) > 2) ? 1 : 0;
// the validation is simplified here it has some more
// complex logics. To ensure validation of requesting source
}
private function preProcessRequest($content){
// The logics to know what to do
// ...
// here you can identify and set properly the post params
}
function processRequestParams($requestParams){
// some logics to process your data
// ...
// some logics to create a Json output
// ...
return $jsonObject;
}
protected function forgivenPage(){
$view = new ViewModel([]);
// set a forgiven message page as output
$view->setTemplate('ajax2l/messages/forgiven.phtml');
// note: the views directory uses lowercase for module names
return $view;
}
}
Hope it helps. I lost a lot of time reading on zend ajax without results. So I hope I was the last losing his time on this topic.
Luis
Related
I'm using ZfcUser with ht-user-registration and scn-social-auth and there are 2 fairly minor enhancements I want to achieve to extend my implementation, but I'm not having much success so far:
ht-user-registration sends new users an email after registering and denies them access until they've activated their account, this is working fine. What I'd like to do to extend this is redirect the user after registration so that they are sent to a page telling them to check their e-mail rather than to the login page, which is the default behaviour of ZfcUser. I have tried adding an event listener to the module bootstrap that looks like this:
$response = $e->getResponse();
// indicate that we intend to redirect after register action
// set the redirection location to the home page
$response->getHeaders()->addHeaderLine('Location', 'home');
$response->setStatusCode(302);
$response->sendHeaders();
$em = \Zend\EventManager\StaticEventManager::getInstance();
$em->attach('ZfcUser\Service\User', 'register', function($event) use ($response) {
// don't allow anything else to process this event
$event->stopPropagation();
// return the redirect response
return $response;
});
This is called correctly but the redirect never happens and the user still ends up at the login page. Is there something else I need to do to execute the redirect? Or maybe there's a better way entirely to achieve this?
I'd like to add layout variables so that I can modify page titles and navigation in my layout template for the ZfcUser pages. In order to do this I made an override of the UserController from ZfcUser like this :
class UserController extends \ZfcUser\Controller\UserController
{
public function loginAction()
{
$this->layout()->setVariables(array(
'view_title' => 'Reports Login',
));
return parent::loginAction();
}
}
And then overrode the invokable for zfcuser in the config like this:
'controllers' => array(
'invokables' => array(
'Application\Controller\Index' => 'Application\Controller\IndexController',
'zfcuser' => 'Application\Controller\UserController',
),
),
The framework tries to instantiate my UserController at the right point but fails with an InvalidArgumentException: 'You must supply a callable redirectCallback' which I can see is required to construct the base controller but doesn't get passed to my overridden version - any clues why not? I can't find a working example of this to help.
Maybe there's a much easier way to inject layout variables into another module's controller actions?
[EDIT] I have found a simple but not very elegant way of doing this. Since my module overrides the views for both login and registration then it's possible to set layout variables within the view, thus I was able to add a one liner to the top of each view to set the layout variable, e.g.:
<?php $this->layout()->setVariable('view_title', 'Register for an account'); ?>
This doesn't feel correct, but works. If there's a better solution I'd like to know.
In Zend Framework 2, I've created a class that extends Zend\Form\Form called MyForm.
In the indexAction of one Controller class, I'll initialize MyForm like this:
public function indexAction()
{
$form = new MyForm('my-name');
$viewModel = new ViewModel(array('form' => $form));
return $viewModel;
}
Then in the corresponding view, I basically just do
$form = $this->form;
$form->prepare();
echo $this->form()->openTag($this->form);
echo $this->formCollection($form);
echo $this->form()->closeTag();
This all works, but you may have noticed that the action for the form is missing.
I have tried to add the action like this in the view:
$form->setAttribute('action', $this->url(NULL, array('controller'=>'Index', 'action' => 'go')));
Then in the go action inside my IndexController I just have this for testing:
public function goAction()
{
die('huh');
}
This did not work at all, I always land at the form view (== index action) again. Why is the go action never executed?
I also know that I could either hardcode the action attribute and let a segment route handle the processing, or I could even define an own route for that.
In what cases form actions should get their own route?
In what cases form actions should be handled using a segment route?
In what cases form actions should be handled like in my example?
If there are no hard rules for this: What intention do the different approaches communicate?
Is it also possible to add form actions in the controller instead of the view?
Continuing on from the comments: That's not how the URL helper works - if you omit the first parameter, the current route is used instead. That's probably why you're not getting the form action you expect.
Personally, I always specify the route name when using the URL helper - makes things clearer.
With any regular page I set up a Zend\Cache\Pattern\CaptureCache, catch the onFinish event, and send the rendered version to a message queue for later processing. So far, so good...
Problem: sometimes I need to render two versions of the same action in the same request; one for mobile and one for desktop and I'm not sure how to do that.
If it helps, the actions that I need to "double render" actually create two discrete ViewModel objects so that I can have full control over that.
Ended up creating a service that given these two view models would figure out which one is mobile and which one is desktop. Then it would grab the layout and render each view model separately. Finally it would bypass any caching and generate both files manually.
With the setTemplate method :)
First you create your phtml view file (already done I suppose).
then you declare it in your view manager config / template_map :
In the Module.config.php file (you create the alias to call it later in your controller) :
return array(
//[...],
'view_manager' => array(
'template_map' => array(
// [...],
'index/test' => __DIR__ . '/../view/mymodule/index/test.phtml',
),
//here your template_path_stack as usual
),
// [...],
);
(of course, put your own path ;) )
Then in your controller :
public function indexAction(){
$view = new ViewModel();
if (/*here your tests*/) {
$view->setTemplate('index/test'); //here the name declared in the template_map
}
return $view;
}
you can learn a lot here about views (and layouts): http://framework.zend.com/manual/2.2/en/modules/zend.view.quick-start.html
Is it possible to call my own controller action to handle the display of exception error messages on an Ajax request?
A wiki has been written on this topic on the Yii website, but it appears to be out of date. The class CErrorHandler appears to have changed since the author wrote the wiki. The wiki recommends adding the following component property to the config file:
'errorHandler' => array(
'errorAction' => 'site/error',
),
This controller action is never reached though because CApplication::displayError() is called in CErrorHandler.
if($this->isAjaxRequest())
$app->displayError($event->code,$event->message,$event->file,$event->line);
Back to my original question, can I set up my own controller action to handle the display of error messages on an Ajax request?
You can try to make something like this:
$handler=$app->getErrorHandler();
$handler->errorAction='my/error';
Didn't test this but might work.
Also you can just push error to ajax success call.
One way to achieve what you want to do is replace (or extend) the CErrorHandler class.
Most probably, you would have to re-implement either handle($event) or handleException or handleError. However, you could also modify isAjaxRequest to always return false, this way it'll just behave as usual.
These are the steps you would need to do:
In protected/ErrorHandler.php:
<?php
/**
* ErrorHandler is the customized error handler for the application.
*/
class ErrorHandler extends CErrorHandler {
public function handle($event) {
// do something special ...
parent::handle($event);
}
// more special things ...
}
In protected/config/main.php:
return array(
// ...
'components'=>array(
// ...
'errorHandler'=>array(
'class' => 'ErrorHandler',
// ...
),
// ...
),
// ...
);
I hope it helps!
It would be nice to be able to format a url in a portable way inside of a controller, for example for a JSON response. Is there an easy way to do this without creating an instance of Zend_View first?
Several thoughts here:
Even if you are generating a JSON response, you can still use the view object and view-scripts via the ContextSwitch and AjaxContext action helpers.
Even if you don't use a view-script for your response, you have probably already instantiated the view back at Bootstrap. So in the controller you wouldn't actually be creating the view as much as accessing it. So no additional overhead there.
If by "portable" you mean "cross-project", then maybe an action-helper? Drop it into another project, configure helper paths, and you're good to go. If by "portable" you mean "more aware of your application's routing", then you're probably stuck using the view object.
If saying "format URL" you meaning create an url from params, url view helper is you answer. You use it in controller the same way as in view and don't need to create new Zend_View instance - if you're using view renderer you have your helper in $this->view. So
//in view
$this->url(array('controller' => 'index', 'action' => 'default'));
//in controller
$this->view->url(array('controller' => 'index', 'action' => 'default'));
But if you look into code of url view helper, you'll see, that it's using router object to assemble routes/url/. So all you need is router object, which you can obtain in several ways, some of them:
//in controller
$router = $this->getFrontController()->getRouter();
//anywhere
$router = Zend_Controller_Front::getInstance()->getRouter();
//and then
$router->assemble(array('controller' => 'index', 'action' => 'default'));
You can also use HelperBroker, retrieve viewRenderer from there, retrieve a view and run helper method.