Angular: Combine array objects and combine values - arrays

I'm retrieving data with http.get.
This provides me with an array of objects like below:
[{
"id”:12345,
"resource_state":2,
"external_id”:”abscdgrft”,
"upload_id”:1234567,
"athlete":{
"id”:123456,
"resource_state":2,
"firstname”:”testname”,
"lastname”:”testlastname”,
"profile_medium”:”image/athlete/medium.png",
"profile":"image/athlete/large.png",
"city”:”testcity”,
"state”:”teststate”,
…
},
"name”:”test name“,
"distance":87223.1,
"moving_time":11026,
"elapsed_time":11173,
"total_elevation_gain":682.3,
…
}]
I would like to combine all those object based on the athlete.firstname + athlete.lastname.
So for example all objects with athlete first name Jim and last name Donalds I want to be combined in one object, the same goes for all the other names.
When combining the objects based on the full name, values like "distance", "moving_time", "elapsed_time" and "total_elevation_gain" needs to be summed.
I tried using the code below but the problem is that I can't get it to work with multiple values like I mention above.
This is working only with one value, distance for example:
var obj = {}; // Initialize the object
angular.forEach(data, function(value, key) {
if (value.start_date > firstdayOfWeek && value.start_date < lastdayOfWeek) {
if (obj[value.athlete.firstname + ' ' + value.athlete.lastname]) { // If already exists
obj[value.athlete.firstname + ' ' + value.athlete.lastname] += value.distance; // Add value to previous value
} else {
obj[value.athlete.firstname + ' ' + value.athlete.lastname] = value.distance; // Add in object
}
} else {
//do nothing
}
});
console.log(obj); // Result
When I modify it like this it is not working anymore.
var obj = {};
angular.forEach(data, function(value, key) {
//console.log(value);
if (value.start_date > startOfLastWeek && value.start_date < endOfLastWeek) {
//console.log(value);
if (obj[value.athlete.firstname + ' ' + value.athlete.lastname]) { // If already exists
obj[value.athlete.firstname + ' ' + value.athlete.lastname] += {
"profile" : value.athlete.profile,
"distance" : value.distance,
"moving_time" : value.moving_time,
"elapsed_time" : value.elapsed_time,
"total_elevation_gain" : value.total_elevation_gain,
}; // Add value to previous value
} else {
obj[value.athlete.firstname + ' ' + value.athlete.lastname] = {
"profile" : value.athlete.profile,
"distance" : value.distance,
"moving_time" : value.moving_time,
"elapsed_time" : value.elapsed_time,
"total_elevation_gain" : value.total_elevation_gain,
}; // Add in object
}
} else {
//do nothing
}
});
console.log(obj); // Result
Thanks!

try to add item by item... You can't add all with += { ... }:
var obj = {};
angular.forEach(data, function(value, key) {
//console.log(value);
if (value.start_date > startOfLastWeek && value.start_date < endOfLastWeek) {
//console.log(value);
if (obj[value.athlete.firstname + ' ' + value.athlete.lastname]) { // If already exists
var aux = obj[value.athlete.firstname + ' ' + value.athlete.lastname];
aux.profile += value.athlete.profile;
aux.distance += value.distance;
...
} else {
obj[value.athlete.firstname + ' ' + value.athlete.lastname] = {
"profile" : value.athlete.profile,
"distance" : value.distance,
"moving_time" : value.moving_time,
"elapsed_time" : value.elapsed_time,
"total_elevation_gain" : value.total_elevation_gain,
}; // Add in object
}
} else {
//do nothing
}
});
console.log(obj); // Result

Use underscore or lodash groupBy, map and reduce
with lodash:
_.chain(myArr).map(function(o) {
return {
fullname: o.athlete.firstname + ' ' + o.athlete.lastname,
distance: o.distance,
moving_time: o.moving_time,
elapsed_time: o.elapsed_time,
total_elevation_gain: o.total_elevation_gain
}
}).groupBy(function(o) {
return o.fullaname
}).map(function(d, fullname) {
var totals = _.reduce(d, function(result, run, n) {
result.moving_time += run.moving_time | 0
result.elapsed_time += run.elapsed_time | 0
result.total_elevation_gain += run.total_elevation_gain | 0
return result
}, {
moving_time: 0,
elapsed_time: 0,
total_elevation_gain: 0
})
totals.fullname = fullname
return totals
})

