AngularJS Custom Filter duplicates - angularjs

I currently meet an issue with a custom filter. I have an array of CVE objects called 'cves' (in my scope), and for each item I generate a tr line in a table using ng-repeat.
Here is the global structure of a CVE:
cve: {
id: integer,
cve: string,
summary: text,
description: text,
cvss:{
score: float,
vector: string
}
}
Here is my HTML code
<input type='text' ng-model='searchField'/>
....
<tr ng-repeat="cve in cves | cveFilter:[ad_filters, searchField] as filtered_cves"
ng-if="cves.length > 0">
<td colspan="7" class="no-padding">
//printing infos in a custom directive
</td>
</tr>
....
Here is my filter :
.filter('cveFilter', function () {
return function (cves, params) {
console.log(cves);
let items = {
ids: params[0],//the ids (array of ids)
text: params[1],//the text
filtered_cves: []//the output
};
// for each cve, if its ID is in items.ids
// AND if one of the property of the CVE match with items.text
// push it to items.filtered_cves
cves.forEach(function (cve) {
if (
items.ids.includes(cve.id) &&
(
cve.cve.match(items.text) ||
cve.summary.match(items.text) ||
cve.description.match(items.text) ||
cve.cvss.score.match(items.text) ||
cve.cvss.vector.match(items.text)
)
) {
items.filtered_cves.push(cve)
}
});
return items.filtered_cves;
};
});
My problem is the following : my filter seems to work, it keeps only the matching CVEs but it displays each CVE in duplicate. That means if I have 6 cves in my $scopes.cves array, i will have 12 lines in my html table.
It's my first custom filter but I think it's a stupid mistake.
Do you know where I failed ?
Thanking you in advance,

