Jquery - Counting JSON objects - javascript

Im working on a chart system and i'm grabing my data from a ajax json reponse. Im just trying to count certain JSON objects and group them into categories.
For example im trying to loop on every JSON objects and seperate all the colors (Red, Blue, Yellow) into groups and within those groups I want to divide the results by month.I could then grab the results and put it into my charts.
Im currently getting the error:
Cannot set property '2016-10' of undefined
Heres an example of what im trying to accomplish. https://jsfiddle.net/awo5aaqb/5/
Here's the code that been causing me issues:
var dateCounts_Red = {};
var dateCounts_Blue = {};
var dateCounts_Yellow = {};
data.d.results.forEach(function(element) {
var date = element.created_date.slice(0, 7);
var yr = date.slice(0, 4);
var Color = element.colorvalue;
if (Color === "Red") {
if (!dateCounts_Red.hasOwnProperty(yr)) {
dateCounts_Red[yr] = {}
}
if (!dateCounts_Red[yr].hasOwnProperty(date)) {
dateCounts_Red[yr][date] = 0
}
dateCounts_Red[yr][date]++;
}else {dateCounts_Red[yr][date] = 0}
if (Color === "Blue") {
if (!dateCounts_Blue.hasOwnProperty(yr)) {
dateCounts_Blue[yr] = {}
}
if (!dateCounts_Blue[yr].hasOwnProperty(date)) {
dateCounts_Blue[yr][date] = 0
}
dateCounts_Blue[yr][date]++;
}else {dateCounts_Blue[yr][date] = 0}
if (Color === "Yellow") {
if (!dateCounts_Yellow.hasOwnProperty(yr)) {
dateCounts_Yellow[yr] = {}
}
if (!dateCounts_Yellow[yr].hasOwnProperty(date)) {
dateCounts_Yellow[yr][date] = 0
}
dateCounts_Yellow[yr][date]++;
}else {dateCounts_Yellow[yr][date] = 0}
});
Red_yr_2015_data = [dateCounts_Red['2015']['2015-01'],dateCounts_Red['2015']['2015-02'],dateCounts_Red['2015']['2015-03'],dateCounts_Red['2015']['2015-04'],dateCounts_Red['2015']['2015-05'],dateCounts_Red['2015']['2015-06'],dateCounts_Red['2015']['2015-07'],dateCounts_Red['2015']['2015-08'],dateCounts_Red['2015']['2015-09'],dateCounts_Red['2015']['2015-10'],dateCounts_Red['2015']['2015-11'],dateCounts_Red['2015']['2015-12']];
Blue_yr_2015_data = [dateCounts_Blue['2015']['2015-01'],dateCounts_Blue['2015']['2015-02'],dateCounts_Blue['2015']['2015-03'],dateCounts_Blue['2015']['2015-04'],dateCounts_Blue['2015']['2015-05'],dateCounts_Blue['2015']['2015-06'],dateCounts_Blue['2015']['2015-07'],dateCounts_Blue['2015']['2015-08'],dateCounts_Blue['2015']['2015-09'],dateCounts_Blue['2015']['2015-10'],dateCounts_Blue['2015']['2015-11'],dateCounts_Blue['2015']['2015-12']];
Yellow_yr_2015_data = [dateCounts_Yellow['2015']['2015-01'],dateCounts_Yellow['2015']['2015-02'],dateCounts_Yellow['2015']['2015-03'],dateCounts_Yellow['2015']['2015-04'],dateCounts_Yellow['2015']['2015-05'],dateCounts_Yellow['2015']['2015-06'],dateCounts_Yellow['2015']['2015-07'],dateCounts_Yellow['2015']['2015-08'],dateCounts_Yellow['2015']['2015-09'],dateCounts_Yellow['2015']['2015-10'],dateCounts_Yellow['2015']['2015-11'],dateCounts_Yellow['2015']['2015-12']];
Red_yr_2016_data = [dateCounts_Red['2016']['2016-01'],dateCounts_Red['2016']['2016-02'],dateCounts_Red['2016']['2016-03'],dateCounts_Red['2016']['2016-04'],dateCounts_Red['2016']['2016-05'],dateCounts_Red['2016']['2016-06'],dateCounts_Red['2016']['2016-07'],dateCounts_Red['2016']['2016-08'],dateCounts_Red['2016']['2016-09'],dateCounts_Red['2016']['2016-10'],dateCounts_Red['2016']['2016-11'],dateCounts_Red['2016']['2016-12']];
Blue_yr_2016_data = [dateCounts_Blue['2016']['2016-01'],dateCounts_Blue['2016']['2016-02'],dateCounts_Blue['2016']['2016-03'],dateCounts_Blue['2016']['2016-04'],dateCounts_Blue['2016']['2016-05'],dateCounts_Blue['2016']['2016-06'],dateCounts_Blue['2016']['2016-07'],dateCounts_Blue['2016']['2016-08'],dateCounts_Blue['2016']['2016-09'],dateCounts_Blue['2016']['2016-10'],dateCounts_Blue['2016']['2016-11'],dateCounts_Blue['2016']['2016-12']];
Yellow_yr_2016_data = [dateCounts_Yellow['2016']['2016-01'],dateCounts_Yellow['2016']['2016-02'],dateCounts_Yellow['2016']['2016-03'],dateCounts_Yellow['2016']['2016-04'],dateCounts_Yellow['2016']['2016-05'],dateCounts_Yellow['2016']['2016-06'],dateCounts_Yellow['2016']['2016-07'],dateCounts_Yellow['2016']['2016-08'],dateCounts_Yellow['2016']['2016-09'],dateCounts_Yellow['2016']['2016-10'],dateCounts_Yellow['2016']['2016-11'],dateCounts_Yellow['2016']['2016-12']];
I know the code is very clunky at the moment, but would anyone know of a better aproach to this?

