Javascript filter with multiple criteria - javascript

I am filtering items based off of multiple criteria. in my sample I have three different criteria but the final code will have 5 different criteria. My current code works but it is not efficient and now that I have added a third criteria it is getting blotted. I know there has to be a better more efficient way to write the code.
My three filters are: Search, Year & Weeks.
return this.list.filter((item) =>{
let results
// if search only
if(this.search) {
results = item.title.toLowerCase().match(this.search.toLowerCase())
}
// if a year is selected only
if(this.whichYear) {
results = this.checkForDefaultYear(item).includes(this.whichYear)
}
// if num of weeks only
if(this.numOfWeeks) {
results = item.units.length === this.numOfWeeks
}
// if both search and year
if(this.search && this.whichYear) {
results = this.checkForDefaultYear(item).includes(this.whichYear) && item.title.toLowerCase().match(this.search.toLowerCase())
}
// if neither search or year
if(!this.search && !this.whichYear && !this.numOfWeeks)
results = item.title
return results
})
The below above works but now that I have added Weeks I have to do something like
if(this.search && this.whichYear && this.numOfWeeks) {}
if(this.search && this.whichYear) {}
if(this.search && this.numOfWeeks) {}
if(this.whichYear && this.numOfWeeks) {}
This is going to get out of control once I have all 5 filters. This does not seem like the best way to write this code.

I'm not sure if I understand correctly, but from what I understand you want to run a series of checks together, depending on whether a user selected certain options or not.
To do so, you can try with this approach:
return this.list.filter((item) =>{
let results = !!item.title; // default filter
// if previous filters and search
if(this.search) {
results = results && item.title.toLowerCase().match(this.search.toLowerCase());
}
// if previous filters and year
if(this.whichYear) {
results = results && this.checkForDefaultYear(item).includes(this.whichYear)
}
// if previous filters and weeks
if(this.numOfWeeks) {
results = results && item.units.length === this.numOfWeeks
}
return results;
});
Here, it works like this:
Apply default filter, in this case, the title must be not empty
If a user selected the search filter, you apply an additional condition to the previous ones (here only default)
If a user selected the whichYear filter, you apply an additional condition to the previous ones (here default and search)
If a user selected the numOfWeeks filter, you apply an additional condition to the previous ones (here default, search, and year)
So you basically extend the conditions chain depending on what your user selects. And this way you can add more filters according to your needs.
You could also do a similar thing using arrays: create a filters array and push() a filter with every condition and in the end, check if every filter returned true.

The filter callback needs a true or false value.
So the most common way is to return false if a check fails, end return true if it's correct :
return this.list.filter((item) =>{
if (condition1) {
return false; // Invalid
}
if (condition2) {
return false; // Invalid
}
// Valid
return true;
})
So in your case, you can do somthing like:
return this.list.filter((item) =>{
// if search only
if (item.title.toLowerCase().match(this.search.toLowerCase()) {
return false
}
// if a year is selected only
if (this.checkForDefaultYear(item).includes(this.whichYear)) {
return false
}
// if num of weeks only
if (item.units.length === this.numOfWeeks) {
return false
}
// ...and the rest...
// Valid
return true;
})

Related

Adding an IF Statement to a Filter Method in Vue

I am creating an app in Vue that will filter items in a JSON object. I am having issues with adding a checkbox type filter to this app, because I only want the checkbox filter to run if at least one checkbox is checked, and not to run if none are checked. So currently I have:
computed: {
filteredJobs: function(){
var filteredList = this.jobs.filter(el=> {
return el.title.toUpperCase().match(this.search.toUpperCase())
&& el.employmentType.toUpperCase().match(this.selectedJobType.toUpperCase())
&& el.customText12.toUpperCase().match(this.selectedLocation.toUpperCase())
&& el.dateAdded >= this.checkedDate
});
if (!this.checkedServiceAreas.length) {
return filteredList;
}else{
return filteredList.filter(job => this.checkedServiceAreas.includes(job.categories.data.map(({name}) => name).join(' ')));
}
}
}
So I am doing most of my filtering using select dropdowns, which makes it easy to use the match method to filter the JSON object, but for checkboxes it is a little more difficult because they can be multiple checked. How I currently have it set up is in an IF statement to only filter the checkedServiceAreas if there is at least one checkbox checked. If there isn't just run my normal filtered method.
What I am trying to do is figure out a way to incorporate my IF statement into my filter method without having to do it in two steps like I have above.
return el.title.toUpperCase().match(this.search.toUpperCase())
&& el.employmentType.toUpperCase().match(this.selectedJobType.toUpperCase())
&& el.customText12.toUpperCase().match(this.selectedLocation.toUpperCase())
&& el.dateAdded >= this.checkedDate
&& (this.checkedServiceAreas.length === 0 ||
this.checkedServiceAreas.includes(el.categories.data.map(({name}) => name).join(' '))));

