Organizing objects hierarchically by two levels in angularjs - javascript

I'm working on an Angular app that utilizes a cell modifier inside a custom cell template in Angular Bootstrap Calendar. Inside each cell, instead of a standard event, I'm placing a set of tables that are used for signing up for shifts at a station for that day. The tables are split into two groups; am and pm, and within each am/pm group, there is a table for each station, with three rows in each table.
AM
| 1 |Position|Name |
| |Pos 1 |Name 1|
| |Pos 2 |Name 2|
| |Pos 3 |Name 3|
| 2 |Position|Name |
| |Pos 1 |Name 1|
| |Pos 2 |Name 2|
| |Pos 3 |Name 3|
PM
| 1 |Position|Name |
| |Pos 1 |Name 1|
| |Pos 2 |Name 2|
| |Pos 3 |Name 3|
| 2 |Position|Name |
| |Pos 1 |Name 1|
| |Pos 2 |Name 2|
| |Pos 3 |Name 3|
Inside of my cell modifier function, I get an array of shift objects for that day, and each shift object contains the ampm value and the station value:
{
"_id": "57776537ac0a88010063b9b9",
"modified": "2016-07-02T06:54:47.518Z",
"data": {
"station": "1",
"date": "2016-07-01T07:00:00.000Z",
"ampm": "pm",
"slots": [
{
"position": "AO",
"name": ""
},
{
"position": "FF",
"name": {
"_id": "57776507ac0a88010063b9b8",
"modified": "2016-07-02T06:53:59.661Z",
"data": {
"group": "suppression",
"driving": {
"n": false,
"d": true,
"ao": false,
"wt": false
},
"emtLevel": "b",
"secondaryPhoneNumber": "",
"primaryPhoneNumber": "5556781234",
"emailAddress": "person.one#mysite.com",
"fullName": "Person One",
"userName": "person.one",
"assignedStation": "18",
"probationary": false
},
"form": "57427ba554ec330100dad645",
"created": "2016-07-02T06:53:59.644Z",
"externalIds": [],
"access": [],
"roles": [
"573511a8ffaa7a0100a5718a"
],
"owner": "57776507ac0a88010063b9b8"
}
},
{
"position": "FF",
"name": {
"_id": "57439d856e67b40100d4c420",
"modified": "2016-05-24T00:17:09.493Z",
"data": {
"userName": "person.two",
"fullName": "Person Two",
"emailAddress": "person.two#mysite.com",
"primaryPhoneNumber": "5555556666",
"secondaryPhoneNumber": "",
"assignedStation": "",
"emtLevel": "b",
"driving": {
"d": true
},
"group": "suppression"
},
"form": "57427ba554ec330100dad645",
"created": "2016-05-24T00:17:09.474Z",
"externalIds": [],
"access": [],
"roles": [
"573511a8ffaa7a0100a5718a"
],
"owner": "5734bba2ffaa7a0100a57029"
}
}
]
},
So the issue is how to take those objects and organize them into the two groupings mentioned above so that I can just loop through them with ngRepeat in my template. What I have so far is this:
vm.cellModifier = function(cell) {
cell.text = 'Test Text';
var shifts = vm.events;
// Get the date for the cell.
this.cellDate = moment(cell.date).format('YYYY-MM-DD');
// Iterate over shifts to get ones for this day.
this.cell = cell;
this.todayShifts = {};
shifts.forEach(function(shift, index, allShifts) {
var shiftDate = moment(shift.data.date).format('YYYY-MM-DD');
// Now we need to see if this shift belongs to this day.
if (moment(vm.cellDate).isSame(moment(shiftDate))) {
// Shift is today, so let's put it into the appropriate array.
if (typeof vm.todayShifts[shift.data.ampm] == 'undefined') {
vm.todayShifts[shift.data.ampm] = shift;
} else {
vm.todayShifts[shift.data.ampm].push(shift);
}
}
});
// Add arrays to cell object.
cell.todayShifts = vm.todayShifts;
};
That gives vm.todayShifts[am] and vm.todayShifts[pm], but I also would like to get the second level so that I have vm.todayShifts[am][1], vm.todayShifts[am][2] etc. Is there an easier way to do what I'm trying to do (I'm fairly certain there is) than adding another section of statements? I'm wondering if a custom directive or component might be cleaner, because then I could just pass my data into that controller, but even then, I would still need to get my data arranged properly so it can be displayed in the proper order.
Hopefully this all makes sense.
Thanks.

