VueJS: $_FILES Not Receiving Data from Frontend to Backend - javascript

I tried different ways to upload images to SQL, but the problem is that I cannot get the file's data at backend. Some answers say that it's because the form does not have an enctype but I tried that too. I'm not sure if it's applicable to Vue since I am using axios. Also, I tried using uploadFile as a parameter for the axios so $_FILES would also read it as $_GET, because it worked on some of my codes as well. By the way, the submitTestData is in another file in another folder, which is inside a store (VueX). I used dispatch to send the data towards the store so that it would finally send a post method to backend.
store.js
submitTestData2 ({ commit }, payload) {
console.log(payload.uploadFile)
return new Promise((resolve, reject) => {
const formData = new FormData()
formData.append('uploadFile', payload.uploadFile)
const config = {
headers: { 'Content-Type': 'multipart/form-data' }
}
axios
.post(
'http://localhost/MyComposer/',
{
token: payload.token,
subject: payload.subject,
timer: payload.timer,
question: payload.question,
answer: payload.answer,
formData
},
{
params: {
submitId: 7,
uploadFile: formData
},
config
}
)
.then(response => {
commit('SAVE_TEST_DATA', response.data)
console.log(response)
resolve(response)
})
.catch(error => {
reject(error)
})
})
},
AddTest.vue
<q-form class="q-pa-md" align="center">
<h5>Test Creation Form</h5>
<!-- <q-btn label="Add Subject" color="primary" to="/addsub" /> -->
<q-btn label="Return to Main" to="/dashboard" color="primary" />
<q-btn label="View Student Answers" color="primary" to="/subjectntestlist" />
<q-btn label="View Student Profile" color="primary" to="/studentprofile" />
<q-card>
<q-separator />
<q-card-section class="q-gutter-md" align="center">
<q-select
filled
v-model="testItems.subject"
:options="option"
map-options
emit-value
option-value="subjectId"
option-label="subjectName"
label="Choose a Subject"
style="width: 250px"
stack-label
input-debounce="0"
/>
<q-file
filled
v-model="testItems.uploadFile"
label="Upload File Here"
style="width: 500px"
/>
<h5>Timer</h5>
<q-input label="Minute(s)" name="timer" v-model="testItems.timer" style="width: 500px" />
<h5>Question</h5>
<q-input name="question" v-model="testItems.question" style="width: 500px" />
<h5>Answer</h5>
<q-input name="answer" v-model="testItems.answer" style="width: 500px" />
<br />
<q-btn label="Save Test Item" #click="submitTestData" />
</q-card-section>
</q-card>
</q-form>
submitTestData1() {
this.$store
.dispatch("submitTestData2", {
token: this.token,
subject: this.testItems.subject,
question: this.testItems.question,
answer: this.testItems.answer,
uploadFile: this.testItems.uploadFile,
timer: this.testItems.timer
})
.then(response => {
alert("Test was added to the database!");
});
},
<?php
namespace Classes;
use Classes\ConnectDb;
class TestClass
{
public function addTest()
{
$datab = new ConnectDb;
$db = $datab->Connect();
if (isset($_GET['submitId']) && $_GET['submitId'] == 7) {
$testdata = file_get_contents('php://input');
$testdecodedData = json_decode($testdata);
$subject = $testdecodedData->{'subject'};
$access_id = $testdecodedData->{'token'};
$question = $testdecodedData->{'question'};
$answer = $testdecodedData->{'answer'};
// $testImage = $testdecodedData->{'uploadFile'};
$testTimer = $testdecodedData->{'timer'};
$name = $_FILES['uploadFile'];
echo $name;
$testdataDb = array(
'SubjectId' => $subject,
'AccessId' => $access_id,
'Question' => $question,
'Answer' => $answer,
// 'TestImage' => $testImage,
'Timer' => $testTimer * 60
);
$testId = $db->insert('testdetails', $testdataDb);
if ($testId) {
echo 'Test details were added!';
}
}
}

You need to pass header as well like below
submitTestData ({ commit }, payload) {
console.log(payload.uploadFile)
return new Promise((resolve, reject) => {
const formData = new FormData()
formData.append('uploadFile', payload.uploadFile)
const config = {
headers:{'Content-Type' : 'multipart/form-data'}
};
axios
.post('http://localhost/MyComposer/',formData,config)
.then(response => {
commit('SAVE_TEST_DATA', response.data)
console.log(response)
resolve(response)
})
.catch(error => {
reject(error)
})
})
},

It looks like you're not getting the file's name properly from the $_FILES superglobal. You have $_FILES['uploadFile'] in your code however the $_FILES array is structured like this for uploads (uploadFile represents the name of the file upload input field from your form so this varies by input field name):
Array
(
[uploadFile] => Array
(
[name] => users_file_name.png
[type] => image/png
[tmp_name] => /path/to/temporary/files/abc123
[error] => 0
[size] => 12345
)
)
So to access the file's name, you need to change the code to this: $_FILES['uploadFile']['name'].
The actual file is stored in a temporary file location on the server so you'll need to grab that temporary file and move it somewhere else on your server. Something like this is what most people do:
$temp_file = $_FILES['uploadFile']['tmp_name'];
$target_upload_destination = 'path/to/desired/directory/' . basename($_FILES['uploadFile']['name']);
// Check to see that the file was moved to desired destination successfully
if (move_uploaded_file($temp_file, $target_upload_destination)) {
// do something here
} else {
// Fallback logic here
}
Obviously there should be some logic checks before moving the temp file on your server but, I hope you get the basic idea behind this. You should use the file path once it's moved for the DB insert. I hope this helps.

There are several problems here both in your client-side and server-side code.
Client side
If you want to send a file, you must use a multipart/form-data request with a FormData payload. You appear to be trying to combine a JSON payload with an embedded FormData which simply won't work.
You need something like this
const formData = new FormData()
Object.entries(payload).forEach(([key, val]) => {
// adds all the properties in "payload" to "formData"
formData.append(key, val)
})
axios.post('http://localhost/MyComposer', formData, {
params: { submitId: 7 }
})
❗ Note that there is no Content-type header added. Passing a FormData instance sets this automatically with the required mime boundaries.
Server-side
On the PHP side, you would get the token, subject, timer, etc values from $_POST
$subject = $_POST['subject'];
$access_id = $_POST['token'];
$question = $_POST['question'];
// etc
The upload file will be available in $_FILES (see https://www.php.net/manual/features.file-upload.post-method.php)
$uploadFile = $_FILES['uploadFile'];
if (!$uploadFile['error']) {
echo $uploadFile['name'];
}

Related

trying to store image from vue form and send to backend laravel

I'm a newbie and i'm trying to create a rest project with Vue and Laravel and I have a form that allows to send an image, but when I try to store it in the db, I got an error:
"Request failed with status code 422"
and
"The image must be an image"
I can't figure how to solve it, any suggestion?
<script>
export default {
data() {
return {
title: undefined,
year: undefined,
director: undefined,
plot: undefined,
rating: undefined,
image: null,
};
},
methods: {
insertedFile(e) {
this.image = e.target.files[0];
},
addFilm() {
const formData = new FormData;
formData.set('image', this.image)
console.log(formData.get('image'));
//
axios
.post("/api/films", {
title: this.title,
year: this.year,
director: this.director,
plot: this.plot,
rating: this.rating,
image:formData
})
.then((response) => {
console.warn(response)
});
},
},
};
</script>
<template>
<form #submit.prevent="addFilm()" enctype="multipart/form-data" method="post">
<input type="text" name="title" placeholder="title" v-model="title" />
<input type="number" name="year" placeholder="year" v-model="year" />
<input
type="text"
name="director"
placeholder="director"
v-model="director"
/>
<input type="text" name="plot" placeholder="plot" v-model="plot" />
<input
type="number"
name="rating"
placeholder="rating"
v-model="rating"
/>
<input
type="file"
name="image"
id="image"
#change="insertedFile($event)"
/>
<button type="submit">Submit</button>
</form>
</template>
Controller:
public function store(Request $request)
{
$request->validate([
'title' => 'required',
'year' => 'required',
'plot' => 'required',
'director' => 'required',
'rating' => 'required',
'image' => 'image|mimes:jpg,png,jpeg,svg|max:2048'
]);
$film = new Film([
'title' => $request->title,
'year' => $request->year,
'plot' => $request->plot,
'director' => $request->director,
'rating' => $request->rating,
"image" => $request->file('image')->store('images', 'public')
]);
$film->save();
return redirect()->route('home')
->with('success', 'film created successfully!');
}
Try combining your payload (data) with your formData and setting the content-type header of your axios request to multipart/form-data:
const config = {
headers: {
'content-type': 'multipart/form-data'
}
}
let data = new FormData();
data.append('title', this.title);
data.append('year', this.year);
data.append('director', this.director);
data.append('plot', this.plot);
data.append('rating', this.rating);
data.append('image', this.image);
axios.post('api/films', data, config)
.then((response) => {
console.warn(response)
})
.catch((error) => {
console.log(error);
});
you're passing the FormData object as image.
in order to make it work, you should give axios the FormData object containing all the data you want to send.
addFilm method should look like this:
const formData = new FormData;
formData.append('image', this.image)
formData.append('title', this.title)
formData.append('year', this.year)
formData.append('director', this.director)
formData.append('plot', this.plot)
formData.append('rating', this.rating)
formData.append('image', this.image)
axios
.post("/api/films", formData)
.then((response) => {
console.warn(response)
});

Serialize Symfony form data to JSON

Using Symfony Forms, HTML is generated that looks like this:
<input type="text" id="form_name" name="form[name]">
<input type="email" id="form_email" name="form[email]">
<textarea id="form_message" name="form[message]"></textarea>
With a bit of JS the entries are transformed to JSON and submitted:
const contactForm = document.getElementById('contact-form');
contactForm.addEventListener('submit', (event) => {
event.preventDefault();
const formData = new FormData(event.target);
const jsonData = JSON.stringify(Object.fromEntries(formData));
// handle submission...
})
JSON that is sent to the backend:
"{"form[name]":"John Doe","form[email]":"example#domain.com","form[message]":"Some message"}"
In my controller (in PHP) I serialize the data into an array: $data = json_decode($request->getContent()); The issue is this data is formatted (as expected) like so:
["form[name]" => "John Doe", "form[email]" => "example#domain.com", "form[message]" => "Some message"];
Is there a built-in way to get the following result (either in PHP or JS)?
[ "name" => "John Doe", "email" => "example#domain.com", "message" => "Some message" ];
I looked into using the Serializer Component without success, and now wonder if I missed something or if the data should be fixed in JS before submission. Might there be a built-in solution?
If I'm not wrong you are submitting from using AJAX. And in that you can directly specify FormData object as body in AJAX API request. At backend you will receive data in $_POST or $_GET array as per your request method.
Here is the example code.
const contactForm = document.getElementById('contact-form');
contactForm.addEventListener('submit', (event) => {
event.preventDefault();
const formData = new FormData(event.target);
fetch('<AJAX API URL>', {
method: 'POST',
body: formData
}).then(function (response) {
if (response.ok) {
return response.json();
}
return Promise.reject(response);
}).then(function (data) {
console.log(data);
}).catch(function (error) {
console.warn(error);
});
})
<form id="contact-form">
<input type="text" name="form['name']" />
<input type="text" name="form['job']" />
<input type="submit" value="submit" />
</form>
Here is how you will get data in POST array.
Array
(
[form] => Array
(
['name'] => 123
['job'] => 123123
)
)

How to properly render new elements after a POST request?

I have a react page that looks like this:
and right now when creating a new category the post request goes through to the database but the categories is not rendered again to display the new category unless you refresh the page (GET request for all categories on page start up).
SideBar.js
createNewCategory = async (input) => {
console.log("CREATING NEW: ", input);
var response = await fetch("http://localhost:8081/api/categories", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Token": 1234,
Accept: "application/json"
},
body: JSON.stringify({
title: input
})
})
let resp = await response.json();
this.setState({
categories: [...this.state.categories, resp]
})
}
CreateCategory.js
handleNewCategory = (event) => {
event.preventDefault()
this.props.createNewCategory(this.state.input)
this.setState({
input: ''
})
}
render(){
return (
<form onSubmit={this.handleNewCategory} className="new-category-form">
<h4>Create Category</h4>
<input onChange={this.handleInput} className="new-category-input" type="text" value={this.state.input} />
<input className="new-category-input" type="submit" value="Create" />
</form>
)
}
CategoriesContainer.js
function CategoriesContainer(props) {
function renderCategories(){
console.log("PROPS: ", props)
return props.categories.map(category => {
console.log("CATEACH: ", category)
return <Category key={category.id} category={category} />
})
}
return(
<div>
{renderCategories()}
</div>
)
}
At the moment if I create a new category with a name of letters I get the err
Uncaught (in promise) SyntaxError: Unexpected token a in JSON at position 0 sidebar.js:46
and if I create it with numbers I get
Warning: Each child in a list should have a unique "key" prop.
Im still new to react so hopefully Im not completely off the mark here, any ideas?
Fixed it. First off I was using response instead of resp to update the state and I was returning just the name rather than the whole object to the POST request.