It fails because if the color is not red, it goes to an else and tries to set the object, but the object was not created.
if (Color === "Red") {
if (!dateCounts_Red.hasOwnProperty(yr)) { <-- you create it here
dateCounts_Red[yr] = {}
}
if (!dateCounts_Red[yr].hasOwnProperty(date)) {
dateCounts_Red[yr][date] = 0
}
dateCounts_Red[yr][date]++;
}else {dateCounts_Red[yr][date] = 0} <-- it needed to be set here too...
So move the first hasOwnProperty check outside of the if since both the if and else need it.

Instead of re-inventing the wheel, try wrapping your head around the problem in this fashion:
Grouping objects by an attribute of the object is a common task. There are many libraries that help with this.
groupBy (documentation from underscore website)
Splits a collection into sets, grouped by the result of running each
value through iteratee. If iteratee is a string instead of a function,
groups by the property named by iteratee on each of the values.
_.groupBy([1.3, 2.1, 2.4], function(num){ return Math.floor(num); });
=> {1: [1.3], 2: [2.1, 2.4]}
_.groupBy(['one', 'two', 'three'], 'length');
=> {3: ["one", "two"], 5: ["three"]}
What is the most efficient method to groupby on a javascript array of objects?

if you want the easy way out
if (Color === "Red") {
if (!dateCounts_Red.hasOwnProperty(yr)) { <-- you create it here
dateCounts_Red[yr] = {}
}
if (!dateCounts_Red[yr].hasOwnProperty(date)) {
dateCounts_Red[yr][date] = 0
}
dateCounts_Red[yr][date]++;
}else {dateCounts_Red[yr]={date:0}} // set your object here, too

Related

On click, loop through each object key

