How to customize the CRUD response toaster message [admin-on-rest] - admin-on-rest

I want to add server response message in CRUD response toaster. For Example when we do an update, we will get 'Element updated' toaster message. Instead of it I want to show some dynamic (not static) server responded message.

This is only supported for error messages currently. If this is really important, please open a feature request and we'll consider it.

A slightly long winded way to do this. But definitely possible.
1) Write a custom restWrapper or RestClient
https://marmelab.com/admin-on-rest/RestClients.html
2) Handle the request and response from it like below.
function handleRequestAndResponse(url, options={}, showAlert={}) {
return fetchUtils.fetchJson(url, options)
.then((response) => {
const {headers, json} = response;
//admin on rest needs the {data} key
const data = {data: json}
if (headers.get('x-total-count')) {
data.total = parseInt(headers.get('x-total-count').split('/').pop(), 10)
}
// handle get_list responses
if (!isNaN(parseInt(headers.get('x-total-count'), 10))) {
return {data: json,
total: parseInt(headers.get('x-total-count').split('/').pop(), 10)}
} else {
return data
}
})
}
3) you now have a place in your code where you can intercept the data from the server. In above code you can define and shoot actions containing your data whenever you need. Create a reducer that takes the data from your action and populates a field in the state you can call it notification.
4) Use the redux-saga select method
How to get something from the state / store inside a redux-saga function?
You can now access the notification data from the store and show custom toaster messages to your heart's content :)

Related

What is the correct way to send validation errors back to a Vue component from a Laravel controller?

