How to check if a laravel validator response is an error or not with Vue.js - laravel

I am using Laravel 7 and Vue.js 2.
I make a delete call with axios and if everything is correct I receive as a response the new data in the related tables to update a select and if there is an error I receive the errors of the Laravel validator.
The problem is that I have to understand with javascript if the response is an error or not... but I don't know how to do that.
This is my Vue component:
<template>
<form method="DELETE" #submit.prevent="removeTask">
<div class="form-group">
<title-form v-model="titleForm" :titleMessage="titleForm"></title-form>
</div>
<hr>
<div class="form-group">
<label for="tasks">Tasks:</label>
<select required v-model="user.tasks" class="form-control" id="tasks" #mouseover="displayResults(false, false)">
<option v-for="task in tasks_user" :value="task.id" :key="task.id">
{{ task.task_name }} - {{ task.task_description }}
</option>
</select>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
<hr>
<div class="form-group">
<validated-errors :errorsForm="errors" v-if="displayErrors===true"></validated-errors>
<!--<success-alert :success_message="successMessage" v-if="displaySuccess===true"></success-alert>-->
</div>
</form>
</template>
<script>
import ValidatedErrors from "./ValidatedErrors.vue"
import SuccessAlert from "./SuccessAlert.vue"
import TitleForm from "./TitleForm.vue"
export default {
components: {
'validated-errors': ValidatedErrors,
'success-alert': SuccessAlert,
'title-form': TitleForm
},
mounted() {
console.log('Component mounted.');
},
props: {
tasks_user: {
type: Array,
required: true,
default: () => [],
}
},
computed: {
titleForm: function () {
return "Remove a task from " + this.tasks_user[0].user_name;
}
},
data: function() {
return {
user: {
tasks: ""
},
errors: {},
displayErrors: false,
displaySuccess: false,
successMessage: "The task has been removed."
}
},
methods: {
removeTask: function() {
alert(this.user.tasks);
//axios.delete('/ticketsapp/public/api/remove_task_user?id=' + this.user.tasks)
axios.delete('/ticketsapp/public/api/remove_task_user?id=' + 101)
.then((response) => {
console.log(response.data);
if(typeof response.data[0].task_id !== "undefined") {
alert("There are no errors.");
} else {
alert("There are errors.");
}
if (typeof response.data[0].task_id === "undefined") { //problem
alert('noviva');
console.log(response.data);
this.errors = response.data;
this.displayErrors = true;
} else {
alert('viva');
this.tasks_user = response.data;
this.errors = {};
}
})
.catch(error => {
alert(noooooo);
console.log(error);
});
},
displayResults(successShow, errorShow) {
this.displayErrors = errorShow;
this.displaySuccess = successShow;
}
},
}
</script>
This is my method in the controller:
public function remove(Request $request) {
$validator = Validator::make($request->all(), [
'id' => 'required|exists:task_user,id'
]);
if ($validator->fails()) {
return response($validator->errors()); //problem
}
$task_user_id = $request->id;
$user_id = TaskUser::where('id', $task_user_id)->pluck('user_id')->first();
TaskUser::find($task_user_id)->delete();
$tasks_user = TaskUser::with(['user', 'task'])->get();
$tasks_user = TaskUser::where('user_id', $user_id)->get();
$tasks_user = TaskUserResource::collection($tasks_user);
return json_encode($tasks_user);
}
To distinguish the type of return I created this condition: if (typeof response.data[0].task_id === "undefined") but when that condition is true everything falls down and I receive the following error in the console:
Uncaught (in promise) ReferenceError: noooooo is not defined
So how can I do to distinguish the type of return of the API call?

Related

Obtain values from Laravel Controller and display in Vue

