How to update a table record using laravel inertia and vue.js - laravel

I use Laravel 8 inertia and vue
I want to update a post and I use this component in a main vue
<template>
<div class="container p-3 bg-green-600 flex flex-col">
<div class="mb-8 text-2xl">{{ current_post.title }}</div>
<div v-if="!edit" id="mode-display">
<div v-html="compiledMarkdown" class="text-gray-700"></div>
</div>
<div v-else id="mode-edit">
<form class="flex flex-col" #submit.prevent="updatePost">
<input type="hidden" name="id" id="id" v-model="current_post.id">
<button type="submit">Mettre à jour</button>
<div class="flex">
<div class="m-5 flex flex-col">
<label for="category">Catégorie du post</label>
<select class="px-2" name="category" id="category" v-model="current_post.category">
<option value="undefined">Sans</option>
<option value="Announcements">Annonce</option>
<option value="Narratives">Récit</option>
<option value="Pages">Page</option>
</select>
</div>
<div class="m-5 flex flex-col">
<label for="diaporama_dir">Dossier du diaporama</label>
<input name="diaporama_dir" id="diaporama_dir" type="text" placeholder="admin|1/Noël2019" v-model="current_post.diaporama_dir">
</div>
</div>
<div class="flex">
<div class="m-5">
<label for="beg_date">Date de début de l'événement</label>
<date-picker name="beg_date" format="YYYY-MM-DD" valueType="format" v-model="current_post.beg_date"></date-picker>
</div>
<div class="m-5">
<label for="end_date">Date de fin de l'événement</label>
<date-picker name="end_date" format="YYYY-MM-DD" valueType="format" v-model="current_post.end_date"></date-picker>
</div>
<div class="m-5">
<label for="close_date">Date de clôture des inscriptions</label>
<date-picker name="close_date" format="YYYY-MM-DD" valueType="format" v-model="current_post.close_date"></date-picker>
</div>
<div class="m-5 flex flex-col">
<label for="receive_registration">Accepte des inscriptions</label>
<select class="px-2" name="receive_registration" id="receive_registration" v-model="current_post.receive_registration">
<option value="false">Non</option>
<option value="true">Oui</option>
</select>
</div>
</div>
<input class="p-5 mb-5 text-xl" type="text" v-model="current_post.title" />
<div class="m-5 flex flex-col">
<label for="abstract">Résumé</label>
<textarea class="markdown bg-green-500 text-gray-100" name="abstract" id="abstract" v-model="current_post.abstract" rows="3"></textarea>
</div>
<div class="m-5 flex flex-col">
<label for="body">Résumé</label>
<textarea class="markdown bg-green-500 text-gray-100" name="body" id="body" v-model="current_post.body" rows="50"></textarea>
</div>
</form>
</div>
</div>
</template>
<script>
import DatePicker from 'vue2-datepicker';
import 'vue2-datepicker/index.css';
import 'vue2-datepicker/locale/fr';
import marked from 'marked';
export default {
name: "PostDetails",
props: ["current_post", "edit"],
components: {
DatePicker
},
data() {
return {
form:{
id:null,
title: null,
abstract: null,
body: null,
category: null,
beg_date: null,
end_date: null,
close_date : null,
receive_registration : null,
diaporama_dir: null
}
};
},
methods:{
updatePost(){
this.$inertia.post('/post', this.form);
}
},
computed: {
compiledMarkdown: function () {
if (this.current_post) {
//transform markdown to html
return marked(this.current_post.body);
}
},
mounted() {},
}
};
</script>
When displaying this template with the form , I receive a correct post and its values are correctly displayed in the various input fields.
My controller, at the moment is very simple :
public function Update(Request $request)
{
dd($request);
}
On submitting this form the dd outputs this:
Illuminate\Http\Request {#43 ▼
#json: Symfony\Component\HttpFoundation\ParameterBag {#35 ▶}
#convertedFiles: null
#userResolver: Closure($guard = null) {#342 ▶}
#routeResolver: Closure() {#351 ▶}
+attributes: Symfony\Component\HttpFoundation\ParameterBag {#45 ▶}
+request: Symfony\Component\HttpFoundation\ParameterBag {#35 ▼
#parameters: array:10 [▼
"id" => null
"title" => null
"abstract" => null
"body" => null
"category" => null
"beg_date" => null
"end_date" => null
"close_date" => null
"receive_registration" => null
"diaporama_dir" => null
]
}
+query: Symfony\Component\HttpFoundation\InputBag {#51 ▶}
+server: Symfony\Component\HttpFoundation\ServerBag {#47 ▶}
+files: Symfony\Component\HttpFoundation\FileBag {#48 ▶}
+cookies: Symfony\Component\HttpFoundation\InputBag {#46 ▶}
+headers: Symfony\Component\HttpFoundation\HeaderBag {#49 ▶}
#content: "{"id":null,"title":null,"abstract":null,"body":null,"category":null,"beg_date":null,"end_date":null,"close_date":null,"receive_registration":null,"diaporama_dir ▶"
#languages: null
#charsets: null
#encodings: null
#acceptableContentTypes: null
#pathInfo: "/post"
#requestUri: "/post"
#baseUrl: ""
#basePath: null
#method: "POST"
#format: null
#session: Illuminate\Session\Store {#392 ▶}
#locale: null
#defaultLocale: "en"
-preferredFormat: null
-isHostValid: true
-isForwardedValid: true
-isSafeContentPreferred: null
basePath: ""
format: "html"
}
It seems to me I am obeying the guidance given in this page https://inertiajs.com/forms but why on earth the form's value are not uploaded to the server?

In your code, you're sending the form object (which is full of null values).
For now, you can fix it like this:
updatePost () {
this.$inertia.post('/post', this.current_post);
}
But probably you're having a vue-warn saying that it's not recommended to edit a prop.
so I suggest also the following:
you receive the post prop as current_post
you should copy it to your form object (on mounted)
link all of your form inputs to the form object instead of current_post

I can not add a comment yet but wanted to add that if You clone a reactive object You are not actually copying it. The cloned object is the same as the original and modifying this new object modifies the original object too and vice versa so basically You are doing exactly the same thing that got You in trouble at the first place: Modifying properties.
Vue states that You should not modify a property because if a parent component modifies a prop your changes are gone.
Lodash clonedeep creates a new object from the property:
this.items = _.cloneDeep(this.data);

Related

Multiple Checkbox with textbox enable/disable & array

I have multiple checkbox field and when any checkbox checked then 3 input field appear. I want to insert data for 1 checkbox value with 3 input value.
Form:
<div class="form-check">
<input class="form-check-input fighting_style" name="txt_fightingStyle[]" type="checkbox" value="MMA" id="MMA">
<label class="form-check-label" for="flexCheckDefault">
MMA
</label>
<section class="fighiting_value" id="MMA_input" style="display: none;">
<div class="row">
<div class="col-3">
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label text-dark">WIN</label>
<input type="text" class="form-control fights txt_win"
name="txt_win[]" onblur="totalFights()" id="txt-win" placeholder="">
</div>
</div>
<div class="col-3">
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label text-dark">LOSS </label>
<input type="text" class="form-control fights txt_loss" data-points = "75"
name="txt_loss[]" onblur="totalFights()" id="txt-loss" placeholder="">
</div>
</div>
<div class="col-3">
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label text-dark">DRAW</label>
<input type="text" class="form-control fights txt_draw" data-points = "150"
name="txt_draw[]" onblur="totalFights()" id="txt-draw" placeholder="">
</div>
</div>
</div>
</section>
</div>
Javascript:
document.querySelectorAll("input[type=checkbox]").forEach(cb=>cb.addEventListener("click",ev=>{
let sibl=(cb.closest("label")??cb).nextElementSibling;
while(sibl&&!sibl.matches("section"))
sibl=sibl.nextElementSibling;
sibl.style.display=ev.target.checked?"":"none";
}));
document.querySelectorAll("input[type=text]").forEach(t=>t.addEventListener("blur",ev=>totalFights()));
This is my dd result
"txt_win" => array:12 [▼
0 => null
1 => null
2 => null
3 => "4"
4 => null
5 => "4"
6 => null
7 => null
8 => null
9 => null
10 => null
11 => null
]
"txt_loss" => array:12 [▼
0 => null
1 => null
2 => null
3 => "5"
4 => null
5 => "5"
6 => null
7 => null
8 => null
9 => null
10 => null
11 => null
]
"txt_draw" => array:12 [▼
0 => null
1 => null
2 => null
3 => "6"
4 => null
5 => "6"
6 => null
7 => null
8 => null
9 => null
10 => null
11 => null
]
my controller:
$dataWin = $request->txt_win;
$dataLoss = $request->txt_loss;
$dataDraw = $request->txt_draw;
foreach ($dataFightingStyle as $fightStyle) {
FighitData::create([
'fighting_style' => $fightStyle,
'winning_game' =>$dataWin,
'lost_game' => $dataLoss,
'draw_game' => $dataDraw,
]);
}
I just want to which checkbox box checked only when the check box is selected only the data of its text field can be taken and the rest will be disabled. Is there any way to do this.
I i understood your problem correctly, you need to get text fields related to checkboxes. In this case you could try something like that :
$dataWin = $request->txt_win;
$dataLoss = $request->txt_loss;
$dataDraw = $request->txt_draw;
foreach ($dataFightingStyle as $key => $fightStyle) {
FighitData::create([
'winning_game' =>$dataWin[$key],
'lost_game' => $dataLoss[$key],
'draw_game' => $dataDraw[$key],
]);
}

How to show Array of errors in Vue.js ? Backend Validation with Laravel

I have some complex data and I want to show the validation error array data in vue file but I can not do it because I have got some data that has an index and showing like contacts.0.name: ["...."].
Please share your opinion how I can show the error.
vue file
<template>
<div>
<form enctype="multipart/form-data" #submit.prevent="handleSubmit">
<div v-for="(contact, index) in contacts" :key="index" class="row">
<div class="col col-md-3">
<div class="form-group mb-4">
<label for="personName">Contact Person Name</label>
<input
id="personName"
v-model="contact.name"
type="text"
class="form-control"
/>
<small> Want to show here the error ? </small
>
</div>
</div>
<!-- Add or Remove button -->
<div class="col col-md-12 text-right">
<div class="row ml-4">
<div v-show="index == contacts.length - 1">
<button
class="btn btn-warning mb-2 mr-2 btn-rounded"
#click.prevent="add"
>
Add More
</button>
</div>
<div v-show="index || (!index && contacts.length > 1)">
<button
class="btn btn-danger mb-2 mr-2 btn-rounded"
#click.prevent="remove"
>
Remove
</button>
</div>
</div>
</div>
</div>
</form>
</div>
</template>
<script>
export default {
data() {
return {
contacts: [
{
name: "",
},
],
errors: [],
};
},
methods: {
handleSubmit() {
let data = new FormData();
data.append("contacts", JSON.stringify(this.contacts));
Request.POST_REQ(data, "/add-institute")
.then(() => {
alert("success");
})
.catch((err) => {
this.errors = err.response.data.errors;
});
},
add() {
this.contacts.push({
name: "",
email: "",
phone: "",
alternate_phone: "",
});
},
remove(index) {
this.contacts.splice(index, 1);
},
},
};
</script>
controller file
public function add_institute(Request $request) {
$request['contacts'] = json_decode($request['contacts'], true);
$request->validate([
'contacts.*.name'=> 'unique:institute_contact_people|distinct',
]);
...rest of code of insert
return response()->json("Success...");
}
Getting Error Response data
errors: {
contacts.0.name: ["The contacts.0.name has already been taken.", "The contacts.0.name field has a duplicate value."]
0: "The contacts.0.name has already been taken."
contacts.1.name: ["The contacts.1.name has already been taken.", "The contacts.1.name field has a duplicate value."]
0: "The contacts.1.name has already been taken."
}
Okay, so your error data is basically an object with array of errors in it.
Pretty much like this
errors: {
'contacts.0.name': [
'The contacts.0.name has already been taken.',
'The contacts.0.name field has a duplicate value.',
],
'contacts.1.name': [
'The contacts.1.name has already been taken.',
'The contacts.1.name field has a duplicate value.',
],
},
For me, it will be better if you could achieve something like this as an error response (an array of objects with errors array in it)
betterErrors: [
{
location: 'contact.0.name',
errors: [
'The contacts.0.name has already been taken.',
'The contacts.0.name field has a duplicate value.',
],
},
{
location: 'contact.1.name',
errors: [
'The contacts.1.name has already been taken.',
'The contacts.1.name field has a duplicate value.',
],
},
],
For me, as of right now, it feels wrong but you can achieve a display of your errors with something like this
<template>
<div>
<div v-for="(error, key) in errors" :key="key">
<hr />
<span v-for="(errorItem, innerKey) in error" :key="innerKey" style="margin-top: 2rem">
{{ errorItem }}
</span>
</div>
</div>
</template>
<script>
export default {
data() {
return {
errors: {
'contacts.0.name': [
'The contacts.0.name has already been taken.',
'The contacts.0.name field has a duplicate value.',
],
'contacts.1.name': [
'The contacts.1.name has already been taken.',
'The contacts.1.name field has a duplicate value.',
],
},
}
},
}
</script>
PS: having a :key with an array looping index is really bad tbh. That's why I do recommend a location field in your error response.
in your controller
$request->validate([
'ClinicName' => 'required|string|min:200',
'Branches.*.BranchName'=>'required|string|min:200'
]);
in your vue3 file, to access the errors which will have keys such as,
'Branches.0.BranchName'
then you can access the above error with for loop similar to this
<p v-if="form.errors['Branches.' + counter + '.BranchName']"
class="mt-2 text-sm text-red-600 dark:text-red-500">
{{ form.errors["Branches." + counter + ".BranchName"] }}
</p>
here the counter can be any counter starting from 0.

How to define an onclick function on Laravel Collectives?

I have implemented a drop down using Laravel collectives. I need to call function setMaterialValue(let x){ console.log(x)} on each time I select a material. This should be specific to each material as cotton-10, wetlook-20, crocodile-30 etc. Without Laravel collective this can be performed as
<option onclick="setMaterialValue(10);">Cotton</option>
How to perform this using Laravel collectives?
My code is as follows:
<div class="card">
<div class="card-header"><h2 class="card-title m-0">Feature Selector</h2></div>
<div class="card-body">
<h5 class="card-title"><b>Material Selector</b></h5>
<div class="row">
<div class="col-md-6">
Textile Material
</div>
<div class="col-md-6">
{{Form::select('material_selector', [
'10' => 'Cotton',
'20' => 'Wet Look',
'30' => 'Crocodile',
], null, ['placeholder' => 'Select Material'],['class'=>'form-control'])
}}
</div>
</div>
<hr>
</div>
</div>
FYI - Where your class declaration is add it and any other html attributes there as well:
{{ Form::select('material_selector',
[
'1' => 'Cotton',
'2' => 'Wet Look',
'3' => 'Crocodile',
],
null,
['placeholder' => 'Select Material'],
[
'class'=>'form-control',
'onclick'=>'setMaterialValue(10)' // <== ADD IT HERE
])
}}
You should probably use jQuery. Then you can address your select element as follows
$(document).ready(function() {
$('.form-control[name="material_selector"]').on('change', showSelectedValue);
function showSelectedValue(event) {
var target = $(event.target);
console.log(target.val() + " = " + target.find('option:selected').text());
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select name="material_selector" class="form-control">
<option value="10">Cotton</option>
<option value="20">Wet Look</option>
<option value="30">Crocodile</option>
</select>
Another option
If you really do not want to use jQuery, then try to use the onChange attribute for the select tag like the example below
{{Form::select(
'material_selector', // name attribute of the select
['10' => 'Cotton', '20' => 'Wet Look', '30' => 'Crocodile'], // option values
null, // selected value, for example '20'
['placeholder' => 'Select Material', 'class' => 'form-control', 'onChange' => 'showSelectedValue(this)'], // attributes for <select>
)}}
function showSelectedValue(element) {
console.log(element.value + " = " + element.options[element.selectedIndex].text);
}
<select name="material_selector" class="form-control" onChange="showSelectedValue(this)">
<option value="10">Cotton</option>
<option value="20">Wet Look</option>
<option value="30">Crocodile</option>
</select>

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'score' cannot be null

Controller
public function getScore(Request $request, $id)
{
// $scores = Criteria::find($id);
$contestants = Contestant::find($id);
foreach ($request->criteria as $id => $criteria){
$criteriaModel = Score::find($id);
$scores = new Score();
$scores->judge_name = $request->input('judge_name');
$scores->contestant = $contestants->name;
$scores->criteria = $criteriaModel->criteria;
$scores->score = $scores->score;
$scores->save();
}
return redirect('/tabulation')->with('status', 'Score saved!');
}
Blade
#foreach ($criterias as $criteria)
<div class="form-group col-md-6">
<label for="{{$criteria->name}}">{{$criteria->name}} </br> (0 - {{$criteria->points}})</label>
<input type="text" name="criteria[{{$criteria->id}}][criteria]" value="{{$criteria->name}}" hidden>
<input type="text" name="score[{{$criteria->id}}][score]" class="form-control" placeholder="Input score" required>
</div>
#endforeach
Form field names can contain brackets to store multiple properties for a single name:
#foreach ($criterias as $criteria)
<div class="form-group col-md-6">
<label for="{{$criteria->name}}">{{$criteria->name}} </br> (0 - {{$criteria->points}})</label>
<input type="text" name="criterias[{{$criteria->id}}][name]" value="{{$criteria->name}}" hidden>
<input type="text" name="criterias[{{$criteria->id}}][points]" class="form-control" placeholder="Input score" max="{{$criteria->points}}" name="score" required>
</div>
#endforeach
The above form would result the $request->criterias variable containing the following value:
array:2 [▼
1 => array:2 [▼
"name" => "test"
"points" => "dd"
]
2 => array:2 [▼
"name" => "tes22t"
"points" => "sdsd"
]
]
This value can be used in the controller for creating multiple scores:
foreach ($request->criterias as $id => $criteria){
$criteriaModel = Criteria::find($id);
$scores = new Score();
$scores->judge_name = $request->input('judge_name');
$scores->contestant = $contestants->name;
$scores->criteria = $criteriaModel->name;
$scores->score = $criteria->points;
$scores->save();
}
First of all you have to change the name of your input to be an array like so:
<input type="text" name="criteria[]" value="{{$criterias->name}}" hidden>
and in your controller you have to loop through the inputs:
foreach ($request->input('criteria') as $criteria){
$scores = new Score();
$scores->judge_name = $request->input('judge_name');
$scores->contestant = $contestants->name;
$scores->criteria = $request->input('criteria');
$scores->score = $request->input('score');
$scores->save();
}

Toggle form in nested v-for loop in VueJS

I have a list of nested comments. Under each comment, I'd like to add a "reply" button that, when click, show a reply form.
For now, everytime I click a "reply" button, it shows the form. But the thing is, I'd like to show only one form on the whole page. So basically, when I click on "reply" it should close the other form alreay opened and open a new one under the right comment.
Edit :
So I was able to make some slight progress. Now I'm able to only have one active form opening on each level of depth in the nested loop. Obviously, what I'm trying to do now is to only have one at all.
What I did was emitting an event from the child component and handle everything in the parent component. The thing is, it would work great in a non-nested comment list but not so much in my case...
Here is the new code:
In the parentComponent, I have a handleSelected method as such:
handleSelected (id) {
if(this.selectedItem === id)
this.selectedItem = null;
else
this.selectedItem = id;
},
And my childComponent:
<template>
<div v-if="comment">
<div v-bind:style=" iAmSelected ? 'background: red;' : 'background: none;' ">
<p>{{ comment.author.name }}<br />{{ comment.created_at }}</p>
<p>{{ comment.content }}</p>
<button class="button" #click="toggle(comment.id)">Répondre</button>
<button class="button" #click="remove(comment.id)">Supprimer</button>
<div v-show="iAmSelected">
<form #submit.prevent="submit">
<div class="form-group">
<label for="comment">Votre réponse</label>
<textarea class="form-control" name="comment" id="comment" rows="5" v-model="fields.comment"></textarea>
<div v-if="errors && errors.comment" class="text-danger">{{ errors.comment[0] }}</div>
</div>
<button type="submit" class="btn btn-primary">Envoyer</button>
<div v-if="success" class="alert alert-success mt-3">
Votre réponse a bien été envoyée !
</div>
</form>
</div>
</div>
<div v-if="comment.hasReply">
<div style="margin-left: 30px;">
<comment v-for="comment in comments"
:key="comment.id"
:comment="comment" #remove-comment="remove"
:is-selected="selectedItem" #selected="handleSelected($event)">
</comment>
</div>
</div>
</div>
</template>
<script>
import comment from './CommentItem'
export default {
name: 'comment',
props: {
isSelected: Number,
comment: {
required: true,
type: Object,
}
},
data () {
return {
comments: null,
fields: {},
errors: {},
success: false,
loaded: true,
selectedItem: null,
}
},
computed: {
iAmSelected () {
return this.isSelected === this.comment.id;
}
},
methods: {
remove(id) {
this.$emit('remove-comment', id)
},
toggle(id) {
this.$emit('selected', id);
},
handleSelected(id) {
if(this.selectedItem === id)
this.selectedItem = null;
else
this.selectedItem = id;
},
},
mounted(){
if (this.comment.hasReply) {
axios.get('/comment/replies/' + this.comment.id)
.then(response => {
this.comments = response.data
})
}
}
}
</script>
Thanks in advance for your help!

Resources