Nuxt with Axios POST to Laravel's API - laravel

guys, :)
I'm no expert (yet) with API call's, am successful with Laravel on its own. So, need your help.
I am able to use Laravel + Nuxt in general. All connected POST and GET working fine on all CRUDs.
I've created a new department to this existing APP.
I am able to call GET and receive in return Data from API, no problem.
I am able to POST with Postman to API to this table/CRUD.
I'm unable to figure out how to POST with my form to API.
I know it's dead simple for some of you, but google this time couldn't answer straight away. Using VUE as an answer didn't help either. So, you're my only hope to be true.
Here is my code in Page file:
<template>
<section class="max-content page">
<TitleBox :title="'Dodaj Towar'" />
<DodajTowar button-text="Submit" submit-form="products" />
</section>
</template>
<script>
import TitleBox from '~/components/global/TitleBox.vue'
import DodajTowar from '~/components/magazyn/towar/DodajTowar.vue'
export default {
components: {
TitleBox,
DodajTowar
}
}
</script>
Here is the components file. They are connected and I can insert the data to DB only what I'll hardcode in this file:
<template>
<section class="container">
<div>
<form #submit.prevent="products">
<p>
<label for="name" class="input-label">Nazwa</label>
<input id="name" type="text" name="name" class="input">
</p>
<p>
<label for="description" class="input-label">Opis</label>
<input id="description" type="text" name="description" class="input">
</p>
<p>
<label for="price" class="input-label">Cena</label>
<input id="price" type="text" name="price" class="input">
</p>
<p>
<button type="submit" value="Submit" class="button btn-primary">
Zapisz
</button>
</p>
</form>
</div>
</section>
</template>
<script>
export default {
products() {
return {
name: '',
description: '',
price: ''
}
},
methods: {
products() {
// this.$axios.$post('api/warehouse/products', console.log(this.products))
this.$axios({
method: 'post',
url: 'api/warehouse/products',
data: {
name: 'Fred',
description: 'Flintstone',
price: '111'
}
})
}
}
}
</script>
Can you please provide me with an example of how should I do it the right way? The form is working fine on its own as well, in the dev tools in VUE, I can see what I'm typing and submit as products.
Sorry if this question was before, but I was unable to find the solution for the last days and run out of options.

You need to make your 'products' elements 'data' elements and bind your data elements to your form.
//change from 'products'
data() {
return {
name: '',
description: '',
price: ''
}
},
Then your form should look like this:
<form #submit.prevent="products">
<p>
<label for="name" class="input-label">Nazwa</label>
<input id="name" v-model="name" type="text" name="name" class="input">
</p>
<p>
<label for="description" class="input-label">Opis</label>
<input id="description" v-model="description" type="text" name="description" class="input">
</p>
<p>
<label for="price" class="input-label">Cena</label>
<input id="price" v-model="price" type="text" name="price" class="input">
</p>
<p>
<button type="submit" value="Submit" class="button btn-primary">
Zapisz
</button>
</p>
</form>
The v-model attribute will bind the data elements to the inputs.
When you access the data elements in your method, you need to use 'this'.
products() {
this.$axios({
method: 'post',
url: 'api/warehouse/products',
data: {
name: this.name,
description: this.description,
price: this.price
}
})
//add a .then() and a .catch() here to deal with response.
}
And that should do it.

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

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.

ASP.NET Core SPAServices Client Side Form Validation VUE2

I haven't been in FE solutions for a while. As a part of my FE education, I would like to create a simple VUE2 SPA app on ASP.NET Core BE using SPAServices/SPATempaltes. In general, I consider this as a great piece of technology, but I came to one problem. How to conduct client side validation. Some time ago I was using jquery validation that was integrated with ASP.NET MVC services. Can someone give me a point (maybe not the exact solution, but the places where to look for) how this can be done nowadays?
Regards
Client side Validation in VueJs or any front framework happens using HTML5 and Javascript. Like you did in Jquery all these frameworks exposes different events to subscribe and you can validate user inputs etc.
I found this post to be really helpful in doing your own validation for Vue (using vee-validation).
https://stu.ratcliffe.io/2017/7/23/integrating-vuejs-with-aspnet-core-mvc
Now it isn't using Vue as a SPA, but this is one that talks about validation. He also has a post on utilizing Vue and ASP.NET Core in a SPA here:
https://stu.ratcliffe.io/2017/07/20/vuejs-serverside-rendering-with-aspnet-core
Here is some of the validation code from the first post (in case of link rot):
(function (Vue, VeeValidate) {
if (document.querySelector('#contact')) {
Vue.use(VeeValidate);
var app = new Vue({
el: "#contact",
data: {
name: '',
email: '',
message: '',
errorMessage: ''
},
methods: {
send: function () {
this.$validator.validateAll().then(result => {
if (result) {
this.reset();
alert('Form submitted!');
} else {
this.errorMessage = 'Please fix all validation errors.'
}
});
},
reset: function () {
this.name = '';
this.email = '';
this.message = '';
this.errorMessage = '';
this.$validator.clean();
}
}
});
}
})(Vue, VeeValidate);
View
#{
ViewData["Title"] = "Contact";
}
<h2>#ViewData["Title"].</h2>
<div id="contact">
<form v-on:submit.prevent="send">
<div v-if="errorMessage.length" class="alert alert-danger">
{{ errorMessage }}
</div>
<div :class="{ 'form-group': true, 'has-error': errors.has('name'), 'has-success': name.length && !errors.has('name') }">
<label for="name">Name</label>
<input autofocus v-model="name" v-validate="'required|min:5'" class="form-control" name="name" type="text" />
<span v-show="errors.has('name')" class="text-danger">{{ errors.first('name') }}</span>
</div>
<div :class="{ 'form-group': true, 'has-danger': errors.has('email'), 'has-success': email.length && !errors.has('email') }">
<label for="email">E-mail</label>
<input v-model="email" v-validate="'required|email'" class="form-control" name="email" type="text" />
<span v-show="errors.has('email')" class="text-danger">{{ errors.first('email') }}</span>
</div>
<div :class="{ 'form-group': true, 'has-danger': errors.has('message'), 'has-success': message.length && !errors.has('message') }">
<label for="message">Message</label>
<textarea v-model="message" v-validate="'required|min:5'" class="form-control" name="message"></textarea>
<span v-show="errors.has('message')" class="text-danger">{{ errors.first('message') }}</span>
</div>
<input type="submit" value="Save" class="btn btn-primary" />
</form>
</div>
The project can be found at:
https://github.com/sturatcliffe/VueDotnetMVC

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 !!}

Resources