Sending an Image From Vue Js to Flask Restful API

I am trying to upload a from on my VueJS client to my Flask Server. I am sending the image in a FormData which includes other form fields. I am able to access the other form fields. However, whatever that is send to my server does not seem to be able to be be saved in my flask app. If it helps the data is being sent to my api as a immutable multi-dictionary.
Vuejs
<template>
<ValidationObserver v-slot="{ handleSubmit }">
<b-form #submit.prevent="handleSubmit(onSubmit)" enctype="multipart/form-data">
<ValidationProvider rules="required" v-slot="{ errors }">
<b-form-group
id="price-group"
label="Price:"
label-for="price-input"
>
<b-form-input
id="price-input"
v-model="productForm.price"
type="text"
required
placeholder="Enter price of the product..."
></b-form-input>
</b-form-group>
<span>{{ errors[0] }}</span>
</ValidationProvider>
<b-button-group>
<b-button
type="submit" variant="primary">Submit</b-button>
</b-button-group>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
productForm: {
price: '',
image_set: {
coverphoto: null,
},
showMessage: false,
notification: '',
},
coverphotoURl: '',
};
},
methods: {
createProduct(payload) {
const path = 'http://127.0.0.1:5000/new_products';
axios.post(path, payload)
.then(() => {
this.showMessage = true;
this.notification = 'Registration Successful';
})
.catch(() => {
this.showMessage = true;
this.notification = payload;
});
},
initForm() {
this.productForm.price = '';
this.productForm.image_set.coverphoto = null;
},
onSubmit() {
const payload = new FormData();
payload.append('price', this.productForm.price);
payload.append('coverphoto', this.productForm.image_set.coverphoto, this.productForm.image_set.coverphoto.name);
this.createProduct(payload);
},
onFileChange(e) {
const file = e.target.files[0];
this.productForm.image_set.coverphoto = file;
this.coverphotoURl = URL.createObjectURL(file);
},
</script>
Flask File
#app.route('/new_products', methods=['POST'])
def new_products():
if request.method == 'POST':
data = request.form
if request.files:
images = request.files.getlist('coverphoto')
else:
return jsonify({'message' : 'No File Uploaded'})
print(images) # Print images will return [<FileStorage: 'Coffee Roasters.jpg' ('image/jpeg')>]
newProduct = Products(str(uuid.uuid4()), data['name'], data['tasting_notes'], data['origination'], data['pairing'], data['price'])
try:
db.session.add(newProduct)
db.session.commit()
filename = secure_filename('coverphoto.jpg')
img_path = os.path.join('/',app.config['UPLOAD_FOLDER'], filename)
images.save(img_path)
new_img = Images(str(uuid.uuid4()), img_path, newProduct.id)
db.session.add(new_img)
db.session.commit()
return jsonify({'message' : 'Upload Successful'})
except:
return jsonify({'message' : 'Upload Unsuccessful'})
It how can i convert [<FileStorage: 'Coffee Roasters.jpg' ('image/jpeg')>] to something that flask is able to save? Thank you for yur help.
I guess you should set the proper header in Axios to be able to retrieve the file on the server.
Use this:
headers: {'Content-Type': 'multipart/form-data' }
})

