count value in a array into ng-repeat - angularjs

I have this data : http://www.monde-du-rat.fr/API/moulinette/radio/posts.json , from cakephp app, it's song by song, with attached votes
I use it as a service into a angularjs app, and i displayed it like this in html :
<div ng-repeat="song in songs | orderBy:'Radio.idSong' | notesong:'Radiovote'" class="list-group-item" id="{{song.Radio.idSong}}" ng-class="{ 'active' : songPlayed_name == song.Radio.name }" ng-if="songs">
<span>{{song.Radio.idSong}} - {{song.Radio.title}}</span><br />
<span>{{note}}%</span>
</div>
So, i want to count each attached vote, and define with values 'good' or 'bad', the % of likes
I try to made this filter :
/* notesong */
app.filter('notesong', function() {
return function(input) {
// counter init
var countGood = 0;
// if there is no votes, so note is zero
if (!angular.isArray(input)) {
var note = 0;
} else {
// loop for each vote (from Radiovote array, each value)
angular.forEach(input, function () {
if (input.value == 'good') {
countGood = countGood + 1;
}
});
var note = (countGood * input.length) / 100;
}
// final return
return note;
};
});
It's not working apparently (no errors, and no data displayed), so, what is the correct way ?

You are applying the filter in the wrong place. Instead of using it on the ng-repeat you should use it on the property you want to bind, like this:
<div ng-repeat="song in songs | orderBy:'Radio.idSong'" class="list-group-item" id="{{song.Radio.idSong}}" ng-class="{ 'active' : songPlayed_name == song.Radio.name }" ng-if="songs">
<span>{{song.Radio.idSong}} - {{song.Radio.title}}</span><br />
<span>{{song.Radiovote | notesong}}%</span>
</div>
There's also a problem with the way you are looping the votes in your filter. Update the following lines:
// loop for each vote (from Radiovote array, each value)
angular.forEach(input, function (item) {
if (item.value == 'good') {
countGood = countGood + 1;
}
});

Related

Angular filter match by character?

I have angular 1.3, and i have the following array:
data : [
{
id :2,
name : "danny davids",
age :9
},
{
id :3,
name : "sanny gordon",
age :9
}
]
I want the filter to do the follwing:
When i start writing the word "s", i want the danny davids to disappear, right now the default behavior is, both of them are still shown (the s is in the end of the last name of danny).
strict mode is something that i dont want to use, the behavior i want is:
if there is no value in the input, i want to see all, if i start to write i want to see the exact one by firstName/lastName.
is there a default filter for this in angular 1.3?
You can filter match by any characters:
Sample condition:
yourDataList.display.toLowerCase().indexOf(searchData) !== -1;
Example:
function createFilterForAnycharacters(searchData) {
var lowercaseQuery = query.toLowerCase();
return function filterFn(yourDataList) {
return (yourDataList.display.toLowerCase().indexOf(searchData) !== -1);
};
}
I suggest using $filter by a custom filter function for you ng-repeat. According to the documentation, $filter expects
function(value, index, array): A predicate function can be used to write arbitrary filters. The function is called for each element of the array, with the element, its index, and the entire array itself as arguments.
And only elements that return true with be shown. So all you have to do is write that function.
Your filter function might look like this:
$scope.filterData = function (obj) {
return anyNameStartsWith(obj.name, $scope.searchFilter);
};
function anyNameStartsWith (fullname, search) {
//validate if name is null or not a string if needed
if (search === '')
return true;
var delimeterRegex = /[ _-]+/;
//split the fullname into individual names
var names = fullname.split(delimeterRegex);
//do any of the names in the array start with the search string
return names.some(function(name) {
return name.toLowerCase().indexOf(search.toLowerCase()) === 0;
});
}
Your HTML might look something like this:
<input type="text" ng-model="searchFilter" />
<div ng-repeat="obj in data | filter : filterData">
Id: {{obj.id}}
Name: {{obj.name}}
</div>
A demo via plnkr
Use this custom filter to get result match starting characters
app.filter('startsWithLetter', function () {
return function (items, letter) {
var filtered = [];
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (item.substr(0,letter.length).toLowerCase() == letter.toLowerCase()) {
filtered.push(item);
}
}
return filtered;
};
});
it works for your scenario, you can create custom filter
below is html code
<div ng-app="app">
<div ng-controller="PersonCtrl as person">
<input type="text" ng-model="letter" placeholder="Enter a letter to filter">
<ul>
<li ng-repeat="a in person.data | startsWithLetter:letter">
{{a.name}}
</li>
</ul>
</div>
</div>
js code
var app = angular.module('app', []);
app.filter('startsWithLetter', function () {
return function (items, letter) {
var filtered = [];
var letterMatch = new RegExp(letter, 'i');
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (letterMatch.test(item.name.substring(0, 1))) {
filtered.push(item);
}
}
return filtered;
};
});
app.controller('PersonCtrl', function () {
this.data = [
{
id :2,
name : "danny davids",
age :9
},
{
id :3,
name : "sanny gordon",
age :9
}
]
});
Need to create a custom filter function to do this. There is no default method to match first character in angular.
https://docs.angularjs.org/guide/filter

