I'd like to implement a filter that takes in up to say 3 attributes and returns result that matches all of them on the .json input, also known as AND filter.
I'm wondering if there is a way to pass the res (result) of my first filter as the input when the second filter is applied. In other words, I'd like to 'change the subject' from the original collection I iterated on to the filtered result, and keep doing that for every added filter.
Please do let me know if there is another practice for using complex filters.
Below is my code:
angular.module('app')
.filter('categoryFilter', function () {
return function (input, ctrl) {
if(ctrl.filtersModel) {
// 1 filter: ctrl.filtersModel > {brand: "bmw"}
// 2 filters: ctrl.filtersModel > {brand: "bmw", speed: "medium"}
var res = [];
angular.forEach(input, function(car){
angular.forEach(ctrl.filtersModel, function(value, key){
if(car[key] === value) {
// this won't obviously work:
res.push(car);
// if I was able apply first filter, then save the result and use it as an input when then 2nd filter is applied, that would work
}
});
});
return res;
}
else {
return input;
}
};
});
Related
(was not sure what to have as a title, so if you have a better suggestion, feel free to come up with one - I will correct)
I am working on an angular application where I have some menues and a search result list. I also have a document view area.
You can sort of say that the application behaves like an e-mail application.
I have a few controllers:
DateCtrl: creates a list of dates so the users can choose which dates they want to see posts from.
SourceCtrl: Creates a list of sources so the user can choose from which sources he/she wants to see posts from.
ListCtrl: The controller populating the list. The data comes from an elastic search index. The list is updated every 10-30 seconds (trying to find the best interval) by using the $interval service.
What I have tried
Sources: I have tried to make this a filter, but a user clicks two checkboxes the list is not sorted by date, but on which checkbox the user clicked first.
If it is possible to make this work as a filter, I'd rather continue doing that.
The current code is like this, it does not do what I want:
.filter("bureauFilter", function(filterService) {
return function(input) {
var selectedFilter = filterService.getFilters();
if (selectedFilter.length === 0) {
return input;
}
var out = [];
if (selectedFilter) {
for (var f = 0; f < selectedFilter.length; f++) {
for (var i = 0; i < input.length; i++) {
var myDate = input[i]._source.versioncreated;
var changedDate = dateFromString(myDate);
input[i]._source.sort = new Date(changedDate).getTime();
if (input[i]._source.copyrightholder === selectedFilter[f]) {
out.push(input[i]);
}
}
}
// return out;
// we need to sort the out array
var returnArray = out.sort(function(a,b) {
return new Date(b.versioncreated).getTime() - new Date(a.versioncreated).getTime();
});
return returnArray;
} else {
return input;
}
}
})
Date: I have found it in production that this cannot be used as a filter. The list of posts shows the latest 1000 posts, which is only a third of all posts arriving each day. So this has to be changed to a date-search.
I am trying something like this:
.service('elasticService', ['es', 'searchService', function (es, searchService) {
var esSearch = function (searchService) {
if (searchService.field === "versioncreated") {
// doing some code
} else {
// doing some other type of search
}
and a search service:
.service('searchService', function () {
var selectedField = "";
var selectedValue = "";
var setFieldAndValue = function (field, value) {
selectedField = field;
selectedValue = value;
};
var getFieldAndValue = function () {
return {
"field": selectedField,
"value": selectedValue
}
};
return {
setFieldAndValue: setFieldAndValue,
getFieldAndValue: getFieldAndValue
};
})
What I want to achieve is this:
When no dates or sources are clicked the whole list shall be shown.
When Source or Date are clicked it shall get the posts based on these selections.
I cannot use filter on Date as the application receives some 3000 posts a day and so I have to query elastic search to get the posts for the selected date.
Up until now I have put the elastic-search in the listController, but I am now refactoring so the es-search happens in a service. This so the listController will receive the correct post based on the selections the user has done.
Question is: What is the best pattern or method to use when trying to achieve this?
Where your data is coming from is pretty irrelevant, it's for you to do the hook up with your data source.
With regards to how to render a list:
The view would be:
<div ng-controller='MyController as myCtrl'>
<form>
<input name='searchText' ng-model='myCtrl.searchText'>
</form>
<ul>
<li ng-repeat='item in myCtrl.list | filter:myCtrl.searchText' ng-bind='item'></li>
</ul>
<button ng-click='myCtrl.doSomethingOnClick()'>
</div>
controller would be:
myApp.controller('MyController', ['ElasticSearchService',function(ElasticSearchService) {
var self = this;
self.searchText = '';
ElasticSearchService.getInitialList().then(function(list) {
self.list = list;
});
self.doSomethingOnClick = function() {
ElasticSearchService.updateList(self.searchText).then(function(list) {
self.list = list;
});
}
}]);
service would be:
myApp.service('ElasticSearchService', ['$q', function($q) {
var obj = {};
obj.getInitialList = function() {
var defer = $q.defer();
// do some elastic search stuff here
// on success
defer.resolve(esdata);
// on failure
defer.reject();
return defer.promise();
};
obj.updateList = function(param) {
var defer = $q.defer();
// do some elastic search stuff here
// on success
defer.resolve(esdata);
// on failure
defer.reject();
return defer.promise();
};
return obj;
}]);
This code has NOT been tested but gives you an outline of how you should approach this. $q is used because promises allow things to be dealt with asynchronously.
I'm working on this codepen. The data comes from an array of objects, and I need to make a filter only by name and amount.
I have this code, but if you type a character in the search box, it only search by amount, and not by name too. In other words, if the you type 'warren' or '37.47' it has to return the same result, but doesn't works.
var filterFilter = $filter('filter');
$scope.filter = {
condition: ""
};
$scope.$watch('filter.condition',function(condition){
$scope.filteredlist = filterFilter($scope.expenses,{name:condition} && {amount:condition});
$scope.setPage();
});
You want to create a custom filter for your app.
directiveApp.filter("myFilter", function () {
return function (input, searchText) {
var filteredList = [];
angular.forEach(input, function (val) {
// Match exact name
if (val.name == searchText) {
filteredList.push(val);
}
// Match exact amount
else if (val.amount == searchText) {
filteredList.push(val);
}
});
input = filteredList;
return input;
};
});
You can write your logic in this filter and now use this filter to filter your list.
Update
You can just implement this filter to your custom filter pagination.
Here is the new version of your code. Codepen
List of updates on your code
Added new filter parameter to your ng-repeat attribute
ng-repeat="expense in filteredlist | pagination: pagination.currentPage : numPerPage : filter.condition"
...
Well, finally (based in the idea of Abhilash P A and reading the docs), I solved my question in this way:
var filterFilter = $filter('filter');
$scope.filter = {
condition: ""
};
$scope.$watch('filter.condition',function(condition){
$scope.filteredlist = filterFilter($scope.expenses,function(value, index, array){
if (value.name.toLowerCase().indexOf(condition.toLowerCase()) >= 0 ) {
return array;
}
else if (value.amount.indexOf(condition) >= 0 ) {
return array;
}
});
$scope.setPage();
});
The final codepen ! (awsome)
I've a custom function used as a filter. How can I get the index of the current element filtered.
<tr ng-repeat="(idx, line) in items | filter:inRange">....</tr>
//this is the filter
$scope.inRange = function(item) {
//how to get the index here?
};
Please note that I do not want to use indexOf
var idx = $scope.items.indexOf(item);
As in another answer on SO with the same kind of issue on filters
Filters don't work on individual items in the array, they transform the entire array into another array.
When defined as filter, inRange will receive the whole items array, not single items.
myModule.filter('inRange', function() {
return function(items) {
var filtered = [];
angular.forEach(items, function(item, index) {
// do whatever you want here with the index
filtered.push(item);
});
return filtered;
}
});
I have a list with the items (name: String, age: Int, checked:Boolean).
This list is displayed with an ng-repeat.
I want to enable the user to search the list using a searchfield, but the search must not affect the checked-values.
the search should only trigger, if the users enters something in the searchfield
if the seachfield is filled, the search should filter as usual, but the checked items must not be filtered out.
I tried to create a custom filter. I have problems in understanding the $filter('filter') function with my OR-logic.
Could anyone help me untie the knot in my brain?
app.filter('mySearchFilter', function($filter) {
return function(data, searchText) {
if(!searchText || searchText.length === 0) {
return data;
}
console.log(searchText);
return $filter('filter')(data, searchText);
//how can I provide additional, OR-concatinated, filter-criteria?
}
});
Check out my plunk for the minimal-example-code.
http://plnkr.co/edit/3xHLOrSPD3XZy2K9U2Og?p=preview
As I understand you, you are looking for a union function. Underscore provide such a function. With this you may write your filter in this way:
app.filter('mySearchFilter', function($filter) {
return function(data, searchText) {
if(!searchText || searchText.length === 0) {
return data;
}
var allChecked = data.filter(function(d){return d.checked});
var allMatched = $filter('filter')(data, searchText);
return _.union(allMatched, allChecked);
}
});
have a look at: http://documentcloud.github.io/underscore/#union and don't forget to include the script:
<script src="http://documentcloud.github.io/underscore/underscore.js"></script>
PLUNKR
Is there a way to configure/work with the angular $filter so that you can pass data to it gradually an not have it always iterate over the whole data but just the previous data filtered ?
I am currently trying to split the a user provided string to run multiple sequential filers on the data. So if I have a data like so: [{name:foo, city:bar},...x250] I could type in the search box "Paris" from that point I should be iteration only on the place in Paris and then type Hotel and then Hilton.... The problem is the filter runs each and every time on the whole data set... Does anyone know how to run the filter sequentially in angular ?
updates...
http://jsfiddle.net/Bretto/ZM4Qa/12/
.filter('searchFilter', function ($filter) {
return function (array, q) {
console.log('data-entry: ', array.length);
var filter = $filter('filter');
var lookFor = function (word, data, cb) {
var dataRes = filter(data, word);
cb(dataRes);
}
var search = function (index, words, data, cb) {
var word = words[index];
lookFor(word, data, function (dataRes) {
console.log('data-current: ', dataRes.length);
if (index < words.length - 1) {
index += 1;
search(index, words, dataRes, cb);
} else {
cb(dataRes);
}
});
}
if (q && q.text) {
var words = q.text.split(' ');
var index = 0;
if(array){
//console.log(array.length);
}
search(index, words, array, function (dataRes) {
array = dataRes;
});
}
return array;
}
})
So this is an attempt to create a filter that gradually iterates over the previous filtered results (type multiple words separated by a space in the input...) the issue is that the filter always starts from the whole data.
is this clearer now ?
Please use multiple filters, so you don't have to go tweaking everything
Take this example as a starting point
http://jsfiddle.net/ed9A2/1/
<tr ng-repeat="player in playersFound = (players | filter:{id: player_id, name:player_name, age:player_age})">