I have a application where after update user should be redirected to previous page from pagination.
let's say there is a gridview and user is at page 3. Then he update some record at that page. There should be a redirect to index page 3. What if, while user is updating record, before save, he opens another controller/action in new tab. Then ReturnUrl is now that new action and after save the record he is updating, he is redirected to that new url.
I've tried to set in every action "index" Url::remember(); and then in action "update" - return $this->goBack().
Also return $this->redirect(Yii::$app->request->referrer);, but it stays at same page.
There is a way to store every index URL in session, but in large project that means many sessions.
You could provide the returnUrl to the link, say:
Url::to(['update','id'=>$model->url,'returnUrl'=> Yii::$app->request->url]);
Then in your controller, use $this->request->queryParams['returnUrl'] to redirect to the previousUrl.
To take it one step further, to always provide the returnUrl, you could extend the Url Helper class:
namespace app\helpers;
use yii\helpers;
class Url extends yii\helpers\Url
public function toRouteAndReturn($route, array $params = [], $scheme = false) {
$params['returnUrl'] = Yii::$app->request->url;
return parent::toRoute($route,$params,$scheme);
}
You could provide in your main config:
'on afterAction' => function($event) {
if(!Yii::$app->getResponse()->isSent && !empty(Yii::$app->getRequest()->queryParams['returnUrl']) {
Yii::$app->getResponse()->redirect(Yii::$app->getRequest()->queryParams['returnUrl']);
}
}
Then you could use app\helpers\Url::toRouteAndReturn() instead of yii\helpers\Url::toRoute() to have it return to the previous url.
You can try below Solution.
First in your index page, get current page url and encode it.
$current_url=base64_encode(\Yii::$app->request->getUrl());
Append this url with your update link as below.
'urlCreator' => function ($action, $model, $key, $index) use ($current_url) {
if ($action === 'update') {
$url = Yii::$app->request->baseUrl . '/controllerName/update?id=' . $model->id.'&prev='.$current_url;
return $url;
}
// ......
}
In Controller, in Update method decode url as below and use for redirection.
public function actionUpdate($id)
{
$model = $this->findModel($id);
$prev=base64_decode($_REQUEST['prev']);
// ......
return $this->redirect($prev); // you will redirect from where update method is called
// ......
}
Isn't it quite easy to pass page param into your update url (<model/update>) like <model>/update?id=<id>&page=<page>?
in your index.php view, edit your ActionColumn as follow:
[
'class' => 'yii\grid\ActionColumn',
'urlCreator' => function ($action, $model, $key, $index) {
return \yii\helpers\Url::to([$action, 'id' => $model->id, 'page' => Yii::$app->request->getQueryParam('page', null)]);
},
],
As you can see, I'm getting page param from request url and pass it to models' action buttons
And when you click to update model, the page that we entered from is stored/placed in url.
Controller:
public function actionUpdate($id, $page = null)
{
$model = $this->findModel($id);
...
if($model->save()) {
return $this->redirect(['index', 'page' => $page]);
}
...
}
Finally, after we successfully update the model, the action redirects us to previous index page.
Related
I have used 'use-ajax' class to render login form in a modal. I want to handle validation errors on same modal without closing it. On successful login it is redirecting correctly, but when an error occurs it closes modal and redirecting to login page i.e user/login and displaying errors on that page. I tried to use ajax callback to display error on modal itself by altering the form which is working. But, it is giving me drupal ajax error. Here is my code :
$form['#prefix'] = '<div id="modal-form">';
$form['#suffix'] = '</div>';
$form['status_messages'] = [
'#type' => 'status_messages',
'#weight' => -10,
];
$form['actions']['submit']['#ajax'] = array(
'callback' => 'setMessage',
'wrapper' => 'modal-form',
);
=========================================================================
function setMessage(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
$response = new AjaxResponse();
if ($form_state->hasAnyErrors()) {
$response->addCommand(new ReplaceCommand('#modal-form', $form));
}
else {
$command = new CloseModalDialogCommand('#modal-form', FALSE);
$response->addCommand($command);
}
return $response;
}
The above code giving me session id also but due to drupal ajax error it does not redirect on success by closing modal.
If I go with non-ajax ie. if I remove the ajax callback function it works on success but errors are not displaying on modal.
First, check if you have added redirection related changes using hook_login in your module file. You can remove that redirection related changes and handle redirection in your callback function.
function setMessage(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
$response = new AjaxResponse();
$current= \Drupal::currentUser();
if ($form_state->hasAnyErrors()) {
$response->addCommand(new ReplaceCommand('#modal-form', $form));
}
else if (!$current->id()) {
$response->addCommand(new ReplaceCommand('#modal-form', $form));
}
else {
$command = new RedirectCommand(' ');
return $response->addCommand($command);
}
return $response;
}
On success it will close the modal and will redirect correctly. If you found any error or if you are not logged in it will stay on modal form.
This may seem a simple example but I am stuck. I am watching a tutorial where we see a contact form that has its own get route to render a view that contains the already said contact form.
$app->get('/contact',function (Request $request, Response $response){
return $this->view->render($response,'contact.twig');
})->setName('contact');
Then we have a post that gets the posted data through that form (notice I am passing the data that has been collected from the form).
$app->post('/contact',function ($request,$response){
$data = $request->getParams();
//var_dump($data);
return $response->withRedirect($this->router->pathFor('contact.confirmed', ['data' => $data]));//https://github.com/slimphp/Slim/issues/1933
})->setName('contact');
Finally we have another get route to render a confirmation view that lets the user know that the information has been submitted successfully.
$app->get('/contact/confirmed',function(Request $request, Response $response, $data){
echo 'params are';
var_dump($data);//They never show! :(
return $this->view->render($response,'contact_confirm.twig',[
'data' => $request->getParams(),
]);//https://github.com/slimphp/Slim/issues/1579
})->setName('contact.confirmed');
In that confirmation view, I want to retrieve the data submitted in the post route just to call the user by their name but I get an error saying that $data is empty.
I have been struggling on how to retrieve the user's name.
As a workaround, I have solved it by rendering the view right from the post route ...
$app->post('/contact',function ($request,$response){
$data = $request->getParams();
//var_dump($data);
return $response->withRedirect($this->router->pathFor('contact.confirmed', ['data' => $data]));//https://github.com/slimphp/Slim/issues/1933
})->setName('contact');
but then I wonder why bothering to use withRedirect() function?
My question is, how do I pass data or parameters from a post route to a get route wher you use a withRedirect() function? And in the get route, how do you retrieve those parameters? So I can pass them to its corresponding view.
Solved
Thanks to jmattheis this is how I solved it:
I just happened to learn how to use controllers in slim framework 3, so the ContactController.php looks like:
<?php
namespace App\Controllers\contact;
use App\Controllers\Controller;
class ContactController extends Controller
{
public function show($request,$response)
{
return $this->c->view->render($response,'contact.twig');
}
public function store($request,$response)
{
echo 'storing comments in db ...';
$data = $request->getParams();
//var_dump($data);
//echo 'Name is: '.$data['name'];
$this->c->flash->addMessage('data', $data);
return $response->withRedirect($this->c->router->pathFor('contact.success'));
}
public function success($request,$response,$data)
{
//echo 'Data to be sent in the redirect: ';
$data = $this->c->flash->getFirstMessage('data');
//var_dump($data);
//die();
return $this->c->view->render($response,'contact_success.twig',compact('data'));
}
}
And I added the following code
session_start();
ini_set('date.timezone', 'America/Mexico_City');
right before
$app = new App([
'settings' => [
'displayErrorDetails' => true
]
]);
And that did the trick.
You can't transfer data over a redirect. The second parameter from the pathFor method on the router is the array for the named paramters. That would be f.ex. id in the route /user/{id} then you'd had to put ['id' => 1] in it.
If you $data has a simple structur, then you could put them in the 3rd parameter which are the query params and later read them out with $request->getQueryParams()
$this->router->pathFor('contact.confirmed', [], $data);
Alternative, you could put the data in a session variable, slimphp/Slim-Flash is a library who that.
Example:
$app->post('/contact',function ($request,$response){
$data = $request->getParams();
$this->flash->addMessage('data', $data);
return $response->withRedirect($this->router->pathFor('contact.confirmed'));
})->setName('contact');
$app->get('/contact/confirmed',function(Request $request, Response $response, $data){
$data = $this->flash->getFirstMessage('data');
// ...
});
Assuming that confirmation template is rendered only after form is being processed, there is no need to declare a separate GET route for such view.
So, keep the route that renders the form:
// This route renders the contact form.
$app->get('/contact', function ($request, $response) {
return $this->view->render($response, 'contact.twig');
})->setName('contact');
And render contact_confirm.twig only as result of form being posted:
// This route is contact form processor.
$app->post('/contact', function ($request, $response) {
// Get submitted data.
$formData = $request->getParams();
// Process form data, e.g. store it, sending it somewhere...
// Render confirmation template, passing user name as template parameter.
$templateParams = [
'userName' => $formData['userName'] ?? 'Name unknown';
];
return $this->view->render($response, 'contact_confirmed.twig', $templateParams);
});
My login form may be called with a re-direct query and I am wondering if there is a simple way to include this in the subsequent post action.
The use case is for SSO login.
My normal login route is:
/customer/login
and when called from a third party client becomes:
/customer/login?redirectTo=http://www.example.com
My login action:
public function loginAction()
{
$prg = $this->prg();
if ($prg instanceof Response) {
return $prg;
} elseif ($prg === false) {
return new ViewModel(['form' => $this->loginForm]);
}
This loads my view and I currently define my action as so:
$form = $this->form;
$form->setAttribute('action', $this->url());
Now when the action is called, I am losing the redirectTo parameter.
So my question is this, is it possible to update the action to include the re-direct url so that when a user clicks to login, it is posted back to my form?
thanks!
EDIT -
Obviously I can create a redirectTo route in the configs and test on the initial call to the page for the existence of such a route and include this in the form. My question however is whether or not this can be done automagically simply from the viewscript.
To generate query string arguments from the view helper, you need to assign them as the third argument using the query key. Please refer to the ZF2 docs http://framework.zend.com/manual/current/en/modules/zend.view.helpers.url.html
$form->setAttribute('action', $this->url('application', array('action' => 'login'), array('query' => array('redirectTo' => 'http://www.example.com,))));
$form->setAttribute('action', $this->url('login', [
'query' => [
'redirectTo' => $this->params()->fromQuery('redirectTo')
]
]);
Where 'login' is the name of the login route.
See Url View Helper
Well my solution is not as elegant as I hoped it would be. I wanted to avoid using the controller for the query params. As #Stanimir pointed out, the view helpers are in fact, to help with view so my original idea was unfounded.
This is an end to end of what I have put together:
Controller:
$redirect_url = $this->params()->fromQuery('redirectTo',null);
Returns this to view on initial load:
return new ViewModel( ['form' => $this->loginForm , 'redirect_url' => $redirect_url] );
View
$form->setAttribute(
'action',
$this->url(
'customer/login', [] ,
[ 'query'=>
[ 'redirectTo' => $this->redirect_url ]
]
)
);
I have a form for the creation of new "groups". I now added a small "go back" image with which the user should be able to go back one step. I don't know why, but when I click this new image, the controller and action used for the form which I want to leave (/admin/creategroup) is called again with HTTP POST set. Therefore, the form validation is done, and I'm stuck at this form with the validation errors displayed.
This is a snippet of the code from my form with both image-buttons. I wan't the "go back"-image to redirect me to the specified controller without validating the form:
$this->addElement('image', 'btnBack', array (
'name' => 'btnBack',
'id' => 'btnBack',
'label' => '',
'title' => 'Go back',
'alt' => 'Go back',
'src' => '/img/undo.png',
'onClick' => "window.location='/admin/groupoverview'"
));
$this->addElement('image', 'btnSave', array (
'name' => 'btnSave',
'id' => 'btnSave',
'label' => '',
'title' => 'Save this new group',
'alt' => 'Save this new group',
'src' => '/img/save.png',
'onClick' => "document.forms[0].submit();"
));
Edit:
I already thought of the possibility to check in /admin/creategroup whether it was called from the 'btnBack'-image or the 'btnSave'-image and skip form validation and redirect correctly if the source was the 'btnBack'-image.
I just think that there should be a nicer solution to directly redirect from the form and circumvent calling /admin/creategroup again.
Edit2:
My view script:
<div id="createGroupMask">
<br/>
Use the form below to create a new group
<?php
$this->form->setAction($this->url());
echo $this->form;
?>
</div>
My action in the controller:
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
// Data for new group is valid
...
} else {
// Form data was invalid
// => This is where I land when pressing the 'back' image
// No further code here
}
}
$this->view->form = $form;
}
Now there is something to work with:
The isValid() loop is incorrect, your form will never evaluate as inValid with respect to the elements you've presented, you will never get to the else.
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
// Data for new group is valid
...
} else {
/* This is incorrect */
// Form data was invalid
// => This is where I land when pressing the 'back' image
// No further code here
}
}
$this->view->form = $form;
}
My problem is that I'm not sure what is going to be submitted from your form, I'm not really familiar with how your using "onClick" and what I presume is javascript. It looks like element btnBack should redirect on click and element btnSave should POST. However this does not seem to be happening.
I have done this type of thing in PHP and ZF with submit buttons, perhaps the flow of what I did will help:
NOTE: for this type of flow to work you must give the button element a label. The label is used as the submit value.
//psuedoCode
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
//I would probably opt to perform this task with a switch loop
if ($form->getValue('btnBack') === some true value) {
$this->_redirect('new url');
}
if ($form->getValue('btnSave') === some true value) {
//Process and save data
}
} else {
//Display form errors
}
$this->view->form = $form;
}
I think when all is said and done the crux of your problem is that you did not give your button elements a label.
I tried adding labels to my images, but this didn't work.
I also tried to use the isChecked() method on my btnBack-image like this:
if ($form->btnBack->isChecked()) {
// 'Go back' image was clicked so this is no real error, just redirect
}
This didn't work either.
I finally was able to check which image was clicked via the following method as answered in Zend form: image as submit button:
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
// Data for new group is valid
...
} else {
// Form data was invalid
if (isset($this->_request->btnBack_x)) {
// 'Go back' image was pressed, so this is no error
// -> redirect to group overview page
$this->_redirect('/admin/groupoverview');
}
}
}
$this->view->form = $form;
}
I guess this doesn't thoroughly answer the original question as the validation is still done and I'm only checking for this 'special case' where the 'Go back' image was clicked, but I'll mark it as answered anyways.
Tim Fountain suggested an even cleaner approach in my somewhat related question:
Zend forms: How to surround an image-element with a hyperlink?
I use the following code over and over in my zend framework application. It is used in action() to check if an article exists. If not, the user shall see an error message:
$article = ArticleQuery::create()->findOneByUrl($this->_getParam('url', ''));
if (!$article) {
$this->getResponse()
->setRawHeader('HTTP/1.1 404 Not Found');
return $this->_forward('error', null, null, array(
'message' => 'Article not found',
));
}
I was wondering how to factor this out into an own method to reduce the code load in all actions.
I came to something like this:
protected function myAction() {
$article = $this->getArticleIfExists($this->_getParam('url', ''));
if ($article == null) {
return;
}
}
protected function getArticleIfExists($url) {
$article = ArticleQuery::create()->findOneByUrl($this->_getParam('url', ''));
if ($article) {
return $article;
} else {
$this->getResponse()
->setRawHeader('HTTP/1.1 404 Not Found');
$this->_forward('error', null, null, array(
'message' => 'Article not found',
));
return nulL;
}
}
I still would like to get rid of the if case in myAction(), but _forward() does not allow to exit the execution (of course, because it still needs to execute the other actions).
Another possibility (I have implemented in some other controllers) is this:
protected function myAction() {
$article = ArticleQuery::create()->findOneByUrl($this->_getParam('url', ''));
if (!$article) {
return $this->notFound('Article does not exist');
}
}
protected function notFound($message) {
$this->getResponse()
->setRawHeader('HTTP/1.1 404 Not Found');
return $this->_forward('error', null, null, array(
'message' => $message,
));
}
Again we have this if check in the action. It’s already better than before, but can we make it even better?
How can I circumvent this? Is there a possibility to do it without losing the current URL? With a Redirector I can of course exit, but then I would lose the current URL (/controller/myaction/url/hello -> /error/error/message/Article%20not%20found)
A possible approach would be to throw an Exception. Because of the Zend_Controller_Plugin_ErrorHandler this will automatically redirect you to the ErrorController without any further code being executed.
If you don't want to get to the ErrorController but only to the current controller's error actions, you can simply modify the plugin in the controller's init method:
public function init()
{
$plugin = Zend_Controller_Front::getInstance()->getPlugin('Zend_Controller_Plugin_ErrorHandler');
$plugin->setErrorHandlerModule('default')
->setErrorHandlerController('MyController')
->setErrorHandlerAction('error');
}
But of course you can also write your own ErrorHandler plugin for a more fine grained error handling. This is described in the Zend Framework Manual on Plugins
For something as simple as just displaying a "* does not exist" against a user request I prefer to leave the user in the application and just hit them with a flashmessenger notice and leave them on the page to make another request (if appropriate):
public function indexAction() {
//get form and pass to view
$form = new Form();
$this->view->form = $form;
try {
//get form values from request object
if ($this->getRequest()->isPost()) {
if ($form->isValid($this->getRequest()->getPost())) {
$data = $form->getValues();
// Do some stuff
}
}
} catch (Zend_Exception $e) {
$this->_helper->flashMessenger->addMessage($e->getMessage());//add message to flashmessenger
$this->_redirect($this->getRequest()->getRequestUri());//perform redirect to originating page so the messenger will flash
}
}
This is simple and works well when the possibility for incorrect user input exists.