element being removes from all arrays in aurelia class - javascript

I'm writing a simple filter "search" functionality for members. I've got 2 array properties one is members which is a list of all the members the other is filteredMembers which will be the filtered list of members:
getMembers() {
return this.userService.getMembers(this.authUser.selectedCompany).then(data => {
if (data.statusCode === 200) {
this.members = data.content.users.sort(function (a, b) {
if (a.firstName < b.firstName) return -1;
if (a.firstName > b.firstName) return 1;
return 0;
});
this.filteredMembers = this.members;
}
}).catch(err => {
console.log('error getting members:', err.response);
});
}
Since there hasn't been any filtering yet I just set the filteredMembers to equal members. So far so good.
Now I can an input search with a keyup event that filters the members depending on what I type in the search box:
<input type="search" value.bind="searchQuery" keyup.delegate="filterMembers()">
and the function:
filterMembers() {
if(!this.searchQuery) {
this.filteredMembers = this.members;
return;
}
let filteredCount = this.filteredMembers.length;
for (var i = 0; i < filteredCount; i++) {
if (this.filteredMembers[i].lastName.toLowerCase().indexOf(this.searchQuery.toLowerCase()) == -1) {
this.filteredMembers.splice(i, 1);
}
}
}
So you can see in the function I just check if what ever the user types in the search box is not found within a member's last name then I remove the member from the filteredMembers array. If the search query is empty the again I make the filteredMembers equal to members.
Here is the issue I'm running into for some reason may be a bug in Aurelia or may be human error but when I remove the element from filteredMembers it is also removing it from the members array. I've tried all sort of variations like not setting filteredMembers to equal members in that initial getMembers() function.

If filteredMembers and members are the same array instance, element removal will be reflected in both properties because both properties are references to the same array instance.
Shallow-clone the members array before assigning it to filteredMembers.
Change this:
if(!this.searchQuery) {
this.filteredMembers = this.members;
return;
}
To this:
if(!this.searchQuery) {
this.filteredMembers = this.members.slice(0);
return;
}

So I was not able to figure out what was causing the issue but I updated my filterMembers function to work around the problem.
filterMembers() {
let members: User[] = [];
if(!this.searchQuery) {
this.filteredMembers = this.members;
return;
}
let membersCount = this.members.length;
for (var i = 0; i < membersCount; i++) {
if (this.members[i].lastName.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1) {
members.push(this.members[i]);
}
}
this.filteredMembers = members;
}

Try the opposite approach: rather than removing elements that don't match, create a new empty array and add the elements that do match. Then set filteredMembers to that new array.
Or use the native filter method (which returns a new array with the results) and write filteredMembers = members.filter(...)
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

Related

Javascript Array.Splice() doesn't remove entry

I'm trying to remove an entry from an array if a certain condition is true, but when I console.log the array of objects, the object has not been removed and I'm confused why. Am I using the Splice() function correctly?
var itemsProcessed;
people.forEach(async function(peep, index, object){
var d = new Date();
var currenttime = d.getTime();
if (peep.endtime < currenttime){
var rolesub = guild.roles.find(r => r.name == roleNameSub);
var user2 = await client.fetchUser(peep.id);
var member = await guild.fetchMember(user2);
member.removeRole(rolesub);
object.splice(index, 1); //This seems to go wrong...
console.log(peep.id + " Lost subscription!");
user2.send("Your subscription ended!");
}
itemsProcessed++;
if (itemsProcessed === object.length){
SaveJson(people, "users.json");
}
});
Your problem is the fact you're splicing the same array you're iterating hence why the indexes will not be correct.
You should create a copy of the array before iterating it and remove elements from the original array by retrieving the index of the element you want to remove, take a look below.
arr.slice(0).forEach(function(item) {
arr.splice(arr.indexOf(item), 1);
});
var arr = [{0:0},{i:1},{i:"test"},{i:"Something else"},{i:"Test"},5];
arr.slice(0).forEach(function(item) {
if(item != 5)
arr.splice(arr.indexOf(item), 1);
});
console.log(arr);
One thing you can consider and change is that when you are iterating the array, don't delete from it while iterating. Just mark the entry for deletion. Then, when you are done, filter out the ones that need to be removed.
For example:
people.forEach( (person,i) => {
if( /* person needs to be removed from people */ ) {
people[i] = null; // use use array from 3rd parameter if you want to pass it in
}
});
// then remove unwanted people
people = people.filter( person => person != null );
Or, if you don't want to set the person to null, then you can set a flag in the object instead to mark it for deletion.
Example:
people.forEach( (person,i) => {
if( /* person needs to be removed from people */ ) {
person.toBeDeleted = true;
}
});
people = people.filter( person => person.toBeDeleted !== true );
What is probably an even better or cleaner approach is to not use forEach but just use filter only.
Example:
people = people.filter( p_person => {
if( /* person stays */ ) {
return true;
} else {
return false;
}
});

