Vue multiselect laravel submit form - laravel

I have simple form:
<form action="" method="post">
<div class="form-group">
<label for="exampleInputEmail1">Email address</label>
<input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email">
<small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
<input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password">
</div>
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="exampleCheck1">
<label class="form-check-label" for="exampleCheck1">Check me out</label>
</div>
<div class="form-group">
<tags-multiselect></tags-multiselect>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
And Vue tags-multiselect component:
<!-- Vue component -->
<template>
<div>
<multiselect v-model="value" :options="options" :multiple="true" :close-on-select="false" :clear-on-select="false" :preserve-search="true" placeholder="Выберите тэги" label="name" track-by="name" :preselect-first="true">
<template slot="selection" slot-scope="{ values, search, isOpen }"><span class="multiselect__single" v-if="values.length && !isOpen">{{ values.length }} выбрано</span></template>
</multiselect>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
export default {
components: { Multiselect },
data () {
return {
value: [],
options: [
{ name: 'Vue.js', language: 'JavaScript' },
{ name: 'Adonis', language: 'JavaScript' },
{ name: 'Rails', language: 'Ruby' },
{ name: 'Sinatra', language: 'Ruby' },
{ name: 'Laravel', language: 'PHP' },
{ name: 'Phoenix', language: 'Elixir' }
]
}
}
}
</script>
<!-- New step!
Add Multiselect CSS. Can be added as a static asset or inside a component. -->
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
I need select field on the my form. When I send form, In controller I need accept array from select tags. How I can do this with vue multiselect? Or I need use simple select multiple for it? Help me please.

You can solve this by making the options only the identifier (in your case the name) and use a custom label that filters itself. Like this:
<multiselect
v-model="rule.id"
:options="types.map(type => type.name)"
:custom-label="opt => types.find(x => x.id == opt).language">
</multiselect>
Also make sure to remove trackBy property, since your options are no longer objects, its now an array of the form ['Vue.js', 'Adonis',...]
Source: https://github.com/shentao/vue-multiselect/issues/432

Related

Using Vue, Inertia, Laravel, how do I update a file object in an edit component?

