Vee-Validate v3 server side validation in rule - vee-validate

Trying to do server side validation in a rule. It is something like this:
validate(value, {fname, fid } ) {
return(axios({
method: 'post',
url: 'xyz.php'
})
.then(function (response) {
return response.msg === 'available'
}
})
)
},
message: 'Not available ',
params:[ 'fname', 'fid' ]
});
However, I get no error message even though my request is returning with the proper validation. I've found 2 examples, but both use the this.$refs (either .form or .observer) which I do not have access to in extend since it is outside of the Vue object. How do I go about setting errors in this situation? Thanks in advance.
The template is this :
<div class="w3-third">
<label>From</label>
<ValidationProvider vid="st" mode="eager" :rules="{'available': [res_date, endTime, 'starter', selectedField.id] }" v-slot="{ errors }">
<dropdown id="starttime" :options="startTimeOptions" v-model="startTime" ></dropdown>
<span class="w3-red">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="w3-third ">
<label>Until</label>
<ValidationProvider vid="et" mode="eager" :rules="{'available': [res_date, startTime, 'ender', selectedField.id] }" v-slot="{ errors }">
<dropdown id="endtime" :options="endTimeOptions" v-model="endTime"></dropdown>
<span>{{ errors[0] }}</span>
</ValidationProvider>
</div>

