Laravel Vuejs form builder - laravel

In my project i have some events, each with a number of tags.
These tags are defined by the administrator user.
Some of these tags may have parameters.
For example, an email tag has two "Sender" and "Receiver" parameters.
Or the transfer tag has 2 parameters "From" and "To". and etc.
Do I have to use the form builder?
How do I implement this using Laravel and Vuejs?

Use Vue JS Component in Laravel like this.
Create Contact.vue component inside resources\assets\js\components then place your Vuejs there.
<template>
<div>
<h1>Contacts</h1>
<form action="#" #submit.prevent="createContact()">
<div class="form-group">
<label>Name</label>
<input v-model="contact.name" type="text" name="name" class="form-control">
</div>
<div class="form-group">
<label>Email</label>
<input v-model="contact.email" type="text" name="email" class="form-control">
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">New Contact</button>
</div>
</form>
</div>
</template>
<script>
export default {
data: function(){
return {
contact:{
name:'',
email:'',
}
}
},
methods: {
createContact: function(){
//call axios to submit the form values in your Laravel controller method
let self = this
axios.post('/contact/store', params)
.then(function(){
self.contact.name = '';
self.contact.email = '';
})
.catch(function(error){
console.log(error);
});
console.log(this.contact);
return;
}
}
}
</script>
In app.js file inside \resources\assets\js add the component
Vue.component('contacts', require('./components/Contacts.vue'));
Finally, call the contacts component inside your blade file.
<div class="container">
<div id="app">
<contacts></contacts>
</div>
</div>

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,
})

Validation Messages not displaying with Vue.js 2 and Laravel 5.6 SPA, Axios