I'm still learning JS and something is harder to understand than others.
Like so:
I am trying to change the theme of google maps by allowing users to click on a custom button.
I was using if else which works great but i wanted to add more themes and using a loop. Each time a user clicks, it selects:
object key 0,
then click again object key 2
and object key 3
and repeat
I can get the object keys and values how I'm lost after that.
This is the theme object
let theme = {
default: null,
night: [multiple objects with nested arrays],
dark: [multiple objects with nested arrays]
}
creating button inside google maps then addEventListener
let themeToggle = document.createElement('button');
themeToggle.classList.add('controlUI');
themeToggle.innerHTML = ('Mode');
themeToggle.title = 'Change map theme';
map.controls[google.maps.ControlPosition.TOP_LEFT].push(themeToggle);
let mode = true;
themeToggle.addEventListener('click', () => {
if (mode) {
map.setOptions({styles: theme.night});
} else {
map.setOptions({styles: theme.default});
}
mode = !mode;
});
Above Works Fine
Im struggling to convert the if else to a loop and select each object key and then adding that to:
map.setOptions({styles: theme.night})
and then on click it loops through each key and repeat
themeToggle.addEventListener('click', () => {
for ( let key in theme) {
map.setOptions({styles: theme[key]});
console.log(theme[key])
}
});
it selects the last one by default and i cant toggle.
Any help would e really appreciated, just trying add all the puzzle together.
Collect the object values into an array, then increment an index with modulo on every click:
const vals = Object.values(theme);
let i = 0;
themeToggle.addEventListener('click', () => {
map.setOptions({styles: vals[i]});
i = (i + 1) % vals.length;
});
While most environments will result in an object's Object.values in ascending numeric followed by insertion order, it's not guaranteed. If you need a guaranteed predictable ordering, use Reflect.ownKeys (or Object.getOwnPropertyNames) instead:
const vals = Reflect.ownKeys(theme)
.map(key => theme[key]);
You can loop through an object like this
var invoice = {
name: 'anik',
age: 29,
designation: 'Full Stack Developer'
}
Object.keys(invoice).map((d,i)=>{
console.log(d +' : '+invoice[d]);
})

Library or object that can check containment of lists

This question is an extension of this one: Checking containment in set of lists in javascript. I want to be able to use a set like function in nodejs or Javascript that can support checking whether or not a list belongs to a collection. For example, given the example in the link, I would like the behavior:
var s = new SetWithListCheckingAbility([[1,2], [2,3]])
s.has([2, 3])
true
I was unable to find any nodejs library that has this functionality, however. The other obvious solution seems to be JSON serializing each object that is added to the set object, and doing checking based on the JSON string, since Javascript equality works for strings. This would probably require subclassing the Set object in ES6. However, I am not sure how to do this for this case...
What you can do is take each member of the set and convert it to a string format (this answer looks like an elegant way to do that conversion from numbers to strings).
For your example, if you want s.has([3, 2]) to return false because [2,3] doesn't count as a match, the array to string conversion would look like array.join(','), otherwise array.sort().join(',') if order doesn't matter.
function setOfListsHasElement(theSet, theElement) {
let newSet = new Set();
theSet.forEach(e => newSet.add(e.join(',')) );
return newSet.has(theElement.join(','));
}
Example usage:
var theSet = new Set();
theSet.add([1,2]);
theSet.add([2,3]);
setOfListsHasElement(theSet, [2,3]); // true
setOfListsHasElement(theSet, [3,2]); // false
setOfListsHasElement(theSet, [2,6]); // false
setOfListsHasElement(theSet, ["1", "2"]); // true - don't know how you want to handle scenarios like this, where the string representation of ["1", "2"] matches that of [1,2]
I figured out how to write a custom class that does what we want:
class SetImproved extends Set{
constructor(){
super();
this.classDict = {};
this._size = 0;
}
get size(){
return this._size
}
add(x){
if(!(JSON.stringify(x) in this.classDict)){
this._size += 1;
}
this.classDict[JSON.stringify(x)] = x;
}
has(x){
return JSON.stringify(x) in this.classDict;
}
delete(x){
if(JSON.stringify(x) in this.classDict){
this._size -= 1;
}
delete this.classDict[JSON.stringify(x)];
}
clear(){
this.classDict = {};
}
keys(){
return Object.keys(this.classDict).map(x => this.classDict[x]);
}
entries(){
return Object.keys(this.classDict).map(x => this.classDict[x]);
}
}
Some examples of the functionality:
var setImproved = new SetImproved()
setImproved.add([1, "b"])
setImproved.add([2, "c"])
setImproved.add(3)
setImproved.add("asdf")
console.log(setImproved.has([1, "b"]))
console.log(setImproved.has([3]))
setImproved.delete([4])
setImproved.delete([1, "b"])
console.log(setImproved.has(3))
console.log(setImproved.entries())
console.log(setImproved.size)

Adding meta data to a primitive in javascript

