AngularJS filter for multiple strings - angularjs

I'm working through the AngularJS tutorial, and understand the basics of
However, the out of the box implementation seems limited to just filter the list of items to the exact word or phrase entered in .
Example: if the query is "table cloth", the result list can include a result with this phrase, "Decorative table cloth", but won't include "Decorative cloth for table" because the filter is just a continuous search string.
I know there's the ability to add custom filters, but at first glance it seems like those are mainly transforms.
Is there any way to add a custom filter so that both "Decorative cloth for table" and "Decorative table cloth" show up in the filtered result set?

Some improvements to the above custom filter:
Instead of using a loop within a loop, counts, and indexOf, this one uses regular expressions to achieve a logical AND and also a logical OR depending on the third argument to the filter (input array of strings, search terms, AND or OR).
Have a look at the forked Fiddle with the two types of filter and results:
http://jsfiddle.net/jonjon/Cx3Pk/23/
angular.module('app', [])
.filter("myFilter", function () {
return function (input, searchText, AND_OR) {
var returnArray = [],
// Split on single or multi space
splitext = searchText.toLowerCase().split(/\s+/),
// Build Regexp with Logical AND using "look ahead assertions"
regexp_and = "(?=.*" + splitext.join(")(?=.*") + ")",
// Build Regexp with logicial OR
regexp_or = searchText.toLowerCase().replace(/\s+/g, "|"),
// Compile the regular expression
re = new RegExp((AND_OR == "AND") ? regexp_and : regexp_or, "i");
for (var x = 0; x < input.length; x++) {
if (re.test(input[x])) returnArray.push(input[x]);
}
return returnArray;
}
});

Please see surfbuds answer below as it is superior
Just roll with your own filter:
.filter("myFilter", function(){
return function(input, searchText){
var returnArray = [];
var searchTextSplit = searchText.toLowerCase().split(' ');
for(var x = 0; x < input.length; x++){
var count = 0;
for(var y = 0; y < searchTextSplit.length; y++){
if(input[x].toLowerCase().indexOf(searchTextSplit[y]) !== -1){
count++;
}
}
if(count == searchTextSplit.length){
returnArray.push(input[x]);
}
}
return returnArray;
}
});
jsfiddle: http://jsfiddle.net/Cq3PF/
This filter makes sure that all search words are found.

Alternatively you could use the default Angular filter within your custom filter like so:
angular.module('app').filter("multiWordFilter", function($filter){
return function(inputArray, searchText){
var wordArray = searchText ? searchText.toLowerCase().split(/\s+/) : [];
var wordCount = wordArray.length;
for(var i=0;i<wordCount;i++){
inputArray = $filter('filter')(inputArray, wordArray[i]);
}
return inputArray;
}
});
This could be embellished further with user2005009's AND_OR comparator.

Here's my version. It uses JonoWilko's method of using the built in filterFilter combined with surfbud's AND/OR flag (defaulted to "AND").
JavaScript
angular.module('filters').filter('searchFilter', function($filter) {
return function(inputArray, searchText, booleanOp) {
booleanOp = booleanOp || 'AND';
var searchTerms = (searchText || '').toLowerCase().split(/\s+/);
if (booleanOp === 'AND') {
var result = inputArray;
searchTerms.forEach(function(searchTerm) {
result = $filter('filter')(result, searchTerm);
});
} else {
var result = [];
searchTerms.forEach(function(searchTerm) {
result = result.concat($filter('filter')(inputArray, searchTerm));
});
}
return result;
};
});
CoffeeScript
angular.module('filters').filter 'searchFilter', ($filter)->
(inputArray, searchText='', booleanOp = 'AND')->
searchTerms = searchText.toLowerCase().split(/\s+/)
if booleanOp is 'AND'
result = inputArray
searchTerms.forEach (word)->
result = $filter('filter')(result, word)
else
result = []
searchTerms.forEach (word)->
result = result.concat $filter('filter')(inputArray, word)
result
'AND' Usage (default)
<div ng-repeat="product in products | searchFilter: searchInputText"></div>
'OR' Usage
<div ng-repeat="product in products | searchFilter: searchInputText : 'OR'"></div>