Related

Tricks or overrides to make ExtJS application strict CSP compatible

when the server sends a restrictive Content-Security-Policy header,
Content-Security-Policy: default-src 'self'; script-src 'self'; img-src 'self'
the following error comes up in Chrome :
Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".
Strict Content-Security-Policy does not allow eval-like mechanisms, unless the 'unsafe-eval' keyword is specified.
Do you have any tricks or overrides to make your ExtJS application strict CSP compatible ?
The first culprit is the following code, where a Function object is being created:
getInstantiator: function(length) {
var instantiators = this.instantiators,
instantiator,
i,
args;
instantiator = instantiators[length];
if (!instantiator) {
i = length;
args = [];
for (i = 0; i < length; i++) {
args.push('a[' + i + ']');
}
instantiator = instantiators[length] = new Function('c', 'a', 'return new c(' + args.join(',') + ')'); //// CSP PB HERE
//<debug>
instantiator.name = "Ext.create" + length;
//</debug>
}
return instantiator;
},
"new Function" is used also here :
makeInitializeFn: function (cls) {
var code = ['var '],
body = ['\nreturn function (e) {\n var data = e.data, v;\n'],
work = 0,
bc, ec, // == beginClone, endClone
convert, expr, factory, field, fields, fs, hasDefValue, i, length;
if (!(fields = cls.rankedFields)) {
// On the first edit of a record of this type we need to ensure we have the
// topo-sort done:
fields = cls.rankFields();
}
for (i = 0, length = fields.length; i < length; ++i) {
// The generated method declares vars for each field using "f0".."fN' as the
// name. These are used to access properties of the field (e.g., the convert
// method or defaultValue).
field = fields[i];
fs = 'f' + i;
convert = field.convert;
if (i) {
code.push(', \n ');
}
code.push(fs, ' = $fields[' + i + ']');
//<debug>
// this can be helpful when debugging (at least in Chrome):
code.push(' /* ', field.name, ' */');
//</debug>
// NOTE: added string literals are "folded" by the compiler so we
// are better off doing an "'foo' + 'bar'" then "'foo', 'bar'". But
// for variables we are better off pushing them into the array for
// the final join.
if ((hasDefValue = (field.defaultValue !== undefined)) || convert) {
// For non-calculated fields that have some work required (a convert method
// and/or defaultValue), generate a chunk of logic appropriate for the
// field.
//expr = data["fieldName"];
expr = 'data["' + field.name + '"]';
++work;
bc = ec = '';
if (field.cloneDefaultValue) {
bc = 'Ext.clone(';
ec = ')';
}
body.push('\n');
if (convert && hasDefValue) {
// v = data.fieldName;
// if (v !== undefined) {
// v = f2.convert(v, e);
// }
// if (v === undefined) {
// v = f2.defaultValue;
// // or
// v = Ext.clone(f2.defaultValue);
// }
// data.fieldName = v;
//
body.push(' v = ', expr, ';\n' +
' if (v !== undefined) {\n' +
' v = ', fs, '.convert(v, e);\n' +
' }\n' +
' if (v === undefined) {\n' +
' v = ', bc, fs, '.defaultValue',ec,';\n' +
' }\n' +
' ', expr, ' = v;');
} else if (convert) { // no defaultValue
// v = f2.convert(data.fieldName,e);
// if (v !== undefined) {
// data.fieldName = v;
// }
//
body.push(' v = ', fs, '.convert(', expr, ',e);\n' +
' if (v !== undefined) {\n' +
' ', expr, ' = v;\n' +
' }\n');
} else if (hasDefValue) { // no convert
// if (data.fieldName === undefined) {
// data.fieldName = f2.defaultValue;
// // or
// data.fieldName = Ext.clone(f2.defaultValue);
// }
//
body.push(' if (', expr, ' === undefined) {\n' +
' ', expr, ' = ',bc,fs,'.defaultValue',ec,';\n' +
' }\n');
}
}
}
if (!work) {
// There are no fields that need special processing
return Ext.emptyFn;
}
code.push(';\n');
code.push.apply(code, body);
code.push('}');
code = code.join('');
// Ensure that Ext in the function code refers to the same Ext that we are using here.
// If we are in a sandbox, global.Ext might be different.
factory = new Function('$fields', 'Ext', code); /// CSP PROBLEM HERE
return factory(fields, Ext);
}
} // static
} // privates
},
This policy prevents new Function(), which rely upon for a performance optimisation in ExtJS, I suppose.
The policy prevents also the use of "eval".
Ext.JSON = (new(function() {
// #define Ext.JSON
// #require Ext
// #require Ext.Error
var me = this,
hasNative = window.JSON && JSON.toString() === '[object JSON]',
useHasOwn = !! {}.hasOwnProperty,
pad = function(n) {
return n < 10 ? "0" + n : n;
},
doDecode = function(json) {
return eval("(" + json + ')'); // jshint ignore:line //////CSP PROBLEM
},
...
// in Template.js
evalCompiled: function($) {
// We have to use eval to realize the code block and capture the inner func we also
// don't want a deep scope chain. We only do this in Firefox and it is also unhappy
// with eval containing a return statement, so instead we assign to "$" and return
// that. Because we use "eval", we are automatically sandboxed properly.
eval($); // jshint ignore:line
return $;
},
//in XTemplateCompiler
evalTpl: function ($) {
// We have to use eval to realize the code block and capture the inner func we also
// don't want a deep scope chain. We only do this in Firefox and it is also unhappy
// with eval containing a return statement, so instead we assign to "$" and return
// that. Because we use "eval", we are automatically sandboxed properly.
eval($);
return $;
},
// in Managet.js
privates: {
addProviderClass: function(type, cls) {
this.providerClasses[type] = cls;
},
onApiLoadSuccess: function(options) {
var me = this,
url = options.url,
varName = options.varName,
api, provider, error;
try {
// Variable name could be nested (default is Ext.REMOTING_API),
// so we use eval() to get the actual value.
api = Ext.apply(options.config, eval(varName)); ////////CSP
provider = me.addProvider(api);
}
// in Ext.dom.Query
eval("var batch = 30803, child, next, prev, byClassName;");
//...
eval(fn.join(""));

How to map and send emails to specific users who don't meet a certain criteria in Javascript/GAS

I am new in Javascript and bit by bit I have used resources here on StackOverflow to build on a project that uses external API to get time entries for users from the 10k ft project management system. I have finally have different functions together as follows:
Calls for user details which includes user_id
Get the time entries and sums up for every user who's approval has a value (pending or approval) in a specific date range. Those without approval will be ignored in the summation and their total entries left at 0.
My challenge now is to have only those with 0 as total hours of time entries receive emails to update their time entries. This code doesn't seem to select only those with 0 and send emails specifically to them. I will appreciate any pointers and/or assistance. after sending the email, this should be recorded on Google sheet
var TKF_URL = 'https://api.10000ft.com/api/v1/';
var TKF_AUTH = 'auth'
var TKF_PGSZ = 2500
var from = '2020-01-20'
var to = '2020-01-26'
var options = {
method: 'get',
headers: {
Authorization: 'Bearer ' + TKF_AUTH
}
};
function getUsers() {
var userarray = [];
var lastpage = false;
var page = 1;
do {
// gets 10kft data
var users = read10k_users(page);
// writes data from current page to array
for (var i in users.data) {
var rec = {};
// pushing of mandatory data
rec.id = users.data[i].id;
rec.display_name = users.data[i].display_name;
rec.email = users.data[i].email;
userarray.push(rec);
}
// checks if this is the last page (indicated by paging next page link beeing null
if (users.paging.next != null) {
lastpage = false;
var page = page + 1;
} else {
lastpage = true;
}
}
while (lastpage == false);
return (userarray);
return (userarray);
}
function read10k_users(page) {
var endpoint = 'users?';
var url = TKF_URL + endpoint + 'per_page=' + TKF_PGSZ + '&auth=' + TKF_AUTH + '&page=' + page;
var response = UrlFetchApp.fetch(url, options);
var json = JSON.parse(response);
//Logger.log(json.data)
return (json);
}
function showTimeData() {
var users = getUsers()
var time_array = [];
for (var i = 0; i < users.length; i++) {
// Logger.log(users[i].id)
var url = 'https://api.10000ft.com/api/v1/users/' + users[i].id + '/time_entries?fields=approvals' + '&from=' + from + '&to=' + to + '&auth=' + TKF_AUTH + '&per_page=' + TKF_PGSZ;
var response = UrlFetchApp.fetch(url, options);
var info = JSON.parse(response.getContentText());
var content = info.data;
var total_hours = 0;
for (var j = 0; j < content.length; j++) {
if (content[j].approvals.data.length > 0) {
total_hours += content[j].hours;
}
}
Logger.log('User name: ' + users[i].display_name + ' ' + 'User id: ' + users[i].id + ' ' + 'total hours: ' + total_hours+ ' ' + 'Email: ' + users[i].email)
}
}
function sendMail(showTimeData){
var emailAddress = user.email;
var message = 'Dear ' + user.display_name + 'Please update your details in the system'
var subject = ' Reminder';
MailApp.sendEmail(emailAddress, subject, message);
}
I was able to get a solution for this as follows:
for (var j = 0; j < content.length; j++) {
if (content[j].approvals.data.length > 0) {
total_hours += content[j].hours;
}
}
Logger.log('User name: ' + users[i].display_name + ' ' + 'User id: ' + users[i].id + ' ' + 'total hours: ' + total_hours + ' ' + 'Email: ' + users[i].email)
if (total_hours == 0) {
sendMail(users[i])
}
}
}
function sendMail(user) {
var emailAddress = user.email;
var message = 'Dear ' + user.display_name + 'Please update your details in the system'
var subject = ' Reminder';
MailApp.sendEmail(emailAddress, subject, message);
}

Angularjs code refactoring

I have my angularjs project, in this I have this code block, which is fired depending upon the dropdown selection. In this code the if and else part is mostly similar, I want to refactor the code so that the code is not repeated.
if (1 === $scope.form.type) {
response = $scope.resource.searchItemSalesInfo(params.get, params.post,function(response, headers) {
angular.forEach(response, function(row, id) {
response[id].prod_info = row.alias + ' (' + row.final_product_id + ') ';
});
$scope.totalCount = headers('x-total-count');
});
} else {
response = $scope.resource.searchOrderSalesInfo(params.get, params.post,function(response, headers) {
angular.forEach(response, function(row, id) {
response[id].prod_info = row.alias + ' (' + row.final_product_id + ') ';
});
$scope.totalCount = headers('x-total-count');
});
}
I tried to take the common functionality out in the below manner, but then the code does not works, and it breaks the functionality.
$scope.callresource = function(resourcename){
response = $scope.resource.resourcename(params.get, params.post,function(response, headers) {
angular.forEach(response, function(row, id) {
response[id].prod_info = row.alias + ' (' + row.final_product_id + ') ';
});
$scope.totalCount = headers('x-total-count');
});
}
if (1 === $scope.form.type) {
$scope.callresource(searchItemSalesInfo);
} else {
$scope.callresource(searchOrderSalesInfo);
}
Alternatively to my other reply, you could pass a reference to the function that you want to be executed in $scope.callresource ... in other words a callback function
I believe this is the more scalable approach.
$scope.callresource = function(resourceCallbackFunction){
response = resourceCallbackFunction(params.get, params.post,function(response, headers) {
angular.forEach(response, function(row, id) {
response[id].prod_info = row.alias + ' (' + row.final_product_id + ') ';
});
$scope.totalCount = headers('x-total-count');
});
}
if (1 === $scope.form.type) {
$scope.callresource($scope.resource.searchItemSalesInfo);
} else {
$scope.callresource($scope.resource.searchOrderSalesInfo);
}
You can't call the function using this syntax $scope.resource.resourcename because this syntax is telling JS to execute a function called resourcename.
Instead, try using the bracket notation:
$scope.resource[resourcename](..)
Also, when invoking the $scope.callresource function, pass the arguments as strings, because searchItemSalesInfo and searchOrderSalesInfo currently JS is trying to find them as variables.
$scope.callresource('searchItemSalesInfo');
Your code would look like this:
$scope.callresource = function(resourcename){
response = $scope.resource[resourcename](params.get, params.post,function(response, headers) {
angular.forEach(response, function(row, id) {
response[id].prod_info = row.alias + ' (' + row.final_product_id + ') ';
});
$scope.totalCount = headers('x-total-count');
});
}
if (1 === $scope.form.type) {
$scope.callresource('searchItemSalesInfo');
} else {
$scope.callresource('searchOrderSalesInfo');
}

I cant break my foreach with ajax inside

I have this:
var addresses_tmp = addresses.slice();
var final_addresses = [];
addresses.forEach(function (current_address, i) {
var near_addresses = [];
near_addresses.push(current_address);
addresses_tmp.forEach(function (next_address, j) {
if (current_address.address.info.code != next_address.address.info.code) {
var data = {
key : $scope.MicrosoftKey,
optmz : "distance",
routeAttributes : "routePath"
}
data["wp.0"] = current_address.address.location.lat + "," + current_address.address.location.lng;
data["wp.1"] = next_address.address.location.lat + "," + next_address.address.location.lng;
ajax.sendApiRequest(data, "GET", "http://dev.virtualearth.net/REST/V1/Routes/Driving", is_url=true).then(
function(response) {
var distance = response.data.resourceSets[0].resources[0].travelDistance;
if (distance < 0.020) {
near_addresses.push(next_address);
addresses_tmp.splice(j, 1);
}
if (j == addresses.length - 1) {
final_addresses.push(near_addresses);
if (near_addresses.length == 1) {
addresses_tmp.splice(j, 1);
}
addresses_tmp.splice(i, 1);
}
// if (count == addresses.length * addresses.length) {
// console.log("ya he acabado todasssss")
// }
},
function(error) {
console.log("error", error);
}
)
}
})
})
And I would like to break all function when the first foreach and the second foreach are finished but I cant put if condition to do this.
As I have ajax inside the second foreach, my variables are crazy so I cant put an if condition to break it.
I need to do this because I am compare two arrays and getting distance between two points (one in the first array and second in the other array)

ExtJS - null safe retrieval of complex objects using JsonReader

I am using a JsonReader to map Json data to variables to be used in a grid/form. The back end is in Java and there are complex objects which I Jsonify and pass to the ExtJS front end.
This is a part of my JsonReader which tries to retrieve a nested object -
{name:'status', type: 'string', mapping: 'status.name'}
This works fine when status has a value (not null in the server), but the grid load fails when status is null. Currently the work around I have is to send an empty object from the server in case of null, but I assume there should be a way to handle this in ExtJS. Please suggest a better solution on the ExtJS side.
I can think of two possibilities - one documented and one undocumented:
use the convert()-mechanism of Ext.data.Field:
{
name:'status',
mapping: 'status',
convert: function(status, data) {
if (!Ext.isEmpty(status) && status.name) {
return status.name;
} else {
return null;
}
}
}
The mapping property can also take an extractor function (this is undocumented so perhaps it may be a little bit risky to rely on this):
{
name:'status',
mapping: function(data) {
if (data.status && data.status.name) {
return data.status.name;
} else {
return null;
}
}
}
Use this safe json reader instead:
Ext.define('Ext.data.reader.SafeJson', {
extend: 'Ext.data.reader.Json',
alias : 'reader.safe',
/**
* #private
* Returns an accessor function for the given property string. Gives support for properties such as the following:
* 'someProperty'
* 'some.property'
* 'some["property"]'
* This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
*/
createAccessor: function() {
var re = /[\[\.]/;
return function(expr) {
if (Ext.isEmpty(expr)) {
return Ext.emptyFn;
}
if (Ext.isFunction(expr)) {
return expr;
}
if (this.useSimpleAccessors !== true) {
var i = String(expr).search(re);
if (i >= 0) {
if (i > 0) { // Check all property chain for existence. Return null if any level does not exist.
var a = [];
var l = expr.split('.');
var r = '';
for (var w in l) {
r = r + '.' + l[w];
a.push('obj' + r);
}
var v = "(" + a.join(" && ") + ") ? obj." + expr + " : null";
return Ext.functionFactory('obj', 'return (' + v + ')');
} else {
return Ext.functionFactory('obj', 'return obj' + expr);
}
}
}
return function(obj) {
return obj[expr];
};
};
}()
});
I have changed Slava Nadvorny's example so that it completely works for ExtJS 4.1.1.
New extended class of Ext.data.reader.Json is below:
Ext.define('Ext.data.reader.SafeJson', {
extend: 'Ext.data.reader.Json',
alias : 'reader.safejson',
/**
* #private
* Returns an accessor function for the given property string. Gives support for properties such as the following:
* 'someProperty'
* 'some.property'
* 'some["property"]'
* This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
*/
createAccessor: (function() {
var re = /[\[\.]/;
return function(expr) {
if (Ext.isEmpty(expr)) {
return Ext.emptyFn;
}
if (Ext.isFunction(expr)) {
return expr;
}
if (this.useSimpleAccessors !== true) {
var i = String(expr).search(re);
if (i >= 0) {
if (i > 0) { // Check all property chain for existence. Return null if any level does not exist.
var a = [];
var l = expr.split('.');
var r = '';
for (var w in l) {
r = r + '.' + l[w];
a.push('obj' + r);
}
var v = "(" + a.join(" && ") + ") ? obj." + expr + " : null";
return Ext.functionFactory('obj', 'return (' + v + ')');
} else {
return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
}
}
}
return function(obj) {
return obj[expr];
};
};
}()),
/**
* #private
* #method
* Returns an accessor expression for the passed Field. Gives support for properties such as the following:
*
* - 'someProperty'
* - 'some.property'
* - 'some["property"]'
*
* This is used by buildExtractors to create optimized on extractor function which converts raw data into model instances.
*/
createFieldAccessExpression: (function() {
var re = /[\[\.]/;
return function(field, fieldVarName, dataName) {
var me = this,
hasMap = (field.mapping !== null),
map = hasMap ? field.mapping : field.name,
result,
operatorSearch;
if (typeof map === 'function') {
result = fieldVarName + '.mapping(' + dataName + ', this)';
} else if (this.useSimpleAccessors === true || ((operatorSearch = String(map).search(re)) < 0)) {
if (!hasMap || isNaN(map)) {
// If we don't provide a mapping, we may have a field name that is numeric
map = '"' + map + '"';
}
result = dataName + "[" + map + "]";
} else {
if (operatorSearch > 0) {
var a = [];
var l = map.split('.');
var r = '';
for (var w in l) {
r = r + '.' + l[w];
a.push(dataName + r);
}
result = "("+a.join(" && ")+") ? "+dataName+"."+map+" : null";
} else {
result = dataName + map;
}
}
return result;
};
}())
});
So you can successfully processing nested JSON-data with null nodes.
Example of JSON:
{
root: [{
id: 1,
name: {
name: "John",
phone: "123"
},
},
{
id: 4,
name: null,
},
]
}
Working example with test data you can find here:
http://jsfiddle.net/8Ftag/

Resources