Counting objects that contain specific property in AngularJS - javascript

I'm trying to read stats out of an array of objects that looks like this:
{
"time":"19.09",
"car":"429",
"driver":"Julia",
"from":"James Hotel",
"destination":"Juba Teaching Hospital",
"pax":"2",
"arrival":"19.09",
"inserted":true
}
{
"date":"25/10/2014",
"time":"19.11",
"car":"396",
"driver":"Tom",
"from":"Drilling company",
"destination":"James Hotel",
"pax":"2",
"comment":"This comment is a test",
"commenttime":"19.11",
"arrival":"19.12",
"inserted":true
}
I'm using the Unique module from AngularUI to be able to make a list of all drivers or all cars, which so far works OK and creates the following table:
<div class="row msf-stats-data-row" >
<div class="col-md-5">
<div class="row" ng-repeat="record in recordlist | unique:'car'">
<div class="col-md-4">{{record.car}}</div>
<div class="col-md-4">Trips</div>
<div class="col-md-4">Time on the road</div>
</div>
</div>
<div class="col-md-6 pull-right">
<div class="row" ng-repeat="record in recordlist | unique:'driver'">
<div class="col-md-6">{{record.driver}}</div>
<div class="col-md-2">Trips</div>
<div class="col-md-4">Time on the road</div>
</div>
</div>
</div>
Every object is a trip. My problem right now is that I want to be able to both count how many objects contain each of the unique record.car or record.driver properties (to be able to determine how many trips a car took), and also to make operations with momentJS to be able to determine how much time a particular car or driver was on the road (timeOnRoad = record.time - record.arrival).
I'm a bit lost on whether this is even possible to do.
Any input?
ANSWER
The answer from Ilan worked perfectly! Here's my code after I adapted it slightly.
var carsDict = {};
angular.forEach($scope.recordlist, function(record) {
carsDict[record.car] = carsDict[record.car] || [];
carsDict[record.car].push(record);
});
$scope.carstats = carsDict;
And the HTML:
<div class="col-md-5">
<div class="row" ng-repeat="carcode in carstats">
<div class="col-md-4">{{carcode[0].car}}</div> <!-- Car code -->
<div class="col-md-4">{{carcode.length}}</div> <!-- NÂș of objects (trips) inside carcode -->
<div class="col-md-4">Time on the road</div>
</div>
</div>

First create a dictionary of cars to store references of records per car.
var carsDict = {};
angular.forEach(recordlist, function(record) {
carsDict[record.car] = carsDict[record.car] || [];
carsDict[record.car].push(record);
});
Then you can make all calculations for each car.

Related

How to call multiple angularjs service calls from within nested ng-repeat