You can do a multiple word search on a object as follows:
.filter("myFilter", function(){
return function(input, searchText){
var returnArray = [];
var searchTextSplit = searchText.toLowerCase().split(' ');
for(var x = 0; x < input.length; x++){
var count = 0;
for(var y = 0; y < searchTextSplit.length; y++){
angular.forEach(input[x], function(item){
if(item.toLowerCase().indexOf(searchTextSplit[y]) !== -1){
count++;
}
});
}
if(count == searchTextSplit.length){
returnArray.push(input[x]);
}
}
return returnArray;
}
});
Working demo in js fiddle

Not a one liner but still quite short and fun
app.filter("filterall",function($filter) {
return function(arr,t){
(t?t.split(/\s+/):[]).forEach(function(v){ arr = $filter('filter')(arr,v); });
return arr;
};
});

Related

Infdig in Custom groupBy

i recently needed some kinda customized repeater, which group data by their key, but not simply group them all together, so i read several references and things,... and at last i come up to copy
groupBy from this article which he seem to complete it at best, ...
http://sobrepere.com/blog/2014/10/14/creating-groupby-filter-angularjs/
And then i customized so it become like this:
the things my group by do... is:
Group Data Together Until It Reach Differences.
but the matter is that though it work, it still generate infdig, i know it's because the digest done call other one, but what i don't know is how to solve it in very easy and understandable manner, as i'm new to JavaScript...
app.filter('groupBy', function () {
var results = {};
return function (data, key) { //Data => My Objects Array - Key => Name Of Filtered Property
if (!(data && key)) return;
var result;
if (!this.$id) {
result = {};
} else {
var scopeId = this.$id;
if (!results[scopeId]) {
results[scopeId] = {};
this.$on("$destroy", function () {
delete results[scopeId];
});
}
result = results[scopeId];
}
for (var groupKey in result)
result[groupKey].splice(0, result[groupKey].length);
var grpKey = -1; //GroupKey
var lastUserId;
for (var i = 0; i < data.length; i++) {
if (!result[grpKey] || lastUserId && lastUserId != data[i][key]) // Ex.: result[data[0]["UserId"]]{ => return UserId
result[++ grpKey] = [];
result[grpKey].push(data[i]);
lastUserId = data[i][key];
}
var keys = Object.keys(result);
for (var k = 0; k < keys.length; k++) {
if (result[keys[k]].length === 0)
delete result[keys[k]];
}
return result;
};
});
In this url is working perfectly...
http://plnkr.co/edit/8jB4wSRtKfVmEsTGZtfV?p=preview
app.filter('groupBy', function ($timeout) {
return function (data, key) {
if (!key) return data;
var outputPropertyName = '__groupBy__' + key;
if(!data[outputPropertyName]){
var result = {};
for (var i=0;i<data.length;i++) {
if (!result[data[i][key]])
result[data[i][key]]=[];
result[data[i][key]].push(data[i]);
}
Object.defineProperty(result, 'length', {enumerable: false, value: Object.keys(result).length});
Object.defineProperty(data, outputPropertyName, {enumerable:false, configurable:true, writable: false, value:result});
$timeout(function(){delete data[outputPropertyName];},0,false);
}
return data[outputPropertyName];
};
});

Using Lodash _.map and Typescript to create new column for filtering

