Problem accessing array using Higcharts, how may i access to x position? - javascript

I'm trying to access an array position passed by UserOptions using Highcharts.
Series declaration, where i declare the array
Implementation
The problem is that, when i try to access an element of this array it is not shown.
Just see:
Accessing array
However, when i access it directly and print it using console.log() this value is shown.
console.log(a.series[1].userOptions.cuotasPendientes[5]);
Accessing directly
Thnx guys.
The code:
var a = Highcharts.chart('grafico-CIPrestamos-161107279383',{
"chart": {
"type": "column",
"style": {
"fontFamily": "Arial, Tahoma, Sans-serif",
"fontSize": "11px"
}
},
"credits": {
"enabled": false
},
"legend": {
"enabled": true,
"useHTML": true,
"itemMarginTop": 5,
"labelFormatter": function() {
return '<div class="highchartsCustom-legend-label">' + this.name + '</div>';
}
},
"title": {
"text": ""
},
"xAxis": {
"type": "datetime",
"dateTimeLabelFormats": {
"month": "%b"
},
"title": "",
"units": [
["month", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]]
],
"tickInterval": 1,
"labels": {
"useHTML": true,
"format": "<span class=\"highchartsCustom-xAxis-label\">{value:%b}</span>",
"rotation": 0
},
"plotLines": [{
"color": "#ccc",
"width": 1,
"value": 1525132800000,
"dashStyle": "Dash",
"label": {
"useHTML": true,
"text": "<span class=\"highchartsCustom-xAxis-plotLine-label\">2018</span>",
"rotation": 90
}
}, {
"color": "#ccc",
"width": 1,
"value": 1546300800000,
"dashStyle": "Dash",
"label": {
"useHTML": true,
"text": "<span class=\"highchartsCustom-xAxis-plotLine-label\">2019</span>",
"rotation": 90
}
}]
},
"yAxis": {
"allowDecimals": false,
"min": 0,
"title": "",
"labels": {
"useHTML": true,
"format": "<span class=\"highchartsCustom-yAxis-label\">{value:,.0f}</span>"
},
"stackLabels": {
"enabled": false
}
},
"tooltip": {
"useHTML": true,
"headerFormat": "<table class=\"highchartsCustom-tooltip\">",
"pointFormat": "<tr><td class=\"highchartsCustom-tooltip-fecha\">{point.x:%B %Y}</td></tr><tr><td class=\"highchartsCustom-tooltip-linkedParent\">{point.series.linkedParent.name}</td></tr><tr><td class=\"highchartsCustom-tooltip-serie\">FIELD:{point.series.userOptions.cuotasPendientes[3]}</td></tr><tr><td class=\"highchartsCustom-tooltip-serie\">{point.series.name}: <span>{point.y}</span></td></tr><tr><td class=\"highchartsCustom-tooltip-total\">Total: <span>{point.stackTotal:,.0f}</span></td></tr>",
"footerFormat": "</table>",
"style": {
"padding": "1px"
}
},
"plotOptions": {
"series": {
"pointStart": 1525132800000,
"pointIntervalUnit": "month",
"pointWidth": 20,
"events": {
"legendItemClick": function(event) {
var cantidadSeleccionables = event.target.chart.series.length - 1;
if (event.target.visible && cantidadSeleccionables > contadorCIPrestamos) {
contadorCIPrestamos += 1;
return true;
}
if (!event.target.visible) {
contadorCIPrestamos -= 1;
return true;
}
return false;
}
}
},
"column": {
"stacking": "normal",
"dataLabels": {
"enabled": false
}
}
},
"series": [{
"name": "BCO 1",
"id": "BCO1",
"linkedTo": null,
"data": null,
"color": "#B07CD8",
"CuotasPendientes": null,
"vigente": "La bco es vighente",
"Estado": null,
"CapitalOriginal": 0.0,
"TotalCuotas": 0
}, {
"name": "PP1",
"id": null,
"linkedTo": "BCO1",
"data": [22.0, 23.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
"color": "#B07CD8",
"cuotasPendientes": [0, 0, 223, 223, 223, 223, 223, 223, 223, 223, 223],
"vigente": "La serie es vigente",
"Estado": null,
"CapitalOriginal": 0.0,
"TotalCuotas": 0
}],
"colors": ["#B07CD8"]})
console.log(a.series[1].userOptions.cuotasPendientes[5]);