In a Laravel 8 view, I have a Vue component with a form.
<contact-form-component contact-store-route="{{ route('contact.store') }}">
</contact-form-component>
ContactFormController.vue
<template>
<div>
<div v-if="success">
SUCCESS!
</div>
<div v-if="error">
ERROR!
</div>
<div v-show="!success">
<form
#submit.prevent="storeContact"
method="POST"
novalidate="novalidate"
#keydown="clearError"
>
<input type="text" name="fullname" v-model="formData.fullname" />
<input type="text" name="email" v-model="formData.email" />
<input type="text" name="phone" v-model="formData.phone"/>
<button type="submit">Submit</button>
</form>
</div>
</template>
<script>
import Form from "./Form.vue";
export default {
mixins: [Form],
props: {
contactStoreRoute: String,
},
data() {
return {
formData: {
fullname: null,
lname: null,
email: null,
phone: null,
message: null,
},
};
},
methods: {
storeContact() {
this.post(this.contactStoreRoute, this.formData);
},
},
mounted() {},
};
</script>
Form.vue
<template></template>
<script>
import FormErrors from "./FormErrors.vue";
export default {
name: "Form",
mixins: [FormErrors],
data() {
return {
success: false,
error: false,
errorMessage: "",
};
},
methods: {
post(url, data) {
this.success = false;
this.error = false;
axios
.post(url, data)
.then((res) => {
this.onSuccess(res.data.message);
})
.catch((error) => {
if (error.response.status == 422) {
this.setErrors(error.response.data.errors);
} else {
this.onFailure(error.response.data.message);
}
});
},
get(url, data) {
this.success = false;
this.error = false;
axios
.get(url, data)
.then((res) => {
this.onSuccess(res.data.message);
})
.catch((error) => {
if (error.response.status == 422) {
this.setErrors(error.response.data.errors);
} else {
this.onFailure(error.response.data.message);
}
});
},
onSuccess(message) {
this.reset();
this.success = true;
},
onFailure(message) {
this.error = true;
this.errorMessage = message;
},
reset() {
this.clearAllErrors();
for (let field in this.formData) {
this.formData[field] = null;
}
},
},
};
</script>
FormErrors.vue
<template></template>
<script>
export default {
name: "FormErrors",
data() {
return {
errors: {},
};
},
methods: {
setErrors(errors) {
this.errors = errors;
},
hasError(fieldName) {
return fieldName in this.errors;
},
getError(fieldName) {
return this.errors[fieldName][0];
},
clearError(event) {
Vue.delete(this.errors, event.target.name);
},
clearAllErrors() {
this.errors = {};
},
},
computed: {
hasAnyError() {
return Object.keys(this.errors).length > 0;
},
},
};
</script>
When the form is submitted, a laravel post route is called and the information is stored in the database.
Route::post('/contact/store', [ContactController::class,'store'])->name('contact.store');
After this, the Vue component now hides the form and displays a "success" message. So far, everything works great.
Now, I would like to add a step. Instead of success message, I want to obtain the last id entered in the db and show a new form with a hidden field last_id. I am unsure of how to obtain this information from the controller.
It would be a continuation of the previous form, but I do not want to gather all the data at once, I want it in steps. Now, it is also important to gather data from the first form, and if the user quits after the first form that is fine, no problem, but if the user continues with the second form I need to "link" it to the previous form through the last_id.
I think that I am not approaching this problem correctly, maybe I need to change the logic of what I am doing.
Adding last_id to ContactController return:
class ContactController extends Controller
{
public function store(Request $request)
{
$data = $request->validate([
'fullname' => 'required',
'email' => 'required|email',
'phone' => 'required',
]);
$contact_form = Contact::create($data);
$last_id = $contact_form->id;
return [
//how do I "send" this to the Vue component?
'last_id' => $last_id
];
}
}
This would be the "second step" form using the last ID. It would have it's own post route.
<h4>Your form has been successfully submitted, now please give us more info that will be linked to the previous form:</h4>
<form>
<input type="hidden" name="last_id" value="{HOW_TO_GET_THE_LAST_ID_HERE?}" />
<textarea name="message" required></textarea>
<input type="submit" value="Submit" />
</form>
You can use a JSON response:
https://laravel.com/docs/9.x/responses#json-responses
You can return the id in your Controller for example like this:
return response()
->json(['last_id' => $last_id])
You will be able to get the value from the response object of the post call
.then((res) => {
//get the id from the response
this.last_id = (res.data.last_id);
})

