ng-options filter (range selecting) - arrays

I can't really explain properly what I want but I try to make a ng-options in angularJS work:
<select ng-options="object.id as object.name for object in objects" ng-model="selected"></select>
So the current output would be :
1950, 1951, 1952, 1953, 1954, 1955, 1956, 1957, 1958, 1959, 1960 ...
What I want to achieve is:
1950 - 1954, 1955 - 1959, ....
So it will be displayed by every X year.
Is there any way to achieve this?
I tryed it with limitTo and then every 5th time + 5 but no success.
Has anyone a idea?

I'd personally just push that range-grouping logic into the javascript controller. Then you can bind to the array of pre-grouped ojects.
Javascript:
$scope.groupedObjects = [];
var groupDictionary = {}; //for keeping track of the existing buckets
var bucketId = 0;
//assuming objects is an array I prefer forEach, but a regular for loop would work
$scope.objects.forEach(function (obj) {
var yearBucket = Math.floor(obj.name / 5) * 5; //puts things into 5-year buckets
var upperBound = yearBucket + 4;
var bucketName = yearBucket + '-' + upperBound; //the name of the bucket
if (!groupDictionary[bucketName]) { //check whether the bucket already exists
groupDictionary[bucketName] = true;
$scope.groupedObjects.push( {id: "id" + bucketId, name: bucketName} );
bucketId += 1;
}
});
And just use groupedObjects
<select ng-options="object.id as object.name for object in groupedObjects"
ng-model="group"></select>
Here's a plunker demonstrating this idea.

Related

Angular loop through JSON with a limited number

Angular has a built-in feature to loop through JSON, for example I can do:
$scope.users = data.users
But I want to only loop through a number of users to enhance the performance like:
$scope.users = data.users[5] to data.users[10]
How would I be able to do this?
P.S. for pure javascript I can do:
for(var i = 5; i <= 11; i++) {
var user = data.users[i];
}
Of course this cannot add user to the scope.
One way is to use limitTo filter in view
<div ng-repeat="item in items | limitTo : limit.itemCount : limit.startIndex">
And in controller set variables that you can easily change to show different items or quantities
$scope.limit={
startIndex :0,
itemCount : 10
}
$scope.next = function(){
$scope.limit.startIndex += $scope.limit.itemCount;
}
You can use the slice method of an array.
$scope.users = data.users.slice(5, 11);

reorder list randomly in ng-repeat