I tried using the formatther and it works, also i doscovered the way to access too.
Instead of using
point.series.cuotasPendientes[2], we have to use point.series.cuotasPendientes.2
Thanks for all.

u can call it directely or loop through CuotasPendientes ur value this way and dont forget to check CuotasPendientes value if its null the loop will not process (i already added it the null check).
console.log(a.series[1].CuotasPendientes[0])
for(let i = 0 ; i < a.series.length; i++){
if (a.series[i].CuotasPendientes != null) {
for (x of a.series[i].CuotasPendientes) {
console.log(x)
}
}
}
Hope its help

Related

Comparison between two Arrays of Objects failing when item is deleted

I have the following function that should determine which elements in a nested object were changed, deleted or added:
static modifiedDiff(o1, o2, deep = false, added = [], updated = [], removed = [], path = "", key = "") {
path += key.length > 0 ? key + "." : '';
for (const prop in o1) {
if (o1.hasOwnProperty(prop)) {
const o2PropValue = o2[prop];
const o1PropValue = o1[prop];
if (o2.hasOwnProperty(prop)) {
if (o2PropValue === o1PropValue) {
//unchanged[prop] = o1PropValue;
} else {
if (deep && this.isObject(o1PropValue) && this.isObject(o2PropValue)) {
this.modifiedDiff(o1PropValue, o2PropValue, deep, added, updated, removed, path, this.modifyPropIfNeeded(prop));
} else {
this.addObjectToArray(updated, path + prop, o2PropValue);
}
}
} else {
this.addObjectToArray(removed, path + prop, o1PropValue);
}
}
}
for (const prop in o2) {
if (o2.hasOwnProperty(prop)) {
const o1PropValue = o1[prop];
const o2PropValue = o2[prop];
if (o1.hasOwnProperty(prop)) {
if (o1PropValue !== o2PropValue) {
if (!deep || !this.isObject(o1PropValue)) {
//updated[prop].oldValue = o1PropValue;
}
}
} else {
this.addObjectToArray(added, path + prop, o2PropValue);
}
}
}
return {
added,
updated,
removed
};
}
This works fine for changes, and elements that are added to the second object.
The result looks like this:
{
"added": [
{
"_objects.4": {
"type": "textbox",
"version": "4.1.0",
"originX": "center",
"originY": "center",
"left": 10,
"top": 10,
"width": 84.51,
"height": 45.2,
"fill": "#000000",
"stroke": null,
"strokeWidth": 1,
"strokeDashArray": null,
"strokeLineCap": "butt",
"strokeDashOffset": 0,
"strokeLineJoin": "miter",
"strokeMiterLimit": 4,
"scaleX": 0.5,
"scaleY": 0.5,
"angle": 0,
"flipX": false,
"flipY": false,
"opacity": 1,
"shadow": null,
"visible": true,
"backgroundColor": "",
"fillRule": "nonzero",
"paintFirst": "fill",
"globalCompositeOperation": "source-over",
"skewX": 0,
"skewY": 0,
"text": "hallo",
"fontSize": 40,
"fontWeight": "",
"fontFamily": "helvetica",
"fontStyle": "",
"lineHeight": 1.16,
"underline": false,
"overline": false,
"linethrough": false,
"textAlign": "left",
"textBackgroundColor": "",
"charSpacing": 0,
"minWidth": 20,
"splitByGrapheme": false,
"id": "text_835392",
"styles": {}
}
}
],
"updated": [
{
"_objects.1.left": 372.81
},
{
"_objects.1.top": 179.99
}
],
"removed": []
}
The problem is the following:
If one element in an array of o2 is deleted, every element of that array moves one index forward.
This results in all elements after the deleted element to be moved into the changed or added array.
Example:
modifiedDiff([1,2,3,4], [1,2,4])
should be:
{
"added": [],
"updated": [],
"removed": [
{
"2": 3
}
]
}
but is:
{
"added": [],
"updated": [
{
"2": 4
}
],
"removed": [
{
"3": 4
}
]
}
Any ideas? Thanks for your help!

SVG tooltip z-index with html title

