map for array and comparing at least one coincidence - javascript

In javascript, I have this situation:
this.props.number is always a number 0, 1,or 2
this.props.columnas is an array of objects like this:
[
{value:'1',text:'AA'},
{value:'2',text:'BB'},
{value:'3',text:'CC'},
{value:'4',text:'DD'},
{value:'5',text:'EE'},
{value:'6',text:'FF'},
{value:'7',text:'GG'}
]
selected is an array of objects too like this:
[{col:"2", ope:"", val:""}, {col:"5", ope:"", val:""}, {col:"7", ope:"", val:""}, ]
And I have this code:
this.props.columnas.map(function(col){
if(this.props.number == 0) {
if (col.value == selected[0].col)
disabledIndice = true
else
disabledIndice = false;
}
else if(this.props.number == 1) {
if (col.value == selected[0].col || col.value == selected[1].col)
disabledIndice = true
else
disabledIndice = false;
}
else if(this.props.number == 2) {
if (col.value == selected[0].col || col.value == selected[1].col || col.value == selected[2].col)
disabledIndice = true
else
disabledIndice = false;
}
return <option value={col.value} disabled={disabledIndice}>{col.text}</option>
}.bind(this))}
(forget the bind. function and es5, I will use arrow functions anyway)
It's too long and it's not doing what I need to do. I want to use map again in selected to find which col.value is the same than the one stored on selected.
In the code above, I'm assuming that I'll have 3 objects for selected, but that's not always the case. Sometimes could be 1, 2 or 3 objects, thats why the code is using those ìf for each case (the numbers of objects is defined by this.props.number). That's ugly.
I've tried something like this:
this.props.columnas.map(function(col){
indicesYaSeleccionado.map(function(item){
if (col.value == item.col)
disabledIndice = true
else disabledIndice = false;
});
return <option key={constant.guid()} value={col.value} disabled={disabledIndice}>{col.text}</option>
}.bind(this))
But that I will always get disabledbecause I'm comparing each value, I need to set as disabledIndice = true at least in 1 coincidence.
Any tip? It was a long explanation, sorry.

From what I've understand, you just have to see if the current col value is in the selected.val array.
this.props.columnas.map(function(col){
let disabledIndice =
selected.findIndex(e => e.col === col.value) !== -1
? true : false
return <option value={col.value} disabled={disabledIndice}>{col.text}</option>
}.bind(this))}
This code should work fine.

You'll have to re-add this.props to your contextualized variables, but this should cover it. And I tried to give you as many arrow functions as I could. Your return will break the code, but if used with something like React's JSX it should work fine.
//removed this.props references for the sake of this example
const columnas = [
{value:'1',text:'AA'},
{value:'2',text:'BB'},
{value:'3',text:'CC'},
{value:'4',text:'DD'},
{value:'5',text:'EE'},
{value:'6',text:'FF'},
{value:'7',text:'GG'}
];
//could be 0, 1, or 2
let curNum = 1
let selected = [{col:"2", ope:"", val:""}, {col:"5", ope:"", val:""}, {col:"7", ope:"", val:""}, ]
columnas.map( el => {
let disabledIndice = selected.every( sel => el.value == sel.col ) ?
true : false;
return <option key={constant.guid()} value={col.value} disabled={disabledIndice}>{col.text}</option>
}.bind(this))
});

Related

ion-select (multiple) not working with array of objects using compareWith property

I am facing issue with ion-select(array objects) (multiple select).
Two items are already selected on page load, but when you open the drop-down, none of the items are checked. Here is stackblitz link to reproduce this issue.
https://stackblitz.com/edit/ionic-angular-v5-kie1wd
I am using Ionic(5.26.0) and angular(8.2.14) for my project.
<ion-item>
<ion-label>Users(Multi)</ion-label>
<ion-select multiple="true" [(ngModel)]="selectedMultipleEmployee" [compareWith]="compareFn"
(ionChange)="multiChange()">
<ion-select-option *ngFor="let user of users" [value]="user">{{user.first + ' ' + user.last}}
</ion-select-option>
</ion-select>
</ion-item>
compareFn(e1: User, e2: User): boolean {
return e1 && e2 ? e1.id === e2.id : e1 === e2;
}
TL;DR
Your compareWith(e1, e2) function must return true if e2 is an array and a single element e1 is in e2, for example:
// defined outside of class
const compareWith = (e1, e2) => {
// e2 may be an array, if multiple="true"
if (Array.isArray(e2) ) {
return e2.indexOf(e1) !== -1;
}
// fallback to single element comparison
return e1 && e2 ? e1.id === e2.id : e1 === e2;
}
class MyPage {
...
compareFn = compareWith;
...
}
Important: also, define your function compareWith outside of the class, otherwise it does not work
Background
I've had the same issue after upgrading from Ionic3 to Ionic5, and I've debugged the ion-select code to see what's causing this issue.
Here's the function that determines if the an option must be checked or unchecked:
const compareOptions = (currentValue, compareValue, compareWith) => {
if (typeof compareWith === 'function') {
return compareWith(currentValue, compareValue);
}
else if (typeof compareWith === 'string') {
return currentValue[compareWith] === compareValue[compareWith];
}
else {
return Array.isArray(compareValue) ? compareValue.includes(currentValue) : currentValue === compareValue;
}
};
The compareValue is an array and the final else clause handles that case if compareWith is not provided. But since you provide your own compareWith function, then it has to be able to check if a single items belongs to that array.
Here's the actual code that worked for my specific case (comparing items by field code):
const compareWith = (f1, f2) => {
if (Array.isArray(f2)) {
if (!f1 || !f1.code) {
return false;
}
return f2.find(val => val && val.code === f1.code);
}
return f1 && f2 ? f1.code === f2.code : f1 === f2;
};
Update: Added a note that compareWith needs to be defined outside of the class.