Vue 3 checkbox component does not work in laravel as checkbox (Accept terms) situation

I'm working on a laravel 9 project that uses vue 3. For each form input has been made field components.Everything works fine except registration checkbox (Accept terms). If the checkbox is used normally in vue, it works fine. as soon as a component is made of it, it no longer works. By clicking the chackbox on and off I get the message that the accept terms is required!
I hope you can help me with this
-Vue Registration Form componetnt
<template>
<form class="text-left" method="post" #submit.prevent="submit()">
<div class="alert alert-success mb-1" v-if="success" v-html="successMessage"></div>
<div class="alert alert-danger mb-1" v-if="errors.message" v-html="errors.message"></div>
<InputFieldWithoutIcon ref="email" type="email" name="email" label="E-mail" :errors="errors" #update:field="fields.email=$event"/>
<InputFieldWithoutIcon ref="password" type="password" name="password" label="Password" :errors="errors" #update:field="fields.password=$event"/>
<InputFieldWithoutIcon ref="password_confirmation" type="password" name="password_confirmation" label="Password Confirmation" :errors="errors" #update:field="fields.password_confirmation=$event"/>
<Checkbox :text="newsletter_text" name="newsletter" type="checkbox" :errors="errors" #update:field="fields.newsletter=$event"/>
<Checkbox :text="policy_text" name="policy_accepted" type="checkbox" :errors="errors" #update:field="fields.policy_accepted=$event"/>
<SubmitButtonWithoutIcon name="create account" type="submit" custom_btn_class="btn-block btn-primary" custom_div_class=""/>
</form>
</template>
<script>
import InputFieldWithoutIcon from "../components/InputFieldWithoutIcon";
import SubmitButtonWithoutIcon from "../components/SubmitButtonWithoutIcon";
import Checkbox from "../components/Checkbox";
export default {
name: "RegistrationUser",
components: {
InputFieldWithoutIcon,
SubmitButtonWithoutIcon,
Checkbox
},
data() {
return {
fields: {
email: '',
password: '',
password_confirmation: '',
policy_accepted: '',
newsletter: '',
},
errors: {},
success: false,
successMessage: '',
newsletter_text: 'I want to receive newsletters',
policy_text: 'I accept <a class="text-bold text-underline" href="" target="_blank" title="privacy policy">the policy</a>',
}
},
methods: {
submit() {
axios.post('/api/registration', this.fields)
.then(response => {
if (response.status === 200) {
this.$refs.email.value = null;
this.$refs.password.value = null;
this.$refs.password_confirmation.value = null;
this.fields = {
email: '',
password: '',
password_confirmation: '',
policy_accepted: '',
newsletter: '',
};
this.errors = {};
this.success = true;
this.successMessage = response.data.message;
}
}).catch(errors => {
if (errors.response.status === 422 || errors.response.status === 401) {
this.errors = errors.response.data.errors;
}
});
}
}
}
</script>
-Vue Checkbox component
<template>
<div class="custom-control custom-checkbox">
<input class="custom-control-input"
:id="name"
:name="name"
:type="type"
v-model="value"
#input="updateField()"
>
<label class="custom-control-label" :for="name">
{{ text }}
</label>
<div class="invalid-feedback" v-text="errorMessage()"></div>
</div>
</template>
<script>
export default {
name: "Checkbox",
props: [
'name',
'type',
'text',
'errors'
],
data: function () {
return {
value: false
}
},
computed: {
hasError: function () {
return this.errors && this.errors[this.name] && this.errors[this.name].length > 0;
}
},
methods: {
updateField: function () {
this.clearErrors(this.name);
this.$emit('update:field', this.value)
},
errorMessage: function () {
if (this.errors && this.errors[this.name]) {
return this.errors[this.name][0];
}
},
clearErrors: function () {
if (this.hasError) {
this.errors[this.name] = null;
}
}
}
}
</script>
-Laravel Registration request
public function rules(): array
{
return [
'email' => 'required|email|unique:users',
'password' => 'required|confirmed|min:8',
'newsletter' => 'nullable|boolean',
'policy_accepted' => 'accepted'
];
}
I've found the solution.
In the checkbox template instead of using #input="updateField()" I replaced that with #change="updateField()"
That's all!