I'm trying to use an HTML title with a very large tooltip. However I can't seem to get the tooltip background to appear above the title.
Here's my code:
var chart = new Highcharts.Chart({
"chart": {
"type": "gauge",
"renderTo": "chart-2-container",
"marginTop": 60
},
"series": [{
"data": [{
"y": 55.6,
"name": "Area",
"tooltip": "Area: 50.6 %<br/>Minimum: 50.6<br/>3rd quartile: 57.1<br/>2nd quartile: 59.4<br/>1st quartile: 64.7<br/>Maximum: 75.7"
}],
"name": "%"
}],
"tooltip": {
"borderColor": "#E2E2E2",
"borderRadius": 5,
"backgroundColor": "white",
"style": {
"color": "#454545",
"fontSize": 14,
"fontFamily": "Arial, sans-serif",
"zIndex": 9999,
"lineHeight": 14 * 1.4
},
"formatter": function() {
return this.point.tooltip;
}
},
"title": {
"floating": true,
"useHTML": true,
"style": {
"zIndex": 1,
},
"text": "This is some link as a very long title which will probably wrap a couple of lines"
},
"yAxis": {
"title": null,
"tickPixelInterval": 72,
"tickLength": 10,
"minorTickLength": 8,
"minorTickWidth": 1,
"min": 50.6,
"max": 75.7,
"plotBands": [{
"from": 50.6,
"to": 57.1,
"color": "#ee2c34",
"thickness": 15,
}, {
"from": 57.1,
"to": 59.4,
"color": "#f07627",
"thickness": 15,
}, {
"from": 59.4,
"to": 64.7,
"color": "#a88735",
"thickness": 15,
}, {
"from": 64.7,
"to": 75.7,
"color": "#2c876d",
"thickness": 15,
}]
},
"pane": {
"startAngle": -150,
"endAngle": 150
}
});
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/highcharts-more.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='chart-2-container' style="width: 400px; height: 300px;">
</div>
(also as a fiddle https://jsfiddle.net/d6q1gt4m/)
The problem is that the background is always behind the title. I could switch to non-html title but then I have the problem that only the 1st line is actually a URL if the title is wrapping (and I need to reapply all my link styles).
Does anyone know how I can get the svg tooltip to appear on top of HTML ?
Note: I would like to avoid having to set useHTML for the tooltip if possible as that opens up another can of worms for me.
With CSS and updating tooltip and tooltip formatter the problem is fixed.
For reference check tooltip
Updated tooltip
"tooltip": {
"backgroundColor": "rgba(255,255,255,0)",
"borderWidth": 0,
"borderRadius": 1,
"shadow": false,
"useHTML": true,
"style": {
"color": "#454545",
"fontSize": 14,
"fontFamily": "Arial, sans-serif",
"zIndex": 9999,
"lineHeight": 1.8
},
"formatter": function() {
return "<div>" + this.point.tooltip + "</div>";
}
},
css
.highcharts-tooltip {
z-index: 9998;
}
.highcharts-tooltip div {
background-color: white;
border: 1px solid #E2E2E2;
opacity: 1;
z-index: 9999!important;
padding: 5px
}
Fiddle demo

Offset guides label in case of text overlapping in amcharts

I would like to include guides into my amcharts graphs and I found very descriptive examples. However I'm struggling with positioning of label text, especially in case when guides are so close that labels overlap.
Here is example code https://jsfiddle.net/Tripy/1wwygcy7/2/
HTML:
<script src="https://www.amcharts.com/lib/3/amcharts.js"></script>
<script src="https://www.amcharts.com/lib/3/serial.js"></script>
<script src="https://www.amcharts.com/lib/3/amstock.js"></script>
<div id="chartdiv" style="width: 100%; height: 500px;"></div>
Javascript:
var chartData = weekendGuides = [];
generateChartData();
function generateChartData() {
var firstDate = new Date();
firstDate.setDate( firstDate.getDate() - 200 );
firstDate.setHours( 0, 0, 0, 0 );
for ( var i = 0; i < 200; i++ ) {
var newDate = new Date( firstDate );
newDate.setDate( newDate.getDate() + i );
var a1 = Math.round( Math.random() * ( 40 + i ) ) + 100 + i;
var b1 = Math.round( Math.random() * ( 1000 + i ) ) + 500 + i * 2;
chartData.push( {
"date": newDate,
"value": a1,
"volume": b1
} );
// add weekend guide
if ( 6 == newDate.getDay() ) {
var toDate = new Date( newDate );
toDate.setDate( newDate.getDate() + 2 );
weekendGuides.push( {
"date": newDate,
"toDate": toDate,
"lineAlpha": 0,
"fillAlpha": 0.05,
"fillColor": "#000",
"expand": true
} );
}
}
}
var chart = AmCharts.makeChart( "chartdiv", {
"type": "stock",
"dataSets": [ {
"title": "first data set",
"fieldMappings": [ {
"fromField": "value",
"toField": "value"
}, {
"fromField": "volume",
"toField": "volume"
} ],
"dataProvider": chartData,
"categoryField": "date"
} ],
"panels": [ {
"showCategoryAxis": false,
"title": "Value",
"percentHeight": 70,
"stockGraphs": [ {
"id": "g1",
"valueField": "value",
"comparable": true,
"compareField": "value",
"balloonText": "[[title]]:<b>[[value]]</b>",
"compareGraphBalloonText": "[[title]]:<b>[[value]]</b>"
} ],
"stockLegend": {
"periodValueTextComparing": "[[percents.value.close]]%",
"periodValueTextRegular": "[[value.close]]"
},
"categoryAxis": {
"guides": weekendGuides
},
"valueAxes": [ {
"guides": [ {
"value": 325,
"lineAlpha": 0.8,
"lineColor": "#0c0",
"label": "Guide #1",
"position": "right"
}, {
"value": 322,
"lineAlpha": 0.8,
"lineColor": "#0c0",
"label": "Guide #2",
"position": "right"
}]
} ]
} ],
"chartScrollbarSettings": {
"graph": "g1"
},
"chartCursorSettings": {
"valueBalloonsEnabled": true,
"fullWidth": true,
"cursorAlpha": 0.1
},
"periodSelector": {
"position": "bottom",
"periods": [ {
"period": "MM",
"selected": true,
"count": 1,
"label": "1 month"
}, {
"period": "YYYY",
"count": 1,
"label": "1 year"
}, {
"period": "YTD",
"label": "YTD"
}, {
"period": "MAX",
"label": "MAX"
} ]
}
} );
Any idea how to push label text below the guide for guides in case that labels are overlapping. Perhaps with CSS code for class name amcharts-guide-[id]?
There isn't a way to do this through the guide properties properties, but you have the right hunch with the css class name. Set addClassNames to true, give your guides IDs and then add a drawn event listener in your stock panel that adjusts the desired guide(s) directly by calling querySelector on the .amcharts-guide-[id] tspan selector and adjusting the y attribute:
AmCharts.makeChart("chartdiv", {
"addClassNames": true,
// ...
"stockPanels": [{
"valueAxes": [{
"guides": [{
"id": "guide-1",
// ..
}, {
"id": "guide-2",
// ..
}]
}],
"listeners": [{
"event": "drawn",
"method": function() {
var guide2Text = document.querySelector('.amcharts-guide-guide-2 tspan');
if (guide2Text) {
guide2Text.setAttribute('y', 20);
}
}
}]
}],
// ..
});
Updated fiddle

How to check if all numbers in an object are within a certain range?

One of the features I'd like to include in my current JS/jQuery project is to allow users to export and import their saved data. The import/export data is just one big object being ran through JSON.stringify and JSON.parse, and implementing a basic version of that has worked well, but I'd like to validate the imported data to make sure it will work(this is less about working as JavaScript and more about working within the rules of the game I'm making).
I'd like to come up with a more efficient script to validate this big complicated object. The only parts of the object I'm concerned with are the numbers, but it also includes strings, arrays, nested objects. Some of the nested objects include numbers I'm concerned with, but also more other junk including even further nested objects. Currently, I'm using jQuery's each() function to loop over each subsection of the object and evaluate it, but this requires me to write an each function for each subsection I'm concerned with.
How can I feed a function this whole object and have it look at just the numbers to see if they're greater than or less than a set value?
Edit: Pasting the object here, the goal is to check if all of the numbers. Some need to be between 0 and 3, some need to be between 0 and 9.
{
"meta": {
"id": 2,
"name": "Test",
"concept": "test description"
},
"coreAttributes": {
"Strength": 3,
"Finesse": 1,
"Perception": 9,
"Stamina": 1,
"Agility": 1,
"Wits": 1,
"Willpower": 1,
"Charisma": 1,
"Intelligence": 1
},
"skills": {
"Toughness": ["Strength", "STR", 0],
"Stealth": ["Finesse", "FIN", 0],
"Alertness": ["Perception", "PER", 3],
"Investigate": ["Perception", "PER", 0],
"Athletics": ["Agility", "AGI", 0],
"Drive": ["Wits", "WIT", 0],
"Survival": ["Wits", "WIT", 0],
"Guts": ["Willpower", "WIL", 4],
"Hardware": ["Intelligence", "INT", 0],
"Humanities": ["Intelligence", "INT", 0],
"Medicine": ["Intelligence", "INT", 0],
"Science": ["Intelligence", "INT", 0],
"Software": ["Intelligence", "INT", 0],
"Charm": ["Charisma", "CHA", 0],
"Manipulate": ["Charisma", "CHA", 0]
},
"unrolledSkills": {
"Contacts": 0,
"Languages": 0,
"Resources": 0
},
"combatSkills": {
"Unarmed": ["Strength", "STR", 0],
"Defense": ["Finesse", "FIN", 0],
"Melee": ["Finesse", "FIN", 0],
"Firearms": ["Perception", "PER", 0],
"Ballistics": ["Perception", "PER", 0],
"Initiative": ["Wits", "WIT", 0]
},
"attacks": {},
"status": {
"currentEndurance": 4,
"currentSpeed": 4,
"currentEntanglement": 4,
"body": {
"upper": {
"wounds": 0,
"armor": ["", 0]
},
"lower": {
"wounds": 0,
"armor": ["", 0]
},
"main": {
"wounds": 0,
"armor": ["", 0]
},
"off": {
"wounds": 0,
"armor": ["", 0]
},
"legs": {
"wounds": 0,
"armor": ["", 0]
}
}
},
"styles": {
"classes": {
"Strength": {
"core": 0,
"spec1": 0,
"spec2": 0,
"spec3": 0,
"aux1": {
"skill": false,
"name": "",
"value": 0
},
"aux2": {
"skill": false,
"name": "",
"value": 0
},
"aux3": {
"skill": false,
"name": "",
"value": 0
}
},
"Finesse": {
"core": 0,
"spec1": 0,
"spec2": 0,
"spec3": 0,
"aux1": {
"skill": false,
"name": "",
"value": 0
},
"aux2": {
"skill": false,
"name": "",
"value": 0
},
"aux3": {
"skill": false,
"name": "",
"value": 0
}
},
"Perception": {
"core": 0,
"spec1": 0,
"spec2": 0,
"spec3": 0,
"aux1": {
"skill": false,
"name": "",
"value": 0
},
"aux2": {
"skill": false,
"name": "",
"value": 0
},
"aux3": {
"skill": false,
"name": "",
"value": 0
}
},
"Stamina": {
"core": 0,
"spec1": 0,
"spec2": 0,
"spec3": 0,
"aux1": {
"skill": false,
"name": "",
"value": 0
},
"aux2": {
"skill": false,
"name": "",
"value": 0
},
"aux3": {
"skill": false,
"name": "",
"value": 0
}
},
"Agility": {
"core": 0,
"spec1": 0,
"spec2": 0,
"spec3": 0,
"aux1": {
"skill": false,
"name": "",
"value": 0
},
"aux2": {
"skill": false,
"name": "",
"value": 0
},
"aux3": {
"skill": false,
"name": "",
"value": 0
}
},
"Wits": {
"core": 0,
"spec1": 0,
"spec2": 0,
"spec3": 0,
"aux1": {
"skill": false,
"name": "",
"value": 0
},
"aux2": {
"skill": false,
"name": "",
"value": 0
},
"aux3": {
"skill": false,
"name": "",
"value": 0
}
},
"Willpower": {
"core": 0,
"spec1": 0,
"spec2": 0,
"spec3": 0,
"aux1": {
"skill": false,
"name": "",
"value": 0
},
"aux2": {
"skill": false,
"name": "",
"value": 0
},
"aux3": {
"skill": false,
"name": "",
"value": 0
}
},
"Charisma": {
"core": 0,
"spec1": 0,
"spec2": 0,
"spec3": 0,
"aux1": {
"skill": false,
"name": "",
"value": 0
},
"aux2": {
"skill": false,
"name": "",
"value": 0
},
"aux3": {
"skill": false,
"name": "",
"value": 0
}
},
"Intelligence": {
"core": 0,
"spec1": 0,
"spec2": 0,
"spec3": 0,
"aux1": {
"skill": false,
"name": "",
"value": 0
},
"aux2": {
"skill": false,
"name": "",
"value": 0
},
"aux3": {
"skill": false,
"name": "",
"value": 0
}
}
},
"arcane": {
"restoration": 0,
"evocation": 0,
"abjuration": 0,
"sublimation": 0,
"paradigm": 0,
"telepathy": 0,
"shift": 0,
"electromagnetism": 0,
"gravitonertia": 0,
"chromodynamism": 0,
"technology": 0
},
"extension": {
"avatar": 0,
"proxy": 0,
"permanence": 0
}
},
"addenda": {}
}
The below uses recursion to search all keys in a multidimensional object for any numbers lower or greater than the supplied numbers.
Simply call the function like checkObject(myStuff, 30, 60); passing in your object, your lowest allowed number, and your highest allowed number
var myStuff = { "results": [
{
"ACL": {
"7UeILO5tC4": {
"count": "45",
"read": true
},
"role:Leads": {
"count": "12",
"read": true,
"write": true
}
},
"createdAt": "2014-12-16T22:04:46.338Z",
"finishDate": "12%2F16%2F2014",
"finishTime": "16%3A4%3A44",
"objectId": "tVldoxxdCB",
"passFail": "Pass",
"passingPercentage": "56",
"passingPoints": "34",
"questions": "21",
"quizName": "Name",
"quizType": "Flights",
"teamMember": "Jame Fellows",
"ttlPossiblePoints": "59",
"updatedAt": "2014-12-16T22:04:46.338Z",
"userName": "Jame.Fellows",
"userPercentage": "95",
"userPoints": "20",
"userRightAnswers": "57"
},
{
"ACL": {
"7UeILO5tC4": {
"count": "44",
"read": true
},
"role:Leads": {
"count": "12",
"read": true,
"write": true
}
},
"createdAt": "2014-12-16T22:04:46.338Z",
"finishDate": "12%2F16%2F2014",
"finishTime": "16%3A4%3A44",
"objectId": "tVldoxxdCB",
"passFail": "Pass",
"passingPercentage": "90",
"passingPoints": "87",
"questions": "21",
"quizName": "Name",
"quizType": "Flights",
"teamMember": "Jame Fellows",
"ttlPossiblePoints": "79",
"updatedAt": "2014-12-16T22:04:46.338Z",
"userName": "Jame.Fellows",
"userPercentage": "76",
"userPoints": "20",
"userRightAnswers": "45"
},
{
"ACL": {
"7UeILO5tC4": {
"count": "45",
"read": true
},
"role:Leads": {
"count": "12",
"read": true,
"write": true
}
},
"createdAt": "2014-12-16T22:04:46.338Z",
"finishDate": "12%2F16%2F2014",
"finishTime": "16%3A4%3A44",
"objectId": "tVldoxxdCB",
"passFail": "Pass",
"passingPercentage": "90",
"passingPoints": "19",
"questions": "21",
"quizName": "Name",
"quizType": "Flights",
"teamMember": "Jame Fellows",
"ttlPossiblePoints": "21",
"updatedAt": "2014-12-16T22:04:46.338Z",
"userName": "Jame.Fellows",
"userPercentage": "95",
"userPoints": "20",
"userRightAnswers": "20"
}
] };
// track how many invalid numbers we find
var hasInvalidData=0;
// call our checkObject() function, pass it
// your object, your lowest allowed number, your highest allowed number
checkObject(myStuff, 30, 60);
if(hasInvalidData > 0){
alert(hasInvalidData + ' invalid numbers were found')
}
function checkObject(object, low, high){
// loop through each property of the object
for (var property in object) {
// make sure it's a real property and not inherited
if (object.hasOwnProperty(property)) {
//get the value of the current property
var value = object[property];
// if this propery is itself an object,
// call this function recursively
if(typeof object[property] == "object" && typeof object[property] !== null){
checkObject(object[property], low, high)
}
else{
// if it's not an object
// check if it a a number and not true or false
// which wihich isNaN sees as 1 and 0
if( !isNaN(value) && typeof value != "boolean"){
console.log(value);
if(value < low){
console.log('^ this value is too small ^');
hasInvalidData++;
}
if(value > high){
console.log('^ this value is too large ^');
hasInvalidData++;
}
}
}
}
}
}
What you're looking for is actually very simple – recursion.
The below code checks if all numbers are larger than or equal to 2 and will return false on first non-matching number.
var x = {
b: 5,
c: {
d: 3,
e: [1, 2, 3],
f: function() {}
},
g: function() {}
};
var recurse = function(a) {
var s = true;
for (prop in a) {
switch (typeof a[prop]) {
case "number":
//Check for conditions here
console.log("found number " + a[prop]);
if (a[prop] < 2) {
return false;
}
break;
case "object":
if (a[prop] !== null) {
s = s && recurse(a[prop]);
}
break;
default:
break;
}
}
return s;
}
recurse(x);
Here is an iterative solution:
var x = {
b: 5,
c: {
d: 3,
e: [1, 2, 3],
f: function() {}
},
g: function() {}
};
function check (obj) {
var queue = [obj];
while (queue.length > 0) {
var current = queue.shift();
for (var prop in current) {
if (!current.hasOwnProperty(prop)) continue;
switch (typeof(current[prop])) {
case "number":
// check for conditions here
if (current[prop] < 2) {
return false;
}
break;
case "object":
if (current[prop] !== null) {
queue.push(current[prop]);
}
break;
default:
break;
}
}
}
return true;
}
alert(check(x));
It's a bit faster since it can finish early
Here you go, you only need this: (it also has some fancy visualisation :P)
function traverse(obj, callback, level) {
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
callback.apply(this, [i, obj[i], level||""]);
(obj[i] !== null && typeof(obj[i])=="object") && traverse(obj[i],callback,(level||"")+" ");
}
}
// it is called like this
traverse(obj, valueCheck);
//obj - is your object as in your question
//valueCheck(key, val, level) - is your own function to check the value
An exemplary valueCheck() function might be as follows
function valueCheck(key, val, level) {
// proceed to check only if the value is a number
if (!isNaN(parseFloat(val)) && isFinite(val)) {
var isError = ! ((0 <= val) && (val <= 9));
console.log(level + key + " : " + val + (isError && " ----------------------- VALUE ERROR" || ""));
} else {
// if it's a new object, print shiny "-" at the begining
(typeof val == "object") && console.log(Array(1+level.length).join("-") + key);
}
}

Why does queryAsync() result in added meta data?

I used queryAsync() as prescribed by an answer to a question, and it works, but it adds lots of extra meta data to my query result, which is otherwise a straightforward query.
Here's what I'm using to process/execute the query and log the result:
var retrieveSettings = Promise.method(function (username, connection) {
console.log('User ' + username + ' retrieving settings...');
var q = 'select * from sales_settings';
return connection.queryAsync(q).then(function (rows, fields) {
list = [];
for (x = 0; x < rows.length; x++) {
list.push(rows[x]);
}
//console.log('Settings: ' + JSON.stringify(list, null, 4));
return list;
});
});
and here is the result that is logged:
Settings: [
[
{
"group_name": "add_auto",
"commission_rate": 0,
"monthly_req": 0,
"category_name": "Auto",
"commission_type": "none",
"group_title": "Added Auto"
},
{
"group_name": "add_fire",
"commission_rate": 0,
"monthly_req": 0,
"category_name": "Fire",
"commission_type": "none",
"group_title": "Added Fire"
},
{
"group_name": "bank_dep",
"commission_rate": 25,
"monthly_req": 0,
"category_name": "Bank",
"commission_type": "static",
"group_title": "Bank Deposit"
},
{
"group_name": "bank_loan",
"commission_rate": 75,
"monthly_req": 8,
"category_name": "Bank",
"commission_type": "static",
"group_title": "Bank Loan"
},
{
"group_name": "health",
"commission_rate": 0.084,
"monthly_req": 4,
"category_name": "Health",
"commission_type": "premium",
"group_title": "Health App"
},
{
"group_name": "life",
"commission_rate": 0.084,
"monthly_req": 8,
"category_name": "Life",
"commission_type": "premium",
"group_title": "Life App"
},
{
"group_name": "new_auto",
"commission_rate": 0.03,
"monthly_req": 32,
"category_name": "Auto",
"commission_type": "rate",
"group_title": "Raw New Auto"
},
{
"group_name": "new_fire",
"commission_rate": 0.03,
"monthly_req": 20,
"category_name": "Fire",
"commission_type": "rate",
"group_title": "Raw New Fire"
}
],
[
{
"catalog": "def",
"db": "officeball",
"table": "sales_settings",
"orgTable": "sales_settings",
"name": "group_name",
"orgName": "group_name",
"filler1": [
12
],
"charsetNr": 33,
"length": 135,
"type": 253,
"flags": 20483,
"decimals": 0,
"filler2": [
0,
0
],
"zeroFill": false,
"protocol41": true
},
{
"catalog": "def",
"db": "officeball",
"table": "sales_settings",
"orgTable": "sales_settings",
"name": "commission_rate",
"orgName": "commission_rate",
"filler1": [
12
],
"charsetNr": 63,
"length": 13,
"type": 246,
"flags": 4097,
"decimals": 3,
"filler2": [
0,
0
],
"zeroFill": false,
"protocol41": true
},
{
"catalog": "def",
"db": "officeball",
"table": "sales_settings",
"orgTable": "sales_settings",
"name": "monthly_req",
"orgName": "monthly_req",
"filler1": [
12
],
"charsetNr": 63,
"length": 11,
"type": 3,
"flags": 4097,
"decimals": 0,
"filler2": [
0,
0
],
"zeroFill": false,
"protocol41": true
},
{
"catalog": "def",
"db": "officeball",
"table": "sales_settings",
"orgTable": "sales_settings",
"name": "category_name",
"orgName": "category_name",
"filler1": [
12
],
"charsetNr": 33,
"length": 135,
"type": 253,
"flags": 4097,
"decimals": 0,
"filler2": [
0,
0
],
"zeroFill": false,
"protocol41": true
},
{
"catalog": "def",
"db": "officeball",
"table": "sales_settings",
"orgTable": "sales_settings",
"name": "commission_type",
"orgName": "commission_type",
"filler1": [
12
],
"charsetNr": 33,
"length": 135,
"type": 253,
"flags": 4097,
"decimals": 0,
"filler2": [
0,
0
],
"zeroFill": false,
"protocol41": true
},
{
"catalog": "def",
"db": "officeball",
"table": "sales_settings",
"orgTable": "sales_settings",
"name": "group_title",
"orgName": "group_title",
"filler1": [
12
],
"charsetNr": 33,
"length": 72,
"type": 253,
"flags": 4097,
"decimals": 0,
"filler2": [
0,
0
],
"zeroFill": false,
"protocol41": true
}
]
]
Why is this unusual meta data being added to my query result?
It looks like you are using bluebird, in that case you can use .spread:
var retrieveSettings = Promise.method(function (username, connection) {
console.log('User ' + username + ' retrieving settings...');
var q = 'select * from sales_settings';
return connection.queryAsync(q).spread(function (rows, fields) {
list = [];
for (x = 0; x < rows.length; x++) {
list.push(rows[x]);
}
//console.log('Settings: ' + JSON.stringify(list, null, 4));
return list;
});
});
The problem with the mysql module is that it doesn't conform to the node js callback standard which is (err, result). Instead it uses (err, result1, result2). Because functions can only throw one exception or return one value, bluebird returns an array of [result1, result2] to avoid information loss.
.spread is like .then except it assumes the fulfulliment value is an array and spreads the array's values over the arguments.
The standard callback from mysql's query() returns both a rows and fields. The asynch wrapper you are using (for example if you Q.denodeify()) is only returning a single thing, so for callbacks with multiple parameters it returns an array of [param1,param2] in this case [rows,fields].
Related to this, your function inside the then needs to accept only one parameter (which would be this array of [rows,fields]).
See the second portion of my answer to this question as an illustration.
Your code should thus be something like:
var retrieveSettings = Promise.method(function (username, connection) {
console.log('User ' + username + ' retrieving settings...');
var q = 'select * from sales_settings';
return connection.queryAsync(q).then(function (results) {
var rows = results[0]; // get the rows
var fields = results[1]; // don't really care, but just for illustration
list = [];
for (x = 0; x < rows.length; x++) {
list.push(rows[x]);
}
//console.log('Settings: ' + JSON.stringify(list, null, 4));
return list;
});
});
And actually, for the above code, there's no reason your entire callback can't just be:
return connection.queryAsync(q).then(function (results) {
return results[0]; // get the rows
});

Categories