How to show element once in ng-repeat

I need to loop through a list order by price and as soon as the price is not there then I show a message with unavailable but I don't want to show it for each empty element. I'm using angular 1.2
<div ng-repeat="item in list | orderBy: 'cost'">
<div ng-if="cost == 0 and not already shown">Sorry the following are unavailable</div>
<div>...my item here...</div>
<div>
You can conditionally display two spans - one if it's 0 (your 'not available' message) and another for anything else.
<ul>
<li ng-repeat="d in newData track by $index">
<span ng-show="d > 0">{{d}}</span>
<span ng-show="d === 0">Not Available</span>
</li>
</ul>
The data can be passed through a function to pull all the 0 after the first one:
$scope.data = [1,2,3,0,1,0,0,1,0,2]
$scope.pullDupes = function(array) {
var newArray = [];
var zero;
for(var i = 0; i < array.length; i++) {
if (array[i] !== 0) {
newArray.push(array[i])
}
if (array[i] === 0 && !zero) {
zero = true;
newArray.push(array[i])
}
}
return newArray;
}
$scope.newData = $scope.pullDupes($scope.data);
Plunker
You can show only the first message see here :
<div ng-repeat="item in list | orderBy: 'cost'">
<div style="color:red" ng-show="item.cost == 0 && $first">Message Goes Here</div>
<hr>
<div>{{item.name}} - Price : {{item.cost}}</div>
</div>
and here is a plunker for it :
http://plnkr.co/edit/RwZPZp9rFIChWxqF71O7?p=preview
also the ng-if you are using it wrong you need to do it like this item.cost for the next time
Cheers !
Here is the best way I could find to get it done.
Markup
<div class="sold-out-message" ng-if="displaySoldOutMessage(item)">Sorry, sold out</div>
Controller
$scope.firstSoldOutItemId = false;
$scope.displaySoldOutMessage = function(item) {
if ( item.cost ) return false;
$scope.firstSoldOutItemId = $scope.firstSoldOutItemId || item.id;
return item.id == $scope.firstSoldOutItemId;
};
You can try to use $scope.$whatch with a boolean variable like this:
<div ng-model="actualItem" ng-repeat="item in list | orderBy: 'cost'">
<div ng-if="cost == 0 && oneMessage == true">Sorry the following are unavailable</div>
<div>...my item here...</div>
<div>
</div>
And in your controller you look at actualItem :
$scope.oneMessage = false;
var cpt = 0; // if 1 so you stop to send message
$scope.$watch('actualItem',function(value){
if(value.cost == 0 && $scope.oneMessage == false && cpt < 1)
// i don't know what is your cost but value is your actual item
{
$scope.oneMessage = true;
cpt++;
}
else if($scope.oneMessage == true)
{
$scope.oneMessage == false;
}
});
I am not sure about this but you can try it. It's certainly not the best way.

