Vuelidate check array has items on blur - vue.js

I am using nuxt-vue-select to allow users to select multiple objects form an array locations and then I want to use Vuelidate to try and validate that at least one item in the array has been selected/set on blur however I can not get vuelidate do do this. What am I doing wrong here?
Template
<b-form-group label="Locations" label-for="locations">
<client-only>
<v-select
v-model="userCopy.locations"
name="locations"
filterable
multiple
placeholder="Select locations"
label="label"
:options="locations"
:class="{ 'is-invalid': v.form.locations.$error }"
#blur="v.form.locations.$each[index].$touch()"
/>
</client-only>
</b-form-group>
Script
data() {
return {
form:{
locations: []
}
}
},
validations() {
return {
form: {
locations: {
$each: {
required
}
}
}
}
}
Array data
{ label: 'Avon' },
{ label: 'Bedfordshire' },
{ label: 'Berkshire' },
{ label: 'City of Brighton and Hove' },
{ label: 'City of Bristol' },

For vuelidate you have to use the same filed name as $data, so you need to replace the name form of validations' object to userCopy at first, and apply minLength for your locations length:
validations() {
return {
userCopy: { // change this
locations: {
// ... to try and validate that at least one item in the array
// use minLength to do that
minLength: minLength(1)
$each: {
required
}
}
}
}
}
To deal with array structure, I recommend to try form-validation.js.

Related

How to make nested properties reactive in Vue

I hava a component with complex nested props:
<template>
<div>
<a-tree :tree-data="data" :selected-keys="[selectedKey]" #select="onSelect" />
<div>
<a-input v-model="sheet[selectedKey].tableName" />
<ux-grid ref="previewTable">
<ux-table-column v-for="field in sheet[selectedKey].fields"
:key="field.name" :field="field.name">
<a-input slot="header" v-model="field.label" />
</ux-table-column>
</ux-grid>
</div>
</div>
</template>
<script>
export default {
props: {
previewData: { type: Array, default: () => [] }
},
data () {
return {
data: this.previewData,
selectedKey: '0-0-0',
sheet: { 'none': { tableName: null, fields: [] } }
}
},
created () {
this.data.forEach((file, fid) => {
file.sheets.forEach((sheet, sid) => {
this.$set(this.sheet, `0-${fid}-${sid}`, {
tableName: sheet.label,
fields: sheet.fields.map(field => ({ ...field }))
})
})
})
},
mounted () {
this.$refs.previewTable.reloadData(this.data[0].sheets[0].data)
},
methods: {
onSelect ([ key ], { node }) {
if (key !== undefined && 'fields' in node.dataRef) {
this.selectedKey = key
this.$refs.previewTable.reloadData(node.dataRef.data)
} else {
this.selectedKey = 'none'
this.$refs.previewTable.reloadData()
}
}
}
}
</script>
And previewData props is something like:
{
name: "example.xlsx",
filename: "80b8519f-f7f1-4524-9d63-a8b6c92152b8.xlsx",
sheets: [{
name: "example",
label: "example",
fields:[
{ label: "col1", name: "col1", type: "NUMBER" },
{ label: "col2", name: "col2", type: "STRING" }
]
}]
}
</script>
This component allows user to edit the label properties. I have to make Object sheet reactive to user input, and I tried $set and Object.assign, it works for sheets.label but fields[].label is still not reactive.
I wish to know what would be the declarative (and optimal) solution for it
You might need a watcher or computed property in React for previewData to be changed.

Dropdown down item required