Angular JavaScript filter function, updating with $scope input

I'm currently writing a little function to take a $scope value from an input field, then add it to a new array containing invited users.
To prevent duplicate entries into the array I'm trying to use JavaScript's filter method.
My problem is when I look at whats being output to the console, the console.log inside the filter function always prints the same Email value, it doesn't seem to update and consider the new input. e.g. a user has added 2 users, the first will be added and the invitesArr will only contain the first Email.
var invitesArr = $scope.invites;
console.log(invitesArr)
console.log($scope.searchContacts)
if ($scope.invites.length >= 0) {
invitesArr.filter(function(invitesArr) {
console.log(invitesArr.Email)
return invitesArr.Email === $scope.searchContacts;
});
if (invitesArr == false) {
courseAccountService.inviteGuestToCourse($scope.courseId,$scope.searchContacts).
then(function(invitation) {
$scope.invites.push(invitation);
});
}
}
Try the below code:
var invitesArr = $scope.invites;
console.log(invitesArr)
console.log($scope.searchContacts)
if ($scope.invites.length >= 0) {
//use arrow operator instead of function.
var duplicateEmails = invitesArr.filter((item) => {
console.log(invitesArr.Email)
//assuming datatype of item.Email and $scope.searchContacts are same else use == for comparision
return item.Email === $scope.searchContacts;
});
//Check for length and then push to your invites
if (duplicateEmails.length==0) {
courseAccountService.inviteGuestToCourse($scope.courseId,$scope.searchContacts).
then(function(invitation) {
$scope.invites.push(invitation);
});
}
}
Hope it helps!!

filtering an element from object array

I have an array of object something like below
Object[0]
canUpload:false
canBeRemoved:true
type:Object
allowMultiple:false
deleted:false
key:"testValue"
Object[1]
canUpload:true
canBeRemoved:true
type:Object
allowMultiple:false
deleted:false
key:"testValue2"
I want to remove an elements from array which contains key:testValue
var myValues = this.testData.data3;
if(!this.testData.canDownload){
myValues= myValues.filter(function(value){
if(!value.canUpload)
return value.type.key==='testValue';
else return false;
});
But its not removing .Whats the right way to do it?
Here is the full code .I can see myValues array of size 2 .If i print myValues after if block its empty.
Code pen:http://codepen.io/developer301985/pen/woGBNg
If your want to filter your array on the basis of two conditions:
canUpload attr is false
type.key is equal to 'testValue'
so you may want to return false in case of canUpload is true to be as follow:
myValues= myValues.filter(function(value) {
if(!value.canUpload)
return value.type.key === 'testValue';
else return false;
});
Otherwise, you just want to filter on type.key is equal to 'testValue', so it will be as follow:
myValues= myValues.filter(function(value) {
return value.type.key === 'testValue';
});
Note, if the callback function you passed into filter returns true, the element will be kept rather than removed. So in your case, you should return false when the value is testValue.
If I understand you correctly, you want to remove item with value.canUpload==false AND type.key === 'testValue'. Then the negate is value.canUpload || value.type.key !== 'testValue'
var myValues = this.testData.data3;
if (!this.testData.canDownload) {
myValues = myValues.filter(function(value) {
return value.canUpload || value.type.key !== 'testValue';
});
}
Let me know whether it works :)

Creating a binding list projection from a list of search terms