[I've revised this question a bit to consider another approach. I also apologize if this question seems unduly long. I suppose I'm a bit long-winded but I'm really just trying to be very clear.]
I am fairly new to Laravel, Vue and Vuetify but, after a lot of struggling, I've gotten my CRUD app to work with MySQL, at least as long as I pass my Controller good data. But now I'm struggling with the right way to do error handling.
I use Vuetify to do my front-end error checking and it seems to work very well. I've been able to write validations for every condition I could think of and display a meaningful error message where it is most appropriate to show it. I know I need to do the same validations in my Laravel controller back-end and writing good validations there seems pretty straightforward too. However, I'm having trouble figuring out how to communicate a back-end validation error to my Vue component.
My CRUD app is doing the classic To Do List. One of the fields on my input form for adding a new task is the due date for the new task and one of the edits that should be done on both the front-end and the back-end is that the due date can't be in the past. I've "accidentally" omitted that check on the front-end but have included it on the back-end to be sure the back-end will detect it when I choose a past date as my due date. My Laravel controller detects that as intended and my browser console shows this:
Clearly, the validations in the controller are working and they have correctly detected the problem with the due date. Now, my problem is how to get the relevant information to my Vue component and, when relevant (as it is in this case), how do I display it to my user?
All the examples I could find that were for Laravel apps that use Vue components had them using the then and catch blocks to deal with the Axios response and error information. The other option that occurs to me is to access the error bag generated by the Laravel controller but I can't find any information on how that could be done in a Vue component so it looks like I have to take the Axios approach....
I cannot figure out how to display the relevant information from the Response Payload in my Vue component in the catch block. In every example I've found, the Response is returned to the then block and the Error is returned to the catch block but when there is an error, the then block never gets executed in favour of the catch block. But when the catch block executes, it can't see the Response so it can't show me anything from the Response, like the validation message. All the searching I've done for answers has left me more confused than enlightened since many answers assume the validation errors are going back to a Laravel blade while others are for much older versions of Laravel. (I am running Laravel 8.x.)
I know that not all errors will be user errors. For example, the database could be down, making all access to the database impossible. I also need to note those situations. The user needs to be told something - perhaps "The database is down. Please try again later." - and the admins need to be advised that the database is down. I'm still trying to figure out the best way to do that but am leaning towards ErrorBoundary as the solution.
For the moment, I'll be delighted if someone can explain how to get the information I want from the Response payload when there is a user-fixable error. Some advice on how to handle situations where the error is NOT user-fixable would be even better.
Here is the relevant bit of my Insert logic in the Vue component:
/* We are creating a new item so determine the id for the new item. It should be
one greater than the highest id currently in the array. Every id of an existing
task should always be at least 1. */
console.log(this.name + ".save() - saving a new item");
var highestTaskID = 0; //must be one less than the lowest possible id value
for (let todo of this.todos) {
if (todo.id > highestTaskID) highestTaskID = todo.id;
}
var newTaskID = highestTaskID + 1; /* Calculate the ID of the new task. */
this.editedItem.id = newTaskID;
this.form.id = this.editedItem.id;
this.form.description = this.editedItem.description;
this.form.status = this.editedItem.status;
this.form.priority = this.editedItem.priority;
this.form.due = this.editedItem.due;
let data = new FormData();
data.append('id', this.form.id);
data.append('description', this.form.description);
data.append('status', this.form.status);
data.append('priority', this.form.priority);
data.append('due', this.form.due);
axios.post('/task', data)
.then((res) => {
console.log(this.name + ".save() - response from insert (then): " + res);
this.snackbarMessage = "Created new task";
this.showMessageSnackbar = true;
this.showTable = true; //start showing ToDo table again
this.form.reset();
this.getTasks();
})
.catch((error) => {
console.log(this.name + ".save() - response from insert (catch): " + res);
console.log(this.name + ".save() - error: " + error);
this.snackbarMessage = "Failed to create new task";
this.showMessageSnackbar = true;
this.showTable = true; //start showing ToDo table again
})
This is the store() method in my TaskController:
public function store(Request $request)
{
app('debugbar')->info('TaskController.store() started');
$today = date('Y-m-d');
$this->validate($request, [
'description' => ['required', 'min:5', 'max:191'],
'status' => ['required'],
'priority' => ['required'],
'due' => ['required', 'after-or-equal:' . Date('Y-m-d')]
],
[
'description.required' => 'You must provide a non-blank task description',
'description.min' => 'The task description must contain at least 5 characters',
'description.max' => 'The task description must not exceed 191 characters',
'status.required' => 'You must provide a task status',
'status.in' => 'You must choose a task status from: Pending or Completed',
'priority.required' => 'You must provide a task priority',
'priority.in' => 'You must choose a task priority from: Low, Medium or High',
'due' => 'You must provide a task due date',
'due.after_or_equal' => 'You must provide a due date greater than or equal to today'
]
);
app('debugbar')->info('TaskController.store() validations completed');
Task::create($request->all());
}
Should I be logging any and all errors I detect in the Controller itself, rather than in the Vue component? That might be a lot easier in some respects, although I still have to be able to detect which errors can be passed back to the user for them to handle and when I simply have to tell them to try later because the app isn't working fully yet.
First of all, I'd advise using axios' Response Interceptor which is essentially a callback that gets executed every time a request has been completed so you don't have to do error handling on each and every request.
Create a new file (for example axiosInstance.js)
const axiosInstance = axios.create({
baseURL: 'https://example.com/api',
});
axiosInstance.interceptors.response.use((response) => {
return response;
}, (error) => {
switch(error.response.status) {
case 422:
// Here you will be able to process Laravel Validation Errors.
// You can for example display an error message using your favorite library
console.log("Error Message: " + error.response.data.message);
console.log("Error Object: " + error.response.data.errors);
break;
}
return Promise.reject(error);
}
export default axiosInstance;
As you can see you can access your response by using error.response. This works for catch blocks in simple axios requests aswell.
axios.get('https://example.com/xyz').catch(e => {
console.log(e.response);
});
The above interceptor in my first code block will do special handling for all Laravel Validation Errors since those are returned using HTTP Status 422 (Unprocessable Entity) by default.
To use it, instead of doing axios.get() you can do:
import axiosInstance from './axiosInstance.js';
axiosInstance.get('https://example.com/xyz');
The logic you defined in the catch block of the interceptor will then be executed every time a request that was initiated using axiosInstance. You can also append .catch() again to the request to handle additional logic if a specific requests fails.
To handle additional error types, extend the interceptors switch conditional statement. For example, exceptions like "The database is not available" are returned with status code 500 by Laravel. Usually, a simple message is then available to display to the user by using error.response.data.message.
tl;dr
This is what you could do achieve what you are trying to do using your code:
axios.post('/task', data)
.then((res) => {
console.log(this.name + ".save() - response from insert (then): " + res);
this.snackbarMessage = "Created new task";
this.showMessageSnackbar = true;
this.showTable = true; //start showing ToDo table again
this.form.reset();
this.getTasks();
})
.catch((error) => {
console.log(this.name + ".save() - response from insert (catch): " + error.response);
// Given that your erroneous response contains 'message' (like laravel validation errors do):
console.log(this.name + ".save() - error: " + error.response.data.message);
this.snackbarMessage = "Failed to create new task";
this.showMessageSnackbar = true;
this.showTable = true; //start showing ToDo table again
})

Vue API Calls and Laravel Middleware

Is it possible to globally set a listener on API calls made with Axios in Vue? The Laravel back-end has middleware set up on each endpoint that will either give the requested data or return a message saying that they need to check their messages. My goal is to capture that message and redirect the user to the page to view their message. I can't think of a way to do this other than setting something on each function that checks for the message and responds accordingly. There are hundreds of functions and that it wouldn't be a clean solution.
Any and all recommendations are welcome!
Using Axios Interceptors you can do something along these lines:
this.$http.interceptors.response.use(response => () {
// Redirect to a new page when you send
// custom header from the server
if (response.headers.hasOwnProperty('my-custom-header')) {
window.location.href = '/another-page';
}
// Or when you get a specific response status code
if (response.status === 402) {
window.location.href = '/another-page';
}
// Or when the response contains some specific data
if (response.data.someKey === 'redirect') {
window.location.href = '/another-page';
}
// ...or whatever you want
});

Intercept observables before subscription callback

I am using the following code to make get request:
makeGetReq$(url:string):Observable{
let getReqObservable;
getReqObservable = this.httpClient.get(url) //code for making get request
return getReqObservable
}
The problem is sometimes my backend might return {error:true, message} with status code 200. (I know thats weird).In that case I want to intecept getReqObservable and not allow its subscription callback to run.
image.component.ts
makeGetReq$(url:string):Observable{
let getReqObservable;
getReqObservable = this.httpClient.get(url)//code for making get request
return getReqObservable
.do((value)=>{
if(value.error){
//do not allow it to propagate further
})
})
You should propagate it further, but as an error rather than an event (i.e. do just like if your backend did the right thing and returned an error response):
makeGetReq$(url: string): Observable<Something> {
return this.httpClient.get<Something>(url).pipe(
mergeMap(value => value.error ? throwError(value) : of(value))
);
}
Otherwise, the calling method has no way to know that an error occurred, and thus can't execute the callbacks it might have registered for errors.
The easiest would probably be filter.
Filter items emitted by the source Observable by only emitting those that satisfy a specified predicate.
It would look like this:
return getReqObservable
.filter(value => !value.error)
It was pointed out, that you lose the notification completely if you just filter out the error case. There is of course the option to create a RxJS error notification with throwError, but it is also possible to just subscribe to the same source observable a second time with a different filter condition.
Be careful however to not call the backend twice, e.g. by using share.

omnipay/Braintree find customer not working

i am working on laravel api, integrated omnipay/braintree, i have successfully created my customer, what i need to get customer data through this,
$mycustomer = $omnipay_gateway->findCustomer(5)->send();
but it giving me bad response like,
<pre>Omnipay\Braintree\Message\CustomerResponse Object
(
[request:protected] => Omnipay\Braintree\Message\FindCustomerRequest Object
(
[braintree:protected] => Braintree\Gateway Object
(
[config] => Braintree\Configuration Object
its a huge chunck of data which i am not pasting here, how i get my customer details through this type of data, and it shows in this format, why not in proper json or some other format?
Note:
Not only findCustomer , all functions give same sort of response,
how we can traverse it.
Call $mycustomer = $omnipay_gateway->findCustomer(5)->send()->getData(); please
The simple answer for future readers is something like
// Process response
if ($response->isSuccessful()) {
// Payment was successful
// $url = $response->getRedirectUrl();
print_r($response->getData());
} elseif ($response->isRedirect()) {
// Redirect to offsite payment gateway
$response->redirect();
} else {
// Payment failed
echo $response->getMessage();
}

How to handle application errors for json api calls using CakePHP?

I am using CakePHP 2.4.
I want my frontend make api calls to my CakePHP backend using ajax.
Suppose this is to change passwords.
Change password action can throw the following application errors:
old password wrong
new password and confirm new passwords do not match
In my frontend, I have a success callback handler and a error callback handler.
The error callback handler handles all the non 200 request calls such as when I throw NotFoundException or UnAuthorizedAccessException in my action.
The success callback handler handles all the 200 request calls including of course, the above 2 scenarios.
My questions are:
Should I continue to do it this way? Meaning to say, inside all success callback handler, I need to watch out for application success and application error scenarios.
Should I send application errors back with actual HTTP error codes?
if I should do 2, how do I implement this in CakePHP?
Thank you.
Don't use http error codes for system errors like:
old password wrong
new password and confirm new passwords do not match
etc etc...
Now using success handler you can show messages and code flow as:
Create Ajax post or get to submit the form, I am showing you post example
var passwordValue = $('#password').val();
$.post( "/updatePassword", { passwordText: passwordValue })
.done(function(response) {
if(response.status === 'Success'){
// Success msg
// whatever
}else{
// Error msg
// whatever
}
});
json response would like:
{
"status": "Failed/Success",
"message": "old password wrong."
}
Create one function in controller
public function updatePassword() {
$myModel = $this->MyModel->find('first' // YOUR CODE LOGIC);
if($this->request->is('ajax') {
$this->layout=null;
// What else?
echo json_encode($myModel);
exit;
// What else?
}
}
Do something like this, hope it will solve your query!

Resources