I am making a simple sports goods shopping app in AngularJs.
I am in a situation where I have three nested ng-repeats.
First loop: Get the brand name. I have written angularjs service that calls the rest endpoint to fetch the lists of brands (Adidas, Yonex, Stiga, etc). I am calling this service as soon as the page(controller) gets loaded.
Second loop: For each brand, I want to display the category of products they are offering. Inside this loop, I want to execute a function/service that will take the brand name as input and get all the categories for the brand. For this, I also have an angularjs service that calls the rest endpoint to fetch the list of categories for a given brand name.
Third loop: For each brand and category, I want to display the products in that category. Inside this loop, I want to execute a function that will take the brand name and category as input and get all the products in that category. I an angularjs service call which will call the rest endpoint to fetch the products given the brand name and category.
Sample data set:
Adidas
-----T-Shirts
----------V-Neck
----------RoundNeck
-----Shoes
----------Sports Shoes
----------LifeStyle Shoes
Yonex
-----Badminton Racquet
----------Cabonex
----------Nanospeed
-----Shuttlecocks
----------Plastic
----------Feather
Stiga
-----Paddle
----------Procarbon
----------Semi-carbon
-----Ping Pong Balls
----------Light Weight
----------Heavy Weight
Please note that because of some constraints I cannot have a domain object on the REST side to mimic the data structure shown above.
I want to display the above data in a tree-like fashion (something on the same lines as shown above possibly with expand/collapse options).
Below are the code snippets.
CONTROLLER:
(function () {
'use strict';
angular.module('SportsShoppingApp.controllers').controller('sportsController', ['sportsService', '$scope', function (sportsService, $scope) {
$scope.brands = [];
$scope.categories = [];
$scope.products = {};
$scope.getBrands = function () {
sportsService.getBrands()
.then(loadBrands, serviceError);
};
var loadBrands = function(response) {
$scope.brands= response.data;
};
$scope.getCategories = function(brand) {
sportsService.getCategories(brand)
.then(loadCategories, serviceError);
};
var loadCategories = function (response) {
$scope.categories = response.data;
};
$scope.getProducts = function(brand, category) {
sportsService.getProducts(brand, category)
.then(loadProducts, serviceError);
};
var loadProducts = function (response) {
$scope.products = response.data;
};
var serviceError = function (errorMsg) {
console.log(errorMsg);
};
$scope.getBrands();
}]);
}());
HTML:
<div class="well">
<div class="row">
<div id="sportsHeader" class="col-md-3">
<div ng-repeat="brand in brands.data">
<div class="row">
<div class="col-md-9">{{brand}}</div>
</div>
<div ng-repeat="category in categories.data" ng-init="getCategories(brand)">
<div class="row">
<div class="col-md-9">{{category}}</div>
</div>
<div ng-repeat="product in products.data" ng-init="getProducts(brand, category)">
<div class="row">
<div class="col-md-9">{{product}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
When I use the above HTML, only the brand names are displayed on the UI. The categories and their corresponding products are not displayed. I know that there is some overlapping that is happening. I am not sure if I am doing it the right way. I might be completely wrong with my approach. I am new to AngularJS. I want to know how to loop in nested ng-repeat so that each ng-repeat could call an angularjs service and also I want to display the data in the tree fashion as shown above. Can someone help me here?
I think that the ng-inits have to be placed on separate tags to the ng-repeats:
<div class="well">
<div class="row">
<div id="sportsHeader" class="col-md-3">
<div ng-repeat="brand in brands.data">
<div class="row">
<div class="col-md-9">{{brand}}</div>
</div>
<div ng-init="getCategories(brand)">
<div ng-repeat="category in categories.data">
<div class="row">
<div class="col-md-9">{{category}}</div>
</div>
<div ng-init="getProducts(brand, category)">
<div ng-repeat="product in products.data">
<div class="row">
<div class="col-md-9">{{product}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
You might have to juggle your bootstrap classes around also, moving ng-init is only to fix the angular part.
Move the ng-init directives outside of the ng-repeat to which they provide data.
<div class="well">
<div class="row">
<div id="sportsHeader" class="col-md-3">
<!-- MOVE init of categories here -->
<div ng-repeat="brand in brands.data" ng-init="getCategories(brand)">
<div class="row">
<div class="col-md-9">{{brand}}</div>
</div>
<!-- MOVE init of products here -->
<div ng-repeat="category in categories.data" ng-init="getProducts(brand, category)">
<div class="row">
<div class="col-md-9">{{category}}</div>
</div>
<div ng-repeat="product in products.data">
<div class="row">
<div class="col-md-9">{{product}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
The ng-init directive has a priority of 450; the ng-repeat, priority 1000. This means that when they are on the same element ng-init executes after the ng-repeat directive. The ng-repeat for categories.data won't execute its ng-init until it has a category. Thus its ng-init can't be used to populate the categories array.
Quick question. Is my approach correct ?
The approach works but it violates the Zen of Angular and the principles of an MV* Model View Whatever framework.
The model is the Single Source of Truth
Because the view is just a projection of the model, the controller is completely separated from the view and unaware of it. This makes testing a snap because it is easy to test your controller in isolation without the view and the related DOM/browser dependency.
--AngularJS Developer Guide -- Data-Binding
Having the ng-repeat and ng-init directives build the model creates a dependency that makes testing and debugging difficult. (As witnessed by this question.)
My advice is to learn how to build the model by chaining promises and using $q.all.

AngularJS dirPaginate Only Filtering current page

I have included dir paginate into my angularjs project to handle pagination easily, it works well up until the point where I want to filter all the pages results.
The controller that contains the data and filter looks like so:
app.controller('listCtrl', function ($scope, services) {
$scope.sort = function(keyname){
$scope.sortKey = keyname; //set the sortKey to the param passed
$scope.reverse = !$scope.reverse; //if true make it false and vice versa
}
$scope.currentPage = 1;
$scope.pageSize = 10;
services.getPosts().then(function(data){
$scope.posts = data.data;
});
});
The dir-paginate looks like so
<div dir-paginate="data in posts | itemsPerPage:5 | orderBy:sortKey:reverse" class="col-xs-12 post">
<div id="title" class="col-xs-3"></div>
<div id="title" class="col-xs-9"></div>
<div id="poster" class="col-xs-3">
<img src="pics/house.jpg" id="avatar">
<a ng-if="data.user_name == '<?php echo $currentUser?>'" href="edit-post/{{data.post_id}}"> Edit </a>
</div>
<div class="col-xs-9">
<div class="col-xs-12">
<p class="timestamp">{{ data.post_datetime }}</p>
</div>
<div id="rant">
<span style="word-wrap: break-word;">{{ data.post_content }}</span>
</div>
<div id="stats" class="col-xs-12"> <img src="pics/comment.png"><span>3</span>
</div>
</div>
</div>
<dir-pagination-controls
max-size="5"
direction-links="true"
boundary-links="true" >
</dir-pagination-controls>
I call the sort function like so:
<button id="changeLikes" ng-click="sort('post_id')">ID</button>
It only sorts the current page of Data, if i use the pagination and click page 2 it will then only filter page 2 and so on, any ideas here would be of great help getPosts() returns post data via php with no group or order by clauses in the sql that would affect the angular.
Have you tried flipping the order of the filters, i.e.
<div dir-paginate="data in posts | orderBy:sortKey:reverse | itemsPerPage:5" class="col-xs-12 post">
The dir-paginate docs state:
itemsPerPage: The expression must include this filter. It is required
by the pagination logic. The syntax is the same as any filter:
itemsPerPage: 10, or you can also bind it to a property of the $scope:
itemsPerPage: pageSize. Note: This filter should come after any other
filters in order to work as expected. A safe rule is to always put it
at the end of the expression.

addClass to multiple distinct elements with jquery

I have some code that generates markup similar to this:
<div id="container">
<div data-number="123">Fred</div>
<div data-number="128">Wilma</div>
<div data-number="129">Barney</div>
<div data-number="123">Fred</div>
<div data-number="123">Fred</div>
<div data-number="129">Barney</div>
<div data-number="111">Dino</div>
<div data-number="008">Betty</div>
<div data-number="123">Fred</div>
<div data-number="123">Fred</div>
</div>
The container will have many duplicates.
The data tags are generated dynamically based on potentially thousands of data-ids. But, a particular view will likely not have more than a dozen or so unique items, so iterating over them shouldn't be a big deal.
What I want to do is add css classes to similar items in a predictable way.
<div class="unique-item-1" data-number="123">Fred</div>
<div class="unique-item-2" data-number="008">Betty</div>
<div class="unique-item-3" data-number="128">Wilma</div>
<div class="unique-item-4" data-number="129">Barney</div>
<div class="unique-item-5" data-number="111">Dino</div>
<div class="unique-item-1" data-number="123">Fred</div>
<div class="unique-item-1" data-number="123">Fred</div>
You'll notice that all the Fred divs (data-number 123) get the same class (unique-item-1) added.
The goal is to have CSS that will add colors or whatever, like so:
.unique-item-1 {color:red;}
.unique-item-2 {color:pink;}
.unique-item-3 {color:green;}
.unique-item-4 {color:black;}
.unique-item-5 {color:wheat;}
I've tried using jquery's "unique"... but I guess I don't know what unique means :)
Use data() attribute in jquery ,
var getArray = [];
$("#container").children("[data-number]").filter(function () {
var index = $.inArray($(this).data("number"), getArray);
if (index == -1) {
getArray.push($(this).data("number"));
$(this).addClass("unique-item-" + getArray.length);
} else {
$(this).addClass("unique-item-" + parseInt(index+1));
}
});
UPDATED DEMO

Angular view not updating

I am quite new to Angular.js and think I am missing something small but important here.
To learn angular I am building this little panel to route video sources to destinations.
I have a list of sources and a list of destinations (each destination with 2 slots).
These will later be loaded from an API, but for now are defined in the js.
When I select one of the sources, the "selectedSource" var gets set with the clicked source.
When I than click a destination-slot is sets that slot's content with the "selectedSource" object.
The console log tells me that the thumb url of the slot has updated, but my html does not show the updated image. I've already messed around with "apply" altough I don't beleieve that is the way to go.
See my simplified code here:
http://jsfiddle.net/f4Tgf/
function app($scope, $filter) {
$scope.selectedSource = null;
$scope.sources = {
source1 : {id:'source1', thumbUrl:'http://lorempixel.com/100/100/sports/1/'},
source2 : {id:'source2', thumbUrl:'http://lorempixel.com/100/100/sports/2/'},
source3 : {id:'source3', thumbUrl:'http://lorempixel.com/100/100/sports/3/'}
}
$scope.destinations = {
dest1 : {id:'dest1', slots: {slot1 : $scope.sources.source2,slot2 : $scope.sources.source3} }
}
$scope.selectSource = function(source){
if($scope.selectedSource == source){
// toggle the selected source off if it is already selected
$scope.selectedSource = null;
}else{
$scope.selectedSource = source;
}
}
$scope.selectSlot = function(slot){
slot = $scope.selectedSource;
console.log(slot.thumbUrl);
//reset selected source
$scope.selectedSource = null;
}
}
HTML:
<body >
<div ng-app ng-controller="app" class="container-fluid">
<div class="row">
<!-- SOURCES -->
<div id="source-container" class="col-xs-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Sources</h3>
</div>
<div class="panel-body row">
<!-- Show all sources -->
<div ng-repeat="source in sources" ng-class="{selected: source==selectedSource}" ng-click="selectSource(source)" class="col-md-6 col-sm-12">
<div class="thumbnail">
<img class="img-responsive" src="{{source.thumbUrl}}" />
</div>
</div>
</div>
</div>
</div>
<!-- SOURCES -->
<!-- DESTINATIONS -->
<div id="sink-container" class="col-xs-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Destination</h3>
</div>
<div class="panel-body row">
<!-- Show all destinations -->
<div ng-repeat="destination in destinations" ng-class="{available: selectedSource!=null}">
<div class="thumbnail">
<div ng-repeat="slot in destination.slots" ng-click="selectSlot(slot)">
<img class="img-responsive" src="{{slot.thumbUrl}}" />
</div>
</div>
</div>
</div>
</div>
</div>
<!-- END DESTINATIONS -->
</div>
</div>
</body>
(before you tell me to use services and so, please remember: this is a first try-learning project)
The problem is that you are trying to change the value parameter, slot, of the selectSlot() function instead of the destination's array of slots itself. Since you can have one ore more destinations slots from a list of destinations then the most probable solution would be to change selectSlot() to accept the current destinations array of slots and the key of the slot that you want to change.
Simply change this HTML code from
<div ng-repeat="slot in destination.slots" ng-click="selectSlot(slot)">
to
<div ng-repeat="(key, slot) in destination.slots" ng-click="selectSlot(destination.slots, key)">
and in your JAVASCRIPT selectSlot() definition should be
$scope.selectSlot = function(slots, key){
slots[key] = $scope.selectedSource;
//reset selected source
$scope.selectedSource = null;
}
See this UPDATE FIDDLE to view it in action.
Note: you can refer to AngularJS' documentation regarding ng-repeat's (key, value) synatx.
Seems this is a pure JavaScript problem.
In you selectSlot():
slot = $scope.selectedSource;
this operation does not actually assign $scope.selectedSource to one of $scope.destinations.dest1.slots as you expected.
You may want to do it this way:
slot.id = $scope.selectedSource.id;
slot.thumbUrl = $scope.selectedSource.thumbUrl;
That will work.
But please also note that in your case, you initialize $scope.destinations.dest1.slots as tow objects of $scope.sources, that means change the slot in $scope.destinations.dest1.slots also results in changing the corresponding one in $scope.sources. It will work fine if as you said, the destinations "will later be loaded from an API".

filtering and sorting divs

I have been looking for a robust and simple way to sort my casestudies but after a couple of hours and a search of stack overflow i could not find a way to filter casestudies the way I want.
Basically I will give each casestudy three categories (year produced, type of project and name) using css classes, for example the markup would look something like this
<div class="name1 home 2013"></div>
<div class="name2 work 2012"></div>
<div class="name3 home 2012"></div>
<div class="name4 charity 2012"></div>
<div class="name5 home 2010"></div>
<div class="name6 work 2007"></div>
Then I want to have buttons so you can choose which category you want to sort the casestudies by. So something like.
<div class="button" id="year">Sort by Year</div>
<div class="button" id="alpha">sort Alphabetically</div>
<div class="button" id="type">sort by type</div>
This is where I am getting stuck. What javascript function can i create so that if you click the button "sort by year" it will create a mark up that looks like this. eg sorting all the casestudies in to divs with casestudies of the same year.
<div>
<div class="name1 home 2013"></div>
</div>
<div>
<div class="name2 work 2012"></div>
<div class="name3 home 2012"></div>
<div class="name4 charity 2012"></div>
</div>
<div>
<div class="name5 home 2010"></div>
</div>
<div>
<div class="name6 work 2007"></div>
</div>
I would use data attributes to make the filtering easier.
<div class="name1 home" data-year="2013">2013</div>
<div class="name2 work" data-year="2012">2012</div>
<div class="name3 home" data-year="2012">2012</div>
<div class="name4 charity" data-year="2012">2012</div>
<div class="name5 home" data-year="2010">2010</div>
<div class="name6 work" data-year="2007">2007</div>
The using JQuery and array.map (could be replaced with a foreach if you want older browser support)
var studies = $('[data-year]')
studies.map(function(index, el) {
var $el = $(el)
year = $el.attr('data-year')
if($('#' + year).length == 0){
$(document.body).append(
$('<div>').attr('id', year)
.css('margin-bottom', '20px')
)
}
$('#' + year).append(el)
})
what this does is take all the elements with a data-year attribute, foreach element check to see if a div with the id of that elements year exists. If it doesn't create one and append it to the body. Then it appends the element into the year container.
see this jsfiddle

Categories