Background
We have much of our data formatted like
var X = {value:'some val',error:'maybe an error',valid:true}
as a result we find ourselves calling X.value ALL the time.
We don't use the .error or .valid nearly as much, but we do use it.
What I want
To quit calling .value everywhere, but to still have access to meta data on a per data point level.
The Question
Is there one of
A) A way to put meta data on a primitive? attaching .error to an int for example? Is it possible for bools or strings?
B) A way to make a class that can be treated as a primitive, providing a specific data member when I do? IE X.value = 5, X+3 returns 8.
C) A better design for our data? Did we just lay this out wrong somehow?
You can set the method toString() to your object and return value.
var X = {
value: 1,
error:'maybe an error',
valid:true,
toString: function() {
return this.value;
}
}
X.value = 5;
console.log(X+3);
You can represent you data as a function object that also has properties:
var X = () => 1;
X.value = 1;
X.error = 'maybe an error';
X.valid = true,
console.log(X()); // 1
console.log(X.valid); // true
For better design you can encapsulate the creation of the data object in another function.

Ember store adding attributes incorrectly

I'm using the latest version of ember-cli, ember-data, ember-localstorage-adapter, and ember.
I have a Node object which has a parent and children. Since I had issues with creating multiple relationships with the same type of object, I decided to store the parentID in a string, and the childIDs in an array of strings. However, when I create a new Node and try to add the new Node's to the parents array of IDs, the ID ends up being added to the correct parent, but also other parents.
level 1 0
/ \
level 2 1 2
| |
level 3 3 4
In a structure like this, 0, 1, and 2 all have correct child and parent IDs. However, after adding 3 and 4, node 1 and node 2's childIDs are [3, 4], instead of [3], [4] respectively.
The Array attribute:
var ArrayTransform = DS.Transform.extend({
serialize: function(value) {
if (!value) {
return [];
}
return value;
},
deserialize: function(value) {
if (!value) {
return [];
}
return value;
}
});
The insertNode code:
insert: function(elem) {
var i,
_store = elem.node.store,
newNodeJSON = elem.node.serialize();
newNodeJSON.childIds = [];
newNodeJSON.level = getNextLevel();
_store.filter('node', function(node) {
return node.get('level') === newnodeJSON.level-1;
}).then(function(prevLevelNodes) {
// if no other nodes yet
if (prevLevelNodes.toArray().length === 0) {
makeNewNode(_store, newNodeJSON, elem.node);
}
// else, generates however many nodes that are in the previous level
else {
prevLevelNodes.toArray().forEach(function(node, idx) {
newNodeJSON.parentId = node.get('id');
makeNewNode(_store, newNodeJSON, elem.node);
});
}
});
}
var makeNewNode = function(_store, newNodeJSON, node) {
console.log(newNodeJSON.parentId); // returns correct value
var newNode = _store.createRecord('node', newNodeJSON);
newNode.save();
var newNodeId = newNode.get('id');
if (newNode.get('parentId')) {
_store.find('node', newNode.get('parentId')).then(function(n) {
var cids = n.get('childIds');
console.log(newNodeId); // returns expected value
console.log(cids); // **DOESN'T RETURN AN EMPTY ARRAY**: returns array with [3,4]
cids.push(newNodeId);
console.log(n.get('childIds')); // returns array with [3,4]
n.save();
});
}
To top this off, this error happens 90% of the time, but 10% of the time it performs as expected. This seems to suggest that there's some sort of race condition, but I'm not sure where that would even be. Some places that I feel like might be causing issues: the ember-cli compilation, passing the entire _store in when making a new node, ember-data being weird, ember-localstorage-adapter being funky... no clue.
For anyone else who may have this problem in the future: the problem lies in two things.
In ArrayTransform, typically I am returning the value sans modification.
In my insert code, I'm passing the same JSON that I defined at the top of the function to makeNewNode.
This JSON contains a reference to a single childIds array; therefore, each new node that gets created uses this same reference for its childIds. Although this doesn't quite explain why the cids array wasn't empty before the push executed (perhaps this is some sort of compiler oddity or console printing lag), it explains why these both Level 3 children were in both Level 2 parents' childIds array.
tl;dr: pass by value vs pass by reference error

AngularJS orderby with empty field

I am ordering a my data and its working all correcty except some fields are empty or have no value. When ordered these empty field come up first. For example when ordering numbers we would get a huge empty list before getting the "0"-values.
I am doing it like thise:
ng-click="predicate = 'name'; reverse=!reverse"
and
ng-repeat="name in names | orderBy:predicate:reverse"
JSFiddle: http://jsfiddle.net/JZuCX/1/
Is there an easy elegant way to fix this? I want the empty fields to come last, no matter what.
How about this for sorting strings:
item in (items|orderBy:['!name', 'name'])
The advantage (apart from being more concise) is it sorts null & undefined with the blank strings.
In my case I wanted the blanks & nulls & undefineds together at the top (nulls and undefineds by default sort to the bottom), so I used:
item in (items|orderBy:['!!name', 'name'])
I'd write a filter that takes items with empty name from ordered array and places them at the end:
<li ng-repeat="item in (items|orderBy:'name'|emptyToEnd:'name')">{{item.name}}</li>
Code might look like this:
.filter("emptyToEnd", function () {
return function (array, key) {
if(!angular.isArray(array)) return;
var present = array.filter(function (item) {
return item[key];
});
var empty = array.filter(function (item) {
return !item[key]
});
return present.concat(empty);
};
});
Working example.
By the way, your fiddle doesn't contain any relevant code. Did you use the wrong link?
Update 2:
Your fiddle with my filter.
Down here! :D
This solution extends the normal functionality of the angularJs orderBy filter to take a third argument specifying whether or not to invert the normal sorting of null and undefined values. It observes the property names it is passed (not just one), and doesn't iterate over items a second as some of the other solutions do. It's used like this:
<li ng-repeat="item in (items|orderBy:'name':false:true)">{{item.name}}</li>
I found a bunch of threads, some not directly about orderBy, and compiled their techniques plus a couple bits of my own into this:
angular.module('lib')
.config(['$provide', function ($provide) {
$provide.decorator('orderByFilter', ['$delegate', '$parse', function ($delegate, $parse) {
return function () {
var predicates = arguments[1];
var invertEmpties = arguments[3];
if (angular.isDefined(invertEmpties)) {
if (!angular.isArray(predicates)) {
predicates = [predicates];
}
var newPredicates = [];
angular.forEach(predicates, function (predicate) {
if (angular.isString(predicate)) {
var trimmed = predicate;
if (trimmed.charAt(0) == '-') {
trimmed = trimmed.slice(1);
}
var keyFn = $parse(trimmed);
newPredicates.push(function (item) {
var value = keyFn(item);
return (angular.isDefined(value) && value != null) == invertEmpties;
})
}
newPredicates.push(predicate);
});
predicates = newPredicates;
}
return $delegate(arguments[0], predicates, arguments[2]);
}
}])
}]);
To use this code verbatim, be to specify 'lib' as a dependency for your app.
Credits to:
$parse
[nullSorter].concat(originalPredicates)
decorator pattern
I don't believe there's an "out of the box" solution for this. I could easily be wrong.
Here's my attempt at a solution using a function as the predicate:
ng-repeat="name in names | orderBy:predicate"
Inside your controller:
$scope.predicate = function(name) {
return name === '' ? 'zzzzzzz' : !name;
/* The 'zzzzzz' forces the empty names to the end,
I can't think of a simpler way at the moment. */
}
In addition to the solution of Klaster_1, add an extra parameter to make the filter more generic:
http://jsfiddle.net/Zukzuk/JZuCX/27/
Implementation
<tr ng-repeat="name in (names | orderBy:predicate:reverse | orderEmpty:'name':'toBottom')">
Filter
.filter('orderEmpty', function () {
return function (array, key, type) {
var present, empty, result;
if(!angular.isArray(array)) return;
present = array.filter(function (item) {
return item[key];
});
empty = array.filter(function (item) {
return !item[key]
});
switch(type) {
case 'toBottom':
result = present.concat(empty);
break;
case 'toTop':
result = empty.concat(present);
break;
// ... etc, etc ...
default:
result = array;
break;
}
return result;
};
});
Thnx Klaster_1!
Sorting, and reverse sorting, using a variable sort column, and keeping the undefined at the bottom, even below the negative values
I love the elegance of Sean's answer above! I needed to give my users the ability to choose the column to sort on, and choice of sort direction, but still require the undefined's to fall to the bottom, even if there are negative numbers.
The key insight from Sean that fixes negative numbers is !!. Use '!'+predicate if you are doing forward sorting and '!!'+predicate if you are doing reverse sorting.
The snippet below demonstrates this. By the way, I have put the variables that set the predicate (choice of propery to sort on) and reverse inside an object ("d") just so that we don't get weird scope issues. You may not need the "d."s in your environment.
Moreover you would probably want to use something better than my crappy buttons at the bottom of the page to control your sort predicate and direction. However this keeps the key parts of the code easy to read.
function mainController($scope) {
$scope.userArray = [
{ name: "Don", age: 20 },
{ name: "Bob", age: 30, height: 170 },
{ name: "Abe", age: 40, height: 160 },
{ name: "Zoe", age: 70 },
{ age: 70, height: 155 },
{ name: "Shorty",age:45,height: -200},
{ name: "TwinkleInEye", age: -1, height: 152 }
]
$scope.d = {}; // Create an object into which info can be stored and not trashed by Angular's tendency to add scopes
$scope.d.predicate = "name"; // This string is the name of the property on which to sort
$scope.d.reverse = false; // True means reverse the sort order
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="" ng-controller="mainController">
<div ng-repeat="user in (userArray | orderBy: (d.reverse ?['!!'+d.predicate,d.predicate]:['!'+d.predicate,d.predicate]) : d.reverse)">
Name {{ user.name }} : Age {{ user.age }} : Height {{ user.height }}
</div>
<br/>
<button ng-click="d.predicate='name';">Name</button>
<button ng-click="d.predicate='age';">Age</button>
<button ng-click="d.predicate='height';">Height</button> Currently: {{d.predicate}}
<br/> Leave undefined at bottom, but otherwise:
<button ng-click="d.reverse= !d.reverse;">Reverse</button> Currently: {{d.reverse}}
</body>
#Klaster_1 was really on to something but as soon as I needed a nested value the filter stopped working. Also, if I was reverse ordering I still wanted my null values to show up before 0. I added $parse to take care of the nested keys and added a reverse parameter to I knew when to put the null values at the top.
.filter("emptyToEnd", function ($parse) {
return function (array, key, reverse) {
if(!angular.isArray(array)) return;
var keyFn = $parse(key);
var present = [];
var empty = [];
angular.forEach(array, function(item){
var val = keyFn(item);
if(angular.isUndefined(val) || val === null) {
empty.push(item);
} else {
present.push(item);
}
});
if (reverse) {
return present.concat(empty);
} else {
return empty.concat(present);
}
};
});
I don't know why other answer suggest to put the null value records at the bottom, If I want to sort normally, means in ASC order all the null on top and in DESC order all the nulls go to bottom, I tried other answers here but could not helped me so change the code to convert the null to '' in my array and it works now smooth like this:
$scope.convertNullToBlank = function (array) {
for (var i = 0; i < array.length; i++) {
if (array[i].col1 === null)
array[i].col1 = '';
if (array[i].col2 === null)
array[i].col2 = '';
}
return array;
}
I created a gist with an alternative filter based on the previous solutions:
https://gist.github.com/360disrupt/1432ee1cd1685a0baf8967dc70ae14b1
The filter extends the existing angular filter:
angular.module 'tsd.orderByEmptyLast', []
.filter 'orderByEmptyLast', ($filter) ->
return (list, predicate, reverse)->
orderedList = $filter('orderBy')(list, if reverse then ['!' + predicate, '-' + predicate] else ['!' + predicate, predicate] )
return orderedList
On newer angular versions you might need to include orderByFilter instead of using $filter
angular.module 'tsd.orderByEmptyLast', ['orderByFilter']
.filter 'orderByEmptyLast', () ->
return (list, predicate, reverse)->
orderedList = orderByFilter(list, if reverse then ['!' + predicate, '-' + predicate] else ['!' + predicate, predicate] )
return orderedList

Categories

Resources