AngularJS cant get category "All" while filtering

I started to play with angular and I am trying to write a simple app that consists of categories containing items. ( I am trying to implement a tutorial for my needs )
Now I am trying to add a filter to select items by categories. I can filter them unless I choose All categories. I cant get all the categories.
I have edges service :
angular.module('swFrontApp')
.controller('EdgesController', function ($scope, edges,categories) {
$scope.edges = edges.query();
$scope.categories = categories.query();
$scope.filterBy = {
search: '',
category: $scope.categories[0]
};
var selectedEdge = null;
$scope.selectEdge = function(edge) {
selectedEdge = (selectedEdge === edge) ? null : edge;
};
$scope.isSelected = function(edge) {
return edge === selectedEdge;
};
$scope.displayRequirements = function(reqs) {
var result = '';
for ( var i = 0; i < reqs.length; i ++) {
if (result !== '' ) { result += ', '}
if (reqs[i].name) {
result += reqs[i].name+ ' ';
}
result += reqs[i].value;
}
return result;
};
});
and I try to filter them using :
angular.module('swFrontApp').filter('edges', function() {
return function(edges, filterBy) {
return edges.filter( function( element, index, array ) {
return element.category.name === filterBy.category.name;
});
};
} );
Here is my html to get edges with categories filter
<select
name="category"
ng-model="filterBy.category"
ng-options="c.name for c in categories"
class="form-control"></select>
<ul>
<li ng-repeat-start="edge in edges | filter:{name: filterBy.search}| edges: filterBy " ng-click="selectEdge(edge)">
<span class="label label-default">{{ edge.category.name }}</span>
{{edge.name}}
<span class="text-muted">({{ displayRequirements(edge.requirements) }})</span>
</li>
<li ng-repeat-end ng-show="isSelected(edge)">
{{edge.description}}
</li>
</ul>
I formed My Plunker link is here.
Thanks
It doesn't work because of the category.name attribute. In your categoriesService.js you return collection where name equals to All. But if you look into EdgesService file, you'll see that there is no such option as 'All'. So this comparison in script.js file (in your filter)
return element.category.name === filterBy.category.name;
will always return false when filterby.category.name equals to 'All'.
The way to fix it is to change it to something like this:
return element.category.name === filterBy.category.name || filterBy.category.name === 'All';
This way it will always return true if 'All' category is selected.
Also later in the course rank option will be introduced as well. You can browse the code for that project here: https://github.com/Remchi/sw-front
Hope that helps. :)

Filtering a nested ng-repeat: Hide parents that don't have children