In my controller I have code that filters just fine but i want to create a new field that concatenates two fields for an Angular filter in html. This is what I have that doesn't work.. Is this possible?
private filterByColumns: string = "";
getData = (): void => {
var vm = this;
this.carHopService.getDetails({ id: this.$state.params["id"], type: this.$state.params["type"] }).then(
(data: any) => {
vm.primaryCarHopData = _.filter(data.carHopList, {
carHopType: "Primary"
});
---> **vm.primaryCarHopData = _.map(data.carHopList, {
vm.filterByColumns=fullName + " " + age
});**
});
};
That's not how map works. In the callback function, you need to return something:
_.map([0,1,2], (x) => x + 1)
> [1,2,3]
// old syntax
_.map([0,1,2], function (x) { return x + 1 })
> [1,2,3]
You can simply replace _.map with _.forEach and you will have your mapped data in data.carHopList.
Clarification:
I'm not good with words so let me put here very simple implementations for both forEach and map:
// these functions do not mock lodash counterparts 100%
// as lodash fns can work with objects too
// and they have some shortcuts, see docs
function forEach(arr, callback) {
for(var i = 0; i < arr.length; i++) {
callback(arr[i], i);
}
return arr;
}
function map(arr, callback) {
var newArr = [];
for(var i = 0; i < arr.length; i++) {
newArr[i] = callback(arr[i], i);
}
return newArr;
}
Someone had steered me in the direction of map. What I ended up doing was using Angular by adding this to controller:
filterTextByCols = (row) => {
return (angular.lowercase(row.fullName).indexOf(angular.lowercase(this.filterQuery) || '') !== -1 ||
angular.lowercase(row.birthDate).indexOf(angular.lowercase(this.filterQuery) || '') !== -1);
}
And then using filterTextByCols in filter:
<div ng-repeat="person in vm.persons | orderBy:sortType:sortReverse | filter: vm.filterTextByCols">

Filter current dates only in ng-repeat array

I have a filter which needs to return only current dates in a data array:
app.filter('currentDates', function() {
return function(eventdata) {
var data_date, filtered_list, i, today;
filtered_list = [];
i = 0;
while (i < eventdata.length) {
today = Date.now();
data_date = eventdata.date;
if (today <= data_date) {
filtered_list.push(data[i]);
}
i++;
}
return filtered_list;
};
});
I'm calling the filter in the html like so:
<div class="event" ng-repeat="data in eventdata | currentDates"></div>
I can't figure out why nothing is being returned. Any help appreciated. Here's the plunkr http://plnkr.co/edit/5aT4CF?p=preview. If you remove the filter the data populates.
Your eventdata is array not single object
You are comparing string with date
You are pushing data[i] instead of eventdata[i]
Try like this
while (i < eventdata.length) {
today = Date.now();
data_date = new Date(eventdata[i].date);
if (today <= data_date) {
filtered_list.push(eventdata[i]);
}
i++;
}
PLUNKR
As was explained in the comments, you are comparing a Date type with a string. You need to perform type conversion on that string.
I also went with a for loop instead of a while loop to save a couple of lines of space.
Your filter function should look like this
app.filter('currentDates', function() {
return function(eventdata) {
var filtered_list = [];
for (var i = 0; i < eventdata.length; i++) {
var today = Date.now();
var data_date = new Date(eventdata[i].date).getTime();
if (today <= data_date) {
filtered_list.push(eventdata[i]);
}
}
return filtered_list;
};
});
Here is the updated plunkr
http://plnkr.co/edit/qZrQEOJDtyBOWSkNnrZS?p=preview

angularjs filter nested array-using-checkboxes-with-angularjs

I am following this approach to filter nested json response. I have a nested property like this:
instances:{
instance:[
{cname:'name1', location:'pa', price:40, model:'2014' },
{cname:'name1', location:'ga', price:30 , model:'2014'},
{cname:'name1', location:'ga', price:20, model:'2010' }
]}
I can filter by top level properties using the above mentioned example but not the child properties.
I have modified above example to show nested properties of my json here.http://jsfiddle.net/jackAndy/qygL2m01/4/. I am new to angularjs.
First of all - why You use instances.instance? It it not principally, use players.instances = [];
Use Group functions only 1 time after data loading; Watching filters - it's not necessary in this case;
Function for get filters values (I use underscore uniq function, You can use Your own algorithm for this):
$scope.getFieldsValues = function(field){
var result = [];
for(var i = 0; i < $scope.players.length; i++){
result.push($scope.players[i][field]);
}
return _.uniq(result);
};
Filter for players:
$scope.testFl = function(el){
for(var filter in $scope.filters){
var filterArray = [];
for(var i in $scope.filters[filter]){
if($scope.filters[filter][i]) filterArray.push(i);
}
//You can make array with instances properties & compare with it;
if(filter === 'location'){
if(el.instances && el.instances.length > 0){
var intersection = el.instances.filter(function(n) {
return filterArray.indexOf(n[filter]) != -1
});
} else if(filterArray.length > 0){return false;}
} else {
if(filterArray.length > 0 && filterArray.indexOf(el[filter]) === -1) return false;
}
}
return true;
};
Template:
<li ng-repeat="player in players | filter:testFl" >
Filter for instances:
$scope.testFl2 = function(el){
var filterArray = [];
for(var i in $scope.filters.location){
if($scope.filters.location[i]) filterArray.push(i);
}
return filterArray.length > 0 && filterArray.indexOf(el.location) === -1 ? false : true;
};
Template:
<span ng-repeat="loc in player.instances | filter:testFl2" >
Fiddle for this;
UPDATE:
Function for count:
$scope.getCount = function(field, value){
var obj = {};
obj[field] = value;
return _.where($scope.players, obj).length;
};
Update fiddle - update underscore, add count function;
I hope this will help you;
For answer were used:
Add underscore to jsfiddle;
variable property name in where underscore.js;