I have an ng-repeat as following :
<ul>
<li ng-repeat="o in villes | limitTo:5">{{o.nomVille}}</li>
</ul>
I want to reorder the villes list randomly before I limit it to 5, so every time I open my page I get 5 different villes each time.
is there a filter in angularjs who can do that for me ?
edit :
I created a costum filter to randomize that list as following :
.filter('random', function() {
return function(val) {
let shuffle = (a) => {
let r = [];
while (arr.length)
r.push(
arr.splice( (Math.floor(Math.random() * arr.length)) , 1)[0]
);
return shuffle(val);
}
};
});
and in ng-repeat I did this :
<li ng-repeat="o in villes | random | limitTo:5">{{o.nomVille}}</li>
but I cant no longer see anything in my page.
this is the example on jsfiddle : https://jsfiddle.net/z10wwhcv/
You'd have to build a custom filter function that randomizes the order and then apply the filters in the correct order (random before limit).
Have a look at this for details:
Using AngularJS how could I randomize the order of a collection?
If you want the order to change each time you load the page you can't do it as a filter as that will presumably change on each digest cycle. You need to store villes on the scope somewhere and generate it in a random order when the page loads, e.g. in your controller function for that page.
use the filter in the controller (which is also a best practice performance boost):
$scope.shuffled = $filter('random',$scope.villes)
you'll need to inject the service in the controller
this is what your filter should look like ( not tested but should work ):
.filter('random', function() {
return function(a) {
var r = [];
while (a.length)
r.push(
a.splice((Math.floor(Math.random() * a.length)), 1)[0]
);
return r;
}
}
This is the solution I created :
shufflemodule.filter('shuffle', function() {
var shuffledArr = [],
shuffledLength = 0;
return function(arr) {
var o = arr.slice(0, arr.length);
if (shuffledLength == arr.length) return shuffledArr;
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
shuffledArr = o;
shuffledLength = o.length;
return o;
};
});
http://jsfiddle.net/aimad_majdou/6mngvo38/

How to sort results after I filtered them with angular

I have a table and each row has a column with an amount of money. That amount can be in different currency. For now I have two different currencies, for example euros and dollars.
In order to sort that table by amount of money (low-to-high or reverse) I should first convert the amount in dollars for example and then sort the table.
So, I have an order function that works well reference : https://docs.angularjs.org/api/ng/filter/orderBy
I created a filter 'currency' that converts the amount from euros to dollars (i have this as default). The currency converter works good.
But, when I click the button for ordering, I see the results with the converted currency but the table is ordered with the numeric value of the first results.
ng-click="changeCurrencyToDollars(); order('bonus_amount');"
For example the initial data is :
10 US Dollar
9 Euros
and it is converted to :
US Dollar
11.14 US Dollar
Any ideas why the sorting is not working on the converted currency (filtered results) ?
Thanks
Controller:
$scope.convertedCurrency = false; //initial table data with mixed currencies
$scope.changeCurrencyToDollars = function (){
$scope.convertedCurrency = $scope.convertedCurrency ? false: true;
};
$scope.order = function(predicate){
$scope.predicate = predicate;
$scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
$scope.operators = orderBy($scope.operators, predicate, $scope.reverse);
};
app.filter('currency', [function() {
var defaultCurrency = 'Dollars';
return function(input, currencySymbol){
var out = "";
currencySymbol = currencySymbol || defaultCurrency;
switch (currencySymbol){
case 'Dollars':
out = input;
break;
case 'EUR':
out = 1.11 * input; // convert to dollars
currencySymbol = defaultCurrency;
break;
default:
out = input;
}
return out.toFixed(0) + ' ' + currencySymbol;
}
}]);
View:
Inside the ng-repeat:
<span class="highlight-word" ng-if="!convertedCurrency">{{operators.bonus_amount}} {{operators.bonus_currency}}</span>
<span class="highlight-word" ng-if="convertedCurrency">{{operators.bonus_amount | currency: operators.bonus_currency}}</span>
How I solved it:
I inserted a new property to every object with the converted value. That way every object had a "dollars" property. And for sorting I used the same $scope.order, since it was working but with the new property of the object. Not the best angular solution, but at least the "weight" was in the controller and not in the view.
you have not given your object but ,let's say that is it like this:
$scope.operator = [{
bonus_amount:'100',
bonus_currency:'Dollars'},
{
bonus_amount:'10',
bonus_currency:'Dollars'},
{
bonus_amount:'150',
bonus_currency:'Dollars'}];
Now let's add the currency filter(you put your custom filter to make the conversion) and the orderby filter, and show the data:
In your ng-repeat you add the 'orderBy' filter and then, when you show the data you add the 'currency' filter:
<li ng-repeat="operators in operator | orderBy:'+bonus_amount'">{{operators.bonus_amount | currency }}</li>
Hope helps, good luck.

Count repeated items in an observableArray in knockout js

i have an observableArray with about 540 records
in the records it has an advert property and a date property i would like to do a function that finds all the adverts with on a particular date and counts the number of records eg
Date = 23/02/2015
Advert 1 (16)
Advert 2 (5)
Advert 3 (10)
Total (31)
This should help you get along, but basically you can have a function that iterates through the object and creates an array of counts:
self.counts = ko.observable();
function updateCounts() {
var leads = self.leads(),
counts = {};
for (var i = 0, j = leads.length; i < j; i++) {
var prop = leads[i].date_enquired();
if (counts[prop] == null) {
counts[prop] = {
name : prop,
count : 1
};
}
else {
counts[prop].count++;
}
}
// set it as an array instead of an object
self.counts(Object.keys(counts).map(function (key) { return counts[key]; }));
}
Run this function at the start and whenever the list updates:
updateCounts();
self.leads.subscribe(updateCounts);
Then display the object in the view in whatever way you'd like:
<div data-bind="foreach: counts">
<div data-bind="text: name + ': ' + count"></div>
</div>
JSFiddle
You could also create your own object that handles pushing to the array and updating the list of counts. That way additions are O(1) instead of O(n) each time an item is added or removed. Overall, it's not too big of a deal because iterating over 500 items is really fast.

angular grouping filter

Following angular.js conditional markup in ng-repeat, I tried to author a custom filter that does grouping. I hit problems regarding object identity and the model being watched for changes, but thought I finally nailed it, as no errors popped in the console anymore.
Turns out I was wrong, because now when I try to combine it with other filters (for pagination) like so
<div ng-repeat="r in blueprints | orderBy:sortPty | startFrom:currentPage*pageSize | limitTo:pageSize | group:3">
<div ng-repeat="b in r">
I get the dreaded "10 $digest() iterations reached. Aborting!" error message again.
Here is my group filter:
filter('group', function() {
return function(input, size) {
if (input.grouped === true) {
return input;
}
var result=[];
var temp = [];
for (var i = 0 ; i < input.length ; i++) {
temp.push(input[i]);
if (i % size === 2) {
result.push(temp);
temp = [];
}
}
if (temp.length > 0) {
result.push(temp);
}
angular.copy(result, input);
input.grouped = true;
return input;
};
}).
Note both the use of angular.copy and the .grouped marker on input, but to no avail :(
I am aware of e.g. "10 $digest() iterations reached. Aborting!" due to filter using angularjs but obviously I did not get it.
Moreover, I guess the grouping logic is a bit naive, but that's another story. Any help would be greatly appreciated, as this is driving me crazy.
It looks like the real problem here is you're altering your input, rather than creating a new variable and outputing that from your filter. This will trigger watches on anything that is watching the variable you've input.
There's really no reason to add a "grouped == true" check in there, because you should have total control over your own filters. But if that's a must for your application, then you'd want to add "grouped == true" to the result of your filter, not the input.
The way filters work is they alter the input and return something different, then the next filter deals with the previous filters result... so your "filtered" check would be mostly irrelavant item in items | filter1 | filter2 | filter3 where filter1 filters items, filter2 filters the result of filter1, and filter3 filters the result of filter 2... if that makes sense.
Here is something I just whipped up. I'm not sure (yet) if it works, but it gives you the basic idea. You'd take an array on one side, and you spit out an array of arrays on the other.
app.filter('group', function(){
return function(items, groupSize) {
var groups = [],
inner;
for(var i = 0; i < items.length; i++) {
if(i % groupSize === 0) {
inner = [];
groups.push(inner);
}
inner.push(items[i]);
}
return groups;
};
});
HTML
<ul ng-repeat="grouping in items | group:3">
<li ng-repeat="item in grouping">{{item}}</li>
</ul>
EDIT
Perhaps it's nicer to see all of those filters in your code, but it looks like it's causing issues because it constantly needs to be re-evaluated on $digest. So I propose you do something like this:
app.controller('MyCtrl', function($scope, $filter) {
$scope.blueprints = [ /* your data */ ];
$scope.currentPage = 0;
$scope.pageSize = 30;
$scope.groupSize = 3;
$scope.sortPty = 'stuff';
//load our filters
var orderBy = $filter('orderBy'),
startFrom = $filter('startFrom'),
limitTo = $filter('limitTo'),
group = $filter('group'); //from the filter above
//a method to apply the filters.
function updateBlueprintDisplay(blueprints) {
var result = orderBy(blueprints, $scope.sortPty);
result = startForm(result, $scope.currentPage * $scope.pageSize);
result = limitTo(result, $scope.pageSize);
result = group(result, 3);
$scope.blueprintDisplay = result;
}
//apply them to the initial value.
updateBlueprintDisplay();
//watch for changes.
$scope.$watch('blueprints', updateBlueprintDisplay);
});
then in your markup:
<ul ng-repeat="grouping in blueprintDisplay">
<li ng-repeat="item in grouping">{{item}}</li>
</ul>
... I'm sure there are typos in there, but that's the basic idea.
EDIT AGAIN: I know you've already accepted this answer, but there is one more way to do this I learned recently that you might like better:
<div ng-repeat="item in groupedItems = (items | group:3 | filter1 | filter2)">
<div ng-repeat="subitem in items.subitems">
{{subitem}}
</div>
</div>
This will create a new property on your $scope called $scope.groupedItems on the fly, which should effectively cache your filtered and grouped results.
Give it a whirl and let me know if it works out for you. If not, I guess the other answer might be better.
Regardless, I'm still seeing the $digest error, which is puzzling: plnkr.co/edit/tHm8uYfjn8EJk3cG31DP – blesh Jan 22 at 17:21
Here is the plunker forked with the fix to the $digest error, using underscore's memoize function: http://underscorejs.org/#memoize.
The issue was that Angular tries to process the filtered collection as a different collection during each iteration. To make sure the return of the filter always returns the same objects, use memoize.
http://en.wikipedia.org/wiki/Memoization
Another example of grouping with underscore: Angular filter works but causes "10 $digest iterations reached"
You can use groupBy filter of angular.filter module,
and do something like this:
usage: (key, value) in collection | groupBy: 'property'or 'propperty.nested'
JS:
$scope.players = [
{name: 'Gene', team: 'alpha'},
{name: 'George', team: 'beta'},
{name: 'Steve', team: 'gamma'},
{name: 'Paula', team: 'beta'},
{name: 'Scruath', team: 'gamma'}
];
HTML:
<ul ng-repeat="(key, value) in players | groupBy: 'team'" >
Group name: {{ key }}
<li ng-repeat="player in value">
player: {{ player.name }}
</li>
</ul>
<!-- result:
Group name: alpha
* player: Gene
Group name: beta
* player: George
* player: Paula
Group name: gamma
* player: Steve
* player: Scruath

Resources