Hello I have a dropdown.
I need to make sure an option is selected, thus I want to make a client validation for required field.
<b-dropdown id="clientData"
name="clientData"
v-model="$v.clientData.selectedOption.$model"
:text="clientData.selectedOption"
class="m-2 col-2" no-flip>
<b-dropdown-item v-for="option in clientData.options"
:key="option.value"
:value="option.value"
#click="clientData.selectedOption = option.value">
{{option.text}}
</b-dropdown-item>
</b-dropdown>
<script>
import { required } from 'vuelidate/lib/validators'
const selectedOptionCheck = (selectedOption) => selectedOption !== 'Choose data type'
data () {
return {
clientData: {
csvFile: null,
selectedOption: 'Choose data type',
options: [
{
value: 'PhoneNumber',
text: 'Phone Number'
},
{
value: 'Email',
text: 'Email'
},
{
value: 'DeviceToken',
text: 'Device Token'
}
]
}
}
},
validations: {
clientData: {
selectedOption: {
required,
selectedOptionCheck
}
}
},
</script>
This is because I do not want to send incorrect request to the server.
I have used Vuelidate and it does not work. I have tried searching for a required tag and I have tried aria-required='Please choose an option' on the b-dropdown, however, again it does not work.
You can do following - set your v-model in your data return equal null.
Than try to use computed and check if something is selected like this:
computed: {
validData: function () {
return this.yourvmodelname
},
}

Passing an Object as a prop and syncing nested form values with Vuetify text fields

I am trying to pass the inputted form data back to the parent.
Parent:
<AddForm
:value.sync="field"
></AddForm>
data: () => ({
field: {
id: "",
name: "",
},
})
Child AddForm:
<v-text-field :value="value.id" #input="addId"></v-text-field>
<v-text-field :value="value.name" #input="addName"></v-text-field>
props: {
value: { type: Object, required: true },
},
methods: {
addId(e) {
this.$emit("update:field.id", e);
},
addName(e) {
this.$emit("update:field.name", e);
},
}
Not sure what I'm missing ?
I think you should only update field without specifying the nested fields by spreading this.value and add the updated field and emitting it as payload :
<v-text-field :value="value.id" #input="addId"></v-text-field>
<v-text-field :value="value.name" #input="addName"></v-text-field>
props: {
value: { type: Object, required: true },
},
methods: {
addId(e) {
this.$emit("update:field",{...this.value, id: e});
},
addName(e) {
this.$emit("update:field", {...this.value,name:e});
},
}
Even though you can do like #BoussadjraBrahim said, I think if that component is a form, it should act like a form, and then you only pass the data back to parent when the user try to submit the form.
Something like this, the code below is not valid, is just to demonstrate my point
FormComponent.vue
<template>
<v-form :valid="valid" #submit.prevent="sendData">
<v-text-field :value="value.id" :rules="[someValidation]" #input="id" />
<v-text-field :value="value.name" #input="name" />
<v-btn type="submit">Save</v-btn>
</v-form>
</template>
<script>
.....
props: {
value: { type: Object, required: true },
},
data() {
return {
valid: false,
id: '',
name: '',
}
},
methods: {
sendData() {
this.$emit("submit",{id: this.id, name: this.name });
},
}
</script>
Then in parent you get the information when the user submits the information.
With this approach you can let the logic to validate the input to the component, which to me makes a lot of sense.

Vee validate return true, but should return false

