Action not found after AJAX request - yii2

Before for submitting I want to send the request to an action. But in return I get 404 not found. The action is obviously there. Also got it in the filters of the controller.
JS:
$('#home-contact').on('beforeSubmit', function(){
$.ajax({
method: 'post',
url: '/site/send-contact',
data: new FormData($(this))[0],
success: function(data){
console.log(data)
}
})
return false
})
Controller filters:
'access' => [
'class' => AccessControl::className(),
'only' => ['logout', 'signup', 'send-contact'],
'rules' => [
[
'actions' => ['signup', 'send-contact'],
'allow' => true,
'roles' => ['?'],
],
[
'actions' => ['logout'],
'allow' => true,
'roles' => ['#'],
],
],
],
And the action also :
public function actionSendContact()
{
Yii::$app->response->format = Response::FORMAT_JSON;
$model = new Contact();
if($model->load(Yii::$app->request->post()) && $model->save()){
return $data['success'] = Yii::t('app', 'Successfully sent message.');
}
return $data['error'] = Yii::t('app', 'Something went wrong. Please try again.');
}
The scenario happens in the frontend if that matters somehow. Thank you!

Not sure about the 404 you are having as the url in the request is correct and the url that would be generated for the ajax request will be like http://example.com/site/send-contact but only if you are using the 'enablePrettyUrl' => true, for the urlManager component, otherwise it should be like index.php?r=site/index that could only be the reason behind the 404, a better way is to get the url from the form action attribute.
Apart from above,
You are using the new FormData() to send the data with the request like
data: new FormData($(this))[0]
which isn't correct and won't send any FormData with the request as it will return undefined, you can check the console or by using the print_r($_POST) inside the action sendContact once you are done with the 404, it should be
data: new FormData($(this)[0]),
you need to get the form via $(this)[0] and pass to the new FormData().
But this is not enough you have to set the contentType and processData to be false to correctly submit the FormData or you will get the exception in console
Uncaught TypeError: Illegal invocation
So your code should be like
$('#contact-form').on('beforeSubmit', function(){
let url=$(this).attr('action');
let form=$(this)[0];
let data=new FormData(form);
$.ajax({
method: 'post',
url: url,
data:data,
contentType: false, // NEEDED, DON'T OMIT THIS (requires jQuery 1.6+)
processData: false,
success: function(data){
console.log(data)
}
});
return false;
})
EDIT
Note: Above all you should use data:$(form).serialize() simply rather than using new FormData() until unless you are planning to upload files along with the form using ajax.

URL in JavaScript seems to be not fully specified.
The valid way would be:
url: 'index.php?r=site/send-contact',
But this works only if your index.php is in root folder.
To make it work with any situation (e.g., index.php is not in root), then you have different solutions. I personally like to use this:
Declare action and id in your form:
Example:
$form = ActiveForm::begin([
'action' => ['site/send-contact'],
'id' => 'my-form',
]);
Use a link (that is generated by Yii2 itself) in your JS code:
Example:
$('#home-contact').on('beforeSubmit', function() {
$.ajax({
method: 'post',
url: $('#my-form').attr('action'),
data: new FormData($(this))[0],
success: function(data) {
console.log(data)
}
});
return false;
});
This should work in all cases, whenever your files are.
Hope that helps!

did you add 'enableAjaxValidation' => true or add action name in active form?
$form = ActiveForm::begin([
'action' => ['controller/action'],
'enableAjaxValidation' => true
]);

Related

How can i upload image file and data object with axios in Vue & Laravel?

