Bein a noob, learning laravel and Vue, I stumble upon the problem where I need to mix strings and files in a single request.
Following a tutorial I managed to create a text based CRUD, based on an api and VUe frontend.
Users can create cards with a title and a description.
I want to make a game where they also add a picture to the card. Now I cannot figure out how to get the request, containing text and image-file to vue methods.
I intend to save the image name to the database, using a fetch('cards/api') and save the file itself to the storage/images folder.
I cannot figure out how get hold of the image-file and save, while sending the filename to the api.
I am lacking the full understanding of content types and handling of the requests...
I tried to implement some examples I found on the fora, but when I change the content type in the fetch-methods breaks the existing, working, application where I at least can add, edit, and delete the text records..
Cannot figure out how to make a request with both the text fields and the image file, like I was used in standard laravel applications.
Can any help me shed some light on this?
My vue component is:
template>
<div>
<h2>Cards</h2>
<form @submit.prevent="addCard" class="mb-3">
<div class="form-group">
<input type="text" class="form-control" placeholder="title" v-model="card.title">
</div>
<div class="form-group">
<textarea class="form-control" placeholder="description" v-model="card.description">
</textarea>
</div>
<div>
<input type="file" v-on:change="onFileChange" id="file_picture_input">
</div>
<button type="submit" class="btn btn-primary btn-block" style="color:white">Save</button>
</form>
<div class="card card-body mb-2" v-for="card in cards" v-bind:key="card.id">
<h3> {{card.title}} </h3>
<p> {{card.description}}</p>
<img class="img-circle" style="width:150px" v-bind:src="card.picture" alt="Card Image">
<button @click="deleteCard(card.id)" class="btn btn-danger m-1" style="width:100px;color:white"> Delete </button>
<button @click="editCard(card)" class="btn btn-warning m-1" style="width:100px;color:black"> Edit </button>
</div>
</div>
</template>
<script>
export default {
mounted(){
console.log('component mounted')
},
data() {
return{
cards:[],
card:{
id:'',
title:'',
description:'',
picture:'',
file:''
},
card_id:'',
edit:false
}
},
created(){
this.fetchCards();
},
methods: {
fetchCards(){
fetch('api/cards')
.then(res => res.json())
.then(res=>{
console.log(res);
this.cards = res;
})
},
deleteCard(id){
if(confirm('Are you sure?')){
fetch(`api/cards/${id}`, {
method: 'delete'
})
.then(res => res.json)
.then(data => {
alert('Card removed');
this.fetchCards();
} )
.catch(err => console.log(err));
}
},
addCard(){
if(this.edit === false){
//add
fetch(`api/cards`, {
method: 'post',
body:JSON.stringify(this.card),
headers:{
'content-type': 'application/json',
}
})
.then(res => res.json)
.then(data => {
this.card.title='';
this.card.description='';
alert('Card added');
this.fetchCards();
})
.catch(err => console.log(err));
} else{
//update
fetch(`api/cards/${this.card.card_id}`, {
method: 'put',
body:JSON.stringify(this.card),
headers:{
'content-type': 'application/json'
}
})
.then(res => res.json)
.then(data => {
this.card.title='';
this.card.description='';
alert('Card updated');
this.fetchCards();
})
.catch(err => console.log(err));
}
},
editCard(card){
this.edit=true;
this.card.id=card.id;
this.card.card_id=card.id;
this.card.title=card.title;
this.card.description=card.description;
},
onFileChange(e){
console.log(e.target.files[0]);
this.file = e.target.files[0];
}
}
}
</script>
The Controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Card;
use Intervention\Image\Facades\Image;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
class CardController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$cards = Card::all()->toArray();
return array_reverse($cards);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//validate data first
$request->validate([
'description'=> 'required'
]);
$card = $request->all();
if ($request->hasFile('file_picture_input')){
$this->validate($request, ['file_picture_input' => 'max:2000'], [__('Maximum file size of 2MB allowed')]);
$image_name = strval(time().Str::random(5)).'.png';
$request->file->move(storage_path('images'),$image_name);
$path = storage_path('images'.$image_name);
$card['picture'] = $path;
}
else {
$card['picture'] = 'img/profile/default_avatar.png';
}
//create a post
return Card::create($card);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
return Card::find($id);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
$card = Card::find($id);
$card->update($request->all());
return $card;
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
return Card::destroy($id);
}
}