I currently have an edit page, which hits an update function to update the Itinerary object when changed.
On this page, if I click submit straight away, and dd() the $request from the ItineraryController, it returns all of the existing form data, as expected.
If I edit the data in the fields, then submit it, it returns successfully with a full request object as expected.
If, however, I choose a file in the "replace file" selector, the entire request object shows as null when the form is submitted, and thus can't be submitted.
How can I adjust this so that the "replace file" input is operational, and fills the request object with the existing itinerary data?
Component:
<template>
<form #submit.prevent="submit">
<div class="row w-75 m-auto">
<h1>Edit Itinerary</h1>
<div class="col-md-6">
<label for="title">Title</label>
<input v-model="form.title" class="form-control" name="title" placeholder="Itinerary Title" type="text" />
</div>
</div>
<div class="row w-75 m-auto">
<div class="col-md-6">
<label for="gen_narrative">Narrative</label>
<textarea v-model="form.gen_narrative" class="form-control" name="gen_narrative" placeholder="Itinerary Narrative"></textarea>
</div>
</div>
<div class="row w-75 m-auto">
<div class="col-md-6">
<label>Current Photo</label>
<img :src="assetUrl(props.itinerary.f_photo)" alt="featured photo" />
</div>
</div>
<br />
<div class="row w-75 m-auto">
<div class="col-md-6">
<label for="f_photo">Replace Photo</label>
<input class="form-control" name="f_photo" type="file" #input="fileChange" />
</div>
</div>
<div class="row w-75 m-auto">
<div class="col-md-6">
<label for="authorid">Author Name</label>
<input v-model="form.authorid" class="form-control" name="authorid" placeholder="Author Name" type="text" />
</div>
</div>
<div class="row w-75 m-auto">
<div class="col-md-6">
<button class="btn btn-primary" type="submit">Edit Itinerary</button>
</div>
</div>
</form>
</template>
<script setup>
import { useForm } from "#inertiajs/inertia-vue3";
function assetUrl(path) {
return process.env.MIX_BASE_URL + "storage/" + path;
}
function fileChange(event) {
form.f_photo = event.target.files[0];
}
let props = defineProps({
itinerary: {
type: Object,
required: true,
},
});
let form = useForm({
title: props.itinerary.title,
gen_narrative: props.itinerary.gen_narrative,
f_photo: null,
authorid: props.itinerary.authorid,
stops: props.itinerary.stops,
});
let submit = () => {
console.log(form.title);
form.patch("/itineraries/" + props.itinerary.id);
form.reset();
};
console.log(form);
</script>
<style scoped></style>
Controller:
public function edit($id)
{
$itinerary = Itinerary::find($id);
return Inertia::render('Itineraries/Edit', [
'itinerary' => $itinerary,
]);
}
public function update(Request $request, $id)
{
$itinerary = Itinerary::find($id);
$itinerary->title = $request->title;
$itinerary->gen_narrative = $request->gen_narrative;
//upload the photo
if ($request->hasFile('f_photo')) {
$itinerary->f_photo = $request->f_photo->store('public/itinerary_photos');
}
$itinerary->authorid = $request->authorid;
$itinerary->save();
return redirect('/itineraries');
}
let form = useForm({
forceFormData: true,
title: props.itinerary.title,
gen_narrative: props.itinerary.gen_narrative,
f_photo: null,
authorid: props.itinerary.authorid,
stops: props.itinerary.stops
})
The best I can come up with is writing your file upload inline
<div class="row w-75 m-auto">
<div class="col-md-6">
<label for="f_photo">Replace Photo</label>
<input class="form-control" name="f_photo" type="file" #input="form.f_photo = event.target.files[0]"/>
</div>
</div>
Based on this example in official documentation you can do
<input type="file" #input="form.f_photo = $event.target.files[0]" />
Your issue is probably because Inertia does not natively support uploading files using a multipart/form-data request for the put, patch or delete methods, as is stated here (in the "Multipart limitations" section).
An alternative way is to submit without form helper using post method with the _method attribute of 'put', like:
Inertia.post("/itineraries/" + props.itinerary.id", {
_method: 'put',
f_photo: form.f_photo,
})

Laravel inertia multi-part form data

