The objective of this plunk is to have a table where the up and down keys will be used to select rows programmatically and scroll through the table. The selected row will have a different background color.
When keying up/down I use e.preventDefault() to avoid the rows to move up/down twice. Problem is that when I start scrolling down the rows stay fixed and the selected row disappears. How to fix this?
HTML
<div id="selector" tabindex="0" ng-keydown="scroll($event)"
style="width:300px;height:80px;border:1px solid gray;overflow-y:auto">
<table>
<tr ng-repeat="item in items">
<td class="td1" ng-class="{'tdactive' : $index==index }">{{item.col}}</td>
<td class="td1" ng-class="{'tdactive' : $index==index }">{{item.dsc}}</td>
</tr>
</table>
</div>
Javascript
var app = angular.module('app', []);
app.controller('ctl', function($scope) {
document.getElementById("selector").focus();
$scope.items = [ {col:"aaa", dsc:"AAA1"}, {col:"bbb", dsc:"BBB2"} , {col:"ccc", dsc:"CCC3"},
{col:"aaa2", dsc:"AAA21"}, {col:"bbb2", dsc:"BBB22"} , {col:"ccc2", dsc:"CCC23"},
{col:"aaa2", dsc:"AAA21"}, {col:"bbb2", dsc:"BBB22"} , {col:"ccc2", dsc:"CCC23"} ];
$scope.index = 0;
$scope.scroll = function(e) {
if (e.which === 40) { // down arrow
if ($scope.index<$scope.items.length - 1)
$scope.index++;
e.preventDefault();
}
else if (e.which === 38) { // up arrow
if ($scope.index>0)
$scope.index--;
e.preventDefault();
}
};
});
First of all you need to add table row id as id="tr-{{$index}}"
You can then prevent your scroll if tr is in the current viewport
$scope.scroll = function(e) {
var parentContainer = document.getElementById("selector");
if (e.which === 40) { // down arrow
if ($scope.index<$scope.items.length - 1)
{
var element = document.getElementById("tr-"+$scope.index);
if(isElementInViewport(parentContainer,element)){
e.preventDefault();
}
$scope.index++;
}
}
else if (e.which === 38) { // up arrow
if ($scope.index>0)
{
var element = document.getElementById("tr-"+$scope.index);
if(!isElementInViewport(parentContainer,element)){
e.preventDefault();
}
$scope.index--;
}
}
};
function isElementInViewport(parent, el) {
if(parent==undefined || el==undefined)
return false;
var elRect = el.getBoundingClientRect(),
parRect = parent.getBoundingClientRect();
//console.log(elRect)
//console.log(parRect)
var elementHeight = elRect.height;
return (
elRect.top >= parRect.top &&
elRect.bottom <= parRect.bottom &&
elRect.bottom+elementHeight<= parRect.bottom
);
}
Working Plunker
Related
I'm looking to implement a table in ReactJS with the following features:
initially empty
rows are dynamically added and removed
when there are no rows, an empty state (e.g. a box saying "Table empty") should be displayed
when a row is removed, there should be a fade out transition
when the first row is added, there should be no fade out transition on the empty state
I came up with two approaches using ReactCSSTransitionGroup.
1. Wrap only rows into ReactCSSTransitionGroup
Codepen: https://codepen.io/skyshell/pen/OpVwYK
Here, the table body is rendered in:
renderTBodyContent: function() {
var items = this.state.items;
if (items.length === 0) {
return (
<tbody><tr><td colSpan="2">TABLE EMPTY</td></tr></tbody>
);
}
const rows = this.state.items.map(function(name) {
return (
<tr key={name}>
<td>{name[0]}</td>
<td>{name[1]}</td>
</tr>
);
});
return (
<ReactCSSTransitionGroup
component="tbody"
transitionName="example"
transitionEnter={false}
transitionLeave={true}>
{rows}
</ReactCSSTransitionGroup>
);}
The issue is that the last row to be removed does not get the fade out transition before disappearing since the ReactCSSTransitionGroup is not rendered when item.length === 0.
2. Wrap table body into ReactCSSTransitionGroup
Codepen: https://codepen.io/skyshell/pen/RpbKVb
Here, the entire renderTBodyContent method is wrapped into ReactCSSTransitionGroup within the render method:
<ReactCSSTransitionGroup
component="tbody"
transitionName="example"
transitionEnter={false}
transitionLeave={true}>
{this.renderTBodyContent()}
</ReactCSSTransitionGroup>
And the RenderTBody method looks like:
renderTBodyContent: function() {
var items = this.state.items;
if (items.length === 0) {
return (
<tr><td colSpan="2">TABLE EMPTY</td></tr>
);
}
const rows = this.state.items.map(function(name) {
return (
<tr key={name}>
<td>{name[0]}</td>
<td>{name[1]}</td>
</tr>
);
});
return rows;}
The issue is that the empty state gets animated too.
Any suggestions on how to obtain the desired behaviour?
Thanks!
Thank you realseanp for your pointers. Using the low level API and TweenMax instead of CSS transitions, I came up with the following solution. First, introduce a Row component:
var Row = React.createClass({
componentWillLeave: function(callback) {
var el = React.findDOMNode(this);
TweenMax.fromTo(el, 1, {opacity: 1}, {opacity: 0, onComplete: callback})
},
componentDidLeave: function() {
this.props.checkTableContent();
},
render: function() {
const name = this.props.name;
return (
<tr>
<td>{name[0]}</td>
<td>{name[1]}</td>
</tr>
);
}
});
Then populate the table based on an isEmpty flag:
var Table = React.createClass({
getInitialState: function() {
return {
items: [],
isEmpty: true
};
},
addRow: function() {
var items = this.state.items;
var firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
var lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
items.push([firstName, lastName]);
this.setState({items: items, isEmpty: false});
},
removeLastRow: function() {
var items = this.state.items;
if (items.length != 0) {
items.splice(-1, 1);
this.setState({items: items});
}
},
checkTableContent: function() {
if (this.state.items.length > 0) {
this.setState({isEmpty: false});
}
else {
this.setState({isEmpty: true});
this.forceUpdate();
}
},
renderTBodyContent: function() {
if (this.state.isEmpty) {
return (
<tr><td colSpan="2">TABLE EMPTY</td></tr>
);
}
var that = this;
const rows = this.state.items.map(function(name) {
return <Row
key={name}
name={name}
checkTableContent={that.checkTableContent} />;
});
return rows;
},
render: function() {
return (
<div>
<button onClick={this.addRow}>Add row</button>
<button onClick={this.removeLastRow}>Remove row</button>
<table>
<thead>
<tr>
<th>First name</th>
<th>Last name</th>
</tr>
</thead>
<ReactTransitionGroup
component="tbody"
transitionName="example"
transitionEnter={false}
transitionLeave={true}>
{this.renderTBodyContent()}
</ReactTransitionGroup>
</table>
</div>
);
}
});
Codepen: https://codepen.io/skyshell/pen/yMYMmv
I have a dropdownlist which contains brand ids. acccording to the id im fetching corresponding products and showing it in a table. There are two buttons in each row that move the products up and down basically by interchanging the ranks. now i am able to do all the functionality of interchanging and re binding.The row is selected when it is clicked. my only problem is i am not able to select the row after it has moved up or down.
<div ng-app="myapp" ng-controller="prodctrl">
<select id="BrandDropdown" class="InstanceList" ng-change="GetBrandProd()" ng-model="Products">
<option>Select Brand</option> //Sample Data
<option value=1>Brand 1<option>
<option value=2>Brand 2<option>
</select>
<table id="prodtab" ng-model="Products">
<tr ng-repeat="P in Products track by $index" ng-click="setselected($index)" class="{{selected}}">
<td>{{P.Id}}</td>
<td>{{P.Rank}}</td>
<td>{{P.Name}}</td>
<td>
<input type="button" value="Move Up" id="moveup" ng-click="getval(P,$index)" /></td>
<td>
<input type="button" value="Move Down" /></td>
</tr>
</table>
</div>
this is the angularjs code
<script>
var app = angular.module('myapp', []);
var prod = null;
var mveup = null;
var mvedwn = null;
var ind = null;
app.controller('prodctrl', function ($scope, $http) {
//getting products for each brand
$scope.GetBrandProd = function () {
cursel = "B";
var Id = $('#BrandDropdown').val();
fetchtype = Id;
brid = Id;
$http({
method: "GET",
url: "/Home/GetProdBrand",
params: {
id: Id
}
})
.success(function (response) {
var data = response;
$scope.Products = data;
prod = data;
});
};
//changing color of row when clicked
$scope.setselected = function (index) {
if ($scope.lastSelected) {
$scope.lastSelected.selected = '';
}
if (mveup == null) {
this.selected = 'trselected';
$scope.lastSelected = this;
}
else {
mveup = null;
//this.selected = '';
$(this).closest('tr').prev().prop('Class', 'trselected');
}
};
//function to move product up in ranking
$scope.getval = function (p, index) {
var Idcur = p.Id;
var Rankcur = p.Rank;
ind = index;
if ($scope.Products[index - 1] != null) {
var IdPrev=$scope.Products[index - 1].Id;
var Rankprev = $scope.Products[index - 1].Rank;
mveup = null;
$scope.lastSelected = this;
if (cursel == "B") {
fetchtype = brid;
}
else if (cursel == "C") {
}
mveup = true;
$http({
method: "GET",
url: "/Home/MoveProd",
params: {
Curid: Idcur,
CurRank: Rankcur,
ChngId: IdPrev,
ChngRnk: Rankprev,
Type: cursel,
Id: fetchtype
}
})
.success(function (response) {
// ranks are interchanged and the data is returned.
var data = response;
$scope.Products = data;
prod = data;
});
}
}
})
</script>
It seems, the way you are handling the row selection is not correct.
I have just changed the way of handling selection here.
<tr ng-repeat="P in Products track by $index" ng-click="setselected($index)" ng-class="{selected: selectedIndex == $index}">
//JS
$scope.setselected = function(index) {
$scope.selectedIndex = index;
};
Also, I have done a plunker with some sample values to imitate your requirement, you can ask more, if it is not fit to your requirement.
Plunker
You already have the id of the product that was clicked on (I think from looking at your code, it's Idcur), so you could loop over your results in the success block of the /Home/MoveProd GET request and set the record with the matching id to selected? Something like
var products = $scope.Products.filter(function(product) {
return product.id == Idcur;
})
if (products && products.length > 0) {
products[0].selected = 'trselected';
}
then, in your page, just update the ng-repeat slightly to pick the selected class from the product, instead of the scope, so:
<tr ng-repeat="P in Products track by $index" ng-click="setselected($index)" class="{{selected}}">
becomes
<tr ng-repeat="P in Products track by $index" ng-click="setselected($index)" class="{{P.selected}}">
or something like that :)
I was under the impression with the pagination directive that the {{numPages}} value would be calculated by the directive. It isn't returning anything for me.
Is there anything I am missing to get this working properly? I don't want to have to calculate it, if the directive is supposed to be doing this for me. Otherwise paging is working great.
<pagination
total-items="totalItems"
ng-model="currentPage"
max-size="maxSize"
items-per-page="itemsPerPage"
class="pagination-sm"
boundary-links="true" rotate="false">
</pagination>
<table class="table table-striped">
<tr>
<td style="width:150px;">GPT ID</td>
<td style="width:250px;">Therapy Area</td>
<td style="width:450px;">GPT Description</td>
<td style="width:150px;">Actions</td>
</tr>
<tr ng-repeat="prGpt in prGpts | orderBy:['therapyArea.therapyArea','gptDesc'] | startFrom:(currentPage -1) * itemsPerPage | limitTo: itemsPerPage">
<td>{{prGpt.id}}</td>
<td>
<span ng-if="!prGpt.editMode">{{prGpt.therapyArea.therapyArea}}</span>
<span ng-if="prGpt.editMode && !createMode">
<select class="form-control" style="width:150px;" ng-model="selectedGpt.therapyArea" ng-options="item as item.therapyArea for item in therapyAreas"/>
</span>
</td>
<td>
<span ng-if="!prGpt.editMode">{{prGpt.gptDesc}}</span>
<span ng-if="prGpt.editMode && !createMode"><input class="form-control" type="text" style="width:400px;" ng-model="selectedGpt.gptDesc" /></span>
</td>
<td>
<span ng-if="!prGpt.editMode" class="glyphicon glyphicon-pencil" ng-click="copySelectedGpt(prGpt);beginEditGpt()"/>
<span ng-if="prGpt.editMode && !createMode" class="glyphicon glyphicon-floppy-disk" ng-click="saveEditGpt()"/>
<span ng-if="prGpt.editMode && !createMode" class="glyphicon glyphicon-thumbs-down" ng-click="cancelEditGpt()"/>
<span ng-if="!prGpt.editMode && !createMode" class="glyphicon glyphicon-remove-circle" ng-click="copySelectedGpt(prGpt);openDeleteDialog()"/>
<span><a ng-href="#!pr/gptProjects/{{prGpt.id}}">Edit Projects</a>
</span>
</tr>
</table>
<br/>
<pre>Page: {{currentPage}} / {{numPages}}</pre>
</div>
controller:
// GPT List Controller
.controller('prGPTCtrl',['$scope', '$modal', '$dialog', 'Restangular', 'prTAService', 'prGPTService', function($scope, $modal, $dialog, Restangular, prTAService, prGPTService) {
// window.alert('prGPTCtrl');
$scope.prGpts = {};
$scope.therapyAreas = {};
$scope.createMode = false;
$scope.selectedGpt = {};
$scope.newGpt = {};
// pagination
$scope.currentPage = 1;
$scope.itemsPerPage = 10;
$scope.maxSize = 20;
$scope.totalItems = $scope.prGpts.length;
Restangular.setBaseUrl('resources/pr');
//call the TA service to get the TA list for the drop down lists
//and then get the gpt list once successful
prTAService.getTAs().then(function(tas) {
$scope.therapyAreas = tas;
prGPTService.getGPTs().then(function(gpts) {
//window.alert('prGPTCtrl:getGPTs');
$scope.prGpts = gpts;
});
});
$scope.$watch('prGpts.length', function(){
$scope.totalItems = $scope.prGpts.length;
});
/*
* Take a copy of the selected GPT to copy in
*/
$scope.copySelectedGpt = function(prGpt) {
$scope.selectedGpt = Restangular.copy(prGpt);
};
$scope.beginEditGpt = function() {
var gpt = {};
var ta = {};
var gpt;
for(var i = 0; i < $scope.prGpts.length;i++) {
gpt = $scope.prGpts[i];
gpt.editMode = false;
}
var index = _.findIndex($scope.prGpts, function(b) {
return b.id === $scope.selectedGpt.id;
});
gpt = $scope.prGpts[index];
gpt.editMode = true;
var taIndex = _.findIndex($scope.therapyAreas, function(b) {
return b.id === $scope.selectedGpt.therapyArea.id;
});
ta = $scope.therapyAreas[taIndex];
$scope.selectedGpt.therapyArea = ta;
$scope.createMode = false;
};
$scope.cancelEditGpt = function() {
var gpt;
for(var i = 0; i < $scope.prGpts.length;i++) {
gpt = $scope.prGpts[i];
gpt.editMode = false;
}
var index = _.findIndex($scope.prGpts, function(b) {
return b.id === $scope.selectedGpt.id;
});
$scope.selectedGpt = null;
$scope.prGpts[index].editMode = false;
};
$scope.saveEditGpt = function() {
$scope.selectedGpt.save().then(function (gpt) {
// find the index in the array which corresponds to the current copy being edited
var index = _.findIndex($scope.prGpts, function(b) {
return b.id === $scope.selectedGpt.id;
});
$scope.prGpts[index] = $scope.selectedGpt;
$scope.prGpts[index].editMode = false;
$scope.selectedGpt = null;
},
function(err) {
window.alert('Error occured: ' + err);
}
);
};
// create a new GPT
$scope.createGpt = function() {
$scope.createMode = true;
var gpt;
for(var i = 0; i < $scope.prGpts.length;i++) {
gpt = $scope.prGpts[i];
gpt.editMode = false;
}
};
$scope.saveNewGpt = function() {
Restangular.all('/gpt/gpts').post($scope.newGpt).then(function(gpt) {
$scope.newGpt = {};
$scope.prGpts.push(gpt);
$scope.createMode = false;
// window.alert('created new GPT ' + gpt.gptDesc + ' with id ' + gpt.id);
});
};
$scope.openDeleteDialog = function() {
var title = "Please confirm deletion of GPT " + $scope.selectedGpt.gptDesc;
var msg = "<b>Delete GPT? Please confirm...</b>";
var btns = [{result:'CANCEL', label: 'Cancel'},
{result:'OK', label: 'OK', cssClass: 'btn-primary'}];
$dialog.messageBox(title, msg, btns, function(result) {
if (result === 'OK') {
$scope.deleteGpt();
}
});
};
$scope.deleteGpt = function() {
$scope.selectedGpt.remove().then(function() {
$scope.prGpts = _.without($scope.prGpts, _.findWhere($scope.prGpts, {id: $scope.selectedGpt.id}));
$scope.selectedGpt = null;
},
function() {
window.alert("There was an issue trying to delete GPT " + $scope.selectedGpt.gptDesc);
}
);
};
}]);
I have a startFrom filter.
.filter('startFrom', function () {
return function (input, start) {
if (input === undefined || input === null || input.length === 0
|| start === undefined || start === null || start.length === 0 || start === NaN) return [];
start = +start; //parse to int
try {
var result = input.slice(start);
return result;
} catch (e) {
// alert(input);
}
};
})
Regards
i
Looks like you're just missing num-pages="numPages" on your <pagination> tag. Essentially you have to provide a variable to pagination in which to return the number of pages. This is done via num-pages
<pagination
num-pages="numPages" <!-- Add this here -->
total-items="totalItems"
ng-model="currentPage"
max-size="maxSize"
items-per-page="itemsPerPage"
class="pagination-sm"
boundary-links="true" rotate="false">
</pagination>
I got this error in my project. Actually, i have implemented pagination using filters in angular Js, while loading the table with pagination and also showing error in the $scope.$apply() which is in the MainController.
there are two errors in the console,
one is "Error: 10 $digest() iterations reached. Aborting!"
and the other: "Uncaught Error: 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [["fn: function (context) {\n for(var i = 0, ii = length, part; i...]]"
and they seem to go together. And paginate() in filter is repetitively calling many times.
How to fix it? Any help would be appreciated.
Following is the filter.js used for pagination
var filters = angular.module('OptimaQuote.filters', []);
filters.filter('filterDependant', function () {
return function (input, dependant) {
var out = [];
if (!jQuery.isEmptyObject(input) && input != "undefined" && input && input.length) {
for (var i = 0; i < input.length; i++) {
if (input[i].Dependant == dependant){
out.push(input[i]);
}
}
}
return out;
};
});
filters.filter('paginate', function() {
return function(input, currentPage,rowsPerPage) {
if (!input) {
return input;
}
return input;
}
});
filters.filter('getPaginator', function() {
return function(input, totolRowCount,rowsPerPage,paginatorSize,currentPage) {
var pageList = [];
var pages = [];
var noOfPages = parseInt(totolRowCount) / rowsPerPage;
function makePage(number, text) {
return {
number: number,
text: text,
};
}
var startPage = 1;
var endPage = noOfPages;
if(noOfPages!= null && noOfPages!= "undefined"){
if(paginatorSize < noOfPages){
startPage = ((Math.ceil(currentPage / paginatorSize) - 1) * paginatorSize) + 1;
endPage = Math.min(startPage + paginatorSize - 1, noOfPages);
}
}
// Add page number links
for (var number = startPage; number <= endPage; number++) {
var page = makePage(number, number);
pages.push(page);
}
// Add links to move between page sets
if ( paginatorSize < noOfPages ) {
if ( startPage > 1 ) {
var previousPageSet = makePage(startPage - 1, '...');
pages.unshift(previousPageSet);
}
if ( endPage < noOfPages ) {
var nextPageSet = makePage(endPage + 1, '...');
pages.push(nextPageSet);
}
}
return pages;
}
});
filters.filter('getEndPage', function() {
return function(input, totolRowCount, rowsPerPage) {
totalPages = [];
function makePage(number, text) {
return {
number: number,
text: text,
};
}
var noOfPages = parseInt(totolRowCount) / rowsPerPage;
var temp = makePage(noOfPages,noOfPages);
totalPages.push(temp);
return totalPages;
}
});
Following is the template used for implementing pagination by calling filters
<tfoot>
<tr>
<td colspan="{{this.paginatorColSpan}}">
<div>
<ul class="paginate">
<li><a ng-class="" ng-click="{{this.firstPageClick}}">First</a></li>
<li><a ng-class="{firstPage:{{this.currentPageNumber}}+1==1}" ng-click="{{this.prevClick}}">Previous</a></li>
<li ng-repeat="value in pageList|getPaginator:{{this.totalRowCount}}:{{this.pageSize}}:{{this.paginatorSize}}:{{this.currentPageNumber}}+1">
<a ng-class="{active: value.number=={{this.currentPageNumber}}+1}" ng-click="{{this.pageClick}}">[[value.text]]</a>
</li>
<li ng-repeat="value in pageList|getEndPage:{{this.totalRowCount}}:{{this.pageSize}}">
<a ng-class="{firstPage:{{this.currentPageNumber}}+1==value.number}" ng-click="{{this.nextClick}}">Next</a></li>
<li ng-repeat="value in pageList|getEndPage:{{this.totalRowCount}}:{{this.pageSize}}">
<a ng-class="" ng-click="{{this.pageClick}}">Last</a></li>
</ul>
</div>
</td>
</tr>
</tfoot>
Try
{firstPage:((currentPageNumber+1)==1)}
instead of
{firstPage:{{this.currentPageNumber}}+1==1}
Also anywhere else you have done {{}}, I see totalRowCount and currentPageNumber and pageSize and ngClick.
Can you create a jsFiddle of your issue? It seems you are using angular incorrectly.
Before I fixed this issue, it was like this:
{
field : 'appId',
cellTemplate : 'EditView',
displayName : 'operation'
}
After I fixed this issue it was like this:
{
field : 'appId',
cellTemplate : 'EditView',
displayName : 'operation'
}
If you want to use <a></a> in this cellTemplate, you can use . It worked for me!
I'm wondering if there's an easy way in Angular to filter a table using ng-repeat on specific columns using or logic, rather than and. Right now, my filter is searching everything in the table (10+ columns of data), when it really only needs to filter on 2 columns of data (ID and Name).
I've managed to get it down to look only at those 2 columns when filtering (by using an object in the filter expression as per the docs and looking at this SO answer), but it's using and logic, which is too specific. I'd like to get it to use or logic, but am having trouble.
My HTML
<input type="text" ng-model="filterText" />
<table>
<tr ng-repeat="item in data"><td>{{ item.id }}</td><td>{{ item.name }}</td>...</tr>
</table>
My filter logic:
$filter('filter')(data, {id:$scope.filterText, name:$scope.filterText})
The filtering works, but again, it's taking the intersection of the matching columns rather than the union. Thanks!
It's not hard to create a custom filter which allows you to have as many arguments as you want. Below is an example of a filter with one and two arguments, but you can add as many as you need.
Example JS:
var app = angular.module('myApp',[]);
app.filter('myTableFilter', function(){
// Just add arguments to your HTML separated by :
// And add them as parameters here, for example:
// return function(dataArray, searchTerm, argumentTwo, argumentThree) {
return function(dataArray, searchTerm) {
// If no array is given, exit.
if (!dataArray) {
return;
}
// If no search term exists, return the array unfiltered.
else if (!searchTerm) {
return dataArray;
}
// Otherwise, continue.
else {
// Convert filter text to lower case.
var term = searchTerm.toLowerCase();
// Return the array and filter it by looking for any occurrences of the search term in each items id or name.
return dataArray.filter(function(item){
var termInId = item.id.toLowerCase().indexOf(term) > -1;
var termInName = item.name.toLowerCase().indexOf(term) > -1;
return termInId || termInName;
});
}
}
});
Then in your HTML:
<tr ng-repeat="item in data | myTableFilter:filterText">
Or if you want to use multiple arguments:
<tr ng-repeat="item in data | myTableFilter:filterText:argumentTwo:argumentThree">
Use this to search on All Columns (can be slow): search.$
AngularJS API: filter
Any Column Search:
<input ng-model="search.$">
<table>
<tr ng-repeat="friendObj in friends | filter:search:strict">
...
To expand on the excellent answer by #charlietfl, here's a custom filter that filters by one column(property) which is passed to the function dynamically instead of being hard-coded. This would allow you to use the filter in different tables.
var app=angular.module('myApp',[]);
app.filter('filterByProperty', function () {
/* array is first argument, each addiitonal argument is prefixed by a ":" in filter markup*/
return function (dataArray, searchTerm, propertyName) {
if (!dataArray) return;
/* when term is cleared, return full array*/
if (!searchTerm) {
return dataArray
} else {
/* otherwise filter the array */
var term = searchTerm.toLowerCase();
return dataArray.filter(function (item) {
return item[propertyName].toLowerCase().indexOf(term) > -1;
});
}
}
});
Now on the mark-up side
<input type="text" ng-model="filterText" />
<table>
<tr ng-repeat="item in data |filterByProperty:filterText:'name'"><td>{{ item.id }}</td><td>{{ item.name }}</td>...</tr>
</table>
I figured it out- I had to write my own custom filter. Here is my solution:
var filteredData;
filteredData = $filter('filter')(data, function(data) {
if ($scope.filter) {
return data.id.toString().indexOf($scope.filter) > -1 || data.name.toString().indexOf($scope.filter) > -1;
} else {
return true;
}
});
I created this filter to perform search in several fields:
var find = function () {
return function (items,array) {
var model = array.model;
var fields = array.fields;
var clearOnEmpty = array.clearOnEmpty || false;
var filtered = [];
var inFields = function(row,query) {
var finded = false;
for ( var i in fields ) {
var field = row[fields[i]];
if ( field != undefined ) {
finded = angular.lowercase(row[fields[i]]).indexOf(query || '') !== -1;
}
if ( finded ) break;
}
return finded;
};
if ( clearOnEmpty && model == "" ) return filtered;
for (var i in items) {
var row = items[i];
var query = angular.lowercase(model);
if (query.indexOf(" ") > 0) {
var query_array = query.split(" ");
var x;
for (x in query_array) {
query = query_array[x];
var search_result = true;
if ( !inFields(row,query) ) {
search_result = false;
break;
}
}
} else {
search_result = inFields(row,query);
}
if ( search_result ) {
filtered.push(row);
}
}
return filtered;
};
};
How to use:
<tr repeat="item in colletion
| find: {
model : model, // Input model
fields : [ // Array of fields to filter
'FIELD1',
'FIELD2',
'FIELD3'
],
clearOnEmpty: true // Clear rows on empty model (not obligatory)
} "></tr>
Easily We can do this type Following written code according you will easily create another field filter....
var myApp = angular.module('myApp',[]);
myApp.filter('myfilter',myfilter);
function myfilter(){
return function (items, filters) {
if (filters == null) {
return items;
}
var filtered = [];
//Apply filter
angular.forEach(items, function (item) {
if ((filters.Name == '' || angular.lowercase(item.Name).indexOf(angular.lowercase(filters.Name)) >= 0)
)
{
filtered.push(item);
}
});
return filtered;
};
}
myApp.controller('mycontroller',['$scope',function($scope){
$scope.filters={Name:'',MathsMarks:''};
$scope.students=[];
var i=0;
for(i=0;i<5;i++){
var item={Name:'',Marks:[]};
item.Name='student' + i;
item.Marks.push({Maths:50-i,Science:50 +i});
$scope.students.push(item);
}
}]);
<html ng-app='myApp'>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.min.js"></script>
</head>
<body ng-controller='mycontroller'>
<input type='text' name='studentName' ng-model="filters.Name" placeholder='Enter Student Name'>
<div ng-repeat="student in students | myfilter: filters">
Name : {{student.Name}} Marks == >
<span ng-repeat="m in student.Marks">Maths:{{m.Maths}} Science:{{m.Science}}</span>
</div>
</body>
</html>
Here is my solution, it's very lazy, it will search on all strings in array on first level, you could update this to recusively go down the tree, but this should be good enough...
app.filter('filterAll', function () {
return function (dataArray, searchTerm, propertyNames) {
if (!dataArray) return;
if (!searchTerm) {
return dataArray;
} else {
if (propertyNames == undefined) {
propertyNames = [];
for (var property in dataArray[0]) {
if(typeof dataArray[0][property] == "string" &&
property != "$$hashKey" &&
property != "UnitName" )
propertyNames.push(property);
}
}
console.log("propertyNames", propertyNames);
var term = searchTerm.toLowerCase();
return dataArray.filter(function (item) {
var found = false;
propertyNames.forEach(function(val) {
if (!found) {
if (item[val] != null && item[val].toLowerCase().indexOf(term) > -1)
found = true;
}
});
return found;
});
}
}
});
see this link Filter multiple object properties together in AngularJS