Is there a way to refactor this AND and XOR logical branch?

I have 2 user IDs and I would like to do different but very similar logic if I have only one or both. Is there a way to consolidate this code, as right now it looks ugly.
function getUserPermission(primaryId, secondaryId, role) {
let permission = userInfo.permissionList.filter( permission => {
//logical AND
if(primaryId && secondaryId){
// have both IDs, use both IDs
(permission.primaryId === primaryId && permission.secondaryId === secondaryId) && permission.role === role
}
//logical XOR
else if((primaryId && !secondaryId) || (!primaryId && secondaryId)) {
// have only 1 ID, use 1 ID
(permission.primaryId === primaryId || permission.secondaryId === secondaryId) && permission.role === role
}
})[0]
return permission
}
First, it seems like this logic doesn't handle when both ids are invalid. You'll want to handle that somehow (looks like both the other answers so far include something along those lines).
Next, since you're only returning the first matching permission, I would suggest using a solution that doesn't keep looping once you've found that first match. In that respect, Array.find() is far better than Array.filter() for this use case.
If I were to put the other answers together, it would be something like this:
function getUserPermission(primaryId, secondaryId, role) {
if (!primaryId && !secondaryId) return null // at least one must be populated, why loop at all?
return userInfo.permissionList.find(permission =>
permission.role === role
&& (!primaryId || primaryId === permission.primaryId) // if primary is populated, it needs to match
&& (!secondaryId || secondaryId === permission.secondaryId) // if secondary is populated, it needs to match
);
}
...but that doesn't handle the case where a perfectly valid ID happens to be 0. That could depend on your data, though. Perhaps you're always using string-based IDs, for example. It also checks for valid ids on every loop (something you may or may not be perfectly comfortable with)
With those potential issues in mind, I made some assumptions about your data/types and threw a more unorthodox approach together in an attempt to solve them:
const getUserPermission = (primaryId = -1, secondaryId = -1, role = '') => {
if (primaryId < 0 && secondaryId < 0) return null
const conditions = [ p => p.role === role ]
.concat(primaryId >= 0 ? [ p => p.primaryId === primaryId ] : [])
.concat(secondaryId >= 0 ? [ p => p.secondaryId === secondaryId ] : [])
return userInfo.permissionList.find(p => conditions.every(fn => fn(p)))
}
...this last one makes an array of functions, conditions, to check each permission against. The first one to match every condition should be returned (in theory, at least - I didn't test it)
What about something like this?
if(!primaryId && !secondaryId) {
throw Error("No ID was provided");
}
let permission = userInfo.permissionList.filter(p => p.role === role);
if(primaryId) {
permission = permission.filter(p => p.primaryId === primaryId);
}
if(secondaryId) {
permission = permission.filter(p => p.secondaryId === secondaryId);
}
return permission[0];
function getUserPermission(primaryId, secondaryId, role) {
return userInfo.permissionList.find(permission =>
permission.role === role
&& (!!primaryId || !!secondaryId)// at least one is populated
&& (!primaryId || primaryId === permission.primaryId)// if primary is populated, it needs to match
&& (!secondaryId || secondaryId === permission.secondaryId)// if secondary is populated, it needs to match
);
}
This is slightly different from your code in that if there is no match, it returns null, whereas yours throws an exception. If you mean to throw, you can always put the result in a variable and check for null.
What about this refactoring :
function getUserPermission(primaryId = undefined,secondaryId = undefined,role) {
primaryId === undefined && secondaryId === undefined && throw new Error("Ids are required");
const userPermission = userInfo.permissionList
.filter((permission) => role && permission.role === role)
.filter((permission) => primaryId && permission.primaryId === primaryId)
.filter(
(permission) => secondaryId && permission.secondaryId === secondaryId
);
return userPermission[0];
}

how to filter strings in nested array?

I'm trying to implement a search function that returns find which has search word in the specified array. Let's say, a collection has [aa, ab, aaa], and search word is "a". In this case, return the object to display. Because at least one of the strings in the array has 'a'.
dataStructure
[
{
name:'aa',
searchWords:['aa','ab','bc'] <- I want to use this array for search
},
{
name:'bb',
searchWords:['bb','bc','de'] <- I want to use this array for search
},
...
]
I tried to fix the issue, by using includes(), filter(),indexOf(). However still it returns nothing or returns data when the search word is exactly matched.
How to fix the code to achieve aiming?
Thank you in advance!
this part works well
let filterStudents = students;
if (searchName.length > 0 && searchTag.length === 0) {
filterStudents = students.filter((student) => {
if (
student.firstName.toLowerCase().includes(searchName.toLowerCase())
|| student.lastName.toLowerCase().includes(searchName.toLowerCase())
) {
return true;
}
return false;
});
Problem happens on this part
} else if (searchName.length === 0 && searchTag.length > 0) {
filterStudents = students.filter(
(student) => {
console.log(student.tags);
student.tags.filter((tag) => {
console.log(tag);
tag.indexOf(searchTag) > -1;
});
},
);
} else if (searchName.length > 0 && searchTag.length > 0) {
} else {
console.log('both');
}
You don't return from the filter callbacks
As a sidenote, there is also String#includes:
filterStudents = students.filter(student =>
student.tags.some((tag) => tag.includes(searchTag))
);
If you only want to search for matches when the corresponding searchTag or searchName is filled out, use the conditional operator inside the filter callback to check whether the filter test should be carried out:
const students = [
{
name:'aa',
searchWords:['aa','ab','bc']
},
{
name:'bb',
searchWords:['bb','bc','de']
},
];
const doFilter = () => {
const [searchName, searchTag] = [...document.querySelectorAll('input')]
.map(input => input.value.toLowerCase());
const filtered = students.filter(({ name, searchWords }) => (
(searchName ? name.toLowerCase().includes(searchName) : true) &&
(searchTag ? searchWords.some(word => word.toLowerCase().includes(searchTag)) : true)
));
code.textContent = JSON.stringify(filtered);
};
window.addEventListener('change', doFilter);
<input placeholder="searchName">
<input placeholder="searchTag">
<div>
<code id="code"></code>
</div>

Optimize a function that ensures an object's properties does not have any of these values

I have an array of objects that represent form fields. I want to make sure that none of the elements in the array have their initial values. As in all fields need to be updated. The initial value is below. I'd like a simple function that checks each element and ensures none of these properties have the default fields.
[
{
key: 0,
name: '',
typeLabel: '',
typeValue: 0,
value: '',
}
...
]
Looking for help on improving this
collection.every((obj) => {
if (obj.key === 0) return false;
if (obj.name === '') return false;
if (obj.typeLabel === '') return false;
if (obj.typeValue === 0) return false;
if (obj.value === '') return false;
return true;
});
EDIT: Actually just thought of this
collection.every((obj) => {
const values = Object.values(obj);
return values.includes(0, '');
});
To be honest I'm not sure how much more better you can make it, it looks pretty decent to me! Any changes I'd make would be subjective changes that don't actually matter. Curious what other people will answer with though, maybe I'm missing something :)
I mean you could do this and it's arguably less if/elses and uses a named function to explain what it is doing which is maybe nicer? But meh I don't think this matters all that much:
function inputValueHasBeenSet(inputObject) {
return !!inputObject.key &&
!!inputObject.name &&
!!inputObject.typeLabel &&
!!inputObject.typeValue &&
!!inputObject.value;
}
collection.every(inputValueHasBeenSet);

How to delete elements of and array by two attributes in AngularJS?

I have an array of objects in AngularJS like this:
var example = [ {id:'1',
read: false,
folder: 'inbox'},
{id:'2',
read: true,
folder: 'inbox'},
{id:'3',
read: true,
folder: 'trash'},
{id:'4',
read: false,
folder: 'trash'}];
And I need to delete any object that has the attributes folder == 'trash' and read == true at the same time.
So I tried to do it like this with lodash:
example = lodash.filter(example, function(value, index) {
return (value.folder !== 'trash') && (value.read !== true);
});
It should delete only the item #3, but it deletes #3 and #4.
Obviously I'm not understanding how lodash.filter really works.
Can someone please help?
You logical operator is not correct. set the condition to folder == 'trash' and read == true and the negate it.
example = lodash.filter(example, function(value) {
return (value.folder == 'trash' && value.read == true) == false;
});
You need to remove the quotes around 'true'. You want to keep all the elements where the folder is not 'trash' or the read property is not true so your logic would look like this
return (value.folder !== 'trash') || (value.read !== true);
The operator !== is false. use == instead.
return (value.folder == 'trash' && value.read == 'true') == false;

Categories