It is duplicating the data, I don't get blank lines.
If I print the content of $scope.filtered_cves, I get (let's say I am supposed to get 8) 16 elements.
I didn't mention that earlier, but $scope.ad_filters is an array of CVE IDs I want to display. A CVE is displayed only if its ID is in $scope.ad_filters AND if one of its property matches with the content of the input form text.
I can't screenshot at the moment, I need to put fake data.
Here is the updated code for my filter (nothing really changed, just added some functions) :
.filter('cveFilter', function () {
return function (cves, params) {
let items = {
ids: params[0],
text: params[1],
filtered_cves: []
};
cves.forEach(function (cve) {
console.log(cve);
if (items.ids.includes(cve.id)) {
if (items.text === '' || items.text === null || items.text === undefined ||
(
cve.cve.toString().includes(items.text) ||
cve.summary.toString().includes(items.text) ||
cve.description.toString().includes(items.text) ||
cve.cvss.score.toString().includes(items.text) ||
cve.cvss.vector.toString().includes(items.text)
)
) {
items.filtered_cves.push(cve)
}
}
});
return items.filtered_cves;
};
});
I ran it and I noticed that it is executed several times, and the last time it prints twice too much lines.

Related

Group objects by field, then group by a conditional field

I have a list of objects that I'm using an ng-repeat to display.
I need to be be able to group these objects by a field, such as an departmentID.
I then need to be able to loop through each department, and then by the 2nd field, which will either be preliminary or final. If there is a final record for a given department, the preliminary record should be hidden. If there is no final for a given department, show the preliminary.
Ex.
Department 1
Preliminary - not visible
Department 1
Final - visible
Department 2
Final - visible
Department 3
Preliminary - not visible
Department 3
Final - visible
Department 4
Preliminary - visible
Data Sample
var data = [
{ departmentID: 1, status: 'preliminary' },
{ departmentID: 1, status: 'final' },
{ departmentID: 2, status: 'final' },
{ departmentID: 3, status: 'preliminary' },
{ departmentID: 3, status: 'final' },
{ departmentID: 4, status: 'preliminary' },
];
Current code below
if (item.result_status !== "preliminary") {
return item;
}
else if (item.result_status === "final") {
return item;
}
else {
return false;
}
<div ng-repeat="(key, value) in Data | groupBy: 'department_ID'">
<div ng-repeat="v in value | filter:prelimFilter(value)">
<a ng-click="showPopUp(key)">{{Some Other Field}} </a>
</div>
</div>
<!-- begin snippet: js hide: false console: true babel: false -->
You are close! Your first half of the code is correct where you group the object based on departmentID using groupBy filter. The issue is with your custom filter. You have created a filter function instead of custom filter and the issue is filter function does not have access to entire array. It has access only to individual elements. This blog explains clearly the different types of filter that can be created in angularjs.
You can create a custom filter which has access to the array and you can return the filtered array based on status
.filter('myFilter', function () {
return function(inputArr) {
var outputArr = [];
outputArr = inputArr.filter(function(obj){return obj.status === "final"});
outputArr = outputArr.filter(function(e){return e}); //To remove empty elements
if(outputArr.length === 0){
outputArr = inputArr; //inputArr does not have obj with status 'final'
}
return outputArr;
};
});
Working jsfiddle here. Good luck!
NOTE: groupBy filter is not part of angular.js You have to include angular.filter module.

Protractor: How to find an element in an ng-repeat by text?

I'm looking to get a specific element inside an ng-repeat in protractor by the text of one of its properties (index subject to change).
HTML
<div ng-repeat="item in items">
<span class="item-name">
{{item.name}}
</span>
<span class="item-other">
{{item.other}}
</span>
</div>
I understand that if I knew the index I wanted, say 2, I could just do:
element.all(by.repeater('item in items')).get(2).element(by.css('.item-name'));
But in this specific case I'm looking for the 'item in items' that has the specific text (item.name) of say "apple". As mentioned, the index will be different each time. Any thoughts on how to go about this?
public items = element.all(by.binding('item.name'))
getItemByName(expectedName) {
return this.items.filter((currentItem) => {
return currentItem.getText().then((currentItemText) => {
return expectedName === currentItemText;
});
}).first();
}
And invoke method like that this.getItemByName('Item 1'). Replace Item 1 with expected string.
function elementThere(specificText, boolShouldBeThere){
var isThere = '';
element.all(by.repeater('item in items')).each(function (theElement, index) {
theElement.getText().then(function (text) {
// Uncomment the next line to test the function
//console.log(text + ' ?= ' + specificText);
if(text.indexOf(specificText) != -1){
element.all(by.repeater('item in items')).get(index).click();
isThere = isThere.concat('|');
}
});
});
browser.driver.sleep(0).then(function () {
expect(isThere.indexOf('|') != -1).toBe(boolShouldBeThere);
});
}
it('should contain the desired text', function () {
elementThere('apple', true);
}
Does this fit your needs?
I was able to solve this by simplifying #bdf7kt's proposed solution:
element.all(by.repeater('item in items')).each(function(elem) {
elem.getText().then(function(text) {
if(text.indexOf('apple') != -1) {
//do something with elem
}
});
});
Also, this particular solution doesn't work for my use case, but I'm sure will work for others:
var item = element(by.cssContainingText('.item-name', 'apple'));
//do something with item

Why two ways are allowed to access json keys in angularjs?

Below is my code snippet
<body ng-app='abc' ng-controller='MainCtrl as ctrl'>
<div ng-repeat="note in ctrl.notes">
<span class="label"> {{note[1]}}</span>
<span class="label"> {{note.label}}</span>
<span class="status" ng-bind="note.done"></span>
</div>
</body>
and my notes object contains
self.notes = [
{ id : 1 , label : 'First Note' , done : false, [1]:3, 'string key':4 },
{ id : 2 , label : 'Second Note' , done : false, 1:2, 'string key':4 },
{ id : 3 , label : 'Done Note' , done : true, 1:2,'string key':4 },
{ id : 4 , label : 'Last Note' , done : false, 1:2 , 'string key':4}
];
I am able to access the label with a.label and a['label'], may I know why these two ways? The second a['label'] only is enough right?
And also if you observe in my notes object, one of the key in first row is [1], I am able to access it with note[1] in html, please let me know how come it is possible?
You can get access by a.label and a['label'] because in Javascript you can get object properties in two ways: object.property and object['property'].
let obj = {
property: "OK"
}
console.log(obj.property);
console.log(obj['property']);
Update:
With reference the article from
javascript-object-syntax
Evaluating each dot or bracket expression takes time. If the same property is used multiple times, then it makes more sense to access the property once, and then store the value in a local variable for all future uses.
The following example uses bar many times within a loop. However, instead of wasting time computing the same value over and over, bar is stored in a local variable.
var object = {
baz: {
foo: {
bar: 5
}
}
};
var bar = object.baz.foo.bar;
var count = 0;
for (var i = 0; i < 100000; i++) {
count += bar;
// better than count += object.baz.foo.bar;
}
If you are accessing a property that is not a valid identifier or if you want to dynamically populate properties from loops or expressions you should use object['property'] otherwise you use object.property.
var obj = {
'prop1': 'Valid identifier',
'2prop': 'Invalid identifier'
};
var invalidProp = Object.keys(obj)[1];
console.log(obj.prop1);
// console.log(obj.2prop); // Throws error
console.log(obj['2prop']);
console.log(obj[invalidProp]);

How can I combine these custom filters?

Im trying to create a filter mechanism using this code, which works perfectly (independently):
// Report Filtering
$scope.filter = {};
$scope.getCategories = function () {
return ($rootScope.reportsData || []).map(function (report) {
return report.type;
}).filter(function (report, idx, arr) {
return arr.indexOf(report) === idx;
});
};
$scope.getPackages = function () {
return ($rootScope.reportsData || []).map(function (report) {
return report.package;
}).filter(function (report, idx, arr) {
return arr.indexOf(report) === idx;
});
};
$scope.filterByCategory = function (reportsData) {
return $scope.filter[reportsData.type] || noFilter($scope.filter);
};
$scope.filterByPackage = function (reportsData) {
return $scope.filter[reportsData.package] || noFilter($scope.filter);
};
function noFilter(filterObj) {
for (var key in filterObj) {
if (filterObj[key]) {
return false;
}
}
return true;
}
and the ng-repeat is:
ng-repeat="item in filtered=(reportsData | filter:filterByPackage)"
This works perfectly if I replace filter: with either filterByPackage or filterByCategory.
Im using this code to iterate through the keys and create checkboxes to toggle the visibility of the items:
<label ng-repeat="cat in getCategories()">
<input type="checkbox" ng-model="filter[cat]" />{{cat}}</label>
However, I would like to use these both in conjunction. If i modify my inline code on the ng-repeat to:
ng-repeat="item in filtered=(reportsData | filter:filterByPackage | filter:filterByCategory)"
then clicking on checkbox makes the entire list disappear. What is the syntax to properly combine these two filters?
If you select a category and a package you only want to display the reportData that matches with both?
The problem you are having is that you are using your "filter" object for both types of filtering and this in combination with your noFilter function that also verifies if anything is checked on the filter is causing that you need to select both a package and category exactly matching the reportData for it to be displayed (you cannot leave a filter unselected or it doesn't display any).
What you can do is initialize your filter as an object in which each criteria is a member, and utilize them as so:
$scope.filter = { packages: {}, categories: {}};
return $scope.filter.categories[reportsData.type] || noFilter($scope.filter.categories);
<label ng-repeat="cat in getCategories()">
<input type="checkbox" ng-model="filter.categories[cat]" />{{cat}}
</label>

How to filter ng-repeat on a child value using AngularJS

I've made a simple table with repeating rows based on an array using ng-repeat.
I've added a filter similar to this example.
My problem comes when I try to filter on the nested object. It is always a single object, basically it is the parent to my current object but it's the full object instead of just the FK value.
If I use filterText.educationCenter.name it filters all rows out of the table and will not return them even if I clear the filter. I have to do a full reload.
If I filter on just filterText.educationCenter it will work but it searches all fields of the entire object.
I would like to keep the JSON as it is and filter the table results based on the name of the educationCenter object.
My HTML
Search: <input ng-model="filterText.$"/>
<input ng-model="filterText.city"/>
<input ng-model="filterText.educationCenter.name"/>
<tr ng-repeat="course in courses | filter:filterText | startFrom:currentPage*pageSize | limitTo:pageSize">
<td width="145">{{course.city}}</td>
<td width="145">{{course.educationCenter.name}}</td>
...
My JSON for a singe object from the array
{"id":"108"
,"city":"My City"
,"educationCenter":{"id":"3"
,"description":"This is the ed center description"
,"name":"test ed center 1"}
,"noOfDays":"1"}
As #fastreload suggested, I think you'll have to make your own filter.
Here is the sample of the recursive filter.
(it does not support $ notation though, and I'm sure it is missing
some edge case support.)
app.filter('recursiveFilter', function() {
var compare = function(input_, cond_) {
if (!cond_) return true;
if (typeof input_ == "string" && typeof cond_ == "string") {
return (new RegExp(cond_)).test(input_);
} else if (typeof input_ == "object" && typeof cond_ == "object") {
for (var s in cond_) {
if (!compare(input_[s], cond_[s])) return false;
}
return true;
}
return false;
};
return function(inputs, cond) {
return $.grep(inputs, function(input) {
return compare(input, cond);
});
};
});

Resources