I want to create a membership form with picture upload and other input data required. I have a form with input file and input text which to be considered as multi-part data. I am using the Laravel 8 and Inertia.js.
Here is what I've tried:
In my Form, I have like this:
<form action="/member" method="POST" #submit.prevent="createMember">
<div class="card-body">
<div class="form-group">
<label for="exampleInputFile">Picture</label>
<div class="input-group">
<div class="custom-file">
<input type="file" class="custom-file-input" id="exampleInputFile" #input="form.picture">
<label class="custom-file-label" for="exampleInputFile">Choose image</label>
</div>
<div class="input-group-append">
<span class="input-group-text">Upload</span>
</div>
</div>
</div>
<div class="form-group">
<label for="exampleInputEmail1">First name</label>
<input type="text" class="form-control" id="fname" placeholder="First name" v-model="form.firstname">
</div>
<div class="form-group">
<label for="exampleInputEmail1">Middle name</label>
<input type="text" class="form-control" id="mname" placeholder="Middle name" v-model="form.middlename">
</div>
<div class="form-group">
<label for="exampleInputEmail1">Last name</label>
<input type="text" class="form-control" id="lname" placeholder="Last name" v-model="form.lastname">
</div>
<div class="col-md-12">
</div>
<div class="form-group">
<label for="exampleInputEmail1">Name ext</label>
<input type="text" class="form-control" id="next" placeholder="Name extension" v-model="form.name_ext">
</div>
<div class="form-group">
<label>Membership Date:</label>
<div class="input-group date" id="reservationdate" data-target-input="nearest">
<input type="text" class="form-control datetimepicker-input" id="mem_date" data-target="#reservationdate" v-model="form.membership_date"/>
<div class="input-group-append" data-target="#reservationdate" data-toggle="datetimepicker">
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
</div>
</div>
</div>
<div class="form-group">
<label>Date of Birth:</label>
<div class="input-group date" id="reservationdate" data-target-input="nearest">
<input type="text" class="form-control datetimepicker-input" id="date_of_birth" data-target="#reservationdate" v-model="form.date_of_birth"/>
<div class="input-group-append" data-target="#reservationdate" data-toggle="datetimepicker">
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
</div>
</div>
</div>
<div class="form-group">
<label>Sex:</label>
<div class="radio-inline">
<label class="radio radio-solid">
<input type="radio" name="travel_radio" class="details-input" value="Male" v-model="form.sex"/> Male
<span></span>
</label>
<label class="radio radio-solid">
<input type="radio" name="travel_radio" class="details-input" value="Female" v-model="form.sex"/> Female
<span></span>
</label>
</div>
</div>
</div>
<!-- /.card-body -->
<div class="card-footer">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
then I have tried vue and inertia implementation like this:
export default {
props: [],
components: {
AppLayout,
},
data() {
return {
form: {
picture: '',
firstname: '',
middlename: '',
lastname: '',
name_ext: '',
date_of_birth: '',
sex: '',
membership_date: '',
}
}
},
methods: {
createMember() {
this.$inertia.post('/member', this.form, {
_method: 'put',
picture: this.form.picture,
onSuccess: () => {
console.log('success');
// $('.toastrDefaultSuccess').click(function() {
// toastr.success('Successfully Added');
// });
},
onError: (errors) => {
console.log(errors);
},
})
}
}
}
then from my backend, I tried to dd the request and got null from picture:
public function store(MemberPost $request) {
dd($request);
}
Assuming you are using inertia 0.8.0 or above, you can use the inertia form helper. This will automatically transform the data to a FormData object if necessary.
Change your data method to:
data() {
return {
form: this.$inertia.form({
picture: '',
firstname: '',
middlename: '',
lastname: '',
name_ext: '',
date_of_birth: '',
sex: '',
membership_date: '',
})
}
}
and the createMember function to:
createMember() {
// abbreviated for clarity
this.form.post('/member')
}
More info: docs. If you need to migrate FormData from inertia < 0.8.0 to a current version, see this page.
if you using multi-upload remove array from files
before: <input type="file" #input="form.image = $event.target.files[0]" multiple/>
after: <input type="file" #input="form.image = $event.target.files" multiple/>
add enctype="multipart/form-data" to form tag
and you need move the picture to directory. example
$name = $file->getClientOriginalName();
$file->move('uploads/posts/',$name);
sorry, i dont know how to implement this code to inertia.
This way works for me
this.$inertia.post('/member', this.form)

Have a form to edit user account, make default values current values but still use v-model?

I'm trying to make a form to edit your user account in Vue/Laravel.
Ideally I'd like to make the name and email boxes pre-filled with the users current values. I could do that easily using
<input type="text" class="form-control" name="user" autocomplete="false" :value="$parent.user.name">
The issue is that I'd also like to use v-model="form.name" like so:
<template>
<form class="row" #submit.prevent="onSubmit">
<div class="form-group col-md-6 col-lg-3">
<label>Name</label>
<input type="text" class="form-control" name="user" autocomplete="false" :value="$parent.user.name" v-model="form.name">
</div>
<div class="form-group col-md-6 col-lg-3">
<label>Email address</label>
<input type="email" class="form-control" placeholder="Enter email" autocomplete="false" name="email" :value="$parent.user.email" v-model="form.email">
</div>
<div class="form-group col-lg-3">
<label>Password</label>
<input type="password" class="form-control" placeholder="Password" autocomplete="new-password" name="password" v-model="form.password">
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</template>
<script>
export default {
data() {
return {
form: new Form({
name: '',
email: '',
password: '',
})
}
},
methods: {
onSubmit() {
// ajax stuff here
}
}
}
</script>
But this throws an error :value="$parent.user.name" conflicts with v-model on the same element because the latter already expands to a value binding internally which I think I understand as it can't be two models.
Is there a better way to go about getting a form filled with values already, but bind it to some sort of model so I can easily submit the form with axios? (I'm trying to use the Form class from the Vue Laracast tutorial but not sure if there's a better method of doing all of this.) https://github.com/laracasts/Vue-Forms/blob/master/public/js/app.js
It appears I can access $parent.user.name in the template part, but can't use this.$parent.user.name in the script part of the file.
This is all in a route Vue file if that makes any difference. Here is my main vue app.
import Nav from './components/Nav';
import AuthUser from './models/AuthUser';
new Vue({
el: '#app',
components: {
Nav
},
data() {
return {
user: []
}
},
created() {
AuthUser.load(user => this.user = user);
},
router
});
v-model already binds the value, so you can't bind the value to one thing and v-model to something else.
To start with the desired values, you can do this:
data() {
return {
form: new Form({
name: this.$parent.user.name,
email: this.$parent.user.email,
password: this.$parent.user.password, //please don't, it's just an example :)
})
}
},
And remove :value from the elements:
<input type="text" class="form-control" name="user" autocomplete="false" v-model="form.name">

