How to properly add custom validation to array in vuelidate - javascript

I have an array of objects with the following structure
varientSections: [
{
type: "",
values: [
{
varientId: 0,
individualValue: ""
}
]
}
]
I created a custom validation called isDuplicate, which checks for duplicate value for the property "type". For example
varientSections: [
{
type: "Basket",
values: [
{
varientId: 0,
individualValue: ""
}
]
},
{
type: "Basket", // ERROR: Duplicate with the "above" object
values: [
{
varientId: 1,
individualValue: ""
}
]
}
],
I was able to get my custom validation to work. However, the $invalid property will be false for all the objects present in the array. Hence, all the objects in the array will be highlighted in red
Below is my validation code:
validations: {
varientSections: {
$each: {
type: {
required,
isDuplicate(type, varient) {
console.log(varient);
const varientIndex = this.varientSections.findIndex(
v => v.type === type
);
var isWrong = true;
this.varientSections.forEach((varObject, index) => {
if (index !== varientIndex) {
if (varObject.type === varient.type) {
isWrong = false;
}
}
});
return isWrong;
}
},
values: {
$each: {
individualValue: {
required
}
}
}
}
}
},

Should be something like this.
<div v-for="(vs, index) in varientSections" :key="index">
<input :class="{'is-error': $v.varientSections.$each[index].type.$error}" type="text" v-model="vs.type">
<input :class="{'is-error': $v.varientSections.$each[index].value.$error}" type="text" v-model="vs.value>
</div>
Change the error class to fit your need.

I had the exact same need and found that the solution was quite simple once you wrap your head around what you're trying to do. Your validator needs to trigger only if the current item is a duplicate of any previous items.
Something like this:
validations: {
varientSections: {
$each: {
isUnique(currItem, itemArr) {
// Find the index of the first item in the array that has the same type
var firstIdx = itemArr.findIndex((item /*, index, arr*/) => currItem.type === item.type );
// If it's the same as the current item then it is not a duplicte
if(currItem === itemArr[firstIdx])
return true;
// All others are considered duplicates
return false;
},
type: { required }
}
}
}

this is worked for me
<b-row v-for="(field,index) in fields" :key="index">
<b-colxx lg="6">
<b-form-group :label="$t('target.name')">
<b-form-input v-model="field.name" :state="!$v.fields.$each[index].name.$error"/>
<b-form-invalid-feedback v-if="!$v.fields.$each[index].name.required">name is required</b-form-invalid-feedback>
</b-form-group>
</b-colxx>
<b-colxx lg="6">
<b-form-group :label="$t('target.value')">
<b-form-input v-model="field.value" :state="!$v.fields.$each[index].value.$error"/>
<b-form-invalid-feedback v-if="!$v.fields.$each[index].value.required">value is required</b-form-invalid-feedback>
</b-form-group>
</b-colxx>
</b-row>
.
data() {
return {
fields: [
{name: null, value: null},
{name: null, value: null} ]
}
},
.
validations: {
fields: {
$each: {
name: {
required
},
value: {
required
}
}
},
},

Related

How to loop over nested JSON array and filter out search Query in Angular?

I have a complex nested JSON Array and I want to filter it(name property) through based on what user enters in input tag and show it as an autocomplete. A basic of it I have created here on stackblitz click here for the code. I have two entries of name "Tom" in two different objects so when user types Tom it should appear twice in the autocomplete as of now it shows only once. So if I press letter "T" it should show me all the names starting with "T". Here in this case "Tom" twice if I press "To" and if I press just "T" then Tiffany and Tom 2 times. Could you please help me here in the code ?
Any help is appreciated. Thanks much!
You can check the below code also, I have updated code you check in stackbliz https://stackblitz.com/edit/angular-ivy-k14se7
matches = [];
ngOnInit() {}
searchKeyup(ev) {
var term: any = ev.target as HTMLElement;
console.log("Value", term.value);
this.matches = [];
let content = [
{
name: 'Robert',
children: [],
},
{
name: 'Doug',
children: [
{
name: 'James',
children: [
{
name: 'John',
children: [
{
name: 'Tom',
children: [],
},
],
},
],
},
],
},
{
name: 'Susan',
children: [
{
name: 'Tiffany',
children: [
{
name: 'Merry',
children: [
{
name: 'Sasha',
children: [],
},
{
name: 'Tommy',
children: [],
},
],
},
],
},
],
},
];
if(term.value.length > 0){
this.filter(content, term.value);
} else {
document.getElementById('list').innerHTML = '';
}
if (this.matches.length > 0) {
document.getElementById('list').innerHTML = this.matches.map(match => match.name).join(",");
} else{
document.getElementById('list').innerHTML = "";
}
}
filter(arr, term) {
arr.forEach((i) => {
if (i.name.includes(term)) {
this.matches.push(i);
}
if (i.children.length > 0) {
this.filter(i.children, term);
}
});
console.log(this.matches);
}
You were on a good path. The only missing thing in this recursive walk is keeping state of matches like this:
filter(arr, term, matches) {
if (term.length == 0) {
matches = [];
document.getElementById('list').innerHTML = '';
}
arr.forEach((i) => {
if (i.name.includes(term)) {
matches.push(i);
}
if (i.children.length > 0) {
this.filter(i.children, term, matches);
}
});
console.log(matches);
if (matches.length > 0) {
document.getElementById('list').innerHTML = matches[0].name;
}
}

