Vue limit the number of true elements in array - javascript

I'm looking for a solution to dynamicly watch the values of an array with objects in vue:
new Vue({
el: "#app",
data: {
options: [
{"id": 1, "title": "One", "value": false},
{"id": 2, "title": "Two", "value": false },
{"id": 3, "title": "Three", "value": false}
]
},
I tried different solutions with watch and methods but none worked properly. How can I watch the objects in "options" for change of "value" and limit the maximum number of true objects to a number in this case 1 for example. If I set 2nd object true the first object need to set to false so that only the last changed object is true?

A computed property does the trick:
...
data: () => ({
options: [...], // your current options
trueValuesLimit: 2 // If you want to set the limit as data a property
})
computed: {
// If limitOn is true, then at least one value property is true
limitOn() {
const count = this.options.reduce((acc, option) => {
// If value is true, then add one to the accumulator
option.value ? acc++ : null
return acc
} , 0)
// Limit sets to 1, update as required
return count >= 1
// You can use a data property to validate the limit
// return count >= this.trueValuesLimit
}
}
...
Now you can use limitOn property to enable/disable the input to select/deselect options.

You just need to update the options, like this (in case of limit: 1):
new Vue({
el: "#app",
data: {
limit: 1, // this is not used in this snippet!
options: [{
"id": 1,
"title": "One",
"value": false
},
{
"id": 2,
"title": "Two",
"value": false
},
{
"id": 3,
"title": "Three",
"value": false
}
]
},
methods: {
setToTrue(id) {
// creating a copy of the options data object
let options = JSON.parse(JSON.stringify(this.options))
this.options = options.map(e => {
const value = e.id === id ? true : false
return { ...e, value }
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="option in options" :key="option.id">
ID: {{ option.id }}<br />
Title: {{ option.title }}<br />
Value: <span style="font-weight: 700; cursor: pointer;" #click="setToTrue(option.id)">{{ option.value }}</span><br />
<hr />
</div>
</div>
For a higher limit (like you'd allow 2 or more true values), you'd need to come up with the ruleset about which ones to keep (beside the new one) - the strategy (ruleset) could be FIFO, LIFO, etc.

Related

Find Efficient way of Filtering of two arrays (ArrayA & ArrayB) to make a third ArrayC

Can anyone help me come up with a faster way of filtering the following ArrayC (see below what it should be equal to. ArrayA is self explanatory. ArrayB has inside of each element an object 'data' that has a property "target" as a string "\_teacher" that I can filter out based on a global variable "userType" to distinguish students vs teachers. "filter_klas_1371" ==> filter_{GroepType }_{GroepID} from Array A. This property changes. breaking the string and filtering has giving me a headache, so if there are faster more efficient ways to filter this please let me know.
let userType='teacher'
arrayA: [{
"ID": 1,
"GroepID": 1371,
"GroepType": "klas"
},
{
"ID": 2,
"GroepID": 1372,
"GroepType": "klas"
},
{
"ID": 3,
"GroepID": 1375,
"GroepType": "graad"
}
]
araayB: [{
"id": "bd5b12ba-b433-4610-801e-e0b78fa72ff8",
data: {
"target": "_teacher",
"filter_klas_1371": "true"
}
},
{
"id": "gggfdgdba-gfgg-fff-ggg-7657657676",
data:{
"target": "_teacher_student",
"filter_klas_1375": "true"
}
} {
"id": "uuuykllk-b433-4610-801e-8888888776",
data: {
"target": "_student",
"filter_klas_1372": "true"
}
} {
"id": "jkjkjkklk-jkhjk-66567-666-99977",
data: {
"target": "_teacher_student",
"filter_klas_1372": "true"
}
},
{
"id": "zzzzzzz-b433-4610-801e-8888888776",
data: {
"target": "_teacher",
"filter_klas_1372": "true"
}
},
]
//should get
arrayC:[{
"id": "bd5b12ba-b433-4610-801e-e0b78fa72ff8",
data: {
"target": "_teacher",
"filter_klas_1371": "true"
}
},
{
data: {
"id": "jkjkjkklk-jkhjk-66567-666-99977",
"target": "_teacher_student",
"filter_klas_1372": "true"
}
},
{
"id": "zzzzzzz-b433-4610-801e-8888888776",
data: {
"target": "_teacher",
"filter_klas_1372": "true"
}
}
]
If I understand correct you would like to filter arrayB based on the userType and the values in arrayA. I suggest making an array of all the possible filter_{groepType}_{groepId} with array map:
const filterKeys = arrayA.map(group => `filter_${group.GroepType}_${group.GroepID}`);
Then you can check if one of those values is set as a key in the keys of arrayB, using an intersection of array with a couple of filters. You can do this in more ways:
arrayC = arrayB.filter(item => {
const intersect = Object.keys(item.data).filter(key => filterKeys.includes(key));
return intersect.length > 0; // you can additionally add the filter for target here
})

add count (.length) each items in option on v-select

I want to add count course.length from api to each item option on v-select and show data to active based filter.
For example : Items in v-select contains options all(count), passed(count) which filtered from online.passed, not complete(count)
which filtered from online.complete, etc.
vue.js template :
<template>
<v-select
v-model="filter"
:items="items"
></v-select>
<v-card v-for="online in onlineCourse" :key="online.id">
<v-card-title>{{online.title}</v-card-title>
<p v-if="online.complete === 100">{{online.complete}}%</p>
<p v-else-if="online.complete < 100 && online.complete > 0">{{online.complete}}%</p>
<p v-else>{{online.complete}}%</p>
</v-card>
</template>
<script>
data () {
return {
onlineCourse: [],
filter: 'All',
items: [
'All',
'passed',
'not complete',
],
}
}
method: {
async getDataOnline () {
try {
const request = await Axios.get('v1/courses')
this.onlineCourse = request.courses
} catch (error) {
console.log(error.message);
}
}
}
</script>
Response JSON :
"courses": [
{
"id": 1,
"title": "title1",
"passed": false,
"completed": 0
},
{
"id": 2,
"title": "title2",
"passed": true,
"completed": 100
},
{
"id": 3,
"title": "title3",
"passed": false,
"completed": 50
}
],
Few Observations in current code you posted :
You are checking for online.complete === 100 but you don't have complete property in online object. Hence, corrected that to completed instead of complete.
closing braces is missing from online.title expression.
Now coming to the original problem :
To achieve the counts in the v-select options. You have to convert your items array from array of elements to array of objects.
items: ['All', 'passed', 'not complete']
to
items: [{
name: 'All',
count: this.onlineCourse.length
}, {
name: 'passed',
count: this.onlineCourse.filter((course) => course.passed)
}, {
name: 'not complete',
count: this.onlineCourse.filter((course) => course.completed === 0)
}]

how to change the structure of the output

This is the start of the object. I should be able to get the elements of celldata.values.
var generatedObj1 = {
"agg": "Glossar",
"ifa": null,
"ifv": null,
"ird": null,
"afv": null,
"ard": null,
"metaData": {
"cellMetaDataList": [{
"cell": "glossar", // <-- cell type
"cmv": null,
"crd": null,
"logicalData": {
"body": [{
"id": "f5d", // <-- body id 1
"name": "name", // <-- attribute name 1
"minLength": 3,
"maxLength": 100,
"minValue": null,
"maxValue": null,
"wertebereich": "ALPHANUMERISCH",
"validationCode": "",
"exampleValues": ["Langer Name", "Kurzer Name", "Kein Name"]
},
{
"id": "42", // <-- body id 2
"name": "kurzname", // <-- attribute name 2
"minLength": 3,
"maxLength": 20,
"minValue": null,
"maxValue": null,
"wertebereich": "ALPHANUMERISCH",
"validationCode": "",
"exampleValues": ["long_desc", "short_desc", "no_desc"]
},
{
"id": "9d", // <-- body id 3
"name": "eindeutig", // <-- attribute name 3
"minLength": 1,
"maxLength": 1,
"minValue": null,
"maxValue": null,
"wertebereich": "WAHRHEITSWERT",
"validationCode": "",
"exampleValues": [true, false]
}
]
}
}]
},
"data": {
"cellData": [{ // row 1 from res1
"cell": "glossar", // <-- cell type
"id": "5b", // <- UUID v4 (generated by the function)
"cmv": null,
"crd": null,
"caption": {
"pers": "string",
"custNr": "string",
"fnam": "string",
"snam": "string",
"sysId": "string"
},
"values": [{
"name": "name", // <-- attribute name 1
"value": "Langer Name",
"bodyRefId": "5d" // <-- body id 1
},
{
"name": "kurzname", // <-- attribute name 2
"value": "no_desc",
"bodyRefId": "42" // <-- body id 2
},
{
"name": "eindeutig", // <-- attribute name 3
"value": false,
"bodyRefId": "9d" // <-- body id 3
}
]
},
{ // row 2 from res1
"cell": "glossar", // <-- cell type
"id": "5c", // <- UUID v4 (generated by the function)
"cmv": null,
"crd": null,
"caption": {
"pers": "string",
"custNr": "string",
"fnam": "string",
"snam": "string",
"sysId": "string"
},
"values": [{
"name": "name", // <-- attribute name 1
"value": "Kein Name",
"bodyRefId": "5d" // <-- body id 1
},
{
"name": "kurzname", // <-- attribute name 2
"value": "short_desc",
"bodyRefId": "42" // <-- body id 2
},
{
"name": "eindeutig", // <-- attribute name 3
"value": true,
"bodyRefId": "9d" // <-- body id 3
}
]
},
{ // row 3 from res1
"cell": "glossar", // <-- cell type
"id": "5d", // <- UUID v4 (generated by the function)
"cmv": null,
"crd": null,
"caption": {
"pers": "string",
"custNr": "string",
"fnam": "string",
"snam": "string",
"sysId": "string"
},
"values": [{
"name": "name", // <-- attribute name 1
"value": "Kurzer Name",
"bodyRefId": "5d" // <-- body id 1
},
{
"name": "kurzname", // <-- attribute name 2
"value": "short_desc",
"bodyRefId": "42" // <-- body id 2
},
{
"name": "eindeutig", // <-- attribute name 3
"value": true,
"bodyRefId": "9d" // <-- body id 3
}
]
}
]
}
}
Blockquote
the returned array should be converted to match the metadata.
Each entry in the array describes an object in the result object.
and should be able to change implement the requirement in the function below.
function add(input1) {
let exampleValues1 = input1.metaData.cellMetaDataList[0].logicalData.body.map(({
exampleValues
}) => exampleValues);
return comb(exampleValues1,20);
}
the combination function .
function comb(args,output) {
var combination = [], max = args.length-1;
function helper(arr, i) {
for (var j=0, l=args[i].length; j<l; j++) {
var a = arr.slice(0); // clone arr
a.push(args[i][j]);
if (i==max)
combination.push(a);
else
helper(a, i+1);
}
}
helper([], 0);
while(combination.length < output){
combination = combination.concat(combination)}
return combination.sort(function() {
return .5 - Math.random();
}).slice(0,output); }
console.log(add(generatedObj1));
Sample result:
[["Langer Name", "no_desc", false],
["Kein Name", "short_desc", true]
["Kurzer Name", "short_desc", true]
];
the returned array should be converted to match the metadata.
Each entry in the array describes an object in the result object.
You can still just call the add function and get your input with it
The input does not change at all we are just changing the output... (that's why the ticket is named adaptation of the output structure)
For every string artay within the output of the comb function the add function should create one object within the cellData element. It should have cell type, an id, a null cmv and crd value, as well es the caption object
Most importantly it should have an values array having an object withe the name of the attribute (from body.name) the value (from the array comb returned) and a bodyRefId (from body.id)
How can I implement this in my function add()?
You can create an object from an array by filling the fields of the object using the Square brackets notation. The method would look like this:
function mapArrayToObject(cellDataValues) {
const obj = {};
cellDataValues.forEach(element => {
const fieldName = element[0];
const fieldValue = element[2];
obj[fieldName] = fieldValue;
});
return obj;
}
Update 09/17/2020 (The question was misunderstood)
After studying your question carefully I guess that your objective is the next one: After the execution of the add function what you want to get is an object with some fields (id, cell, caption...) and a values field that contains the values computed by the comb function, but parsed into objects that contains information about the body.
In order to achieve it I purpose a solution divided in 3 steps inside the add function:
Create an object with all the fields that are not related to the values computed by the comb function.
Execute the comb function.
Iterate through the values obtained, using the object created at step one as a template (cloning it) and use the index of the value you are iterating through to know which body you are pointing to in order to obtain the name and bodyRefId for each value.
The implementation would be:
function add(input1) {
// FIRST STEP: Creating the template object
const metaData = input1.metaData.cellMetaDataList[0];
const bodies = metaData.logicalData.body;
let emptyObj = {
id: "ID GENERATED BY THE FUNCTION",
cell: metaData.cell,
cmv: null,
crd: null,
caption: {
"pers": "string",
"custNr": "string",
"fnam": "string",
"snam": "string",
"sysId": "string"
}
};
// SECOND STEP: Calling to the comb function
let exampleValues1 = bodies.map(({ exampleValues }) => exampleValues);
const valuesArr = comb(exampleValues1,20);
// THIRD STEP: Parsing (Tricky part)
// Iterating through each collection of values -> [], [], []
const valuesObj = valuesArr.map(arr => {
// Cloning the template object
const obj = { ...emptyObj };
// Iterating through each value -> 'val1', 'val2', 'val3'
const valuesArr = arr.map((value, index) => {
// Using the index in order to know the body.
// For example, when index is 0 we know that we can get the name from bodies[0]
return {
name: bodies[index].name,
value: value,
bodyRefId: bodies[index].id
}
})
// Assigning the parsed values to the cloned template object
obj.values = valuesArr;
// Returning the object as we are using the map function
return obj;
});
// Now, valuesObj should have the expected structure
return valuesObj;
}

Modify object while passing through react component

I am trying to pass through an object that looks like this
{
"nodes": [
{
"attributes": null
},
{
"attributes": {
"nodes": [
{
"attributeId": 1,
"name": "pa_color",
"options": [
"gray"
]
},
{
"attributeId": 2,
"name": "pa_size",
"options": [
"large"
]
}
]
}
},
{
"attributes": {
"nodes": [
{
"attributeId": 1,
"name": "pa_color",
"options": [
"blue"
]
}
]
}
}
]
}
into a react component that renders all the different options under all the unique names. However, the way the data is structured means that I receive duplicates of names and options.
I am trying to convert the object into this object
{
"node": {
"attributeId": 1,
"name": "pa_color",
"values": [
{
"name": "gray"
},
{
"name": "blue"
}
]
},
"node": {
"attributeId": 2,
"name": "pa_size",
"values": [
{
"name": "large"
}
]
},
}
Current code looks like this
export interface Category_products_edges_node_attributes_edges_node {
__typename: "ProductAttribute";
/**
* Attribute Global ID
*/
name: string;
/**
* Attribute options
*/
options: (string | null)[] | null;
/**
* Attribute ID
*/
attributeId: number;
}
export interface ProductFiltersProps {
attributes: Category_products_edges_node_attributes_edges_node[]
}
export const ProductFilters: React.FC<ProductFiltersProps> = ({
attributes,
}) => (
<div className="product-filters">
<div className="container">
<div className="product-filters__grid">
{attributes.map(attribute => (
I have tried to do
{groupBy(attributes, 'attributeId').map(attribute => (
With the Lodash library, but receive the error
This expression is not callable. Type
'Category_products_edges_node_attributes_edges_node[]' has no call
signatures.
What is the best way to do this?
Thank you
lodash groupBy returns an Object not an Array therefore the javascript .map call will not work on it. Also groupBy is used to group items with similar property under one key inside an object, it isn't used to remove duplicates.
To remove duplicates use the lodash uniqBy method. This method can be called on an array and returns an array without duplicates.
Update:
To view in more detail how you can remove duplicates based on more than one property of object please see great answer
Also the output object you are trying to achieve has similar keys, I think that is not what you want, a Javascript object should not have duplicate keys. So my output gives keys as node0, node1 instead of node
You can achieve this as follows:
const nodes = {
nodes: [
{ attributes: null },
{
attributes: {
nodes: [
{ attributeId: 1, name: "pa_color", options: ["gray"] },
{ attributeId: 2, name: "pa_size", options: ["large"] }
]
}
},
{
attributes: {
nodes: [{ attributeId: 1, name: "pa_color", options: ["blue"] }]
}
}
]
}
const attributes = []
nodes.nodes.forEach(e => {
if (e.attributes && Array.isArray(e.attributes.nodes)) {
attributes.push(...e.attributes.nodes)
}
})
const uniqueAttributes = _.uniqBy(attributes, (obj) => [obj.attributeId, obj.name, obj.options].join())
const uniqueNodes = uniqueAttributes.map((e, i) => ({ ["node" + i]: e }))
console.log("Unique Nodes: ", uniqueNodes)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.10/lodash.min.js"></script>

Vue.js : passing item as parameter to computed prop in v-for, returning child array sorted ?

Have been googling around for quite a while to figure out how to do this..
I have a list of "Intents", which each of them got a list of "Entities", presented in a nested v-for.
Intents are already computed, but i need to also sort the "Entities" list on the fly, so therefore i thought that making that list also computed..
Error :
**TypeError: _vm.entityList is not a function**
This is my current approach :
< script >
import uniq from 'lodash/uniq'
import orderby from 'lodash/orderby'
import Util from "#/components/util.js"
export default {
data() {
return {
// ....
}
},
computed: {
nluData() {
return orderby(this.$store.getters.nlujson.filter(item => {
return item.intent.toLowerCase() === this.selectedIntent
}), ['intent', 'text'], ['asc', 'asc'])
},
entityList(item) {
return orderby(item.entities, ['entity', 'value'], ['asc', 'asc'])
},
},
created() {
this.$store.dispatch('getNluJson')
},
methods: {
// ......
}
</script>
// parent structure
<div v-for="(item, key, index) in nluData">
// displaying nluData content through item.mydata // child structure
<div v-for="ent in entityList(item)">
// displaying entities data through computed prop. // item.entities is the array
</div>
</div>
{
"id": "J4a9dGEBFtvEmO3Beq31",
"text": "This is Intent 1",
"intent": "shelf_life",
"entities": [
{
"start": "33",
"end": "44",
"value": "fridge",
"entity": "ingredient_placement"
},
{
"start": "10",
"end": "20",
"value": "duration",
"entity": "shelf_life"
},
{
"start": "25",
"end": "30",
"value": "spareribs",
"entity": "ingredient"
}
]
},
You can pass parameters to a computed property with an anonymous function like so:
computed: {
entityList() {
return (item) =>
orderby(item.entities, ['entity', 'value'], ['asc', 'asc']);
},
},
A computed property don't get any params. In your case, the prop entityList() must be a method :
methods : {
entityList(item) {
return orderby(item.entities, ['entity', 'value'], ['asc', 'asc'])
},
},
https://v2.vuejs.org/v2/guide/computed.html

Categories

Resources