Bit of a lengthy one so those of you who like a challenge (or I'm simply not knowledgeable enough - hopefully it's an easy solution!) read on!
(skip to the actual question part to skip the explanation and what I've tried)
Problem
I have a site that has a dataset that contains an object with multiple objects inside. Each of those objects contains an array, and within that array there are multiple objects. (yes this is painful but its from an API and I need to use this dataset without changing or modifying it.) I am trying to filter the dataset based of the key-value pairs in the final object. However, I have multiple filters being executed at once.
Example of Path before looping which retrieves the key-value pair needed for one hall.
["Hamilton Hall"]["Hire Options"][2].Commercial
After Looping Path of required key-value pair for all halls, not just one (the hall identifier is stored):
[0]["Hire Options"][2].Commercial
Looping allows me to check each hall for a specific key-value pair (kind of like map or forEach, but for an object).
After getting that out of the way back to the question.
How would I go about filtering which of the looped objects are displayed?
What I have Tried
(userInput is defined elsewhere - this happens on a btn click btw)
let results = Object.keys(halls);
for (key of results) {
let weekend = [halls[ `${key}` ][ 'Hire Options' ][4][ 'Weekend function' ]];
if(userInput == weekend) {
outputAll([halls[ `${key}` ]]);
}
}
That filters it fine. However, I run into an issue here. I want to filter by multiple queries, and naturally adding an AND into the if statement doesn't work. I also dont want to have 10 if statements (I have 10+ filters of various data types I need to sort by).
I have recently heard of ternary operators, but do not know enough about them to know if that is the correct thing to do? If so, how? Also had a brief loook at switches, but doesnt seem to look like what I want (correct me if I am wrong.)
Actual Question minus the babble/explanation
Is there a way for me to dynamically modify an if statements conditions? Such as adding or removing conditions of an if statement? Such as if the filter for 'a' is set to off, remove the AND condition for 'a' in the if statement? This would mean that the results would only filter with the active filters.
Any help, comments or 'why haven't you tried this' remark are greatly appreciated!
Thanks!
Just for extra reference, here is the code for retrieving each of the objects from the first object as it loops through them:
(Looping Code)
halls = data[ 'Halls' ];
let results = Object.keys(halls);
for (key of results) {
let arr = [halls[ `${key}` ]];
outputAll(arr);
}
You can use Array.filter on the keys array - you can structure the logic for a match how you like - just make it return true if a match is found and the element needs to be displayed.
let results = Object.keys(halls);
results.filter(key => {
if (userInput == halls[key]['Hire Options'][4]['Weekend function']) {
return true;
}
if (some other condition you want to match) {
return true;
}
return false;
}).forEach(key => outputAll([halls[key]]));
I trying to build a product list based on multiple filters. I thought this should be very straight forward but it's not for me at least.
Here is the plunkr http://plnkr.co/edit/vufFfWyef3TwL6ofvniP?p=preview
Checkboxes are dynamically generated from respective model e.g. sizes, colours, categories. Subcategory checkbozes should perform 'OR' query but cross section it should perform 'AND' query.
basically something like
filter:{categories:selectedcategories1} || {categories:selectedcategories2} | filter:{categories:selectedsizes1} || {categories:selectedsizes2}
problem is generating these filters dynamically. I also tried with filter in controller as-
var tempArr = [{'categories':'selectedvalue1'}, {'categories':'selectedvalue2'}];
var OrFilterObjects = tempArr.join('||');
$scope.products = $filter('filter')($scope.products, OrFilterObjects, true);
But couldn't find a way to assign correct value for OrFilterObjects.
Now as latest attempt (which is in plunkr) I am trying to use a custom filter. It's also not returning OR result.
Right now I am using it as productFilter:search.categories:'categories' if it would have returned OR result then I'd planned to use it as-
`productFilter:search.categories:'categories' | productFilter:search.colours:'colours' | productFilter:search.sizes:'sizes'`
Since I am here seeking help, it would be nice to have like productFilter:search.
I've spent considerable amount of time to find solution of this supposedly simple problem but most of examples use 'non-dynamic' checkboxes or flat objects.
May be I am thinking in wrong direction and there is a more elegant and simple Angular way for such scenarios. I would love to be directed towards any solution to similar solution where nested objects can be filtered with automated dynamically generated filters. Seems to me very generic use case for any shopping application but till now no luck getting there.
First thing you need to understand: this problem is not, by any definition, simple. You want to find a match based on a property of an object in an array which is a property of an object inside an input array you're supplying, not to mention [OR intra group] + [AND inter group] relations, search properties defined by either .title or .name, as well as criteria selection being completely dynamic. It's a complex problem.
Though it's a common scenario for shopping cart websites, I doubt that any web framework will have this kind of functionality built into its API. It's unfortunate but I don't think we can avoid writing the logic ourselves.
At any rate, since ultimately you want to just declare productFilter:search, here it is:
app.filter('productFilter', function($filter) {
var helper = function(checklist, item, listName, search) {
var count = 0;
var result = false;
angular.forEach(checklist, function(checked, checkboxName) {
if (checked) {
count++;
var obj = {};
obj[search] = checkboxName;
result = result || ($filter('filter')(item[listName], obj, true).length > 0);
}
});
return (count === 0) || result;
};
return function(input, definition) {
var result = [];
if (angular.isArray(input) && angular.isObject(definition)) {
angular.forEach(input, function(item) {
var matched = null;
angular.forEach(definition, function(checklist, listName) {
var tmp;
if (listName !== 'colours') {
tmp = helper(checklist, item, listName, 'title');
} else{
tmp = helper(checklist, item, listName, 'name');
}
matched = (matched === null) ? tmp : matched && tmp;
});
if (matched) result.push(item);
});
}
return result;
};
});
A couple of notes:
How to use: ng-repeat="product in products | productFilter:search".
The filter only does some basic checks: input must be an array, and definition must be an object. If you need more, you may do so there.
I would say that *.name is an exception to the rule (I assume that most of the criteria is defined by *.title). So, we handle that in if/else.
The count variable in a helper function is used to track how many checked checkbox(es) we went through for a particular criteria group. If we went through none, it means that whole criteria group is inactive, and we just return true.
It's a good design to create a filter that doesn't mutate the states of other objects outside it. That's why using count is better than calling cleanObj(). This is especially crucial when designing common components for other devs to use in a team: you want to minimize the element of surprise as much as possible.
I have 2 questions based on the graphic below:
How can I tell if one of the 'data-conversationmessageuserid' data attributes with a specific value exists - say 1000000003? I believe data selectors is what I need and have tried the following but its not working yet:
if($('#conversationsInBoxMessagesWrapperDIV')['data-conversationmessageuserid=1000000003']) {
// do something
}
How could I get all the 'data-conversationmessageuserid' data attributes into an array and the loop through them? I'm still playing with this code but its far from publishable. Trying to use .map
.map(function()
thankyou so much
Try:
if($('#conversationsInBoxMessagesWrapperDIV [data-conversationmessageuserid=1000000003]').length)
or
$('#conversationsInBoxMessagesWrapperDIV').find('[data-conversationmessageuserid=1000000003]') //Or children only to look at one level.
To get all the data values you could do:
var conversationmessageuserids = $('#conversationsInBoxMessagesWrapperDIV').children().map(function(){
return $(this).data('conversationmessageuserid');
}).get();
jQuery supports data attributes: http://api.jquery.com/data/
So you could do if($('#conversationsInBoxMessagesWrapperDIV').data('conversationmessageuserid') === 1000000003)
I have an array of arrays where the position of the value is important in that this data is used to ultimately layout a grid.
[[a,b,c,d],[a,b,c,d][a,b,c,d],[a,b,c,d]] // Original data
Based on an action, values within this "dataset" can change, but the size will never change.
[[a,b,c,d],[a,b,b,b][a,c,c,c],[a,b,c,d]] // Modified data
What I'd like to do is to return a dataset that only contains the delta values:
[[null,null,null,null,],[null,null,b,b],[null,c,null,c],[null,null,null,null,]]
Now, I do always know the max X and Y of the dataset, and could simply loop through the original dataset, comparing it's value to the corresponding value and build a new array, but it seems like there could be a more efficient way to accomplish this.
That said, my js-fu is minimal at best, and that's why I'm brining the problem here. Are there any language provided methods for accomplishing this? Suggested approaches? etc?
A bit of array mapping should do the trick.
Here's a fiddle: http://jsfiddle.net/C8jnr/
Given the following:
var arr_A = [[a,b,c,d],[a,b,c,d],[a,b,c,d],[a,b,c,d]];
var arr_B = [[a,b,c,d],[a,b,b,b],[a,c,c,c],[a,b,c,d]];
function deltaArrays(arr1, arr2)
{
return arr1.map(function(el_arr,i,arr){
return el_arr.map(function(el, j){
return (el == arr2[i][j]) ? null : arr2[i][j];
});
});
}
Calling deltaArrays on the two will yield the expected delta array;
deltaArrays(arr_A, arr_B) = [[null,null,null,null,],[null,null,b,b],[null,c,null,c],[null,null,null,null,]]
I don't believe there is a built in way to do this. Your best bet is to loop through each of the array elements and compare them with the new array, adding it to a list/dictionary if it is different.
OK, I'm missing something here and I just can't seem to find it because the logic seems correct to me, but I'm certain I'm not seeing the error.
var VisibleMarkers = function() {
var filtered = _.reject(Gmaps.map.markers, function(marker) {
return marker.grade != $('.mapDataGrade').val() && !_.contains(marker.subjects,$('.mapDataSubjects').val())
});
return filtered
}
I'm using underscore.js and jQuery to simplify my javascript work.
So right now, I'm checking by means of selects which data gets to be rejected and then I display the filtered markers on the (google) map (if it helps at all, this is using gmaps4rails which is working perfectly fine, its this bit of javascript that's making me lose the last of the hairs on my head).
Currently, the code functions 100% correctly for the ".mapDataGrade" select, but the ".mapDataSubjects" isn't. Now the markers object has a json array of the subjects (this is for students) and each item in the array has its ID. Its this ID that I am supposed to be checking.
Can someone see what I'm doing wrong?
If there's more info that needs to be included, please let me know.
This is on plain javascript on a RoR application using gmaps4rails
Now the markers object has a json array of the subjects (this is for students) and each item in the array has its ID. Its this ID that I am supposed to be checking.
_.contains compares a values, but it sounds like you want your iterator to compare a value to an object's "id" property. For that, _.some would work; it's like contains, except that, instead of comparing values, you can write the comparison as a function:
Returns true if any of the values in the list pass the iterator truth test.
Here's how you'd use it:
!_.some(marker.subjects, function(subject) {
return subject.id == $('.mapDataSubjects').val();
})
If I'm right, the whole line should be like this:
return marker.grade != $('.mapDataGrade').val() &&
// check that none of the subjects is a match
!_.some(marker.subjects, function(subject) {
// the current subject is a match if its ID matches the selected value
return subject.id == $('.mapDataSubjects').val();
});