I am trying to create a filtered list projection from a collection of search terms. For instance, if I have one search term, I can do something like this:
if (options.groupKey == "filtered") {
this._items = Data.getItemsFromGroup(this._group);
var query = Windows.Storage.ApplicationData.current.localSettings.values["filters"];
this._items = this._items.createFiltered(function (item) {
if (item.content.search(query) > -1) {
return true
} else {
return false
}
})
}
But what if the 'filters' local setting is a CRLF delimited list, like this:
Cisco
Microsoft
Dell
Currently, the search will compare each term to 'Cisco/nMicrosoft/nDell' which obviously won't work. content.search doesn't accept an array. Should I just do a loop in the createFiltered function somehow? That doesn't seem to be in the spirit of the projection. What is the generally accepted way to do this?
What about storing and object in the "filters" settings where every filter is a property? will that work for you?
if (options.groupKey == "filtered") {
this._items = Data.getItemsFromGroup(this._group);
var query = Windows.Storage.ApplicationData.current.localSettings.values["filters"];
this._items = this._items.createFiltered(function (item) {
return Object.keys(query).indexOf(item) > -1;
})
}
The query object would be something as follows:
{
Cisco: "",
Microsoft: "",
Dell: ""
}
Does that make sense?
edit: made a little change in the code since I believe if (query[item]) would always return false because of javascript type-casting

Selecting n elements belonging to a user in a MapReduce for CouchDB

Please bear with me, I'm pretty new to the whole CouchDb stuff.
The db looks like:
** item ** count ** user **
A 20 bob
B 30 bob
C 10 bob
D 15 john
I want to write a MapReduce that selects all the items belonging to bob and only return the top 2, sorted. so it should return [{item:"B",count:"30"},{item:"A",count:"20}]
I'm not sure how this can be done? Seems like I have to emit(doc.item, doc.count), but how do I know if the user owns the doc? How do I run another MapReduce to select the top elements?
One solution would be to write your view to use a complex key, such as:
function (doc) {
emit([doc.user, doc.count], doc.item);
}
If you add descending=true to your query string, that would give you a view result like:
{"total_rows":4,"offset":0,"rows":[
{"id":"53f359b7cd360da296dd9aab3d0029bd","key":["john",15],"value":"D"},
{"id":"53f359b7cd360da296dd9aab3d001a0e","key":["bob",30],"value":"B"},
{"id":"53f359b7cd360da296dd9aab3d000fec","key":["bob",20],"value":"A"},
{"id":"53f359b7cd360da296dd9aab3d002668","key":["bob",10],"value":"C"}
]}
It's sorted already by user, then count. (with the item type as the value)
Then you can use a _list function to do the rest. The code below basically loops through the view, and returns the top 2 results for each user. If you specify user=bob in the query string, you'll only get the results for bob.
function (head, req) {
// specify that we're sending JSON as our response
provides('json', function () {
var results = [],
result, user, count, row;
while (row = getRow()) {
// if the user doesn't match the last iteration, reset our counter
if (user != row.key[0]) {
user = row.key[0];
count = 0;
}
// we only need the top 2
if (count++ >= 2) {
continue;
}
// start building a result object
result = {
item: row.value,
count: row.key[1]
};
// if we provide user=?
if (req.query.user) {
// check to see if it matches the current user
if (req.query.user === user) {
// if so, add it to the results
results.push(result);
}
// by default, we'll return the top 2 for every user
} else {
// add the user key to the result object
result.user = row.key[0];
// and add it to the result set
results.push(result);
}
}
// send outside the loop, since it needs to be sent as valid JSON
send(JSON.stringify(results));
});
}
If you put user and count in the key of the view, you can use startkey=["bob",""] and endkey=["bob"] to select the user, and descending=true and limit=2 to get the top two items.
I tried the following map function:
function(doc) {
if(doc.user && doc.count && doc.item) {
emit([doc.user, doc.count], doc);
}
}
with the query string ?startkey=["bob",""]&endkey=["bob"]&descending=true&limit=2 it returns:
{"total_rows":4,"offset":1,"rows":[
{"id":"item_B_bob","key":["bob",30],"value":{"_id":"item_B_bob","_rev":"1-b23bd22fb719c7d59b045bce0932df8c","item":"B","count":30,"user":"bob"}},
{"id":"item_A_bob","key":["bob",20],"value":{"_id":"item_A_bob","_rev":"2-515bca46eab383cfeaaa2a101d180291","item":"A","count":20,"user":"bob"}}
]}
Please note:
startkey and endkey are reversed because descending=true.
["bob",""] is a key greater then ["bob", ANY NUMBER] as specified in view collation.

Categories

Resources