EITHER OR with Angular JS checkboxes - javascript

I'm using multiple checkboxes to filter properties using angularjs. Currently, I am using a custom filter to show all properties of a certain type. I have multiple checkboxes that as you check each new one it filters the results.
At present, each new checkbox you check narrows your search (i.e. which properties are both rural AND coastal) and I would like to widen the search (i.e. which properties are either rural OR coastal). I'm really new to this.
Here is my app:
propertyApp.controller('PropertyListControl', function ($scope) {
$scope.properties = [
{
title: "Sharrow Bay Hotel",
location:['rural', 'coastal']
},
{
title: "The Royal Oak Inn",
location:['rural']
},
{
title: "Scale Hill Cottages",
location:['urban']
},
];
$location = {}
// Currently using this great custom filter:
}).filter('filteredLocation', function() {
return function(properties, location) {
var result = properties.slice(); // copy array
angular.forEach(location, function(value, key) {
if(value) {
for(var index = 0; index < result.length; index++) {
property = result[index];
if(property.location.indexOf(key) == -1) {
result.splice(index--,1);
}
}
}
});
return result;
};
});
And my checkboxes:
<label><input type="checkbox" ng-model="location.rural"/>Rural</label>
<label><input type="checkbox" ng-model="location.urban"/>Urban</label>
<label><input type="checkbox" ng-model="location.coastal"/>Coastal</label>

That filter starts with all your locations:
var result = properties.slice();
and removes any that don't match your test:
result.splice(index--,1);
Thus it's acting like an "and" since, as in your example, anything without "coastal" is removed and then anything without "Rural" is removed. So the only items left are ones that match both conditions.
To turn it into an "or" filter I'd start with an empty array:
var result = [];
and add the results as they match (so any that match either test will be added):
result.push(property);
To avoid duplicates I've also switched the loops so the outer loop now covers the list of properties and the inner loop goes over the list of locations to filter. Then we can abort out of the inner loop once we find that the property matches any of the locations.
Here's the entire function:
.filter('filteredLocation', function() {
return function(properties, location) {
var result = [];
for(var index = 0; index < properties.length; index++) {
var added = false;
angular.forEach(location, function(value, key) {
if(value && !added) {
property = properties[index];
if(property.location.indexOf(key) != -1) {
result.push(property);
added = true; // Mark as added so we don't add duplicates
}
}
})
};
return result;
};
demo fiddle

Related

element being removes from all arrays in aurelia class

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

Search for the key in the given array of objects and replace the value;

Implementing the multiple sort functionality; Where need to toggle the array which hold the sorting fieldname and sorting order;
Example
Click Sort By Name:
[{"sortKey":"name","sortValue":"desc"}]
Again Click Sort By Name:
[{"sortKey":"name","sortValue":"asc"}]
Click Sort By Age:
[{"sortKey":"name","sortValue":"asc"},{"sortKey":"age","sortValue":"desc"} ]
Again Click Sort By Name:
[{"sortKey":"name","sortValue":"desc"},{"sortKey":"age","sortValue":"desc"} ]
DEMO
if (checkIfObjectExists($scope.sortList, sortingObject)) {
if (!$scope.sortList.hasOwnProperty(sortingObject.sortType)) {
console.log($scope.sortList);
// replace the value for the key
}
} else {
$scope.sortList.push(sortingObject);
}
I changed some things in your implementation. Problem was you were checking if the whole object is not same then push in the array. But what you need if sorKey is same reverse the sortValue.
DEMO
Changed your function checkIfObjectExists to updateArray.
function updateArray(array, newObject) {
var i = 0;
for (i = 0; i < array.length; i++) {
var object = array[i];
if (object.sortKey == newObject.sortKey) {
object.sortValue= (object.sortValue==='asc')?'desc':'asc';
return;
}
}
array.push(newObject);
}
And while calling I will just call like this in $scope.clickMe.
updateArray($scope.sortList, sortingObject);

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;
});

Javascript how to know if an array is subarray of another

How could I do the following in javascript in efficient way?
I've counter of 7 items(item0, item1, item2...item6) in order..
like in counts = [0,2,0,5,6,0,9];
There are 3 mutually exclusive groups:
group1: item0, item1, item2
group2: item3, 4, 5
group3: item6
A group is considered selected if and only if counters of the group member elements are >= 0.
Now I want to know which group is selected?
After clarification by understack, the OP, a group is selected if [at least] one of its elements is selected.
This in turn makes the "mutually exclusive" part of the question ambiguous, because the example supplied (counts = [0,2,0,5,6,0,9]) all 3 group would be selected...
Never the less...
The problem of identifying which group is selected can be optimally resolved by relying on on JavaScript short-circuit evaluation of boolean expressions.
A tentative solution would look like the following:
counts = [0,2,0,5,6,0,9]; // as stated an bad value for counts,
// if groups are to be mutually exclusive
if (counts[0] || counts[1] || counts[2])
{
GroupSelected = 1;
}
else if (counts[3] || counts[4] || counts[5])
{
GroupSelected = 2;
}
else if (counts[6] > 0)
{
GroupSelected = 3;
}
else
{
GroupSelected = -1; // none of the groups is selected !!!
}
Note: A possible optimization would come from a "prior" knowledge of the probabilities of a given element to be selected (relative to others, in its group), as well as the probability for a given group to be selected.
With such knowledge, the above snippet can be rewritten to first test for the most likely groups first, and within each group to test for the most likely elements first.
Here is a data structure to do what you want.
var DS = {
Items: {},
Groups: {},
setItem: functon(index, item, group){
this.Items[index] = item;
if ( typeof this.Groups[group] === 'undefined' )
this.Groups[group] = [index];
else
this.Groups[group].push(index);
},
isSelected: function(item) {
return item >= 0;
},
isSelectedGroup: function(group) {
var Group = this.Groups[group];
for ( var i in Group ) {
var Item = this.Items[i];
if ( this.isSelected(Item) ) {
return true;
}
}
},
getSelectedGroups: function(){
var selected = [];
for ( var group in this.Groups ) {
var Group = this.Groups[group];
if ( this.isSelectedGroup(Group) ) {
selected.push(group);
}
}
return selected;
}
}
To use with your items: 0,2,0,5,6,0,9. Do the following:
DS.setItem(0,0,1);
DS.setItem(1,2,1);
DS.setItem(2,0,1);
DS.setItem(3,5,2);
DS.setItem(4,6,2);
DS.setItem(5,0,2);
DS.setItem(6,9,3);
To test:
DS.isSelectedGroup(3);
// or to get all selected groups
DS.getSelectedGroups();
Should work, if not you should be able to figure it out :)
Using the lodash library, to determine if all elements of a (sub)array can match an element in another array,
function arrayMatchesSubarray(arr, subarr) {
var filteredSubarray = _.filter(subarr, function(subarrelement) {
return _.any(arr, function(arrelement){
return arrelement === subarrelement;
});
});
return _.isEqual(subarr, filteredSubarray);
};
Result:
arrayContainsSubarray([1,2,3,4],[1,2]); // true
arrayContainsSubarray([1,2,3,4],[5,6,1]); // false
arrayContainsSubarray([1,2,3,4],[1,4,3]); // true

Categories

Resources