Naming convention of v-model for Vuejs and Axios in a POST request (Issues encoding a Vue Object into JSON)

this is going to be super long winded!
Admittedly I am not a good frontend designer and my Javascript skills leave much to be improved upon.
TL:DR - How is the working code different from the JSON encoded data object (merchant) in the non-working code, in regards to the Axios POST request? Shouldn't they produce the same result?
Some background first: I am building a Laravel REST backend that is feature complete, form validators, policies, the works. I have tested the backend with the ARC REST client for Chrome and have verified my backend is fully functional.
The problem: While designing my frontend using Vuejs, Vue-Router and Axios, I am having issues POSTing data to my backend. Specifically it is failing form validation with an HTTP error 422. I have narrowed down this issue to be relating to object encapsulation in either Vue or Axios.
Here is the non-working Vue component:
<div class="panel panel-default">
<div class="panel-heading">Create Merchant</div>
<div class="panel-body">
<form action="#" #submit.prevent="createMerchant()">
Primary
<input v-model="merchant.primary" type="text" name="primary" class="form-control" autofocus>
Alternate
<input v-model="merchant.alternate" type="text" name="alternate" class="form-control">
Contact
<input v-model="merchant.contact" type="text" name="contact" class="form-control">
Street
<input v-model="merchant.street" type="text" name="street" class="form-control">
City
<input v-model="merchant.city" type="text" name="city" class="form-control">
State
<input v-model="merchant.state" type="text" name="state" class="form-control">
Country
<input v-model="merchant.country" type="text" name="country" class="form-control">
Postal Code
<input v-model="merchant.postalCode" type="text" name="postalCode" class="form-control">
<button type="submit" class="btn btn-primary">Create Merchant</button>
</form>
</div>
</div>
<div v-if='errors && errors.length' class="panel panel-default">
<div class="panel-heading">Error</div>
<div class="panel-body">
<ul>
<li v-for='error of errors'>
{{error.message}}
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
merchant: {
primary: '',
alternate: '',
contact: '',
street: '',
city: '',
state: '',
country: '',
postalCode: ''
},
errors: []
};
},
methods: {
createMerchant() { console.log(JSON.stringify(this.merchant));
axios.post('/payment-gateway/public/api/v1/merchant', JSON.stringify(this.merchant))
.then((response) => {
console.log(response.data.id);
this.$router.push({ name: 'merchantList' });
})
.catch(e => {
this.errors.push(e);
});
}
}
}
</script>
The data being posted, from my point of view appears to be correct:
{"primary":"Widget Company","alternate":"Widget Co","contact":"555-555-0055","street":"123 Main St","city":"Olympia","state":"WA","country":"USA","postalCode":"98501"}
But the above code always results in a HTTP 422 error.
Now for the part that is confusing me, this is working code:
<div class="panel panel-default">
<div class="panel-heading">Create Merchant</div>
<div class="panel-body">
<form action="#" #submit.prevent="createMerchant()">
Primary
<input v-model="merchant.primary" type="text" name="primary" class="form-control" autofocus>
Alternate
<input v-model="merchant.alternate" type="text" name="alternate" class="form-control">
Contact
<input v-model="merchant.contact" type="text" name="contact" class="form-control">
Street
<input v-model="merchant.street" type="text" name="street" class="form-control">
City
<input v-model="merchant.city" type="text" name="city" class="form-control">
State
<input v-model="merchant.state" type="text" name="state" class="form-control">
Country
<input v-model="merchant.country" type="text" name="country" class="form-control">
Postal Code
<input v-model="merchant.postalCode" type="text" name="postalCode" class="form-control">
<button type="submit" class="btn btn-primary">Create Merchant</button>
</form>
</div>
</div>
<div v-if='errors && errors.length' class="panel panel-default">
<div class="panel-heading">Error</div>
<div class="panel-body">
<ul>
<li v-for='error of errors'>
{{error.message}}
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
merchant: {
primary: '',
alternate: '',
contact: '',
street: '',
city: '',
state: '',
country: '',
postalCode: ''
},
errors: []
};
},
methods: {
createMerchant() { console.log(JSON.stringify(this.merchant));
axios.post('/payment-gateway/public/api/v1/merchant', {
primary: this.merchant.primary,
alternate: this.merchant.alternate,
contact: this.merchant.contact,
street: this.merchant.street,
city: this.merchant.city,
state: this.merchant.state,
country: this.merchant.country,
postalCode: this.merchant.postalCode
})
.then((response) => {
console.log(response.data.id);
this.$router.push({ name: 'merchantList' });
})
.catch(e => {
this.errors.push(e);
});
}
}
}
</script>
So my question is, how is the working code different from the JSON encoded data object (merchant) in the non-working code?
In one instance you're sending an object, in the other a string. Although they will both be transferred as a string eventually, when you pass the object, the ContentType is set under the hood to application/json.
That being said, if you set the ContentType to application/json for the one you're passing as a string, it will sort the issue.