Real-time search engine with VueJS and Laravel

I am doing the search engine section in VueJS and Laravel, but I have a problem that does not allow me to advance in the other sections. The search engine opens and everything but when I write it only sends the first letter or 2 but not all of them like this in this image:
image of the data you send
the data that I write
After that it shows me the following error in console:
Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: "/search?q=th"
Now showing my search engine code:
<template>
<div class="form_MCycW">
<form autocomplete="off" #sumbit.prevent>
<label class="visuallyhidden" for="search">Search</label>
<div class="field_2KO5E">
<input id="search" ref="input" v-model.trim="query" name="search" type="text" placeholder="Search for a movie, tv show or person..." #keyup="goToRoute" #blur="unFocus">
<button v-if="showButton" type="button" aria-label="Close" #click="goBack">
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15"><g fill="none" stroke="#fff" stroke-linecap="round" stroke-miterlimit="10" stroke-width="1.5"><path d="M.75.75l13.5 13.5M14.25.75L.75 14.25"/></g></svg>
</button>
</div>
</form>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
data() {
return {
query: this.$route.query.q ? this.$route.query.q : ''
}
},
computed: {
showButton() {
return this.$route.name === 'search';
},
...mapState({
search: state => state.event.fromPage
})
},
mounted() {
this.$refs.input.focus();
},
methods: {
goToRoute() {
if (this.query) {
this.$router.push({
name: 'search',
query: { q: this.query },
});
} else {
this.$router.push({
path: this.fromPage,
});
}
},
goBack() {
this.query = '';
this.$router.push({
path: '/',
});
},
unFocus (e) {
if (this.$route.name !== 'search') {
const target = e.relatedTarget;
if (!target || !target.classList.contains('search-toggle')) {
this.query = '';
this.$store.commit('closeSearch');
}
}
}
}
}
</script>
This is the other section of the search engine:
<template>
<main class="main">
<div class="listing">
<div class="listing__head"><h2 class="listing__title">{{ title }}</h2></div>
<div class="listing__items">
<div class="card" v-for="(item, index) in data.data" :key="index">
<router-link :to="{ name: 'show-serie', params: { id: item.id }}" class="card__link">
<div class="card__img lazyloaded"><img class="lazyload image_183rJ" :src="'/_assets/img/covers/posters/' + item.poster" :alt="item.name"></div>
<h2 class="card__name">{{ item.name }}</h2>
<div class="card__rating">
<div class="card__stars"><div :style="{width: item.rate * 10 + '%'}"></div></div>
<div class="card__vote">{{ item.rate }}</div>
</div>
</router-link>
</div>
</div>
</div>
</main>
</template>
<script>
import { mapState } from 'vuex';
let fromPage = '/';
export default {
name: "search",
metaInfo: {
bodyAttrs: {
class: 'page page-search'
}
},
computed: {
...mapState({
data: state => state.search.data,
loading: state => state.search.loading
}),
query() {
return this.$route.query.q ? this.$route.query.q : '';
},
title() {
return this.query ? `Results For: ${this.query}` : '';
},
},
async asyncData ({ query, error, redirect }) {
try {
if (query.q) {
this.$store.dispatch("GET_SEARCH_LIST", query.q);
} else {
redirect('/');
}
} catch {
error({ message: 'Page not found' });
}
},
mounted () {
this.$store.commit('openSearch');
this.$store.commit('setFromPage', fromPage);
if (this.data.length == 0 || this.data === null) {
this.$store.dispatch("GET_SEARCH_LIST", this.query);
}
setTimeout(() => {
this.showSlideUpAnimation = true;
}, 100);
},
beforeRouteEnter (to, from, next) {
fromPage = from.path;
next();
},
beforeRouteUpdate (to, from, next) {
next();
},
beforeRouteLeave (to, from, next) {
const search = document.getElementById('search');
next();
if (search && search.value.length) {
this.$store.commit('closeSearch');
}
}
};
</script>
In my routes section it is defined as follows:
{
name: 'search',
path: '/search',
component: require('../views/' + themeName + '/control/search/index').default
}
It is supposed to be a real-time search engine. I would appreciate your help in solving this problem...
What you need is a debounce. What it does is that it wait or delay till the user had finished typing before the model get updated or before you send it to the server.
An example of how it works is here
Here is a package for it.
https://github.com/vuejs-tips/v-debounce