Im using Vuetify, and have a form where im using VeeValidate for form validation...
When im using this:
this.$validator.validateAll().then((result) => {
console.log("result form", result);
//result ? this.onSubmit() : scrollTo(0, 250);
});
It always returns true, even if the validation on my input field isn't valid...
The input looks like:
<v-textarea
filled
name="string"
:label="placeholderText"
auto-grow
single-line
:placeholder="placeholderText"
v-model="answer"
:required="isRequired"
v-validate:computedProp="checkRequired"
:error-messages="errors.collect('string')"
data-vv-name="string"
:hint="hintText"
#blur="updateAnswer"
></v-textarea>
The code for the input component:
export default {
$_veeValidate: {
validator: 'new'
},
name: 'String',
props: {
placeholderText: {
default: 'Add a value'
},
hintText: {
default: 'Add a value'
},
isRequired: {
default: true
}
},
data: () => ({
answer: ''
}),
computed: {
checkRequired() {
return this.isRequired ? 'required' : ''
}
},
methods: {
updateAnswer() {
this.$validator.validateAll();
this.$emit('updateAnswer', this.answer);
}
}
}
Im calling this.$validator.validateAll() in another component... The input component is a standalone component... I have it all wrappet in a form tag, and running the validate function on a submit
You have two choice:
Pass to the component the v-validate from the $attrs
Inject the $validator to the component
Parent
export default {
name: "App",
components: {
YourComponent
},
provide() {
return {
$validator: this.$validator
};
},
Child
$_veeValidate: {
validator: "new"
},
inject: ["$validator"],
name: "String",
You can also simplify your html code, see the VeeValidate Syntax
Html
v-validate="{ required: this.isRequired }"
And you can safely remove
:required="isRequired"

vuejs reactivity of complex objects

I am using Vue.js 2.5.17 I have two components, App (parent) and TreeNode (child), which display a tree structure of items from a deeply nested object. Each node in the tree is presented with the TreeNode component which is a recursive component.
TreeNode component
const TreeNode = Vue.component('TreeNode', {
name: 'TreeNode',
template: '#treenode',
props: {
model: Object,
},
data() {
return {
open: false,
};
},
computed: {
isExpandable() {
return this.model.children &&
this.model.children.length;
},
},
methods: {
toggle() {
if (this.isExpandable) {
this.open = !this.open;
}
},
},
});
TreeNode template
<script type="text/x-template" id="treenode">
<li>
<input type="checkbox" :id="model.name" style="display:none;"/>
<label :for="model.name" style="color:gray;" #click="toggle">
{{ model.name }}
{{ model.data.example }}
</label>
<ul v-show="open" v-if="isExpandable">
<TreeNode
class="item"
v-for="(model, index) in model.children"
:key="index"
:model="model">
</TreeNode>
</ul>
</li>
</script>
App component template
<script type="text/x-template" id="oulist">
<div>
<div id="unitsTable" class="row filterlist treelist b_widget2" style="width:85%;">
<div class="css-treeview">
<TreeNode
class="item"
:model="items">
</TreeNode>
</div>
</div>
</script>
App component
const App = Vue.component('App', {
template: '#oulist',
components: {
TreeNode,
},
data() {
return {
items: {
name: 'item1',
data: { example: '1' },
children: [
{
name: 'item11',
children: [],
data: { example: '1' },
},
{
name: 'item12',
children: [
{ name: 'item121', children: [], data: { example: '1' } },
{ name: 'item122', children: [], data: { example: '1' } },
],
data: { example: '1' },
},
],
},
};
},
methods: {
updateItem(currNode, name, data) {
if (currNode.name === name) {
Object.assign(currNode.data, data);
this.items = Object.assign({}, this.items); // tried to create a new object here and overwrite it, but it didn't help
return;
}
if (currNode.children) {
currNode.children.forEach(c => this.updateItem(c, name, data));
}
},
},
});
The object posted above is just an example, my actual object has a lot more nested levels and items per level.
The problem am I facing is that whenever a property deep within my items object is changed (more specifically, the example property of the data object inside a node), the DOM is not updated. I read through the reactivity caveats and saw that adding new properties is not reactive by default, but I am not adding new properties here, just changing the existing ones.
When data from a tree node is updated, I traverse the object to find the correct node and update its properties as follows:
updateItem(currNode, name, data) {
if (currNode.name === name) {
Object.assign(currNode.data, data);
this.items = Object.assign({}, this.items); // tried to create a new object here and overwrite it, but it didn't help
return;
}
if (currNode.children) {
currNode.children.forEach(c => this.updateItem(c, name, data));
}
},
this.updateItem(this.items, 'item121', { example: 'newVal' });
Any tips ? Thanks!
EDIT: The data is always changed only in the parent (App) component.