I have a nuxt frontend using the vue-filepond adapter, users have the option to upload images with there post. This is then send to a laravel API that will handle the request.
<client-only>
<file-pond
name="image"
ref="pond"
class="filepond"
:allow-multiple="false"
accepted-file-types="image/jpeg, image/png"
server="http://127.0.0.1:8000/api/posts"
allowRevert="false"
:files="form.image"
/>
</client-only>
using mostly default filepond options,
data() {
return {
errors: [],
form: {
title: '',
content: '',
image: [],
}
}
},
Data is uploaded to the api like so
methods: {
createPost() {
this.$axios.$post('http://127.0.0.1:8000/api/posts', this.form)
this.$toast.show({
type: 'success',
title: 'Success',
message: 'Your post has been created'
})
}
}
Now since filePond is async the file is uploaded earlier then my form when I post it.
so in the laravel part
public function store(Request $request): void
{
if ($request->hasFile('image')) {
$path = Storage::putFile('avatars', $request->file('image'));
}
$request->validate([
'title' => 'required|string|max:24',
'content' => 'required|string|max:254',
'image' => 'nullable|image'
]);
Post::create([
'title' => $request->get('title'),
'slug' => Str::slug($request->get('title'), '-'),
'content' => $request->get('content'),
'image' => $path ?? null
]);
}
The image would be stored, but if I click submit on my form to upload a title and some content the ìmage part in the Post::create method is always NULL.
How can I make it so that filePond is not uploaded async anymore? so that when I hit submit on my form the title , content and image are all uploaded equally
Figured it out thanks to kissu and reading through filePond's docs.
const file = this.$refs.pond.getFiles()[0].file
const data = new FormData()
data.append('title', this.form.title)
data.append('content', this.form.content)
data.append('image', file)
this.$axios.$post('http://127.0.0.1:8000/api/posts', data)
and in your backend (laravel in my case)
if ($request->hasFile('image') && $request->file('image')->isValid()) {
$post->addMediaFromRequest('image')->toMediaCollection('image');
}
Related
I have a form in my application that allows users to create posts and while doing so upload multiple images to the post being created.
I am using Laravel Livewire and Filepond to achieve this.
The problem I am having is I need to allow the user to reorder the images (as it is a gallery and the order is important), and save the order in the database when the form in submitted.
Another issue I am running into is allowing a user to edit their post later. I need their pre-existing post images loaded in filepond, and also allow them to upload more, delete, and/or reorder.
When the user saves the post I need to be able to update my database and file system.
All info online is how to upload files, but no info on how to reorder, or pre-populate with pre-existing files.
Here is my current code for reference:
<div
x-data=""
x-init="
FilePond.setOptions({
allowMultiple: true,
allowReorder: true,
itemInsertLocation: 'after',
server: {
process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {
#this.upload('images', file, load, error, progress)
},
revert: (filename, load) => {
#this.removeUpload('images', filename, load)
},
load: (source, load, error, progress, abort, headers) => {
var myRequest = new Request(source);
fetch(myRequest).then(function(response) {
response.blob().then(function(myBlob) {
load(myBlob)
});
});
},
},
});
const pond = FilePond.create($refs.input, {
acceptedFileTypes: ['image/png', 'image/jpeg'],
maxFileSize: '7MB',
allowImageCrop: true,
allowReorder: true,
allowImageResize: true,
imageResizeTargetWidth: '1000px',
imageResizeTargetHeight: '1000px',
filePosterMaxHeight: '256px',
files: {{ $existingImages }} // used for when editing a post and it already has images. see php component on how I set this variable
});
"
>
<div wire:ignore wire:key="images">
<div class="form-group text-center">
<input
id="image-upload"
type="file"
x-ref="input"
multiple
data-allow-reorder="true"
data-max-file-size="3MB"
data-max-files="10"
>
</div>
</div>
</div>
My Livewire PHP component:
public $images = [];
public $existingImages;
public function mountMedia($post) {
if($post){
$this->existingImages = $post->images->map(function ($image) use ($post) {
return [
'source' => $image->id,
'options' => [
'type' => 'local',
'file' => [
'name' => $image->getUrl(),
'size' => $image->file_size,
'type' => $image->mime_type,
],
'metadata' => [
'poster' => $image->getUrl(),
'position' => $image->position
],
],
];
});
}
}
public function saveImage($file, $post, $position) {
// Create a unique random string
$randString = Str::random(3);
// Get time
$time = time();
// Set file name
$filename = $time. '-' . $randString.'-'.auth()->user()->id;
$extension = '.'.$file->getClientOriginalExtension();
// Save images for gallery
$regImage = $file->storeAs('/'. $post->id, $filename.$extension, 'post_images');
// Create a new image in db
Image::create([
'user_id' => auth()->user()->id,
'post_id' => $post->id,
'position' => $position,
'filename' => $filename,
'extension' => $extension,
'src' => 'post_images',
'mime_type' => $file->getMimeType(),
'file_size' => $file->getSize(),
]);
}
public function saveMedia($post) {
// Make sure user owns post
abort_unless($post->user_id == auth()->user()->id, 403);
// Set default position
$position = 1;
// Save each image
foreach ($this->images as $file) {
$this->saveImage($file, $post, $position);
// Increment position for next image
$position++;
}
}
}
For sorting items in Livewire I would use https://github.com/livewire/sortable.
Sortable is very easy to use.
For filepond if the original image should be used again later I would save that image as well with a relation to the edited version.
In my update form i have a file field that i want to update so i'm using following code and created a form object but when i pass form object i cannot get the data of form object in my controller.
let formData = new FormData();
formData.append('name', this.form.name);
formData.append('description', this.form.description);
formData.append('file_update', this.form.file_update);
axios.put(`/api/saveClass/${this.ItemId}`,formData,{headers:header})
.then((response)=>{
var msg = response.data.message;
if(response.status ==200){
this.$Progress.finish();
}else{
this.$Progress.fail();
}
this.$router.push(`/api/showClass`);
})
.catch(() => {
});
},
Laravel Controller:
public function update(Request $request,$id)
{
$ItemStore = class::find($id);
$this->validate($request,[
'name' => 'required|string|max:191|unique:class,name,'.$id,
'description' => 'required',
]);
}
I am getting name and description field is required but i'm passing data to it.
I want to upload the image from Flutter. But I am getting this error:
The GET method is not supported for this route. Supported methods:
POST.
But I set my api route as POST method. And I also sending POST method request, but still I am getting this error.
But one more thing, it works on POSTMAN and INSOMNIA. There is no problem.
I use this header:
Content-Type: multipart/form-data
Authorization: ....
Please help me.
My route is:
Route::post('/avatar/update', 'Api\ProfileController#avatar_update')->name('api.avatar.update');
My controller:
public function avatar_update(Request $request){
$request->validate(array(
'avatar' => 'required|image',
));
try{
$image = Image::make($request->avatar)->fit(250, 250);
$photo_name = Auth::user()->username."-".strtotime(now()).'.'.$request->avatar->extension();
$path='images/avatars/'.$photo_name;
$image->save($path);
if(File::exists(Auth::user()->avatar)) {
File::delete(Auth::user()->avatar);
}
Auth::user()->update([
'avatar' => 'http://attendance.isadma.com/'.$path,
]);
return response()->json([
'status' => 1,
'message' => 'Picture updated.',
'image' => Auth::user()->avatar
], 200);
}
catch (\Exception $e){
return response()->json([
'status' => 0,
'message' => $e->getMessage()
], 500);
}
}
Flutter request code:
#override
Future<String> uploadProfilePic(File profilePic, String token) async {
var postUri = Uri.parse("$url/avatar/update");
print(profilePic.path);
print(postUri);
var request = http.MultipartRequest("POST", postUri);
request.headers['authorization'] = "Bearer $token";
request.headers['Content-Type'] = "multipart/form-data";
request.files.add(
await http.MultipartFile.fromPath(
'avatar',
profilePic.path,
contentType: MediaType('image', 'jpg'),
filename: basename(profilePic.path),
),
);
print(request.headers);
request.send().then((res) async {
print(res.headers);
print(res.statusCode);
print(await res.stream.bytesToString());
}).catchError((e) {
print(e);
});
}
make sure you are sending the csrf data (_token) in your post request
I have registration form with username, email, password and avatar(image) fields. Everything works except image filed, which can be null. I am using Vue as front-end and send data with axios to Laravel.
This is validation:
public function register(Request $request)
{
$request->validate([
'username' => 'required|string|max:255|unique:users',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6',
'avatar' => 'nullable|image|mimes:jpg,jpeg,png|max:1999'
]);
$fileNameToStore = 'noimage.jpg';
return User::create([
'username' => $request->username,
'email' => $request->email,
'password' => Hash::make($request->password),
'avatar' => $fileNameToStore
]);
}
And this is how I send data:
register(context, data) {
let formData = new FormData();
formData.append('avatar', data.avatar)
formData.append('username', data.username)
formData.append('email', data.email)
formData.append('password', data.password)
return new Promise((resolve, reject) => {
axios.post('/register', formData,{
headers: {
'Content-Type': 'multipart/form-data'
}
})
.then(response => {
resolve(response)
})
.catch(error => {
reject(error)
})
})
}
If I fill out every filed it works fine and also other things like checking extension or file size works, only problem is when I don't select any image. I have nullable included in validation, but I still get error message from validation that it must be an image and which extension it needs to have.
If your data.avatar is undefined or null, your server will eiter receive a undefined or null string as a value for your avatar field. Therefore, Laravel will be testing the image rule on the string.
To fix it, you can make sure your image is not undefined to send it in your request.
if (data.avatar) {
formData.append('avatar', data.avatar);
}
Or
formData.append('avatar', data.avatar ? data.avatar : '');
this is because of data.avatar send to backend as an empty string and
you can write a watch for data.avatar, which every time data.avatar is empty string change it to null . like the following code :
watch(){
'data.avatar'(value){
if(value == '' || value == undefined){
this.data.avatar = null
}
}
}
This is how I did mine and it worked for me very well, I just added a ref attribute on the input that should be the user avatar and then retrieved the value of the input through Vue as follows this.$refs.photo.files[0]:
In my HTML using Vue
<input id="image" class="opacity-0" type="file" ref="photo">
and in my js using Vue
var data = new formData();
if(this.$refs.photo.files.length > 0)
data.append('photo', this.$refs.photo.files[0]);
axios.post(...).then(...);
i want to post ajax request using vue-resource this.$http.post request. it worked perfectly fine if i passed all validation rules but i want to get some validations if it fails. so far i keep getting 500 error if i don't fill out some input fields. it's hard for me to debug the error because it didn't appeared on the network tab.
here's what i've done so far
//my modal component
<script>
export default {
props: ['show'],
data() {
return {
input: {
id: '',
name: '',
address: '',
email: ''
},
errorInputs: {}
}
},
methods: {
createStudent() {
this.$http.post('/students', this.$data.input)
.then((response) => {
alert('added new row!)
}, (response) => {
console.log(response.data);
});
}
}
}
</script>
// my controller
public function store(Request $request) {
$validator = $this->validate($request,[
'id' => 'required',
'name' => 'required|unique:students',
'email' => 'required|unique:students|email',
'address' => 'required',
]);
if($validator->passes()){
Student::create($request->all());
return response()->json([], 201);
}
$errors = json_decode($validator->errors());
return response()->json([
'success' => false,
'message' => $errors
],422);
}
any helps and references would be appreciated. i am using laravel 5.3 and vue js 2
$this->validate() returns 422 error response alongside your validation errors, so you should get those errors in then() second callback (like you do now). Your vue component body should be like this:
{
data() {
// ...
},
createStudent() {
this.$http
.post('/students', this.input)
.then(this.handleSuccess, this.handleError)
},
handleSuccess(res) {
alert('student created')
},
handleError(res) {
if (res.status === 422) {
this.errorInputs = res.body
} else {
alert('Unkown error!')
}
}
}
Remember to add v-model="input.fieldName" properties to your inputs.
Remember to include your session token along with your post, unless of course you are disabling csrf tokens for that route.
Since Laravel 5.1 you can disable this in your verifytoken middleware
<?php namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as ...
class VerifyCsrfToken extends ... {
protected $except = [
'payment/*',
];
}