TypeError: _this2.categoryOptions.find is not a function

I'm trying to create a select option that will show the category when it's been saved. The problem I'm having is that I'm getting this error in my console
[Vue warn]: Error in render: "TypeError: _this2.categoryOptions.find is not a function"
Here is my code
<template>
<div>
<select class="form-control" v-model="addCategory" name="category">
<option v-for="category in categoryOptions" :value="category.id">{{ category.name }}</option>
</select>
</div>
</template>
<script>
export default {
props: ['product', 'categories'],
data() {
return {
addCategory: null,
categoryOptions: []
}
},
mounted() {
axios.get('/admin/products/'+this.product.id+'/category').then((response) => {
this.categoryOptions = response.data;
});
},
computed: {
categoryOptions(){
let options = [];
options.push({id:0, text: "Please select one"});
let filteredCategory = this.categories.filter(category => {
return this.categoryOptions.find(selected => categoryOptions.category_id === category.id) == null;
});
filteredCategory.forEach(sc => {
options.push({id: sc.id, text: sc.name});
});
return options;
}
},
}
</script>
Replace:
let filteredCategory = this.categories.filter(category => {
return this.categoryOptions.find(selected => categoryOptions.category_id === category.id) == null;
});
per
let filteredCategory = this.categories.filter(category => {
return this.categoryOptions.find(selected => selected.category_id === category.id) == null;
});
note that you just forgot to replace categoryOptions with selected. But to ensure that the component is loaded, I advise you to make the props categories required, and ensure that it is persisted for the component before rendering.
<script>
export default {
props: {
'product',
'categories': {
type: [Array, Object],
required: true,
},
},
...
}
</script>
Another tip if you use the chrome browser, is to use a very cool extension which is Vue.js devtools to follow the status of your application.

The localStorage is not refreshing in Vuex

