Angular JS multiple filter query - angularjs

I'm a newcomer to Angular/JS so please be kind ...
I'm having a problem (probably with myself trying to wrap my brain around Angular) with some code where I want to use two different filters (I think) on a dataset with ng-repeat. Firstly here is a screen shot of the html form items and data output I am working with;
Here is the basic (stripped down) HTML code for the setup above;
<div ng-controller="customersCrtl">
Records Per Page:
<select ng-model="entryLimit" class="form-control">
<option>50</option>
<option>100</option>
<option>250</option>
<option>500</option>
<option>1000</option>
</select>
Record Type:
<select ng-model="entryType" class="form-control">
<option>All</option>
<option>Baptism</option>
<option>Marriage</option>
<option>Burial</option>
</select>
Filter Text:
<input type="text" ng-model="search" ng-change="filter()" placeholder="Filter" class="form-control" />
Filtered {{ filtered.length }} of {{ totalItems }} Total Records
<div ng-show="filteredItems > 0">
<table>
<thead>
<th>Event Date <a ng-click="sort_by('eventDate');">sort</a></th>
<th>Event Type <a ng-click="sort_by('type');">sort</a></th>
<th>Indivdual(s) <a ng-click="sort_by('name');">sort</a></th>
<th>Location <a ng-click="sort_by('location');">sort</a></th>
<th></th>
</thead>
<tbody>
<tr ng-repeat="data in filtered = (list | filter:search | orderBy : predicate :reverse) | startFrom:(currentPage-1)*entryLimit | limitTo:entryLimit">
<td>{{data.eventDate | date:'mediumDate'}}</td>
<td>{{data.type}}</td>
<td>{{data.name}}</td>
<td>{{data.location}}</td>
<td>View Record</td>
</tr>
</tbody>
</table>
</div>
... and here is the associated Angular JS code I currently have;
var app = angular.module('myApp', ['ui.bootstrap']);
app.filter('startFrom', function() {
return function(input, start) {
if(input) {
start = +start; //parse to int
return input.slice(start);
}
return [];
}
});
app.controller('customersCrtl', function ($scope, $http, $timeout) {
$http.get('ajax/getMarkers.php').success(function(data){
$scope.list = data;
$scope.currentPage = 1; //current page
$scope.entryLimit = 50; //max no of items to display in a page
$scope.entryType = 'All'; //Record type to display in a page
$scope.filteredItems = $scope.list.length; //Initially for no filter
$scope.totalItems = $scope.list.length;
});
$scope.setPage = function(pageNo) {
$scope.currentPage = pageNo;
};
$scope.filter = function() {
$timeout(function() {
$scope.filteredItems = $scope.filtered.length;
}, 10);
};
$scope.sort_by = function(predicate) {
$scope.predicate = predicate;
$scope.reverse = !$scope.reverse;
};
});
getMarkers.php in the above Angular code snippet returns JSON results formatted like so;
{"id":"646","eventDate":"1576-05-13","name":"John Phillip","type":"Baptism","churchName":"St. Marys","locationid":"563","location":"Swainswick, Somerset, UK","lat":"51.414211","lng":"-2.351418"},
{"id":"647","eventDate":"1577-07-01","name":"Jane Volantyne","type":"Baptism","churchName":"St. Mary the Virgin","locationid":"564","location":"Horsham, Sussex, UK","lat":"51.059750","lng":"-0.330879"},
{"id":"132","eventDate":"1634-05-09","name":"Katherine Stanley","type":"Burial","churchName":"St. James","locationid":"567","location":"Titsey, Surrey, UK","lat":"51.276505","lng":"0.018909"}
... etc. etc.
This currently works in so-far-as if a user types something in the "Filter Text" box, the table updates to only show entries that match the user text.
This is fine, but what I also want to be able to do is use the drop down "Record Type" box (with the defined entries All, Baptism, Burial, Marriage) to only show/filter the records with that particular "Event Type" in the table.
Is this even possible? Can you apply two concurrent filters to an ng-repeat in Angular, because I've tried and failed several times and am on the verge of giving up!!!
Am I asking something stupidly difficult or stupidly easy?