how to filter on multiple keys with OR condition

According to the docs,
{name:"M", phone:"1"} predicate will return an array of items which have property name containing "M" and property phone containing "1".
I want to implement an or between those keys. for example it should return the rows which have property name containing "M" and also the rows having phone property containing "1".
Is there any expression by which I can do that? or I'll have to implement a custom filter for the same
you may try array filter method
list.filter(function(element){ return element.name.includes('M'); })
Can also try jquery
$.grep:
$.grep(list, function(element){ return element.name.includes('M'); })
Not sure if it's too early to conclude but it looks like creating a custom filter is the solution.
Here's it is,
.filter('orFilter', function () {
return function (list, filterOn) {
var out = [];
for (var i = 0; i < list.length; i += 1) {
var item = list[i];
for (var key in filterOn) {
var val = filterOn[key];
if (~item[key].indexOf(val)) {
out.push(item);
break;
}
}
}
return out;
}
});
I'll wait if someone posts a better solution.

How can I make these two index-locating filters into one generic one?

I have two filters, findEdited and getUnitIndex. They both do exactly the same thing (find the index of an element in an array), but in different parts of an array. I would like to combine them into one filter, getIndex.
Here's the first one:
myApp.filter('findEdited', function(){
return function(food, foods) {
for (var index in foods) {
if (foods[index].uid == food.uid) {
return index;
}
}
return -1;
}
});
In the controller:
var index = $filter('findEdited')(food, $scope.editedFood);
And the second one:
myApp.filter('getUnitIndex', function () {
return function(list, item) {
for( var i = 0; i < list.length; i++ ) {
if( list[i].gram == item.gram ) {
return(i);
}
}
return(-1);
}
});
Controller:
var unitIndex = $filter('getUnitIndex')(food.unit, $scope.editedFood[index].unit.selected);
As near as I can tell, the only functional difference between them is the .uid & .gram identifier, which is telling the loop which part of the object to match. I've tried to rewrite these into one filter, like this, with ref standing in for this identifier:
myApp.filter('findIndex', function () {
return function(list, item, ref) {
for( var i = 0; i < list.length; i++ ) {
if( list[i].ref == item.ref ) {
return(i);
}
}
return(-1);
}
});
And called like this, if I want ref to be uid:
var unitIndex = $filter('findIndex')(food.unit, $scope.editedFood[index].unit.selected, 'uid');
This doesn't work. The example above returns 0 on every run. Any suggestions on how to pass the desired reference to this filter so that I can use it generically to find the index of any array item in any array?
Plunkr
Update
I can't get this to work for the filter "findEdited". I have written my generic filter like this:
myApp.filter('getIndex', function(){
return function(list, item, ref) {
for (var index in list) {
if (list[index][ref] == item[ref]) {
return index;
}
}
return -1;
}
});
Which works if call it like this, to find the index of a food unit by matching 'gram':
var unitIndex = $filter('getIndex')(food.unit, $scope.editedFood[index].unit.selected, 'gram');
But it doesn't work if I call it like this, to find out if a food unit exists in the array editedFood:
var foodIndex = $filter('getIndex')(food, $scope.editedFood, 'uid');
I can see that I am passing in different search objects & search contexts, but the foodIndex search works if I pass it to the almost-identical filter findEdited filter above. Any ideas why?
Here's an updated Plunkr.
You must use the array-like notation here (you can use it for objects as well). item.ref would mean the property called ref, while item[ref] will mean the property called whatever the expression in the [] evaluates to (in this case, whatever is stored in the variable called ref).
if( list[i][ref] == item[ref] ) {
In other words, writing item.ref is equivalent to writing item['ref'].

Underscore.js .filter() and .any()

I have an array of event objects called events. Each event has markets, an array containing market objects. Inside here there is another array called outcomes, containing outcome objects.
In this question, I asked for a [Underscore.js] way to find all of the events which have markets which have outcomes which have a property named test. The answer was:
// filter where condition is true
_.filter(events, function(evt) {
// return true where condition is true for any market
return _.any(evt.markets, function(mkt) {
// return true where any outcome has a "test" property defined
return _.any(mkt.outcomes, function(outc) {
return outc.test !== "undefined" && outc.test !== "bar";
});
});
});
This works great, but I'm wondering how I would alter it if I wanted to filter the outcomes for each market, so that market.outcomes only stored outcomes that were equal to bar. Currently, this is just giving me markets which have outcomes which have some set test properties. I want to strip out the ones that do not.
Make it a simple loop, using the splice method for the array removals:
var events = [{markets:[{outcomes:[{test:x},...]},...]},...];
for (var i=0; i<events.length; i++) {
var mrks = events[i].markets;
for (var j=0; j<mrks.length; j++) {
var otcs = mrks[j].outcomes;
for (var k=0; k<otcs.length; k++) {
if (! ("test" in otcs[k]))
otcs.splice(k--, 1); // remove the outcome from the array
}
if (otcs.length == 0)
mrks.splice(j--, 1); // remove the market from the array
}
if (mrks.length == 0)
events.splice(i--, 1); // remove the event from the array
}
This code will remove all outcomes that have no test property, all empty markets and all empty events from the events array.
An Underscore version might look like that:
events = _.filter(events, function(evt) {
evt.markets = _.filter(evt.markets, function(mkt) {
mkt.outcomes = _.filter(mkt.outcomes, function(otc) {
return "test" in otc;
});
return mkt.outcomes.length > 0;
});
return evt.markets.length > 0;
});

How to re-label Object in array?

Extending a previous question about JavaScript and jQuery, I'm trying to make an array and then look into it, but the array contains dimensions and I can't get it right..
var markers = [];
$.getJSON('GetList', "id"= + data.id,
function(data){
$.each(data.list, function(index, data) {
}
markers.push( {
category:data.category,
id:data.id,
location:{
latitude:data.location.latitude,
longitude:data.location.longitude
}
});
});
});
return markers;
}
The first thing that strikes me is that every item will now be called "Object", while default, I'm still wondering if they can be labeled instead?
Further down the line, I try to access the id to compare it to a selection by the user,
but it doesn't work.
var selection = $(div_id).val();
var arr = $.inArray(selection, markersArray.id);
if( arr >= 0) {
return search(selection, div_id);
} else {
throw("selection not found in selectionSearch()");
}
What am I doing wrong here..?
To label the objects, add a toString function, like this:
markers.push({
toString: function () { return this.something; },
category:data.category,
id:data.id,
location:{
latitude:data.location.latitude,
longitude:data.location.longitude
}
});
To search the array, you'll need your own loop.
jQuery's $.inArray function searches the array for an exact match; it cannot be used to find an object with a matching property.
You can find a match yourself, like this:
for(var i = 0; i < markers.length; i++) {
if (markers[i].id === selection)
return search(selection, div_id);
}
throw("selection not found in selectionSearch()");
The type of each item is Object, there is nothing strange about that. There is no reason to try to change that.
To filter out items from an array based on a property in the item, you use the grep method:
var arr = $.grep(markers, function(e){ return e.id === selection; });
To check if the array is empty, you don't compare the array to a number, you use the length property:
if (arr.length > 0) {

Categories

Resources