Your validate function needs to return the promise that axios produces:
validate(value, {fname, fid } ) {
return axios({
//...

It turns out that the issue was because I was using my custom component, Dropdown, which didn't have v-model set in a way that worked with vee-validate. So Ryley's answer about returning the promise was correct.

Related

Vue 3 Axios post call works but v-model lags a step behind?

I am using Vue.js 3, Laravel and Axios to make a simple post call:
processFilters() {
self = this;
let tags = self.checkedTags.slice();
axios.post('/api/process-filters', {
filters: tags,
})
.then(function (response) {
// self.checkedTags = response.data.filters;
console.log(response.data.filters);
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
});
}
My Laravel backend function just tests if I can give back the same values to the js console:
public function processFilters(Request $request)
{
$data['filters'] = $request->input('filters');
// Return the response in json
return response()->json($data);
}
In the dev tools, my v-model checkedTags updates fine but always lags a step behind. For example, I am running processFilters() every time a checkbox is clicked:
<div v-for="(tagValue, tagKey) in tags" :key="tagKey" class="relative flex items-start py-2">
<div class="min-w-0 flex-1 text-sm">
<label :for="tagKey" class="font-medium text-gray-700 select-none">
<input v-model="checkedTags" #click="processFilters()" :id="tagKey" :value="tagKey" type="checkbox" class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-600 rounded">
<span class="ml-2">{{ tagKey }}</span>
</label>
</div>
<div class="ml-3 mr-1 flex items-center h-5">
{{ tagValue }}
</div>
</div>
When I click a checkbox, I get back an empty array. When two are clicked, I get an array size one, and so on?

vue3 Cannot read property 'insertBefore' of null

I'm getting this error when i try to update my data fields in vuejs.
data() {
return {
form : useForm({
imageFile : null,
img_id : null,
}),
product_images : this.images,
}
},
here I'm updating the product_images after the success of post request sent by the user.
like this.
this.form.post(url, {
onStart : () => {
Inertia.on('progress', (event) => {
if (event.detail.progress.percentage) {
NProgress.set((event.detail.progress.percentage / 100) * 0.98);
}
});
},
onFinish : () => {
this.product_images = this.images;
},
})
showing the product_images in template be like
<div v-for="image in product_images" class="col-xl-4 col-lg-6 col-12 mb-3" :key="image.id">
<input-image #changeImage="onChangeImage" :id="image.id" :image="image.image" />
</div>
when product_images changes after the post request, product_images not update in DOM but get this error why?
app.js:12889 Uncaught (in promise) TypeError: Cannot read property 'insertBefore' of null
at insert (app.js:12889)
at mountElement (app.js:9604)
at processElement (app.js:9545)
at patch (app.js:9465)
at patchKeyedChildren (app.js:10315)
at patchChildren (app.js:10093)
at processFragment (app.js:9839)
at patch (app.js:9461)
at patchKeyedChildren (app.js:10174)
at patchChildren (app.js:10117)
Unfortunately the vue error message is not very helpful.
In my case, I had some uninitialized data in the html template like this:
<template>
<div>
{{ data.xyz }}
</div>
</template>
Change this to either:
<template v-if="!!data">
<div>
{{ data.xyz }}
</div>
</template>
Or:
<template>
<div>
{{ data?.xyz }}
</div>
</template>
I fixed it by updating my Vue to the latest version (vue#3.2.20).
In my case, was mistakenly calling app.mount('#app') twice.

"TypeError: Cannot read property 'title' of undefined" in form

An error is returned when I try to post the form.
The form is in a component, and the same structure is used in another component but does not generate any error.
I tried to find the mistake by myself but impossible to find the solution.
<template>
<div class="card" style="width: 18rem;margin:0 0 1rem 1rem;">
<div class="card-body">
<h4 class="mt-3 text-center" style="cursor:pointer;" #click="show=!show" >Add list</h4>
<form v-show="show" #submit.prevent="submitList">
<div class="form-group">
<label>Title</label>
<input type="text" class="form-control" :class="{'is-invalid':errors.title}" v-model="form.title"/>
<p class="text-danger" v-if="errors.title" v-text="errors.title[0]"></p>
</div>
<button type="submit" class="btn btn-lg btn-success mb-4">Submit</button>
</form>
</div>
</div>
</template>
<script>
export default {
data() {
return {
show : false,
form: {
title: '',
},
errors: {}
}
},
methods: {
submitList() {
axios.post('/list', this.form)
.then(({data}) => {
this.$emit('newList', data),
this.form.title = '',
this.show = false,
this.errors = {}
})
.catch(error => {
this.errors = error.response.data.errors
})
}
}
}
</script>
Error in render: "TypeError: Cannot read property 'title' of undefined"
Reference this at the start of the method submitList and then use the reference in the axios response.
let that = this;
then that.form.title;
submitList () {
let that = this;
axios.post('/list', this.form)
.then(({ data }) => {
that.$emit('newList', data),
that.form.title = '',
that.show = false,
that.errors = {}
})
.catch(error => {
that.errors = error.response.data.errors
})
}
There's not really enough information here to answer the question. Since it's a render issue my guess is that it's one of these lines:
<input type="text" class="form-control" :class="{'is-invalid':errors.title}" v-model="form.title"/>
<p class="text-danger" v-if="errors.title" v-text="errors.title[0]"></p>
The question is what you get from the backend in your catch method. You should probably log that value and check that it's formated the way you think it is.
A nice tool for debugging Vue is the browser extension, maybe it will help with clearing up the problem.
If this does not solve your problem you need to provide more info:
When does the error occur
What is the value of the data-properties when it occurs
Maybe a screenshot of a more thorough error-message

Displaying error messages in vuetify when validating nested object with vuelidate

I am using vuelidate to validate my form input and display the error messages using vuetifyjs. I managed to do the basic object validation and am able to show the error messages.
However I'm having issues with displaying the error messages when I validate a collection.
ISSUE
Example data structure:
contact: {
websites: [
{
url: 'http://www.something.com',
label: 'Website',
}
]
}
Example validation:
validations: {
websites: {
$each: {
url: {
url,
}
}
},
}
Example template:
<template v-for="(website, index) in websites">
<v-layout row :key="`website${index}`">
<v-flex xs12 sm9 class="pr-3">
<v-text-field
label="Website"
:value="website.url"
#input="$v.websites.$touch()"
#blur="$v.websites.$touch()"
:error-messages="websiteErrors"
></v-text-field>
</v-flex>
</v-layout>
</template>
Example computed error message:
websiteErrors() {
console.log('websites',this.$v.websites) // contains $each
const errors = []
if (!this.$v.websites.$dirty) {
return errors
}
// Issue is that all of them show must be valid, even if they are valid.
// Validation is basically broken.
// I also tried this.$v.websites.$each.url
!this.$v.websites.url && errors.push('Must be valid url')
return errors
},
Example method (Update, also tried method with passing index):
websiteErrors(index) {
console.log('this.$v.entity.websites', this.$v.entity.websites.$each.$iter, this.$v.entity.websites.$each.$iter[index], this.$v.entity.websites.minLength, this.$v.entity.websites.$each.$iter[index].url)
const errors = []
if (!this.$v.entity.websites.$dirty) {
return errors
}
!this.$v.entity.websites.$each.$iter[index].url && errors.push('Must be valid url')
return errors
},
However when I do this, it will always be true and therefore never show the error.
EXPECTED
I would like to have the same example working as seen in vuelidate sub-collection validation The difference is instead of looping in the template I would like to generate the message programmatically.
REFERENCE
Example provided by vuelidate:
import { required, minLength } from 'vuelidate/lib/validators'
export default {
data() {
return {
people: [
{
name: 'John'
},
{
name: ''
}
]
}
},
validations: {
people: {
required,
minLength: minLength(3),
$each: {
name: {
required,
minLength: minLength(2)
}
}
}
}
}
<div>
<div v-for="(v, index) in $v.people.$each.$iter">
<div class="form-group" :class="{ 'form-group--error': v.$error }">
<label class="form__label">Name for {{ index }}</label>
<input class="form__input" v-model.trim="v.name.$model"/>
</div>
<div class="error" v-if="!v.name.required">Name is required.</div>
<div class="error" v-if="!v.name.minLength">Name must have at least {{ v.name.$params.minLength.min }} letters.</div>
</div>
<div>
<button class="button" #click="people.push({name: ''})">Add</button>
<button class="button" #click="people.pop()">Remove</button>
</div>
<div class="form-group" :class="{ 'form-group--error': $v.people.$error }"></div>
<div class="error" v-if="!$v.people.minLength">List must have at least {{ $v.people.$params.minLength.min }} elements.</div>
<div class="error" v-else-if="!$v.people.required">List must not be empty.</div>
<div class="error" v-else-if="$v.people.$error">List is invalid.</div>
<button class="button" #click="$v.people.$touch">$touch</button>
<button class="button" #click="$v.people.$reset">$reset</button>
<tree-view :data="$v.people" :options="{rootObjectKey: '$v.people', maxDepth: 2}"></tree-view>
</div>
WHAT WENT WRONG
Shared computed property which causes the issue where all siblings share the same error message. (Solved by writing it inline)
Reactivity not triggered due to the array not being updated in a "reactive way" (Make sure to take note of Change Detection Caveats in this case instead of updating the index: I copy the array, replace item and then set the whole array.)
Wrong place to use vuelidate $each.$iter: Moved it from computed error message to v-for
SOLUTION
This is how to do it (Fixes 1 & 3):
<template v-for="(v, index) in $v.websites.$each.$iter">
<v-layout row :key="`website${index}`">
<v-flex xs12 sm9 class="pr-3">
<v-text-field
label="Website"
:value="v.$model.url"
#input="$v.websites.$touch()"
#blur="$v.websites.$touch()"
:error-messages="v.$dirty && !v.required ? ['This field is required'] : !v.url ? ['Must be a valid url'] : []"
/>
</v-flex>
</v-layout>
</template>
This is how my update method is now (Fixes 2):
updateWebsite(index, $event) {
const websites = [...this.websites];
websites[index] = $event;
this.updateVuex(`websites`, websites)
this.$v.websites.$touch()
},
Originally it was like this:
updateWebsite(index, $event) {
this.updateVuex(`websites[${index}]`, $event)
this.$v.websites.$touch()
},
ALTERNATIVE
There is another option, which is to wrap in this case website inside a component. That way you can keep the computed error message as it will not be shared.

Laravel Select2 old input after validation

I'm using Select2 in my webapplication. I load my Select2 boxes with Ajax. When validation fails, all the inputs are filled as before except the Select2 box. How can I restore the old value after the form validation fails? My bet was using Request::old('x'), but this inserts the value (in my case an user ID) instead of the selected text. So for example the text John would become 27 in the selectbox. How can I get the text back?
<select id="customer" name="customer" class="searchselect searchselectstyle">
</select>
The js:
token = '{{csrf_token()}}';
$(".searchselect").select2({
ajax: {
dataType: "json",
type: "POST",
data: function (params) {
return {
term: params.term,
'_token': token,
'data' : function(){
var result = [];
var i = 1;
$('.searchselect').each(function(){
result[i] = $(this).val();
i++;
});
return result;
}
};
},
url: function() {
var type = $(this).attr('id');
return '/get' + type;
},
cache: false,
processResults: function (data) {
return {
results: data
};
}
}
});
Edit
The only (dirty) solution I found so far is the following:
<select id="customer" name="customer" class="searchselect searchselectstyle">
#if(Request::old('customer') != NULL)
<option value="{{Request::old('customer')}}">{{$customers->where('id', intval(Request::old('customer')))->first()->name}}</option>
#endif
</select>
$customers is a list of all customers, so this means that for each Select2 box I need to query a big list of items in order to make it work. This will be pretty inefficient if we're talking about thousands of rows per Select2 box.
I guess there must be a better solution. Who can help me?
Normally to programmatically set the value of a select2, you would expect to use the .val() method followed by a .trigger('change') call as per their documentation (and other queries like this on SO). However, select2 themselves have something in their documentation about preselecting options for remotely sourced data.
Essentially their suggestion boils down to (after initalizing your AJAX-driven <select>):
make another AJAX call to a new API endpoint using the pre-selected ID
dynamically create a new option and append to the underlying <select> from a promise function (.then()) after the AJAX call is finished
could also use some of the regular jQuery callback chaining functions for this
trigger a change event
trigger a select2:select event (and pass along the whole data object)
Assuming you're already flashing the old data to the session, Laravel provides handy access to the previously requested input in a variety of ways, notably these three:
static access via the Request class e.g. Request::old('customer') as in the OP
the global old() helper e.g. old('customer'), which returns null if no old input for the given field exists, and can have a default as a second parameter
using the old() method on the Request instance from the controller e.g. $request->old('customer')
The global helper method is more commonly suggested for use inside Blade templates as in some of the other answers here, and is useful when you don't need to manipulate the value and can just plug it straight back in, which you would with things like text inputs.
The last method probably provides you with the answer you're looking for - instead of querying the entire collection from inside of the view, you're able to either manipulate the collection from the controller (similar to the OP, but should be nicer since it's not parsing it in the view) or make another query from the controller based on the old ID and fetch the data you want without having to trawl the collection (less overhead):
$old_customer = Customer::find($request->old('customer'));
Either way, you'd have the specific data available at your fingertips (as a view variable) before the blade template processes anything.
However you choose to inject the data, it would still follow the pattern suggested by select2:
get the pre-selected data
create an option for it
trigger the appropriate events
The only difference being you don't need to fetch the data from another API endpoint (unless you want/need to for other programmatic reasons).
I end up using similar flow like your. But my blade template is using htmlcollection package.
Controller:-
Let's say you are in create() method. When validation failed, it will redirect back to the create page. From this page, you can repopulate the list.
$customer_list = [];
if(old('customer') != NULL){
$customer_list = [old('customer') => $customers->where('id', old('customer'))->first()->name];
}
Blade View:
{{ Form::select('customer', $customer_list, null, ['class' => 'searchselect searchselectstyle', 'id' => 'customer']) }}
I did it with an input hidden for the text and it works well:
This form is showed in a Popup and ajax (using Jquery-UJS)
Form:
<form action="{{ route('store_item', $order) }}" method="POST" data-remote="true">
{{ csrf_field() }}
<div class="form-group{{ $errors->has('item_id') ? ' has-error' : '' }}">
<label class="control-label col-sm-2" for="item_id">Item: </label>
<div class="col-sm-10">
<select name="item_id" class="form-control" id="item_id">
#if(old('item_id') != null)
<option value="{{ old('item_id') }}" selected="selected">
{{ old('item_title') }}
</option>
#endif
</select>
</div>
{!! $errors->first('item_id', '<p class="text-center text-danger"<strong>:message</strong></p>') !!}
</div>
<input type="hidden" id="item_title" name ="item_title" value="{{ old('item_title') }}" />
<div class="form-group{{ $errors->has('quantity') ? ' has-error' : '' }}">
<label class="control-label col-sm-2" for="quantity">Cantidad: </label>
<div class="col-sm-10">
<input name="quantity" type="number" class="form-control" id="quantity" value="{{ old('quantity') }}"/>
</div>
{!! $errors->first('quantity', '<p class="text-center text-danger"><strong>:message</strong></p>') !!}
</div>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary" data-disable-with="Guardando...">Guardar</button>
</form>
JAVASCRIPT:
<script type="text/javascript">
$(document).ready(function(){
$('#item_id').select2({
placeholder: 'Elige un item',
ajax: {
url: '{{ route('select_item_data') }}',
dataType: 'json',
delay: 250,
processResults: function (data) {
return {
results: $.map(data, function (item) {
return {
text: item.title,
id: item.id
}
})
};
},
cache: true
}
});
$('#item_id').on('change', function(e){
var title = $(this).select2('data')[0].text;
$('#item_title').val(title);
});
});
</script>
VALIDATION IN STORE METHOD (CONTROLLER):
$validator = Validator::make($request->all(), [
'item_id' => 'required',
'quantity' => 'required'
]);
if ($validator->fails()) {
return redirect()
->route('create_item', $order)
->withInput($request->all())
->withErrors($validator);
}
It's very important to send 'withInput' and 'withErrors' in the redirection, because we are working with a popup and ajax that is created again and doesn't keep the old values.
Maybe you can try (once the ajax call has ended) :
var oldCustomer = $('#customer > option[value={{ Request::old('customer') }}]');
if (oldCustomer.length > 0) {
oldCustomer.attr('selected', 'selected');
}
Same problem; I'm using a similar solution: If the old $id is set, I get the name and I use it as a variable for the view; Note that I also forward the id because I also used this method to pre-fill the form (coming from another place), but in this case, the name only should have been used, and for the id {{ old('author_id') }} can be used in the view:
In the controller:
elseif (($request->old('author_id') !== null) && ($request->old('author_id') != '')) {
$my_author_id = $request->old('author_id');
$my_name = Author::find($my_author_id)->name;
return view('admin/url_author.create', compact('my_name', 'my_author_id'));
}
And in the view (more precisely, in a partial used for creation & edition):
#if (isset($record)) // for use in edit case with laravelcollective)
<select class="form-control js-data-author-ajax" id="author_id" name="author_id">
<option value="{{ $record->author_id }}">{{ $record->author->name }}</option>
</select>
#else
#if (isset($my_name)) // old input after validation + pre-filling cases
<select class="form-control js-data-author-ajax" id="author_id" name="author_id">
<option value="{{ $my_author_id }}">{{ $my_name }}</option>
</select>
#else // for create cases
<select class="form-control js-data-auteur-ajax" id="auteur_id" name="auteur_id">
<option></option>
</select>
#endif
#endif
Your code is bit confusing. I don't understand why you are using a POST request to get data using ajax to fill a select2 box.
Assuming the data returned using ajax call is in the below format.
[
{
"id": "Some id",
"text": "Some text"
},
{
"id": "ID 2",
"text": "Text 2"
},
]
Now what you can do is pass in an extra parameter to your ajax call as below
url: function() {
var type = $(this).attr('id');
#if(old('customer'))
return '/get' + type + '?customer='+ {{ old('customer') }};
#else
return '/get' + type;
#endif
}
Now in your controller while returning data you can throw an extra attribute selected:true for an ID matching that particular ID.
if( Request::has('customer') && Request::input('customer') == $id )
{
[
"id" => $id,
"text" => $text,
"selected" => "true"
]
}
else
{
[
"id" => $id,
"text" => $text,
]
}
If I understood you right I can recommend you to have for each your select2 box hidden input <input type="hidden" name="customer_name" value="{{old('customer_name', '')}}"> where after change event for select2 you can insert selected name (etc. John). So if validation is fails you have:
<select id="customer" name="customer" class="searchselect searchselectstyle">
#if(!is_null(old('customer')))
<option value="{{old('customer')}}">{{old('customer_name')}}
</option>
#endif
</select>
I think your own solution is pretty much correct. You say the list of $customers will get pretty big.
$customers->where('id', intval(Request::old('customer')))->first()
Do you need to have the list stored in a variable $customers? You could just search the id you want
App\Customer::where('id', intval(Request::old('customer')))->first()
Searching by id should not be inefficient. Otherwise you could send the name with the form and store it in the old request. Shown below with some (dirty) javascript.
$("#form").submit( function() {
var sel = document.getElementById("customer");
var text= sel.options[sel.selectedIndex].text;
$('<input />').attr('type', 'hidden')
.attr('name', "selected_customer_name")
.attr('value', text)
.appendTo('#form');
return true;
});
Then like yrv 16s answer:
<option value="{{old('customer')}}">{{old('selected_customer_name')}}
You could do something like this:
First in controller pass tags to view using pluck helper like below:
public function create()
{
$tags= Customer::pluck('name','name');
return view('view',compact('tags'));
}
Then in your form try this:
{!! Form::select('tag_list[]',$tags,old('tag_list'),'multiple','id'=>'tag_list']) !!}
Don't forget to call the select2 function.
$('#tag_list').select2();
And finally in controller:
public function store(ArticleRequest $request)
{
$model = new Model;
$tags=$request->input('tag_list');
$model->tag($tags);
}
Notice tag function is not a helper in Laravel, You implement it! The function takes names and attaches them to the instance of some thing.
Good Luck.

Resources