Yes, its very possible to filter the table based on multiple conditions, You could add a filter function to the table as:
<tr ng-repeat="data in filtered = (list | filter:filterEvents | orderBy : predicate :reverse) | startFrom:(currentPage-1)*entryLimit | limitTo:entryLimit">
<td>{{data.eventDate | date:'mediumDate'}}</td>
<td>{{data.type}}</td>
<td>{{data.name}}</td>
<td>{{data.location}}</td>
<td>View Record</td>
</tr>
Basically, $scope.filterEvents is called every time the table is populated and you could filter out the required items using this in the controller:
$scope.filterEvents = function(item){
//Here item is the array element from the table
which is automatically passed in the filter function.
return item.type.toLowerCase().indexOf($scope.entryType.toLowerCase()) != -1 ||
item.name.toLowerCase().indexOf($scope.search.toLowerCase()) != -1
}
Where , $scope.entryType is the option selected in the dropdown and $scope.search is the keyword for searching.
Ps: You would need to remove the ng-change=filter() from search textbox and handle the return if $scope.entryType = "All"

Related

Filter on column and also sort in descending order in AngularJS

How can I sort in descending order but also filter on column in AngularJS? I have the following code that will default to the most recent timestamp.
<tr ng-repeat="item in log | orderBy:'-timestamp' ">
<td>{{item.timestamp | date: 'MM/dd/yyyy HH:MM'}}</td>
<td>{{item.Action | removeHTMLTags | strip }}</td>
<td>{{item.AO}}</td>
</tr>
I also have the following js code that will work for filtering on the column header.
$scope.reverseOrder = true;
$scope.sortField = '';
$scope.sortBy = function(sortField) {
$scope.reverseOrder = ($scope.sortField == sortField) ? !$scope.reverseOrder : false;
$scope.sortField = sortField;
}
<tr ng-repeat="item in log | orderBy:sortField:reverseOrder">
<td>{{item.timestamp | date: 'MM/dd/yyyy HH:MM'}}</td>
<td>{{item.Action | removeHTMLTags | strip }}</td>
<td>{{item.AO}}</td>
</tr>
I would like to both filter on the column but also default to show the most recent timestamps on top.
I tried the following but it did not work.
ng-repeat="item in log | orderBy:sortField:reverseOrder | orderBy: '-timestamp'"
In order to understand why your code isn't working you must understand first, how the orderBy filter works in angularjs. In your ng-repeat the orderBy filter has two parameters sortField and reverseOrder. For them to work you must have a controller that has two $scope variables named respectively sortField and reverseOrder. The first parameter is to determine which key should orderBy use to order your array and the second one is a boolean that specifies would the order be reversed or not.
In my attempt to reproduce your example, I took the liberty of using unix timestamps in my logs array. So in the controller,
$scope.searchText = "";
$scope.sortField = "timestamp";
$scope.reverseOrder = false;
$scope.logs = [
{
timestamp : 2147450400000,
Action : 'bal sal',
AO : 'abcd'
},
{
timestamp : 979840800000,
Action : 'bal sal2',
AO : 'abcd2'
},
{
timestamp : 1579370400000,
Action : 'bal sal2',
AO : 'abcd2'
}
]
$scope.sortBy = function(sortField) {
$scope.reverseOrder = ($scope.sortField === sortField) ? !$scope.reverseOrder : false;
$scope.sortField = sortField;
};
Also in the example you provided, they passed the column name via a function. So in your html you need to have something like this.
<input type="text" ng-model="searchText">
<table>
<thead>
<th ng-click="sortBy('timestamp')">Time Stamp</th>
<th ng-click="sortBy('Action')">Action</th>
<th ng-click="sortBy('AO')">AO</th>
</thead>
<tr ng-repeat="item in logs | orderBy:sortField:reverseOrder| filter:searchText">
<td>{{item.timestamp | date :'MM/dd/yyyy HH:MM'}}</td>
<td>{{item.Action}}</td>
<td>{{item.AO}}</td>
</tr>
</table>
Hope this clears your confusions.

