Filter with multiple values - angularjs

I have items which should have multiple (e.g. categories). Now I want to filter my items to these categories.
I think the task is not possible with the filter-directive without using a custom filter, right?
I came up with a solution, but it looks dirty and wrong to me:
$scope.filterList = function (item) {
var found = false;
var allFalse = true;
angular.forEach(item.attributes, function (value, key) {
if ($scope.activeAttributes[value.name] === true) {
found = true;
}
});
angular.forEach($scope.activeAttributes, function (value, key) {
if (value === true) {
allFalse = false;
}
});
$log.log("length: " + Object.keys($scope.activeAttributes).length);
if (found === true || Object.keys($scope.activeAttributes).length === 0 || allFalse === true) {
return true;
}
};
Demo JSFiddle of my code
I thought with Angular, that the code should be simple and most of the work should be done by Angular. What if I need to filter more attributes?

Related

Multiple optional filters in angular

I am very new to angular and, I am not sure how to control the behavior of my filters.
In the app, I have two different single-select drop down controls that filter the results of my data set and fill a table. However, even though these filters work, the results are dependent of both controls and if both are not being used , the empty set is returned. So, my question is: How can I use these filters optionally? So, the app returns every result when the filters are not used or returns the filtered results by one of the controls or both?
Thank you
Here is the code:
AngularJS
The filters for each control. They look very similar:
.filter('byField', function () {
return function (results, options) {
var items = { options: options, out: [] };
angular.forEach(results, function (value, key) {
for (var i in this.options) {
if ((options[i].value === value.fieldId &&
options[i].name === "Field" &&
options[i].ticked === true)) {
this.out.push(value);
}
}
}, items);
return items.out;
};
})
.filter('byClass', function () {
return function (results, options) {
var items = { options: options, out: [] };
angular.forEach(results, function (value, key) {
for (var i in this.options) {
if ((options[i].value === value.documentClass &&
options[i].name === "Class" &&
options[i].ticked === true)) {
this.out.push(value);
}
}
}, items);
return items.out;
};
})
HTML
This is what I am doing to populate the rows of the table:
<tr ng-repeat="result in results | byField:outputFields | byClass:outputClasses">
<td>{{result.documentId}}</td>
...
</tr>
Dorado7.1 in all event listeners provides a view implicit variable pointing to the current event host's view, the variable can completely replace the use of this scenario.
Well, as I imagined the answer was more related to set theory than to angular.
I just made an union between the empty set and every result, and it worked.
.filter('byField', function () {
return function (results, options) {
var items = { options: options, out: [] };
angular.forEach(results, function (value, key) {
if (options.length) {
for (var i in this.options) {
if ((options[i].value === value.fieldId &&
options[i].name === "Field" &&
options[i].ticked === true)) {
this.out.push(value);
}
}
} else {
this.out = results.slice();
}
}, items);
return items.out;
};
})

knockout array filter based on list of checkbox selected

I am struggling to filter list in the body based on the selection of list of checkboxes under the protocol in the left see on jsfiddle please help.
filteredRecords: function(){
return ko.utils.arrayFilter(viewModel.protocoldocs,function(protocoldoc){
var flag = false;
foreach(selprotocol in viewModel.selectedprotocol)
{
if(selprotocol.id === protocoldoc.pronumber)
flag = true;
}
return flag;
})};
You could use a computed observable for this purpose like below
viewModel.filteredProtocols = ko.computed(function () {
var selectedProtocols = ko.utils.arrayFilter(viewModel.protocol(), function (p) {
return p.selected();
});
if (selectedProtocols.length == 0) //if none selected return all
return viewModel.protocoldocs();
else { //other wise only return selected protocoldocs
return ko.utils.arrayFilter(viewModel.protocoldocs(), function (item) {
return ko.utils.arrayFilter(selectedProtocols, function (p) {
return p.id == item.id
}).length > 0;
});
}
})
and bind your result table to this filteredProtocol. A couple of things that i have also modified are
I added a selected flag for protocol to retain checked values
<input type="checkbox" data-bind="checked:selected, attr: {id: 'checkBox' + id}">
...
function protocol(id, name) {
this.id = id;
this.name = name;
this.selected = ko.observable(false);
}
you can find a working sample here http://jsfiddle.net/prc4pqnm/3/

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

Test multiple properties using _.every

I was asked to change some code using lodash's _.every:
//for every item in collection, check if "someProp" is true,
//but only if "someProp2" isn't "-1". If "someProp" is true for
//every item in collection, return true.
$scope.areAllTrue = function() {
for(var i=0; i<$scope.collection.length; i++){
if($scope.collection[i].someProp2 === -1) {
continue;
}
if(!$scope.collection[i].someProp) {
return false;
}
}
return true;
};
So following the lodash example of:
_.every(users, 'active', false);
We get:
$scope.areAllTrue = function() {
return _.every($scope.collection, 'someProp', true)
};
This handles the "For every item in the collection, check if someProp is true, if all are true, return true." But can I do the "continue" check here somehow?
Edit: Can I use two predicates with "_.every" somehow? Like if someProp1 === true || someProp2 === -1 ?
_.every() can use a predicate function:
_.every(users, function(user) {
return user.someProp2 === -1 || user.someProp;
});
You can also skip lodash, and use Array.prototype.every:
users.every(function(user) {
return user.someProp2 === -1 || user.someProp;
});

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.

Resources