Group objects by field, then group by a conditional field - angularjs

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.

Related

Adding Selected Option Prices for Checkboxes - Using Svelte

I'm trying to get update the total as the checkboxes are selected and unselected (incase the user changes their mind and no longer wants the item). But I'm not sure how to get the actual value I have assigned to each toy. For example: If the user selects toy 1 and 3 they should see: You ordered Toy1, Toy3 and your total is $6.00. For now I assigned the values with the names themselves which isn't what I want but I just put that to show what I'm trying to do. Is their something else I should be using actually perform an operation to get the total?
<script>
let toylist = [];
let toynames = [
'Toy1 5.00',
'Toy2 5.00',
'Toy3 1.00',
];
function join(toylist) {
return toylist.join(', ');
}
</script>
{#each toynames as toy}
<label>
<input type=checkbox bind:group={toylist} value={toy}> {toy}
</label>
{/each}
{#if toylist.length === 0}
<p>Pick at least one toy</p>
{:else}
<p>
You ordered {toylist.join(', ')} and your total is
</p>
{/if}
Ok, first you should separate the toynames array into an array of names and values. Then you should bind to the checked property of the input.
In order to display the current state to the user, we need a reactive declaration. Let's call it total. It contains two functions. The first one gives back an array of the selected names, the second the sum of the selected values.
Both work by looking at the selected property of the object in the toylist array. This updates due to our binding of the checked attribute. I created a repl for you to toy ;-) around with
<script>
let toylist = [
{name: "Toy", value: 5, selected: false},
{name: "Elephant", value: 6, selected: false},
{name: "Choo-Choo", value: 1, selected: false}
]
$: total = {
names: toylist.reduce((acc, cV) => {
if (cV && cV.selected) {
return [...acc, cV.name];
} else return [...acc];
}, []),
values: toylist.reduce((acc, cV) => {
if (cV && cV.selected) {
return parseInt(acc) + parseInt(cV.value);
} else return parseInt(acc);
}, 0)
};
</script>
{#each toylist as {name,value, selected}}
<label>
<input type=checkbox bind:checked={selected} bind:value={value}> {name}
</label>
{/each}
{#if toylist.length === 0}
<p>Pick at least one toy</p>
{:else}
<p>
You ordered {total.names.length < 1 ? "nothing": total.names} and your total is {total.values}
</p>
{/if}
EDIT:
Here is the total function with a more classic syntax:
$: total = {
names: toylist.reduce(function(acc, cV) {
if (cV && cV.selected) {
return [...acc, cV.name];
} else return [...acc];
}, []),
values: toylist.reduce(function(acc, cV) {
if (cV && cV.selected) {
return parseInt(acc) + parseInt(cV.value);
} else return parseInt(acc);
}, 0)
};
And here without the ? operator:
<script>
function renderNames() {
if (total.names.length < 1) {
return "nothing";
} else {
return total.names;
}
}
</script>
<p>You ordered {renderNames()} and your total is {total.values}</p>
The best way to isolate the price of each toy would be to make your array of toys into an array of objects where 'name' is one key value pair and price another. For manipulating the data it would be helpful if each toy had an id, and I've added a 'selected' boolean value to each toy that is updated if they are added or removed from the "toylist". I've also added a 'total' variable to hold the total of selected toys prices.
I have played with your code a bit to make this work. I have used buttons instead of checkboxes but you could do it in any way you like. So give this code a go and it should be doing what you want.
<script>
let toylist = [];
let toys = [
{id: 1, name: 'Toy 1', price: 5.00, selected: false},
{id: 2, name: 'Toy 2', price: 5.00, selected: false},
{id: 3, name: 'Toy 3', price: 1.00, selected: false}
];
let total = 0.00
function join(toy) {
if(toy.selected === false) {
toylist = [...toylist, toy.name]
total = total+toy.price
let i = toys.findIndex(t => t.id === toy.id)
toys[i].selected = true
toys = toys
} else {
total = total-toy.price
let i = toys.findIndex(t => t.id === toy.id)
let i2 = toylist.findIndex(t => t === toy.name)
toylist.splice(i2, 1)
toylist = toylist
toys[i].selected = false
toys = toys
}
}
</script>
{#each toys as toy}
<label>
{toy.name}: ${toy.price} <button on:click="{() => join(toy)}" value={toy.name}>{toy.selected ? 'remove' : 'add'}</button>
</label>
{/each}
{#if toylist.length === 0}
<p>Pick at least one toy</p>
{:else}
<p>
You ordered {toylist} and your total is ${total}
</p>
{/if}

AngularJS Custom Filter duplicates

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.

AngularJS nested filter hiding elements without destined property

I'm using AngularJS on a project and I need to implement a select box with a filter to a nested property. My object (I have an array of those and I'm iterating through them via ng-repeat) has a structure similar to this:
{
id: 1,
name: 'Example',
groups: [
{ id: 1, name: 'Group 1' },
{ id: 2, name: 'Group 2' }
]
}
I need to filter the group ID of the elements, and after searching I've come up with two ways to do it:
1.
| filter: { $: { id: search.item_id } }
Which has these problems:
Apparently it searches for any nested properties named ID, so if I have more than one object inside my main object, with a property called ID too, it would add it to the filter. Example:
{
id: 2,
name: 'Example 2',
groups: [
{ id: 1, name: 'Group 1' },
{ id: 2, name: 'Group 2' }
],
categories: [
{ id: 1, name: 'Cat 1' },
{ id: 2, name: 'Cat 2' }
]
}
Filtering for ID 1 would select not only group with ID 1, but category with ID 1 too.
Also, with this method, even before setting the filter (search.item_id model is null), objects without groups are being filtered and not appearing in the list. These objects are like this:
{
id: 3,
name: 'Example 3',
groups: []
}
and the other way is:
2.
| filter: { groups: [{ id: search.item_id }] }
In this case, the problem is that it simply doesn't work, it filters everything, leaving the list blank, no matter if it's set or which option is selected.
How can I make it work? I've been searching and couldn't find anything about this. Filtering nested properties is (or should be) a very basic thing.
Update:
So, xtx first solution kinda did it, but only if I use input text or number, but I need to use a select box (more specifically uib-dropdown, but working in a regular select is the next step). My select box is looking like this:
<select name="filter_classes_groups_test" ng-model="search.group_id">
<option val="group.id" ng-repeat="group in classesGroups">{{ group.name }}</option>
</select>
When I interact with it, nothing happens.
If creating a custom filter works for you, here is how you can do that:
app.filter('groupsFilter', function() {
return function(input, groupId) {
var out = [];
if (!groupId || isNaN(parseInt(groupId))) {
return input;
}
angular.forEach(input, function(item) {
if (item.groups && angular.isArray(item.groups)) {
angular.forEach(item.groups, function (group) {
if (group.id === parseInt(groupId)) {
out.push(item);
}
});
}
});
return out;
}
});
As you can see, the custom filter has name groupsFilter, and takes group id to search for as a parameter. The filter can be applied like this:
<div ng-repeat="item in data | groupsFilter:search.item_id">
...
</div>
UPDATE:
Instead of creating a custom filter, you can just create a function that implements filtering logic, in scope like this:
$scope.groupsFilterLocal = function(value) {
if (!$scope.search.item_id || isNaN(parseInt($scope.search.item_id))) {
return true;
}
if (!value || !value.groups || !angular.isArray(value.groups)) {
return false;
}
for (var i = 0; i < value.groups.length; i++) {
if (value.groups[i].id === parseInt($scope.search.item_id)) {
return true;
}
}
return false;
};
and then apply it using build-in filter like this:
<div ng-repeat="item in data | filter:groupsFilterLocal ">
...
</div>
Notice that in this case you can't pass the value to search for (search.item_id) into your function groupsFilterLocal like it is done for the custom filter groupsFilter above. So groupsFilterLocal has to access search.item_id directly
UPDATE 2: How to create a select properly
The reason why the filter is not applied when you pick a group in your dropdown, is in the way how you defined the select. Instead of the id of the selected group, search.group_id gets assigned group's name.
Try defining the select like shown below:
<select name="filter_classes_groups_test" ng-model="search.item_id" ng-options="group.id as group.name for group in classesGroups">
<option value="">-- Choose Group --</option>
</select>
To ensure that search.item_id gets id of the group that is selected (and not its name), try temporarily adding {{ search.item_id }} somewhere in your template.

Use ng-repeat for only some items of a collection [duplicate]

I want to use parameter in filter, when I iterate some arrays with ng-repeat
Example:
HTML-Part:
<tr ng-repeat="user in users | filter:isActive">
JavaScript-part:
$scope.isActive = function(user) {
return user.active === "1";
};
But I want to be able to use filter like
<tr ng-repeat="user in users | filter:isStatus('4')">
But its not working. How can I do something like that?
UPDATE: I guess I didn't really look at the documentation well enough but you can definitely use the filter filter with this syntax (see this fiddle) to filter by a property on the objects:
<tr ng-repeat="user in users | filter:{status:4}">
Here's my original answer in case it helps someone:
Using the filter filter you won't be able to pass in a parameter but there are at least two things you can do.
1) Set the data you want to filter by in a scope variable and reference that in your filter function like this fiddle.
JavaScript:
$scope.status = 1;
$scope.users = [{name: 'first user', status: 1},
{name: 'second user', status: 2},
{name: 'third user', status: 3}];
$scope.isStatus = function(user){
return (user.status == $scope.status);
};
Html:
<li ng-repeat="user in users | filter:isStatus">
OR
2) Create a new filter that takes in a parameter like this fiddle.
JavaScript:
var myApp = angular.module('myApp', []);
myApp.filter('isStatus', function() {
return function(input, status) {
var out = [];
for (var i = 0; i < input.length; i++){
if(input[i].status == status)
out.push(input[i]);
}
return out;
};
});
Html:
<li ng-repeat="user in users | isStatus:3">
Note this filter assumes there is a status property in the objects in the array which might make it less reusable but this is just an example. You can read this for more info on creating filters.
This question is almost identical to Passing arguments to angularjs filters, to which I already gave an answer. But I'm gonna post one more answer here just so that people see it.
Actually there is another (maybe better solution) where you can use the angular's native 'filter' filter and still pass arguments to your custom filter.
Consider the following code:
<li ng-repeat="user in users | filter:byStatusId(3)">
<span>{{user.name}}</span>
<li>
To make this work you just define your filter as the following:
$scope.byStatusId = function(statusId) {
return function(user) {
return user.status.id == statusId;
}
}
This approach is more versatile because you can do comparisons on values that are nested deep inside the object.
Checkout Reverse polarity of an angular.js filter to see how you can use this for other useful operations with filter.
If you have created an AngularJs custom filter, you can send multiple params to your filter.Here is usage in template
{{ variable | myFilter:arg1:arg2... }}
and if you use filter inside your controller here is how you can do that
angular.module('MyModule').controller('MyCtrl',function($scope, $filter){
$filter('MyFilter')(arg1, arg2, ...);
})
if you need more with examples and online demo, you can use this
AngularJs filters examples and demo
This may be slightly irrelevant, but if you're trying to apply multiple filters with custom functions, you should look into:
https://github.com/tak215/angular-filter-manager
Example I have a students list as below :
$scope.students = [
{ name: 'Hai', age: 25, gender: 'boy' },
{ name: 'Hai', age: 30, gender: 'girl' },
{ name: 'Ho', age: 25, gender: 'boy' },
{ name: 'Hoan', age: 40, gender: 'girl' },
{ name: 'Hieu', age: 25, gender: 'boy' }
];
I want to filter students via gender to be boy and filter by name of them.
The first I create a function named "filterbyboy" as following:
$scope.filterbyboy = function (genderstr) {
if ((typeof $scope.search === 'undefined')||($scope.search === ''))
return (genderstr = "")
else
return (genderstr = "boy");
};
Explaination: if not filter name then display all students else filter by input name and gender as 'boy'
Here is full HTMLcode and demo How to use and operator in AngularJs example

Angular js, Unique filter is not working

I'm trying to apply a check box filter to a list, but the options for the check boxes should also be come the list of items only,
it works fine if i am iterating it for all the check boxes,
the problem is coming when i am trying to apply the unique filter to display check boxes options.
i have included
angular 1.4 and ui-utils
<script src="js/angular.min.js"></script>
<script src="js/ui-utils.min.js"></script>
my view and controller are defined as:
<div ng-controller="Test">
<div ng-repeat="person in persons | unique:type">
<!-- record that this person has been selected -->
<input type="checkbox" ng-checked="person.checked" ng-model="person.checked" /> {{ person.type }}
</div>
<ul>
<li ng-repeat="person in persons | selectedTypes">{{person.name}}</li>
</ul>
</div>
and script is
<script>
var app = angular.module("MyApp", ['ui.utils']);
app.controller("Test", function($scope) {
$scope.persons = [
{ type: 1, name: 'Ankit Balyan' },
{ type: 1, name: 'Abhilaksh' },
{ type: 2, name: 'Sanket Srivastav' },
{ type: 2, name: 'Sachin Sharma' },
{ type: 2, name: 'Rohan Rathor' },
{ type: 2, name: 'Jim' },
];
$scope.$watch('persons', function() {
console.log($scope.persons);
})
});
// Define our filter
app.filter('selectedTypes', function($filter) {
return function(persons) {
var i, len;
// get persons that have been checked
var checkedPersons = $filter('filter')(persons, {checked: true});
// Add in a check to see if any persons were selected. If none, return
// them all without filters
if(checkedPersons.length == 0) {
return persons;
}
//console.log(checkedPersons);
// get all the unique cities that come from these checked persons
var types = {};
for(i = 0, len = checkedPersons.length; i < len; ++i) {
// if this checked persons cities isn't already in the cities object
// add it
if(!types.hasOwnProperty(checkedPersons[i].type)) {
types[checkedPersons[i].type] = true;
}
}
// Now that we have the cities that come from the checked persons, we can
//get all persons from those cities and return them
var ret = [];
for(i = 0, len = persons.length; i < len; ++i) {
// If this person's city exists in the cities object, add it to the
// return array
if(types[persons[i].type]) {
ret.push(persons[i]);
}
}
//console.log(persons.length);
// we have our result!
return ret;
};
});
</script>
You have to put the name of the property as a string :
<div ng-repeat="person in persons | unique: 'type'">
instead of
<div ng-repeat="person in persons | unique: type">
Edit: If you don't provide quotes, you are applying a unique filter by the value of the variable type, which is not defined in your case, therefore the filter has no effect.

Resources