I write codes with Vuex to login and logout in my Laravel single page application it's working well but when i login to an account the profiles information (name, address, Email, ...)doesn't show in profile but after i reload the page the profile information loads, and when another user try the profile the data of the last person that login shown to him/her
auth.js:
export function registerUser(credentials){
return new Promise((res,rej)=>{
axios.post('./api/auth/register', credentials)
.then(response => {
res(response.data);
})
.catch(err => {
rej('Somthing is wrong!!')
})
})
}
export function login(credentials){
return new Promise((res,rej)=>{
axios.post('./api/auth/login', credentials)
.then(response => {
res(response.data);
})
.catch(err => {
rej('The Email or password is incorrect!')
})
})
}
export function getLoggedinUser(){
const userStr = localStorage.getItem('user');
if(!userStr){
return null
}
return JSON.parse(userStr);
}
store.js:
import {getLoggedinUser} from './partials/auth';
const user = getLoggedinUser();
export default {
state: {
currentUser: user,
isLoggedIn: !!user,
loading: false,
auth_error: null,
reg_error:null,
registeredUser: null,
},
getters: {
isLoading(state){
return state.loading;
},
isLoggedin(state){
return state.isLoggedin;
},
currentUser(state){
return state.currentUser;
},
authError(state){
return state.auth_error;
},
regError(state){
return state.reg_error;
},
registeredUser(state){
return state.registeredUser;
},
},
mutations: {
login(state){
state.loading = true;
state.auth_error = null;
},
loginSuccess(state, payload){
state.auth_error = null;
state.isLoggedin = true;
state.loading = false;
state.currentUser = Object.assign({}, payload.user, {token: payload.access_token});
localStorage.setItem("user", JSON.stringify(state.currentUser));
},
loginFailed(state, payload){
state.loading = false;
state.auth_error = payload.error;
},
logout(state){
localStorage.removeItem("user");
state.isLoggedin = false;
state.currentUser = null;
},
registerSuccess(state, payload){
state.reg_error = null;
state.registeredUser = payload.user;
},
registerFailed(state, payload){
state.reg_error = payload.error;
},
},
actions: {
login(context){
context.commit("login");
},
}
};
general.js:
export function initialize(store, router) {
router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
const currentUser = store.state.currentUser;
if(requiresAuth && !currentUser) {
next('/login');
} else if(to.path == '/login' && currentUser) {
next('/');
} else {
next();
}
if(to.path == '/register' && currentUser) {
next('/');
}
});
axios.interceptors.response.use(null, (error) => {
if (error.resposne.status == 401) {
store.commit('logout');
router.push('/login');
}
return Promise.reject(error);
});
if (store.getters.currentUser) {
setAuthorization(store.getters.currentUser.token);
}
}
export function setAuthorization(token) {
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`
}
I think that this issue is relate to my localstorage, how can i fix this?
I'm novice at the Vue and don't have any idea what is the problem.
Login Component:
<template>
<main>
<form #submit.prevent="authenticate">
<div class="grid-x grid-padding-x">
<div class="small-10 small-offset-2 cell" v-if="registeredUser">
<p class="alert success">Welcome {{registeredUser.name}}</p>
</div>
<div class="small-10 small-offset-2 cell" v-if="authError">
<p class="alert error">
{{authError}}
</p>
</div>
<div class="small-2 cell">
<label for="email" class="text-right middle">Email:</label>
</div>
<div class="small-10 cell">
<input type="email" v-model="formLogin.email" placeholder="Email address">
</div>
<div class="small-2 cell">
<label for="password" class="text-right middle">Password:</label>
</div>
<div class="small-10 cell">
<input type="password" v-model="formLogin.password" placeholder="Enter password">
</div>
<div class="small-10 small-offset-2 cell">
<div class="gap"></div>
<input type="submit" value="Login" class="button success expanded">
</div>
</div>
</form>
</main>
</template>
<script>
import {login} from '../../partials/auth';
export default {
data(){
return {
formLogin: {
email: '',
password: ''
},
error: null
}
},
methods:{
authenticate(){
this.$store.dispatch('login');
login(this.$data.formLogin)
.then(res => {
this.$store.commit("loginSuccess", res);
this.$router.push({path: '/profile'});
})
.catch(error => {
this.$store.commit("loginFailed", {error});
})
}
},
computed:{
authError(){
return this.$store.getters.authError
},
registeredUser(){
return this.$store.getters.registeredUser
}
}
}
</script>
Localstorage data is once loaded on page load, so when you use setItem, this won't be visible until the next time.
You should store the data to vuex store, and use that as the source. Only set and get the data from localstorage on page loads.
Otherwise use something like: https://github.com/robinvdvleuten/vuex-persistedstate
I solved the problem.I have this code in my EditProfile component.
methods: {
getAuthUser () {
axios.get(`./api/auth/me`)
.then(response => {
this.user = response.data
})
},
}
this.user = response.data is wrong, I changed to this:
getAuthUser () {
this.user = this.$store.getters.currentUser
},

Resources