angularjs filter nested array-using-checkboxes-with-angularjs - 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;

Related

How to make a filter attached to $scope of a controller (angular)?

I wrote a litlle program in angular using ui-select. And I wrote a filter that do an OR search in different fields.
Here is my original filter : (whic works perfectly)
app.filter('orSearchFilter', function($parse) {
return function(items, props) {
var out = [];
if (angular.isArray(items)) {
var keys = Object.keys(props);
items.forEach(function(item) {
var itemMatches = false;
for (var i = 0; i < keys.length; i++) {
var prop = $parse(keys[i])(item);
var text = props[keys[i]].toLowerCase();
if (prop && prop.toString().toLowerCase().indexOf(text) !== -1) {
itemMatches = true;
break;
}
}
if (itemMatches) {
out.push(item);
}
});
} else {
out = items;
}
return out;
};
});
And here is my original plunker (which works) : http://plnkr.co/edit/IdqO5dtLXmC6gtqLxRdP?p=preview
The problem is that my filter won't be generic and I will use it in my final code just inside its controller. So, I want to attach it.
Here is the new version of the filter which is attached to the controller : (I didn't do any change...)
$scope.orSearchFilter = function($parse) {
return function(items, props) {
var out = [];
if (angular.isArray(items)) {
var keys = Object.keys(props);
items.forEach(function(item) {
var itemMatches = false;
for (var i = 0; i < keys.length; i++) {
var prop = $parse(keys[i])(item);
var text = props[keys[i]].toLowerCase();
if (prop && prop.toString().toLowerCase().indexOf(text) !== -1) {
itemMatches = true;
break;
}
}
if (itemMatches) {
out.push(item);
}
});
} else {
out = items;
}
return out;
};
};
Finally, in my html, I called this new filter by using this line :
<ui-select-choices group-by="groupByLetter"
repeat="contract in (contracts |
filter : orSearchFilter(contracts, {id.id: $select.search, policy.info.name : $select.search } ) |
orderBy: 'name') track by contract.name">
{{contract.name}} - {{contract.value}} ---- {{contract.id.id}} *** {{contract.policy.info.name }}
</ui-select-choices>
Can you help me please to fix that problem and help me to attach this filter to the scope of the controller?
Thank you !
Use the $filter service to programmatically fetch your filter function.
//Don't forget to inject $filter in your controller ofcourse
$scope.orSearchFilter = $filter('orSearchFilter');
Attach the filter directly to scope:
/* REMOVE constructor function
$scope.orSearchFilter = function($parse) {
return function(items, props) {
*/
// INSTEAD
$scope.orSearchFilter = function(items, props) {
var out = [];
//...
return out;
};
//};
Of course, also be sure that $parse is added to the injectables of the controller construction function.

AngularJs - Filter an object only by certain fields in a custom filter

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)

AngularJS Filter on nested properties