I'm struggling to upload an image and several text/numeric data fields to a MySQL DB with Vue, Laravel, and Axios.
Here's the method in my Vue component:
addProductToShop() {
const imageData = this.$refs.inputUpload.files[0];
const formData = new FormData();
formData.append("image", imageData);
axios
.post("api/createProduct", {
formData,
...this.newProduct
})
.then(res => {
console.log(res);
});
},
And here's the method in my Laravel controller:
public function create(Request $request)
{
$product = Product::create([
'name' => $request->name,
'price' => $request->price,
'amountInStock' => $request->amountInStock,
'image' => $request->image->store('ProductImages', 'public'),
'measuringUnitId' => $request->measuringUnitId,
'categoryId' => $request->categoryId,
'shopId' => $request->shopId,
]);
return response()->json([
'status' => 'success',
'message' => 'Product added seccessfully',
'product' => $product
], 200);
}
Here's the request body in telescope:
{
"formData": [],
"name": "111",
"categoryId": null,
"price": "111",
"amountInStock": "111",
"measuringUnitId": 2,
"visability": true
}
And here's the error that i'm getting: Call to a member function store() on null
I've tried adding multi-form data headers:
headers: {
'Content-Type': 'multipart/form-data'
}
However, they generated this error: Missing boundary in multipart/form-data POST data in Unknown on line 0
So, they seem obsolete now, because axios knows the corrent formData based on the FormData().
Can someone point out what am i doing wrong? Thanks in advance!
Error here:
'image' => $request->image->store('ProductImages', 'public')
Correct way:
'image' => $request->file('image')->store('ProductImages', 'public')

new FormData works on one site but not on other

In my previous project I have:
let send = (form) => {
form = $(form)
...........
$.ajax({
method: 'post',
url: form.attr('action'),
data: new FormData(form[0]),
contentType: false,
processData: false
})
..........
return false
}
And my form looks like:
<?php $form = \yii\widgets\ActiveForm::begin([
'id' => 'w0',
'action' => 'site/send-contact',
'fieldConfig' => [
'options' => [
'tags' => false
]
]
]) ?>
................ some fields ............
\yii\widgets\ActiveForm::end(); ?>
Here, using the new FormData(..) works totally fine. You can check it here in the console networks tab. I have added var_dump($_POST) in the action. Now on my new project, new FormData(..) doesn't work. Absolutely no idea why. The dumped array is empty.
$.ajax({
method: 'get',
url: 'site/index',
data: {
data: new FormData($("#w0")[0])
},
})
And the form:
$form = \yii\bootstrap\ActiveForm::begin();
........... some fields here ............
\yii\bootstrap\ActiveForm::end()
Tried with post,get,contentType,processData just for any case but still empty array. Any suggestions? Thank you!
You can use Form submit event to submit form using ajax:
jQuery(document).ready(function($) {
$(".formclass").submit(function(event) {
event.preventDefault(); // stopping submitting
var data = $(this).serializeArray();
var url = $(this).attr('action');
$.ajax({
url: url,
type: 'post',
dataType: 'json',
data: data
})
.done(function(response) {
if (response.data.success == true) {
alert("Wow you commented");
}
})
.fail(function() {
console.log("error");
});
});
});
For more info

Yii2 : ajax post request always redirect to login page

I'm trying to send data using ajax POST request but I'm always redirected to login page even if I authorize the method to anonymous users (['actions' => ['update', 'test'], 'allow' => true]). When I test with a GET request there is no problem.
My controller :
public function behaviors() {
return [
'access' => [
'class' => AccessControl::className(),
'rules' => [
['actions' => ['update', 'test'], 'allow' => true],
],
],
];
}
public function actionTest() {
Yii::$app->request->enableCsrfValidation = false;
echo 'ok';
}
I use Postman to test requests
The solution you used is not a solution actually, its like if you cant open the lock with a key just remove the lock.
Mostly you get the 400 Bad Request if you are making an Ajax Post request without sending the CSRF parameters, I never faced the problem like being redirected to the login page.
But if your problem is resolved by disabling the CSRF Validation then you should follow this method while making any ajax requests.
In your config, you define the csrf parameter name using csrfParamin the request component like this
'request' => [
'csrfParam' => '_csrf-app',
],
This can be different for you if it is already defined.
You have to send this param _csrf-app with the csrf value in the ajax request along with your post data. and for retrieving the value for the csrf you can use javascript yii.getCsrfToken() method, or Yii::$app->request->csrfToken if in view via php.
See this example call you can hardcode the name of the param _csrf-app or use Yii::$app->request->csrfParam if your script is inside the view file.
$.ajax({
url:'/add',
data:{'_csrf-app':yii.getCsrfToken()}
success:function(data){}
});
Hope this solves your problem.
lubosdz's suggestion solved the problem
Modified controller :
public function behaviors() {
return [
'access' => [
'class' => AccessControl::className(),
'rules' => [
['actions' => ['update', 'test'], 'allow' => true],
],
],
];
}
public function beforeAction($action) {
if ($action->id == 'test') {
$this->enableCsrfValidation = false;
}
return parent::beforeAction($action);
}
public function actionTest() {
echo 'ok';
}
I found a solution that looks a bit like yours Muhammad Omer Aslam : by serializing the form in JavaScript I can send directly all the fields and the csrf token generated by the ActiveForm.
$.ajax({
url: ...,
type: 'POST',
data: $('#myForm').serialize(),
success: (response) => { ... }
})