Difficulty returning an array of indexes of each element from an array that matches search term

Hi I am really struggling making code for a function that 'returns an array of indexes from an already populated array containing strings, when it matches the search term'.
So if the search term matches a word or characters in the element of the array regardless of its order, it should return the indexes in which those words are apparent in the array. Not using jquery grep feature.
Here is some code to illustrate what I mean.
array_test = ["Today was hot","Tomorrow will be hot aswell", "Yesterday the weather was not so good","o1234 t12345"]
function locateSearch(array_test,searchTerm){
var myArray = [];
for(i=0;i<array_test.length;i++){
if(...) // what should i be testing here, this is where i have been going wrong...
myArray[myArray.length] = i;
}
return myArray;
}
document.write(locateSearch,'ot') //this should return indexes [0,1,2,3]
Sorry If I have not explained this so well, appreciate your help. Thank you.
array_test = ["Today was hot","Tomorrow will be hot aswell", "Yesterday the weather was not so good","o1234 t12345"];
function locateSearch(array_test,searchTerm){
searchTerm = searchTerm.toLowerCase();
var myArray = [];
for(var i = 0; i < array_test.length; i++){
for(var j = 0; j < searchTerm.length; j++) {
if(array_test[i].toLowerCase().indexOf(searchTerm[j]) >= 0) {
myArray.push(i);
break;
}
}
}
return myArray;
}
alert(locateSearch(array_test, 'To').toString());
Also see this jsfiddle.
=== UPDATE ===
If every char has to be in the same string:
array_test = ["Today was hot","Tomorrow will be hot aswell", "Yesterday the weather was not so good","o1234 t12345"];
function locateSearch(array_test,searchTerm){
searchTerm = searchTerm.toLowerCase();
var myArray = [];
var bFound;
for(var i = 0; i < array_test.length; i++){
bFound = true;
for(var j = 0; j < searchTerm.length; j++) {
if(array_test[i].toLowerCase().indexOf(searchTerm[j]) == -1) {
bFound = false;
break;
}
}
if (bFound) {
myArray.push(i);
}
}
return myArray;
}
alert(locateSearch(array_test, 'es').toString());
Also see my updated jsfiddle.
if I understood your question correctly...
if(array_test[i].indexOf(searchTerm).toLowercase() != -1)
myArray.push(i);
You didn't mention if the search was case sensitive, but assuming it is I threw it in there
if you need your test to be case insensitive use the regEx match method instead of indexOf()
so... if(array[i].match(/searchTerm/i) != null){ myArray.push(i) }
Try this:
array_test = ["Today was hot","Tomorrow will be hot aswell", "Yesterday the weather was not so good","o1234 t12345"]
function locateSearch(array_test,searchTerm) {
var myArray = [];
for(i=0;i<array_test.length;i++) {
if(array_test[i].indexOf(searchTerm) != -1) // what should i be testing here, this is where i have been going wrong...
myArray.push(i);
}
return myArray;
}
document.write(locateSearch,'ot') //this should return indexes [0,1,2,3]
This will return indexes of all elements in array_test that contain the search term.

Resources