how can I fetch arrays of the object in an object in vue or javascript

I have an object in my Vue instance with the name of items
<script>
export default {
data() {
return {
selected: "",
items: {
item1: [{ selected: "", inputType: "", inputTarget: "" }],
item2: [{ selected: "", inputType: "", inputTarget: "" }]
},
textarea: ""
};
},
methods: {
selectboxAction(index) {
this.items.item1.forEach(val => {
if (val.selected.toLowerCase() === "file") {
this.$refs.inputData[index].type = "file";
} else {
this.$refs.inputData[index].type = "text";
}
});
}
}
};
</script>
how can I fetch an array of it, I want to put some condition over every item, but it could have item more than 2 items, maybe in the future it reaches 100
as you can see in selectboxAction method, I was able to fetch only one item which is item1 in this case
how can I fetch all of the arrays from items, not just one item1
I suggest you format your data using a computed getter and use Object.keys as others have suggested.
get itemsArray() {
return Object.keys(this.items).map((key) =>
return this.items[key];
});
}
Instead of naming your 'items' properties 'item1', 'item2', etc, it would be better make 'items' an array and add an 'id' property to each 'items' object:
data() {
return {
selected: "",
items: [
{ id: 1, selected: "", inputType: "", inputTarget: "" },
{ id: 2, selected: "", inputType: "", inputTarget: "" }
],
textarea: ""
};
},
you can do something like
methods: {
selectboxAction(index) {
Object.keys(this.items).forEach(val => {
this.items[val].forEach(item => {
if (item.selected.toLowerCase() === "file") {
this.$refs.inputData[index].type = "file";
} else {
this.$refs.inputData[index].type = "text";
}
});
});
}
}

Search implementation for Multilevel array