How do I replace the default auth.basic response with a JSON response?

I have a route group that looks like this:
Route::group(['prefix' => 'recipe','middleware'=>['auth.basic']], function (){
//Some things to do
});
When credentials are invalid Laravel outputs "Invalid credentials." How do I override this response with my own JSON response?
In AuthController, try this :
public function postLogin(Request $request)
{
$this->validate($request, [
'email' => 'required', 'password' => 'required',
]);
$credentials = [
'email' => $request->input('email'),
'password' => $request->input('password')
];
if (Auth::attempt($credentials, $request->has('remember')))
{
return redirect()->intended($this->redirectPath())
->with('success', 'You are successfully logged in');
}
return Response::json(array(
'success' => false,
'errors' => $this->getFailedLoginMessage(),
));
}
I just had a look at the Illuminate\Auth\SessionGuard. The method getBasicResponse() seems to be responsible for the response on a failed login attempt (with basic auth).
protected function getBasicResponse()
{
$headers = ['WWW-Authenticate' => 'Basic'];
return new Response('Invalid credentials.', 401, $headers);
}
How to actually overwrite it seems a little tricky though. You probably need to extend the SessionGuard Class and implement your own getBasicResponse() method. Thats the easy part, how to actually instantiate your own guard instead of the default one, I don't know yet.

Keep getting a 404 when using Ajax in drupal

Hello i've got this problem with an ajax call in my (newbie) Drupal site. I'm trying to save some data from a form field, by posting it with jQuery to a function in my Drupal module. Here is my code:
// in drupal
function mymodule_menu() {
$items = array();
$items['mymodule/set/data'] = array(
'page callback' => 'mymodule_set_data',
'type' => MENU_SUGGESTED_ITEM,
'access arguments' => array('access content'),
);
return $items;
}
function mymodule_set_data($var) {
drupal_json_output(array('status' => 'OK', 'data' => "return_something"));
}
// in my js file
jQuery("#form_element").on('blur',function(){
jQuery.ajax({
type: 'POST',
url: "mymodule/set/data",
dataType: 'json',
data:{
fu: 'bar'
},
success: function(data) {
console.log(data);
},
error: function(jqXHR, textStatus, errorThrown){
console.log(errorThrown);
}
});
});
Everything goes well, the jQuery get's triggerd, the ajax call is being catched on the server, and i do get the {"status":"OK","data":"return_something"} back from the server. Except for the fact that the status of the call is a 404... :(
I found an solution to my problem.
Ik was moving my app to an other host and there for i needed to change the url the ajax action was calling. I decided to make it dynamic with the following code:
drupal_add_js(array('url' => $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']), 'setting');
I changed my ajax call url into:
"http://"+Drupal.settings.url + "/set/data
This resulted in my JS calling the following URL:
http://host/drupal/?q=mymodule/set/data
instead of the old one:
http://host/drupal/mymodule/set/data
Which was something Drupal did like :)
Still i don't understand why my drupal accepted http://host/drupal/mymodule/set/data, handled my data but returned a 404.