Prevent users from selecting same value in multiple dropdowns

I am loading a table from an API call , table rows are dynamic and it is based on the returned values from API call. I am displaying sort order and value should be unique and user shouldn't select a previously selected values. I tried to follow as per this (http://jsfiddle.net/jnash21/oqezom4y/) but i am not able to achieve as mine is dynamic.
I tried this (http://embed.plnkr.co/QU4r05n9rQprwyL9Ltxh/) .
editor.controller('EditorController', function($scope) {
$scope.entities = [{name:"pencil",sortOrder:""} ,{name:"notepad",sortOrder:""} ,
{name:"bookshelf",sortOrder:""}
];
$scope.sortOrderValues=[1,2,3];
});
<table>
<tr ng-repeat="x in entities">
<td>{{ x.name }}</td>
<td><select ng-model="x.sortOrder"
ng-options="col for col in sortOrderValues">
</select>
<span ng-show="!x.sortOrder"> * sort order required</span>
</td>
</tr>
</table>
How can I prevent a user from selecting same sort order in each row using angular js?
This plunker might help you.
First of all, genereate an array from 1 to entities.length (this case, 3).
When you select an option, tirgger the optionSelected function. This function will generate your inital array and calculate the used sortOrders by your entities. Then it filters the second ones from the first array.
HTML
<div ng-controller="EditorController">
<table>
<tr ng-repeat="x in entities">
<td>{{ x.name }}</td>
<td><select ng-model="x.sortOrder"
ng-options="col for col in sortOrderValues"
ng-change="optionSelected()">
</select>
<span ng-show="!x.sortOrder"> * sort order required</span>
</td>
</tr>
</table>
</div>
CONTROLLER
editor.controller('EditorController', function($scope) {
$scope.entities = [{name:"pencil",sortOrder:""} ,{name:"notepad",sortOrder:""} ,
{name:"bookshelf",sortOrder:""}
];
// Genereate all the numbers between 1 and $scope.entities.length
$scope.sortOrderValues= $scope.entities.map(
function (item, index) {
return index + 1;
}
);
// Function executed when you select a sortOrder
$scope.optionSelected = function () {
// Genereate all the numbers between 1 and $scope.entities.length
var allIndexes = $scope.entities
.map(function (entity, index) { return index + 1; });
// Get all the sortOrder used
var usedIndexes = $scope.entities
.map(function(e) { return e.sortOrder; });
// Remove from the [1, .., $scope.entities.length] array all the sortOrder used
$scope.sortOrderValues = allIndexes
.filter(function (order) {
return !usedIndexes.find(function(index) { return index === order; });
});
}
});

How to bind two ng-models values together

I have a JSON file displayed as a table, I want to search inside this table using two steps:
Selecting an option from a select menu to choose in which column you want to search.
an input, for typing the keyword you want to search for.
So how can I concatenate the value of the "selected option form select tag" with the value of the "input"?
For example, the User selected the option "Names" from the select menu then he entered "John" inside the input
Here is my code:
https://jsfiddle.net/p1nkfpez/7/
var app = angular.module("myApp", []);
app.controller("myCtrl", function($scope, $http) {
$scope.selection = "nm";
$http.get("https://api.myjson.com/bins/i9h1v")
.then(function(response) {
$scope.myRows = response.data;
$scope.rowsStatus = response.status;
$scope.rowsStatusText = response.statusText;
});
});
I want the Angular filter to be like:
filter:keyword.selection
You can create a function to create the filter object dynamicly:
$scope.getFilter = function() {
return {
[$scope.selection]: $scope.keyword
}
}
And use it like this:
<tr data-ng-repeat="row in myRows | filter:getFilter()">
...
</tr>
You can create a custom filter in which you can access your properties by name.
Custom filter
$scope.myCustomFilter = function(row){
if($scope.keyword == undefined || $scope.keyword.length == 0)
return true;
if(row[$scope.selection].toLowerCase().indexOf($scope.keyword) >= 0){
return true;
}
else
return false;
}
And add filter to your ng-repeat
<tbody>
<tr data-ng-repeat="row in myRows | filter: myCustomFilter">
<td>{{$index + 1}}</td>
<td>{{row.nm}}</td>
<td>{{row.cty}}</td>
<td>{{row.hse}}</td>
<td>{{row.yrs}}</td>
</tr>
</tbody>
Updated Fiddle

How to create sum of numbers from ng-repeat directive

I'm trying to sum a column of numbers but when I use code below, the "total" field is empty. Any thoughts on what I might be doing wrong?
HTML:
<tr ng-repeat="project in projectList.projects>
<td>{{project.description}}</td>
<td>{{project.type.cost | currency}}</td>
</tr>
<h2>Total: {{ total() | currency }}</h2>
Javascript:
myApp.controller('ProjectListCtrl', function ProjectListCtrl(Projects) {
var projectList = this;
projectList.total = function(){
var total = 0;
angular.forEach(projectList.projects, function(project) {
total += project.type.cost;
});
return total;
};
})
It appears that you're using the controllerAs syntax, such that ng-controller="ProjectListCtrl as projectList"
With that, you need to call projectList.total() from the view instead of just total()

AngularJS: How to set Filter by click

How do I set specific search conditions like: search.date > someDate() or search.amount > 0
I've tried: Pos only etc.etc.
I currently have an input with:
<input type="text" class="form-control" placeholder="Search all" ng-model="search" />
That works great and searches all fields. It breaks when I try a specific filter putting [object Object] in the search input.
<tr ng-repeat="tenant in tenants | filter:search | orderBy:sort" >
<td>{{tenant.rent | currency: "€ "}}</td>
<td>{{tenant.name}}</td>
<td>{{tenant.tel}}</td>
<td>{{tenant.mail}}</td>
<td>{{tenant.amount }}</td>
<td>{{tenant.date}}</td>
</tr>
Fiddle
How do I set these kind of filters (without breaking the global search)?
You could use an ng-hide statement for virtual filtering ng-hide="search.minAmount > tenant.account". Since you used search as an object, I initialised it as one in the controller:
$scope.search = {};
$scope.search.text = "";
$scope.search.minAmount = null;
And set the filter as along with an ng-hide:
<tr ng-repeat="tenant in tenants | filter:search.text | orderBy:sort:reverse" ng-class="{danger: payment.diff > 0}" ng-hide="search.minAmount != null && search.minAmount > tenant.account">
Demo
PS: You can change what you want positive, account or rent.
First of all, if you want to pass a object to filter, this object must contain the same attributes of your list objects, for example, to find out tenants whose name contains "a", you can pass a object {name:"a"} to filter
tenant in tenants | filter:{name:"a"} | orderBy:sort
So you may misunderstand the filter usage...
To achieve what you want, you can customize your own filter with the $filter service. Here is an example:
In controller:
angular.module('ngAppRentManager', []).
controller('RentCtrl', ['$scope', '$filter', function ($scope, $filter) {
$scope.search = {
keyword: "",
pos_only: false
}
$scope.getFilteredTenants = function() {
var filtered_tenants = $scope.tenants;
filtered_tenants = $filter("filter")(filtered_tenants, $scope.search.keyword);
if ($scope.search.pos_only) {
filtered_tenants = $filter("filter")(filtered_tenants, function(tenant) {
return tenant.amount > 0;
});
}
filtered_tenants = $filter("orderBy")(filtered_tenants, $scope.sort, $scope.reverse);
return filtered_tenants;
};
}]);
In html:
<li>Pos only</li>
<li>All</li>
<input type="text" class="form-control" placeholder="Search all" ng-model="search.keyword" />
<tr ng-repeat="tenant in getFilteredTenants()" ng-class="{danger: payment.diff > 0}">
<td>{{tenant.rent | currency: "€ "}}</td>
<td>{{tenant.name}}</td>
<td>{{tenant.tel}}</td>
<td>{{tenant.mail}}</td>
<td>{{tenant.amount}}>
<td>{{tenant.address}}</td>
</tr>
Fiddle

Resources