i have JSON like below.
const testData = [
{
menu: 'Test',
submenu: [
{
menu: 'Test1',
submenu: [
{
menu: 'Test1.1',
},
{
menu: 'Test1.2',
},
{
secondLevel: [
{
menu: 'Test1.3',
submenu: [
{
menu: 'Test1.4',
},
{
menu: 'Test1.5',
},
],
},
],
},
],
},
i have used reduce function to traverse to search the expected word like Test1.1 i am getting proper value, whereas while search the Test1.4 is not coming up properly as it has secondLevel as parent object.
The code i used is below which is suggested in stackoverflow.
function search(data, value) {
return data.reduce((r, e) => {
const object = { ...e }
const result = search(e.submenu || [], value)
if (result.length) object.submenu = result
if (e.menu == value || result.length) r.push(object)
return r;
}, [])
}
Please suggest best way to search the element in secondLevel object as well. Thanks in advance

Updated nested object by matching ID

I have an array with nested objects that I need to update from another array of objects, if they match.
Here is the data structure I want to update:
const invoices = {
BatchItemRequest: [
{
bId: "bid10",
Invoice: {
Line: [
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "10110" },
},
},
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "11110" },
},
Amount: 2499,
},
],
},
},
{
bId: "bid10",
Invoice: {
Line: [
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "10110" },
},
},
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "10111" },
},
Amount: 2499,
},
],
},
},
],
};
Here is the array of objects I want to update it from:
const accounts = [
{ AccountCode: "10110", Id: "84" },
{ AccountCode: "11110", Id: "5" },
{ AccountCode: "10111", Id: "81" },
];
I want to update invoices, using accounts, by inserting Id if AccountCode matches, to get the following structure:
const invoices = {
BatchItemRequest: [
{
bId: "bid10",
Invoice: {
Line: [
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "10110", Id: "84" },
},
},
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "11110", Id: "5" },
},
Amount: 2499,
},
],
},
},
{
bId: "bid10",
Invoice: {
Line: [
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "10110", Id: "84" },
},
},
{
SalesItemLineDetail: {
ItemAccountRef: { AccountCode: "10111", Id: "81" },
},
Amount: 2499,
},
],
},
},
],
};
I have tried various methods, such as the following:
const mapped = invoices.BatchItemRequest.map((item1) => {
return Object.assign(
item1,
accounts.find((item2) => {
return item2 && item1.Invoice.Line.ItemAccountRef.AccountCode === item2.AccountCode;
})
);
});
Problem with this approach (it doesn't work as I think I need to do another nested map), but it also creates a new array, only including the nested elements of invoices.
Does anyone know a good approach to this?
This isn't the cleanest of code but it gets the job done:
function matchInvoiceWithAccount(invoices, accounts) {
const mappedInvoices = invoices.BatchItemRequest.map((request) => {
// Shouldn't modify input parameter, could use Object.assign to create a copy and modify the copy instead for purity
request.Invoice.Line = request.Invoice.Line.map((line) => {
const accountCode = line.SalesItemLineDetail.ItemAccountRef.AccountCode;
// If accounts was a map of AccountCode to Id you would't need to search for it which would be more effective
const account = accounts.find((account) => account.AccountCode === accountCode);
if (account) {
line.SalesItemLineDetail.ItemAccountRef.Id = account.Id;
}
return line;
});
return request;
});
return {
BatchItemRequest: mappedInvoices,
};
}
What you could and probably should do to improve this is to not modify the input parameters of the function, but that requires that you in a better way copy the original, either using Object.assign or spread operator.
At first, it will be good to create Map from your accounts array. We will go one time for array with O(n) and then will read ids by code with O(1). And nested fors is O(m*n), that will be much more slower at big arrays.
const idsByAccountCodes = new Map();
accounts.forEach((data) => {
idsByAccountCodes.set(data.AccountCode, data.Id);
})
or shorter:
const idsByAccountCode = new Map(accounts.map((data) => [data.AccountCode, data.Id]))
then if you want to mutate original values you can go through all nesting levels and add values
for ( const {Invoice:{ Line: line }} of invoices.BatchItemRequest){
for ( const {SalesItemLineDetail: {ItemAccountRef: item}} of line){
item.Id = idsByAccountCodes.get(item.AccountCode) || 'some default value'
// also if you don't have ids for all codes you need to define logic for that case
}
}
If you don't need to mutate original big object "invoices" and all of nested objects, then you can create recursive clone of if with something like lodash.cloneDeep

REACTJS: obj.push() and obj.concat is not a function

I am having an error that my obj.push() and obj.concat() is not a function but I am not so sure why. Here is my code:
onSearch = () => {
var obj = {
product: [
{
field: "is_published",
filter_value: 1
},
{
field: "order_mode",
filter_array: [
"fcfs",
"purchase-order"
]
},
{
relationship: "store",
filter_object: {
field: "slug",
filter_value: "sample"
}
}
]
}
if (this.state.search !== "") {
obj.push(
{
field: "name",
text_search: this.state.search
}
)
}
var obj2 = {
taxonomies: [
[
{ field: "type",
filter_value: "seller"
},
{ field: "slug",
filter_value: "brand"
}
]
]
}
var conc = obj.concat(obj2);
var { getProductSearch } = this.props
getProductSearch(obj.concat(obj2))
}
product and taxonomies are stored in different variables but I need to pass them as one array to getProductSearch and for that, I need to use concat(). then I need to use push() because I want to add an object to the array obj. What am I doing wrong?
The simple answer is because you can't push onto an object. Push is used for arrays.
To make this work you could instead change your code to push onto the array in your object by doing.
obj.product.push(
{
field: "name",
text_search: this.state.search
}
)
If you are trying to make product dynamic where there are multiple products like [fruits, veggies, meat] then you could change it simply by doing
onSearch(productName)
obj[productName].push(
{
field: "name",
text_search: this.state.search
}
)
This would let you call onSearch(veggies) and push only to that array if you set it up that way.

Categories

Resources