How would I successfully re-direct to my .js file (reactjs) instead of my php file upon submitting info?

As of now, I'm successfully inserting information into the database (SQL, phpMyAdmin) via Home.js but the problem is that every time the user enters information & hits submit, it gets redirected to my demo.php file instead of Next.js.
In other words, how can I make it so that upon the user information successfully entering the database and go to the next page? (Next.js)?
I know <form action="http://localhost/demo_react/api/demo.php" method={"POST"} encType="multipart/form-data"> will inevitably take me to demo.php but if I don't use this, then nothing gets submitted to my db.
What am I doing wrong and how can I fix this?
Here's Home.js:
import React, { Component } from 'react';
import Next from '../Home/Next';
class Home extends Component {
constructor(props) {
super(props);
this.state = {
show: false
};
this.getPHP = this.getPHP.bind(this);
}
getPHP(e) {
this.setState({show: true});
let formData = new FormData();
fetch(`http://localhost/demo_react/api/demo.php`, {
method: 'POST',
body: formData
}).then(res => res.json())
.then(response => {
console.log('response');
console.log(response);
e.preventDefault();
});
}
render() {
const goNext = this.state.show;
if(goNext) {
return <Next/>;
}
return (
<div>
<form action="http://localhost/demo_react/api/demo.php" method={"POST"} encType="multipart/form-data">
<div className="form-group">
<label htmlFor="username">Email</label>
<input className="form-control" type="text" name="username"/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<input className="form-control" type="password" name="password"/>
</div>
<input className="btn btn-primary" type="submit" value="Login" onSubmit={e => this.getPHP(e)} name={"submit"}/>
</form>
</div>
);
}
}
export default Home;
Here's demo.php:
$connection = mysqli_connect("localhost", "root", "", "loginapp");
$username = $_POST['username'];
$password = $_POST['password'];
if(isset($_POST['submit'])) {
$query = "INSERT INTO users(username, password) ";
$query .= " VALUES('$username', '$password')";
$result = mysqli_query($connection, $query);
if (!$result) {
die("Query failed" . mysqli_error($connection));
} else {
echo "check database";
}
}
Do not use a type="submit" button. Use a type="button" or a <button>. If your form didn't have a submit, the problem is solved.
Obviously, in your JS code you need to send the information. You can collect it and send to demo.php without triggering form submit. I'll show you a basic example for doing it.
var request = window.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest;
var user = (document.getElementById(form).username) ? encodeURIComponent(document.getElementById(form).username.value) : '';
var pass = (document.getElementById(form).password) ? encodeURIComponent(document.getElementById(form).password.value) : '';
var data = 'username=' + user + '&password=' + pass;
request.open('POST', document.getElementById(form).action, false);
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset:UTF-8");
request.setRequestHeader("Content-length", data.length);
request.send(data);
if (request.status == 200) {
alert('Send OK.');
} else {
alert('Send error.');
}
The included snippet get the URL to send information from the form action itself. You can optimize the code a lot probably, but it's a starting point. This code uses plain JS, you can change it to jQuery for example (jQuery version its shorter for example).
Also, if you need to eventually do a redirect, use a JS redirect instead.

Categories

Resources