Sending Old Data back to Vue Component

I'm trying to figure out how I can set up this Vue component so that if there is an error in my backend validation from Laravel then I can save the inputs and when it redirects back to the create page then auto-populate with the old data.
As of right now it's not sending the old data to the prop for my component. Is there something I'm doing wrong?
Blade File
<component name="{{ old('name') }}" locationType="{{ old('location_type') }}" paymentType="{{ old('payment_type') }}"></component>
Vue Component
<template>
<div>
<div>
<p class="mbxs"><b>Choose Location Type <span style="color: red">*</span></b></p>
<label class="custom-control custom-radio" v-for="choice in choices">
<input name="location_type" type="radio" v-model="chosen" :value="choice" class="custom-control-input" required>
<span class="custom-control-indicator"></span>
<span class="custom-control-description">{{ choice }}</span>
</label>
</div>
<template v-if=" chosen == 'Residential' ">
<div class="row">
<div class="col-12 col-sm-6">
<div class="form-group form-group-required">
<label for="name" class="control-label">Last Name</label>
<input class="form-control" required name="last_name" type="text">
</div>
</div>
<div class="col-12 col-sm-6">
<div class="form-group form-group-required">
<label for="name" class="control-label">First Name</label>
<input class="form-control" required name="first_name" type="text">
</div>
</div>
</div>
</template>
<template v-if=" chosen == 'Commercial' ">
<div class="form-group form-group-required">
<label for="name" class="control-label">Name</label>
<input class="form-control" required name="name" type="text">
</div>
</template>
</div>
</template>
<script>
module.exports = {
http: {
headers: {
'X-CSRF-TOKEN': window.Laravel.csrfToken
}
},
props: [ 'name', 'locationType', 'paymentType' ],
data: function(){
return {
name: '',
locationType: '',
locationName: '',
paymentType: '',
insuranceType: '',
choices: [ 'Commercial', 'Residential'],
chosen: ''
};
}
}
</script>
Well you're defining the props this.name, this.locationType, this.PaymentType, then you're assigning them as empty in your data declaration, so you have a collision.
Given your architecture, its not the most elegant, but it will probably do:
props: [ 'name', 'locationType', 'paymentType' ],
created: function() {
this.form.name = this.name;
this.form.locationType = this.locationType;
this.form.paymentType = this.paymentType;
},
data: function() {
form: {
name: '',
locationType: '',
locationName: '',
paymentType: '',
insuranceType: '',
choices: [ 'Commercial', 'Residential'],
chosen: ''
}
}
We've namespaced our data to form and assign the values from our properties to the form namespace.
Update your bindings as such to v-model="form.name" etc.

Resources