I think you are actually pretty close... Try this which will always put the data in the format you are asking for.
shifts.forEach(function(shift, index, allShifts) {
var shiftDate = moment(shift.data.date).format('YYYY-MM-DD');
// Now we need to see if this shift belongs to this day.
if (moment(vm.cellDate).isSame(moment(shiftDate))) {
// Shift is today, so let's put it into the appropriate array.
if (typeof vm.todayShifts[shift.data.ampm] == 'undefined') {
// Initialize the shifts as an array.
vm.todayShifts[shift.data.ampm] = [];
}
// Push the shift onto the array.
vm.todayShifts[shift.data.ampm].push(shift);
}
});

Related

Sorting teams in group by 3 variables (wins, loses and seed)

I'm working on a tournament project and was looking for good sorting method to sort teams in group by its wins, loses and seed.
Group object:
[
{
"groupId": 1,
"signature": "A",
"teams": [
"team": {
"id": 45,
"name": "Team A",
"seed": {
"id": 1,
"name": "TOP"
},
"wins": 0,
"loses": 0
},
"team": {
"id": 2,
"name": "Team B",
"seed": {
"id": 2,
"name": "HIGH"
},
"wins": 0,
"loses": 0
} etc.
Seed order looks like this: TOP -> HIGH -> MID -> LOW
I have a group A:
Team A TOP | 0:0
Team B HIGH | 0:0
Team C MID | 0:0
Team D LOW | 0:0
In this case it's easy to sort this group (using seed, obviously), but how do I sort them by wins and loses?
Let's say some matches were played already:
Case 1:
Team B HIGH | 2:0
Team A TOP | 1:1
Team D LOW | 1:1
Team C MID | 0:2
Case 2:
Team A TOP | 2:1
Team B HIGH | 2:1
Team D LOW | 1:2
Team C MID | 0:3
How to sort teams when (case 1) teamAWins = teamDWins and teamALoses = teamDLoses and then sort those two by seed? As well as in case 2, where team A has the same wins and loses amount as teamB?
Is it possible? If not, how to do it using only wins and loses?
Thanks in advice. I really need that help.
You could take an object for getting the right numerical value for seed and sort by
wins ascending,
loses descending and
seeds with the numerical value ascending.
const seeds = { TOP: 1, HIGH: 2, MID: 3, LOW: 4 };
array.sort((a, b) =>
a.wins - b.wins ||
b.loses - a.loses ||
seeds[a.seed.name] - seeds[a.seed.name]
);

Remove item from json object on checkbox uncheck in angularjs

I am using angularjs v1.4.7. I have fetched result set from db and constructed data as jsonobject.
$scope.originalEmpList=
{
"depts": [
{
"id": 1,
"name": "IT",
"software_team": "Ram, Rahim",
"hr_team": "",
"fin_team": ""
},
{
"id": 2,
"name": HR,
"software_team": "",
"hr_team": "Mohan",
"fin_team": ""
},
{
"id": 3,
"name": PM,
"software_team": "Ram",
"hr_team": "Mohan",
"fin_team": "John"
}
],
"softwarelist": [
{
"id": 1,
"employee_name": "Ram",
"employee_role": "Software",
"dept_id": "1"
},
{
"id": 2,
"employee_name": "Rahim",
"engineer_role": "Software",
"dept_id": "1"
},
{
"id": 3,
"employee_name": "Ram",
"engineer_role": "Software",
"dept_id": "3"
}
],
"hrlist": [
{
"id": 4,
"employee_name": "Mohan",
"employee_role": "HR",
"dept_id": "2"
},
{
"id": 5,
"employee_name": "Mohan",
"employee_role": "HR",
"dept_id": "3"
}
],
"finlist": [
{
"id": 6,
"employee_name": "John",
"employee_role": "Account",
"dept_id": "3"
}
]
}
and showing below table on UI side from above jsonobject
Select All Checkbox Dept Softwares HRs Fins
Checkbox1 IT Ram, Rahim
Checkbox2 HR Mohan
Checkbox3 PM Ram Mohan John
Based on above checbox selection respective team members will be shown.
For Eg: If Checkbox1 is selected then only show names for that dept.
Softwares : Ram, Rahim
Similarly if we select checkbox1 and checkbox2 then show names for checked depts.
Softwares : Ram, Rahim
Hrs: Mohan
And if we select all 3 checkboxes then show names.
Softwares : Ram, Rahim, Ram
Hrs: Mohan, Mohan
Fins: John
I have kept unchanged the original emp list and copied it to employeeList
$scope.employeeList = $scope.originalEmpList;
Update object based on checkbox selection.
$scope.UpdateOnCheckUncheck = function () {
$scope.employeeList = $scope.originalEmpList;
$scope.filteredArtist = [];
// Collect unchecked depts
$scope.unchecked_depts = filterFilter($scope.employeeList.depts,
function (dept) {
return !dept.Selected;
});
$scope.filteredSoftware= [];
// Passing unchecked depts to remove from employeelist
angular.forEach($scope.unchecked_depts, function(dept) {
$scope.updateCheckedDept(dept);
});
};
$scope.updateCheckedDept = function(dept) {
**// Approach 1 using reduce to copy into new array and then assign back to employeeList**
Object.keys($scope.employeeList.softwarelist).reduce((object,
key) => {
if (dept.id !=$scope.employeeList.softwarelist[key].dept_id)
{
$scope.filteredArtist.push($scope.prismlist.artistlist[key]);
}
//return object
}, {})
$scope.employeeList.softwarelist= $scope.filteredSoftware;
**//Approach 2 using splice
angular.forEach($scope.employeeList.softwarelist,
function(soft, index){
if(dept.id === soft.dept_id){
$scope.employeeList.softwarelist.splice(index);
}
});
**//Approach 3 using slice**
};
//Approach 4 - Thinking to call DB and construct query and filter at server side but calling db on every checkbox change will be costly.
Actually after updating back to $scope.employeeList , it works fine for the first time uncheck but when uncheck another checkbox i assign $scope.employeeList = $scope.originalEmpList; but this doesn't get the initial data fetched from db rather than it updated to first time uncheck object value.
On Every check/uncheck how to update employeelist to populate the output as shown above. Also suggest me the best approach to use in terms of performance. Thanks in advance
$scope.employeeList = $scope.originalEmpList;
is like referencing to $scope.originalEmpList. Any updates to $scope.employeeList is the same as updating $scope.originalEmpList.
Instead, you try angular.copy() which creates a deep copy of the array.
$scope.employeeList = angular.copy($scope.originalEmpList);

Group By - Angular JS

I have the following JSON format, and I am looking to combine the "Virtual" and "Physical" objects into one by grouping by cluster and idc
[
{
"_id": {
"cluster": 1,
"idc": "LH8",
"type": "Virtual"
},
"SumCores": 112,
"SumMemory": 384
},
{
"_id": {
"cluster": 1,
"idc": "LH8",
"type": "Physical"
},
"SumCores": 192,
"SumMemory": 768
},
{
"_id": {
"cluster": 2,
"idc": "LH8",
"type": "Virtual"
},
"SumCores": 232,
"SumMemory": 469
},
{
"_id": {
"cluster": 2,
"idc": "LH8",
"type": "Virtual"
},
"SumCores": 256,
"SumMemory": 1024
}
Currently I have all of the output to screen using ng-repeat:
<div ng-repeat="item in servers | orderBy:['idc','cluster'] "><p>IDC:{{item._id.idc}} - Cluster: {{item._id.cluster}} - Type: {{item._id.type}} - Sum Cores: {{ item.SumCores }} </p></div>
Which produces something similar to:
IDC: LH8 - Cluster: 1 - Type: Virtual - Sum Cores: 192
IDC: LH8 - Cluster: 1 - Type: Physical -Sum Cores: 112
IDC: LH8 - Cluster: 2 - Type: Virtual - Sum Cores: 256
IDC: LH8 - Cluster: 2 - Type: Physical -Sum Cores: 232
Ideally I want to group this into a table with this as the ideal format:
+---------+--------------------+--------------------+
| Cluster | LH5 | LH8 |
+---------+--------------------+--------------------+
| | Physical | Virtual | Physical | Virtual |
+---------------------------------------------------+
| 1 | Value | Value | Value | Value |
| 2 | Value | Value | Value | Value |
| 3 | Value | Value | Value | Value |
| 4 | Value | Value | Value | Value |
+---------+----------+---------+----------+---------+
Obviously there is a lot more data than in my sample and value would represent the SumCores.
I also have access to the controller if you think the change would be better made in there:
Machine.aggregate( [ { $match : { $and: [ {"idc": req.query.idc }, {"customer":req.query.customer} ] } } ,{"$group":{_id: {"cluster":"$cluster","idc":"$idc","type":"$type"},"SumCores":{"$sum":"$cores"},"SumMemory": { "$sum":"$memory" }}}, { $sort : { idc : -1, cluster: 1 } } ]).exec(function(err, agg) {
res.json(agg);
});
Fiddle here: http://jsfiddle.net/8n227L2o/
I've forked your Fiddle, and now I've used Underscore.js to group and filter your data according to your example table.
http://jsfiddle.net/pdc5rvyo/1/
It is quite basic, and uses nested tables. You should be able to customize it by allowing uses to change the order of the lists for example.
Code example:
var lhSortedList = _.groupBy(servers, function(item) {
return item._id.idc;
});
$scope.lh8Virtual = _.filter(lhSortedList['LH8'], function(item) {
return item._id.type === 'Virtual';
});
Here is an overview of how to do what you want dynamically:
var lhList = ['LH5', 'LH8']; // sourced from server ?
var lhSortedList = _.groupBy(servers, function(item) {
return item._id.idc;
});
$scope.lhData = {};
lhList.forEach(function(lhName) {
$scope.lhData[lhName + 'Virtual'] = _.filter(lhSortedList[lhName], function(item) {
return item._id.type === 'Virtual';
});
$scope.lhData[lhName + 'Physical'] = _.filter(lhSortedList[lhName], function(item) {
return item._id.type === 'Physical';
});
});

JavaScript Multidimensional Array - Object Linking

I am working with javascript multidimensional array.
Here is the scenario:-
I have a educational institution where user can take classes from any subject. Subjects are not fixed it could be vary. Now there is exam day so suppose there is PHP Lang faculty who will enter his subject name then student name and then marks. If student is enrolled himself for more then 1 subject so its marks will listed in same row.
for example Mr. Anand has enrolled for PHP and HTML and Mr. Deep has enrolled himself for Php only.
Additionally I also want to show minimum and maximum marks as well.
So on result day result card will
Name\Subject | PHP | HTML | Java
--------------------------------------
Anand | 80 | 60 | --
Deep | 70 | -- | --
Sachin | 55 | 56 | 45
so on ... | -- | -- | 80
--------------------------------------
Min Marks | 70 | 56 | 45
Max Mark | 80 | 60 | 80
I have created a multidimensional array but unable to reproduce code as per visual. I think I am doing something wrong.
Below is the code which I have created as of now :-
var data = [
["HTML", [{
"name": "Anand",
"marks": 90
}, {
"name": "Deep",
"marks": 79
}, {
"name": "Raman",
"marks": 34
}]],
["Php", [{
"name": "Anand",
"marks": 90
}, {
"name": "Ekam",
"marks": 40
}]]
]
for (var i = 0; i < data.length; i++) {
document.write("<h2> " + data[i][0] + " </h2>");
var secondLevelData = data[i][1],
secondLen = secondLevelData.length;
for (var j = 0; j < secondLen; j++) {
document.write(secondLevelData[j].name + " -- " + secondLevelData[j].marks + " <br/>");
}
}
Please help me to get the desired result. I am also working on it.
Thanks for your help!!
By changing the JSON we can achieve this. Also added jQuery and underscore libraries for DOM and array manipulations
JS Fiddle Link : https://jsfiddle.net/8Lb7x01u/3/
var data = [
{
"name": "Anand",
"score": [
{
"subject": "HTML",
"marks": 90
},
{
"subject": "Php",
"marks": 90
}
]
},
{
"name": "Deep",
"score": [
{
"subject": "HTML",
"marks": 79
}
]
},
{
"name": "Raman",
"score": [
{
"subject": "HTML",
"marks": 34
}
]
},
{
"name": "Ekam",
"score": [
{
"subject": "Php",
"marks": 40
}
]
}
];
var allScores = _.pluck(data,"score");
var allSubjects = _.groupBy(_.flatten(allScores),"subject");
var allStudents = _.pluck(data,"name");
var headerRow = $("<tr></tr>");
$("<th></th>").html("Name\\Subject").appendTo(headerRow);
for(var subject in allSubjects){
$("<th></th>").html(subject).appendTo(headerRow);
}
headerRow.appendTo(".scoreCard");
for(var i=0;i<allScores.length;i++){
var individualScores = _.groupBy(allScores[i],"subject");
var tr = $("<tr></tr>");
$("<td></td>").html(allStudents[i]).appendTo(tr);
for(var subject in allSubjects)
{
if(individualScores[subject]){
$("<td></td>").html(individualScores[subject][0].marks).appendTo(tr);
}else
{
$("<td></td>").html("...").appendTo(tr);
}
}
tr.appendTo(".scoreCard tbody")
}
renderMaxMin("max");
renderMaxMin("min");
function renderMaxMin(param){
var footerRow = $("<tr></tr>");
$("<td></td>").html(param+" marks").appendTo(footerRow);
for(var subject in allSubjects){
var marks = _.pluck(allSubjects[subject],"marks");
var value =(param === "max") ? _.max(marks) : _.min(marks);
$("<td></td>").html(value).appendTo(footerRow);
}
footerRow.appendTo(".scoreCard tfoot")
}
You are not using the markup in the right manner. You should form the correct markup first, in this case it would be tables.
Also, the way you are storing data could be improved by using associative arrays. It will help you to manage it and use it better as the size of data grows. Here is how you can do that :
var data = [{'HTML' :
[{"name":"Anand","marks":90,
{"name":"Deep","marks": 79},
{"name":"Raman","marks": 34}]
}],
{'PHP' :
[{"name":"Anand","marks": 90},
{"name":"Ekam","marks": 40}]
}]]

AngularJS : custom iterations/data transformations and grouping... when simple ng-repeat just won't cut it

Still this problem Angular.js more complex conditional loops but I felt that the answer to the question as it was asked was right so I accepted it.
So let me elaborate more than I did in the original question.
I'm trying to get this
<h3>11.4.2013</h3>
<ul>
<li>oofrab | 4 | 11.4.2013 14:55 <button>remove</button></li>
<li>raboof | 3 | 11.4.2013 13:35 <button>remove</button></li>
</ul>
<h3>10.4.2013</h3>
<ul>
<li>barfoo | 2 | 10.4.2013 18:10 <button>remove</button></li>
<li>foobar | 1 | 10.4.2013 12:55 <button>remove</button></li>
</ul>
from this data structure
[
{
"id": 4,
"name": "oofrab",
"date": "2013-11-04 14:55:00"
},
{
"id": 3,
"name": "raboof",
"date": "2013-11-04 13:55:00"
},
{
"id": 2,
"name": "barfoo",
"date": "2013-10-04 18:10:00"
},
{
"id": 1,
"name": "foobar",
"date": "2013-10-04 12:55:00"
}
]
Basically the only extra thing over the standard ng-repeat I want to add are those headings. And I simply can't believe I'd have to go thru so many problems by adding them.
This is what I ended up with using the answer I got in the first question http://plnkr.co/edit/Zl5EcsiXXV92d3VH9Hqk?p=preview
Note that there can realistically be up to 400 entries. And I need to be able to add/remove/edit entries on the fly
What the example on plunker is doing is this:
iterating thru the original data creating a new data structure looking like this
{
"2013-10-05": [
{
"id": 4,
"name": "oofrab",
"date": "2013-10-05 14:55:00",
"_orig_index": 0
},
{
"id": 3,
"name": "raboof",
"date": "2013-10-05 13:55:00",
"_orig_index": 1
}
],
"2013-10-04": [
{
"id": 2,
"name": "barfoo",
"date": "2013-10-04 18:10:00",
"_orig_index": 2
},
{
"id": 1,
"name": "foobar",
"date": "2013-10-04 12:55:00",
"_orig_index": 3
}
]
}
allowing me to then get the result I wanted by doing this
<div ng-repeat="(date,subItems) in itemDateMap">
<h3>{{date}}</h3>
<ul>
<li ng-repeat="item in subItems">
{{item.name}} | {{item.id}} | {{item.date}}
<button type="button" ng-click="removeItem(item._orig_index)">x</button>
</li>
</ul>
</div>
Great. But it comes with a cost of shizzload of problems. Everytime a new item is added I have to rebuild the itemDateMap, everytime an item is deleted I have to rebuild the itemDateMap, everytime date is changed, I have to rebuild the itemDateMap. When I want to remove an item, I have to first get index of its original reference. And everytime itemDateMap is rebuilt, the whole thing is re-rendered. And it can't be sorted, as it's an object rather than an array.
When there's a couple of hundred of entries, it also becomes really, really slow. I read somewhere that ng-repeat is quite intelligent, watching values, moving nods in dom rather than re-rendering everything and stuff, but it surely doesn't work this way when I rebuild the whole structure.
This can't be right, all this hassle to do a very, very simple thing..
What should I do?
This is my suggestion - just work with one structure, and only expose one structure to the scope (the map). And create a function to add an array of items to the map, and a function that transforms the map into an array (I assume you need this array for server communication or something).
var toKey=function(item){
return moment(item.date).format("YYYY-MM-DD");
}
$scope.itemDateMap = {};
$scope.addItemToDateMap=function(item){
var key = toKey(item);
if(!$scope.itemDateMap[key]){
$scope.itemDateMap[key] = [];
}
$scope.itemDateMap[key].push(item);
}
$scope.removeItemFromDateMap=function(item){
var key = toKey(item), subitems = $scope.itemDateMap[key];
var index = subitems.indexOf(item);
subitems.splice(index,1);
if(subitems.length === 0){
delete $scope.itemDateMap[key];
}
}
var addArrayToMap = function(items){
for(var i=0; i<items.length; i++){
var item = items[i];
$scope.addItemToDateMap(item);
}
};
$scope.mapToArray = function(){
var items = [];
for(var key in $scope.itemDateMap){
var subitems = $scope.itemDateMap[key];
for(var j=0;j<subitems.length;j++){
var item = subitems[j];
items.push(item);
}
}
return items;
}
I've updated your plnkr with my suggestion. I think it performs quite well.
Oh - I just noticed you want it sorted - I don't have time to update my example, but it is not very complicated. Use this structure instead (array with objects with arrays, instead of object with array) - this way you can use the orderBy:'date' on the root array:
[
{
date:"2013-10-05",
items: [
{
"id": 4,
"name": "oofrab",
"date": "2013-10-05 14:55:00"
},
{
"id": 3,
"name": "raboof",
"date": "2013-10-05 13:55:00"
}
]
},
{
date:"2013-10-04",
items: [
{
"id": 2,
"name": "barfoo",
"date": "2013-10-04 18:10:00"
},
{
"id": 1,
"name": "foobar",
"date": "2013-10-04 12:55:00"
}
]
}
]

Categories