I want to make some kind of project list from a JSON file. The data structure (year, month, project) looks like this:
[{
"name": "2013",
"months": [{
"name": "May 2013",
"projects": [{
"name": "2013-05-09 Project A"
}, {
"name": "2013-05-14 Project B"
}, { ... }]
}, { ... }]
}, { ... }]
I'm displaying all data using a nested ng-repeat and make it searchable by a filter bound to the query from an input box.
<input type="search" ng-model="query" placeholder="Suchen..." />
<div class="year" ng-repeat="year in data | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.months | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in month.projects | filter:query | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>
If I type "Project B" now, all the empty parent elements are still visible. How can I hide them? I tried some ng-show tricks, but the main problem seems so be, that I don't have access to any information about the parents filtered state.
Here is a fiddle to demonstrate my problem: http://jsfiddle.net/stekhn/y3ft0cwn/7/
You basically have to filter the months to only keep the ones having at least one filtered project, and you also have to filter the years to only keep those having at least one filtered month.
This can be easily achieved using the following code:
function MainCtrl($scope, $filter) {
$scope.query = '';
$scope.monthHasVisibleProject = function(month) {
return $filter('filter')(month.children, $scope.query).length > 0;
};
$scope.yearHasVisibleMonth = function(year) {
return $filter('filter')(year.children, $scope.monthHasVisibleProject).length > 0;
};
and in the view:
<div class="year" ng-repeat="year in data | filter:yearHasVisibleMonth | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.children | filter:monthHasVisibleProject | orderBy:sortMonth:true">
This is quite inefficient though, since to know if a year is accepted, you filter all its months, and for each month, you filter all its projects. So, unless the performance is good enough for your amount of data, you should probably apply the same principle but by persisting the accepted/rejected state of each object (project, then month, then year) every time the query is modified.
I think that the best way to go is to implement a custom function in order to update a custom Array with the filtered data whenever the query changes. Like this:
$scope.query = '';
$scope.filteredData= angular.copy($scope.data);
$scope.updateFilteredData = function(newVal){
var filtered = angular.copy($scope.data);
filtered = filtered.map(function(year){
year.children=year.children.map(function(month){
month.children = $filter('filter')(month.children,newVal);
return month;
});
return year;
});
$scope.filteredData = filtered.filter(function(year){
year.children= year.children.filter(function(month){
return month.children.length>0;
});
return year.children.length>0;
});
}
And then your view will look like this:
<input type="search" ng-model="query" ng-change="updateFilteredData(query)"
placeholder="Search..." />
<div class="year" ng-repeat="year in filteredData | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.children | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in month.children | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>
Example
Why not a custom $filter for this?
Efficiency: the nature of the $diggest cycle would make it much less efficient. The only problem is that this solution won't be as easy to re-use as a custom $filter would. However, that custom $filter wouldn't be very reusable either, since its logic would be very dependent on this concrete data structure.
IE8 Support
If you need this to work on IE8 you will have to either use jQuery to replace the filter and map functions or to ensure that those functions are defined, like this:
(BTW: if you need IE8 support there is absolutely nothing wrong with using jQuery for these kind of things.)
filter:
if (!Array.prototype.filter) {
Array.prototype.filter = function(fun/*, thisArg*/) {
'use strict';
if (this === void 0 || this === null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== 'function') {
throw new TypeError();
}
var res = [];
var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
for (var i = 0; i < len; i++) {
if (i in t) {
var val = t[i];
if (fun.call(thisArg, val, i, t)) {
res.push(val);
}
}
}
return res;
};
}
map
if (!Array.prototype.map) {
Array.prototype.map = function(callback, thisArg) {
var T, A, k;
if (this == null) {
throw new TypeError(" this is null or not defined");
}
var O = Object(this);
var len = O.length >>> 0;
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
if (thisArg) {
T = thisArg;
}
A = new Array(len);
k = 0;
while(k < len) {
var kValue, mappedValue;
if (k in O) {
kValue = O[ k ];
mappedValue = callback.call(T, kValue, k, O);
A[ k ] = mappedValue;
}
k++;
}
return A;
};
}
Acknowledgement
I want to thank JB Nizet for his feedback.
For those who are interested: Yesterday I found another approach for solving this problem, which strikes me as rather inefficient. The functions gets called for every child again while typing the query. Not nearly as nice as Josep's solution.
function MainCtrl($scope) {
$scope.query = '';
$scope.searchString = function () {
return function (item) {
var string = JSON.stringify(item).toLowerCase();
var words = $scope.query.toLowerCase();
if (words) {
var filterBy = words.split(/\s+/);
if (!filterBy.length) {
return true;
}
} else {
return true;
}
return filterBy.every(function (word) {
var exists = string.indexOf(word);
if(exists !== -1){
return true;
}
});
};
};
};
And in the view:
<div class="year" ng-repeat="year in data | filter:searchString() | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.children | filter:searchString() | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in month.children | filter:searchString() | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>
Here is the fiddle: http://jsfiddle.net/stekhn/stv55sxg/1/
Doesn't this work? Using a filtered variable and checking the length of it..
<input type="search" ng-model="query" placeholder="Suchen..." />
<div class="year" ng-repeat="year in data | orderBy:'name':true" ng-show="filtered.length != 0">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.months | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in filtered = (month.projects | filter:query) | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>

Filter by multiple columns with ng-repeat

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

Resources