I am getting a 422 response from the server when expected and the correct server-side messages appear in the console but I cant seem to get these messages to display in my view. Any ideas?
I am using Laravel 5.6 with Vue.js 2 and Axios and following the lessons for Vus.js on laracasts (great lessons).
<template>
<div class="container">
<div class="page-header">
<h1>Create Person</h1>
</div>
<form #submit.prevent="onSubmit" action="POST">
<div>
<label for="fname" class="col-md-3 control-label ">First Name:</label>
<div class="col-md-8">
<input type="text" name="fname" v-model='fname' id="fname" autocomplete='off'>
<span v-text="errors.get('fname')"></span>
</div>
</div>
<div>
<label for="lname" class="col-md-3 control-label ">Last Name:</label>
<div class="col-md-8">
<input type="text" name="lname" v-model='lname' id="lname" autocomplete='off'>
<span v-text="errors.get('lname')"></span>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group">
<div class="col-md-8 col-md-offset-4">
<button type="submit" class="btn btn-trans-green square pull-right card-1">
Submit
</button>
</div>
</div>
</div>
</div>
</form>
</div>
</template>
<script>
class Errors {
constructor(){
this.errors = {};
}
get(field){
if (this.errors[field]){
return this.errors[field][0];
}
}
record(errors){
this.errors = errors;
}
}
export default {
data: function() {
return {
fname:'',
lname:'',
errors: new Errors()
}
},
methods: {
onSubmit(){
axios.post('/customers', this.$data)
.then(response => alert('Success'))
.catch(error =>
this.errors.record(error.response.data));
}
}
}
</script>
In Laravel 5.5 (I think) the JSON response for validation errors changed from being just an object of errors to an object containing errors and a single message. So to access the error messages in the response you will actually need to do:
this.errors.record(error.response.data.errors)
I would suggest, if you don't have it already, to get Vue Devtools the addon/extension to help you debug your code and just see what's going on in general.
Finally, just an FYI, Spatie made a package for this a while back (based on Jeff's lesson). You could use it as a point of reference if you're just going through the lessons and want to build it yourself.
Spatie - Form Backend Validation

Getting a slug to save in vue

I'm using both vue and laravel and I'm trying to create a product form that creates a slug as you type. I've managed to get
the slug to show up as you type but I'm not sure as to how I should save it to my database.
My vue
<template>
<div class="product-form">
<form v-on:submit="onSubmit(model)">
<div class="form-group">
<label for="title">Title</label>
<input type="text" id="title" class="form-control" name="title" v-model="model.title">
<input type="hidden" name="slug" title="slug" :value="slug(model.title)">
<p>{{ slug(model.title) }}</p>
</div>
<input type="submit" class="btn btn-default" value="Save"></input>
</form>
</div>
</template>
<script>
export default {
data() {
return {
model: {
title: '',
slug: '',
},
};
},
methods: {
slug() {
var title = this.model.title;
var slug_test = title.replace(/\s+/g, '-');
return slug_test;
},
onSubmit: function(model){
event.preventDefault();
this.$http.post('/database/articles', model)
}
},
}
</script>
It suffices to keep the slug property of your model up to date with the title property.
The cleanest approach here would be a watcher on your title that updates the slug, or by computing model.slug right before you POST it to the server.
You can append slug in Model Resource if you are using Laravel

Laravel CSRF Field in Vue Component

I would like to ask how can I add the csrf_field() in my vue component. The error is
Property or method "csrfToken" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
Here's the code:
<script>
export default {
name: 'create',
data: function(){
return {
msg: ''
}
},
props:['test']
}
</script>
<template>
<div id="app">
<form action="#" method="POST">
{{csrfToken()}}
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" class="form-control">
</div>
<div class="form-group">
<label for="location">Location</label>
<input type="text" id="location" class="form-control">
</div>
<div class="form-group">
<label for="age">Age</label>
<input type="number" id="age" class="form-control">
</div>
<div class="form-group">
<input type="submit" class="btn btn-default">
</div>
</form>
</div>
</template>
As I already wrote here, I would simply suggest to put this in your PHP file:
<script>
window.Laravel = <?php echo json_encode(['csrfToken' => csrf_token()]); ?>
</script>
This way you're able to easily import your csrfToken from the JS part (Vue in this case).
Moreover, if you insert this code in your PHP layout file, you can use the token by any component of your app, since window is a JS global variable.
Source: I got the trick from this post.
Laravel version 5 or later
First you need to store your CSRF token in a HTML meta tag in your header:
<meta name="csrf-token" content="{{ csrf_token() }}">
Then you can add it to your script:
<script>
export default {
name: 'create',
data: function(){
return {
msg: '',
csrf: document.head.querySelector('meta[name="csrf-token"]').content
}
},
props:['test']
}
</script>
And in the template:
<template>
<div id="app">
<form action="#" method="POST">
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" class="form-control">
</div>
<div class="form-group">
<label for="location">Location</label>
<input type="text" id="location" class="form-control">
</div>
<div class="form-group">
<label for="age">Age</label>
<input type="number" id="age" class="form-control">
</div>
<div class="form-group">
<input type="submit" class="btn btn-default">
</div>
<input type="hidden" name="_token" :value="csrf">
</form>
</div>
</template>
Try this:
<script>
export default {
name: 'create',
data: function(){
return {
msg: '',
csrf: window.Laravel.csrfToken
}
},
props:['test']
}
</script>
And in your markup just use
<input type="hidden" name="_token" :value="csrf" />
EDIT
Bit of a rabbit hole but, one great feature of Vue is that it can easily handle POST, PATCH, etc. requests using AJAX and the vue-resource extension. Instead of using a <form> here you can process this data using your Vue component. If you were to take this route, you can set default headers to send with each request no matter what method it is, so you can always send your CSRF token.
exports deafult{
http: {
headers: {
'X-CSRF-TOKEN': window.Laravel.csrfToken
}
}
}
if you look at the /resources/assets/js/bootstrap.js you will find these lines
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
I believe you are using axios for your requests. this means you need to add
<meta name="csrf-token" content="{{csrf_token}}">
in your <head> tag.
Came across this problem whilst building multiple forms in a vue v-for loop.
added to data;
csrf: document.head.querySelector('meta[name="csrf-token"]').content,
Added hidden form element
<input type="hidden" name="_token" :value="csrf" />
I think you need to write it like this:
{{ crsf_token() }}
and not like this:
{{ csrfToken() }}
If that doesn't work, maybe try this:
{!! csrf_token !!}

How to add and item to an Array using Vue JS?

I'm using VUE JS and for some reason I don't get the new item displayed on the view. if I use the push method, nothing happens. If I use this.$set('productos',producto) the item is displayed, but when I tried to push another item to the array, the previous item is deleted.
new Vue({
el: '#ticket',
data:{
newCodigo:{
codigo: '',
},
productos:[],
},
computed:{
errors: function(){
for(var key in this.newCodigo){
if(! this.newCodigo[key])
return true;
}
return false;
}
},
methods:{
addProducto: function(){
var codigo = this.newCodigo;
this.$http.post('/api/addProducto', codigo).success(function(producto){
this.productos.push(producto);
//this.$set('productos',producto);
});
}
}
});
I'm able to retrieve the data from the DB, but it's not displayed on the view.
<div class="container">
<div class="row">
<div id="ticket">
<form action="POST" v-on:submit.prevent="addProducto">
<input type="hidden" name="_token" id="token" value="{{ csrf_token() }}" >
<div class="form-group">
<label for="Codigo">
Escanea:
<span class="error" v-if="! newCodigo.codigo">*</span>
</label>
<input type="text" name="codigo" id="codigo" class="form-control" v-model="newCodigo.codigo">
</div>
</form>
<article v-for="producto in productos" :data="productos">
<h3>#{{producto.descripcion}}</h3>
<h3>#{{producto.precio}}</h3>
</article>
</div>
</div>
In the callback function of your ajax call, this does not point to the vm instance. Just change it to:
this.$http.post('/api/addProducto', codigo).success(function (producto) {
this.productos.push(producto);
}.bind(this));

Resources