I would like to create a filter on nested objects like this:
Object 1 :
property1
property2
property3
children : Child 1 :
propertyChild1
propertyChild2
Child 2 :
And so on. An object can have multiple child. There is no depth limit specified.The problem is that I want to search only on certain properties of the object so I used:
ng-repeat="groupLevel1 in groupLevel2.children | filter: {lineDescription: searchKeyword}"
This is searching on all levels but if a parent does not contain the searchKeyword, all the children (which may contain the search) aren't displayed. I want that all parent levels are displayed in order to display the children that contains the search keyword even if the parents do not match the search.
I tried some complicated script but it does not work:
appReportingHoliday.filter('globalFilter', function(){
return function(array, predicate){
return array.filter(function(val){
var formattedObj = parseFloatInternational(predicate);
var re = new RegExp(formattedObj, 'i');
var initialArray = [];
initialArray.push(val);
var childIsNeeded = false;
var toReturnTemp;
var parents = [];
var toReturn = [];
while(initialArray!=null){
angular.forEach(initialArray, function (currentVal) {
toReturnTemp = false;
//We check if the val is concerned by the search
toReturnTemp = re.test(currentVal.lineDescription) || re.test(currentVal.acquiredHolidays) || re.test(currentVal.tokenHolidays) || re.test(currentVal.availableHolidays)
|| re.test(currentVal.dailyCost) || re.test(currentVal.valuation);
if (toReturnTemp) {
//if it is, we need to add the result to the toReturn array and also the parents that we could have saved in the according array
toReturn.push(currentVal);
toReturn.push(parents);
parents = [];
}
else {
//else we save it in the parents array if a child is needed
if(currentVal.children!=null) {
parents.push(currentVal);
}
}
var index = initialArray.indexOf(currentVal);
initialArray.splice(index, 1);
if(currentVal.children!=null) {
angular.forEach(currentVal.children, function (currentChild) {
initialArray.push(currentChild);
});
}
});
if(initialArray.length==0) initialArray = null;
}
return toReturn;
});
}
});
The display is made like this:
<tr class="groupReportingTreeDatatable" ng-repeat-start="groupLevel3 in myData | filter: {lineDescription: searchKeyword}" ng-init="$index < 2 ? groupLevel3.hideRows = false : groupLevel3.hideRows = true;" ng-class-even="'dataTable_row1'" ng-class-odd="'dataTable_row2'" spinner-handler-directive="">
...
<tr class="groupReportingTreeDatatable" ng-hide="groupLevel3.hideRows" ng-init="groupLevel2.hideRows = true" ng-repeat-start="groupLevel2 in groupLevel3.children | filter: {lineDescription: searchKeyword}" ng-class-even="'dataTable_row1'" ng-class-odd="'dataTable_row2'">
...
<tr ng-hide="groupLevel2.hideRows || groupLevel3.hideRows" ng-repeat="groupLevel1 in groupLevel2.children | filter: {lineDescription: searchKeyword}" ng-class-even="'dataTable_row1'" ng-class-odd="'dataTable_row2'" ng-repeat-end="">
EDIT :
I tried something else which works for some searches but not all of them :(
appReportingHoliday.filter('globalFilter', function() {
return function (array, predicate) {
return array.filter(function (val) {
var formattedObj = parseFloatInternational(predicate);
var re = new RegExp(formattedObj, 'i');
var found = re.test(val.lineDescription) || re.test(val.acquiredHolidays) || re.test(val.tokenHolidays) || re.test(val.availableHolidays)
|| re.test(val.dailyCost) || re.test(val.valuation);
var child = val.children;
while(child!=null && found == false){
angular.forEach(child, function (currentChild) {
if(found == false) {
console.log(currentChild.lineDescription)
found = re.test(currentChild.lineDescription) || re.test(currentChild.acquiredHolidays) || re.test(currentChild.tokenHolidays) || re.test(currentChild.availableHolidays)
|| re.test(currentChild.dailyCost) || re.test(currentChild.valuation);
}
});
child = child.children;
}
return found;
});
}
});
Wouldn't it be easier to have a second variable where you flatten all of the children of the first one? You could define a recursive function doing that, something like...
var mainObject = ... ;//that's your object
var flattened = new Array();
function flatten(main,flat) {
var children = main.children;
for (var i=0;i<children.length;i++) {
flatten(children[i],flat); // recursive call, depth-first
delete children[i].children; // those are already treated
flat.push(children[i]); // add children
}
delete main.children;
flat.push(main);
}
Now you can filter on the properties directly.
I don't see how to integrate this in my code. I cannot change the base variable because I need it in this structure to display it on the screen. I can create another flattened array but the filter has to be applied on the var I display, no ? So I cannot use the flattened var. I have to admit I am a bit lost ^^
I fixed the depth to 3 to make it easier so now I have a this:
appReportingHoliday.filter('globalFilter', function() {
return function (array, predicate) {
return array.filter(function (val) {
var formattedObj = parseFloatInternational(predicate);
var re = new RegExp(formattedObj, 'i');
var found = re.test(val.lineDescription) || re.test(val.acquiredHolidays) || re.test(val.tokenHolidays) || re.test(val.availableHolidays)
|| re.test(val.dailyCost) || re.test(val.valuation);
var child = val.children;
if(child!=null && found == false){
angular.forEach(child, function (currentChild) {
if(found == false) {
found = re.test(currentChild.lineDescription) || re.test(currentChild.acquiredHolidays) || re.test(currentChild.tokenHolidays) || re.test(currentChild.availableHolidays)
|| re.test(currentChild.dailyCost) || re.test(currentChild.valuation);
}
if(currentChild.children!=null && found == false){
angular.forEach(currentChild.children, function (currentGrandChild) {
if(found == false) {
found = re.test(currentGrandChild.lineDescription) || re.test(currentGrandChild.acquiredHolidays) || re.test(currentGrandChild.tokenHolidays) || re.test(currentGrandChild.availableHolidays)
|| re.test(currentGrandChild.dailyCost) || re.test(currentGrandChild.valuation);
}
});
}
});
child = child.children;
}
return found;
});
}
});
The only problem that remains is that if the search is on a parent, I want all the child t be displayed but right now only the matched children are displayed :s I cannot find the parent from a child, I have only the link from parent to child not the other way around :s

AngularJS, Add Rows

Morning,
We are trying to implement this add row Plunkr, it seems to work however our input data seems to repeat. Does anyone know of a solution to add a unique id to preview duplicated fields ?
Here is our current Plunkr and LIVE example.
$scope.addRow = function(){
var row = {};
$scope.productdata.push(row);
};
$scope.removeRow = function(index){
$scope.productdata.splice(index, 1);
};
$scope.formData you have is not an array, but just one object. All your rows are bound to that object and hence all of them reference the same data.
The reason you get a new row added is because your ng-repeat is bound to $scope.productData and you add extra record in it. You should bind your form elements to the properties in the row object that you create
a simple example is :
In your template
<div ng-repeat="product in products">
<input type="text" ng-model="product.title">
</div>
In your controller
$scope.addProduct = function(){
var product = {};
$scope.productData.add(product);
}
You'd then always only work with the productData array and bind your model to them.
Even in your backend calls, you'd use productData instead of your formData.
Hope this helps.
U can use a filter : This will return Unique rows only
app.filter('unique', function () {
return function (items, filterOn) {
if (filterOn === false) {
return items;
}
if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) {
var hashCheck = {}, newItems = [];
var extractValueToCompare = function (item) {
if (angular.isObject(item) && angular.isString(filterOn)) {
return item[filterOn];
} else {
return item;
}
};
angular.forEach(items, function (item) {
var valueToCheck, isDuplicate = false;
for (var i = 0; i < newItems.length; i++) {
if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
newItems.push(item);
}
});
items = newItems;
}
return items;
};
});
I think the reason why this is happening is that the addRow() function is just pushing an empty son object into the $scope.productdata array, whereas all input fields are bound to $scope.formData[product.WarrantyTestDescription]. I think you mean to bind the input fields to the properties of the product object.

AngularJS filter for multiple strings

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

Resources