Google Visualizations: Where does JS execution begin again after drawing charts? - javascript

A small project using Google Visualizations, https://jsfiddle.net/brisray/qsgewt2d/ - works as far as I have it, but I have a question about it.
Once the graphs are drawn, I assumed that control would pass back to "mainFunction". This is a function that loops through the object array I made creating the queries, then calls other functions that draws the tables and charts. I was hoping to add more code to that function to call other functions for other things I want to do.
What I find is that it doesn't work that way. A simple JS alert AFTER the loop shows none of the drawing is done until the alert is acknowledged.
Am I missing something? Perhaps an event handler that's triggered after the last of the array is processed and the last of the initial graphs drawn? Something to do with the asynchronous natures of the drawing.
Is there something fundamentally wrong with the code and the way I've written it?
Whatever is happening, I cannot see it, so would appreciate some help.
Ray
var SCOB_metrics = (function() {
// Create the object array that is global to the namespace
var graphArray = [];
// Create objects and push them to the array
// Object properties are: ID, sheet, GID, datatable, graphtype, rotated
createObject('MSDC', '1RCZiWWsEKPs6-1ULXeHjWmaXUEHCaRPtKT9U_6FzCJ4', '1835225366', 'MSDC_data', 'Column', false);
createObject('StudentPop', '1RCZiWWsEKPs6-1ULXeHjWmaXUEHCaRPtKT9U_6FzCJ4', '3256521', 'StudentPop_data', 'Column', false);
createObject('EnrolTrends', '1RCZiWWsEKPs6-1ULXeHjWmaXUEHCaRPtKT9U_6FzCJ4', '1037635451', 'EnrolTrends_data', 'Column', false);
google.charts.load('current');
google.charts.setOnLoadCallback(mainFunction);
function mainFunction() {
for (i = 0; i < graphArray.length; i++) {
makeQuery(graphArray[i]);
}
// Now everthing is drawn, set up the listener for the drawingArea div
// so that the graphs can be updated if needed
var theParent = document.querySelector("#drawingArea");
theParent.addEventListener("change", whichDrop, false);
}
function makeQuery(myObject) {
// Create the querystring and send it
var queryStr = "https://docs.google.com/spreadsheets/d/" + myObject.sheet + "/gviz/tq?gid=" + myObject.GID + "&headers=1";
var query = new google.visualization.Query(queryStr);
/* You can't send a variable though the normal query.send method
query.send(handleQueryResponse);
so do this */
query.send(function(response) {
visuals(response, myObject);
});
}
function whichDrop(e) {
// Find which dropdown was changed, get it's value, find the index of the graphArray.ID it belongs to and redraw the graph
if (e.target !== e.currentTarget) {
var changedItem = e.target.id;
}
e.stopPropagation();
var findID = changedItem.substr(0, changedItem.length - 4);
arrayIndex = graphArray.findIndex(x => x.ID == findID);
var e = document.getElementById(changedItem);
var chosenGraph = e.options[e.selectedIndex].value;
graphArray[arrayIndex].graphtype = chosenGraph;
drawGraphs(graphArray[arrayIndex]);
}
function visuals(response, myObject) {
// Create the data table and draw both the table and graph
myObject.datatable = response.getDataTable();
drawTables(myObject);
drawGraphs(myObject);
}
function drawTables(myObject) {
// Draw the table
var tableArea = myObject.ID + "_table_div";
var cssClassNames = {
'headerRow': 'header-css',
'headerCell': 'border-css'
};
theTables = new google.visualization.ChartWrapper({
chartType: 'Table',
containerId: tableArea,
dataTable: myObject.datatable,
options: {
'allowHtml': true,
'cssClassNames': cssClassNames
}
});
theTables.draw();
}
function drawGraphs(myObject) {
// Draw the graph
var graphArea = myObject.ID + "_graph_div";
var chartType = myObject.graphtype + "Chart";
theGraphs = new google.visualization.ChartWrapper({
chartType: chartType,
containerId: graphArea,
dataTable: myObject.datatable,
// May have to use IF or SWITCH statements depending on chartType
options: {
height: 400,
hAxis: {
maxTextLines: 4, // maximum number of lines to wrap to
maxAlternation: 4, // maximum layers of labels (setting this higher than 1 allows labels to stack over/under each other)
minTextSpacing: 1, // minimum space in pixels between adjacent labels
},
textStyle: {
fontSize: 9
}
}
});
theGraphs.draw();
}
function transposeDataTable(myObject) {
// Transpose the datatable
dataTable = myObject.datatable;
// Toggle rotated boolean
myObject.rotated = !myObject.rotated;
// Rotate the datatable
var rows = []; //the row tip becomes the column header and the rest become
for (var rowIdx = 0; rowIdx < dataTable.getNumberOfRows(); rowIdx++) {
var rowData = [];
for (var colIdx = 0; colIdx < dataTable.getNumberOfColumns(); colIdx++) {
rowData.push(dataTable.getValue(rowIdx, colIdx));
}
rows.push(rowData);
}
var newTB = new google.visualization.DataTable();
newTB.addColumn('string', dataTable.getColumnLabel(0));
newTB.addRows(dataTable.getNumberOfColumns() - 1);
var colIdx = 1;
for (var idx = 0; idx < (dataTable.getNumberOfColumns() - 1); idx++) {
var colLabel = dataTable.getColumnLabel(colIdx);
newTB.setValue(idx, 0, colLabel);
colIdx++;
}
for (var i = 0; i < rows.length; i++) {
var rowData = rows[i];
console.log(rowData[0]);
newTB.addColumn('number', rowData[0]); //assuming the first one is always a header
var localRowIdx = 0;
for (var j = 1; j < rowData.length; j++) {
newTB.setValue(localRowIdx, (i + 1), rowData[j]);
localRowIdx++;
}
}
return newTB;
}
function createObject(ID, sheet, GID, datatable, graphtype, rotated) {
// Create the data objects and push them to the graphArray array
graphArray.push({
ID: ID,
sheet: sheet,
GID: GID,
datatable: datatable,
graphtype: graphtype,
rotated: rotated,
});
}
})();

I think that you are looking for 'ready' event. https://developers.google.com/chart/interactive/docs/events#the-ready-event

I abandoned what I was trying to do, which was to step through a list of various chart types to see what would be the best for what I wanted to display.
One of the things I wanted to look at was to create PNGs of the various charts. But, I ran foul of the "ready" event handlers again.
Just before I drew the charts I used
google.visualization.events.addListener(theGraphs, 'ready', function() {
document.getElementById(imgLink).innerHTML = 'Printable Graph';
});
but I was getting "Cannot read property 'getImageURI' of null" errors.
Presumably because of the loop the drawing of these graphs is in, it was running too fast for the event handler to catch?
I tried removing the event handler after the drawing of the charts using
google.visualization.events.removeListener(theGraphs);
and even
google.visualization.events.removeAllListeners(theGraphs);
I had thought of doing something like trying to count the number of times the event was triggered and comparing that to the number of times the function was called but went for a simpler, but probably not the best method, of adding a setTimeout function.
setTimeout(function() {
document.getElementById(imgLink).innerHTML = 'Printable Graph';
}, 100);
theGraphs.draw();
Probably not the most elegant solution, but the delay of just 1/10 of a second made the problems I've been having go away.
https://jsfiddle.net/brisray/z49jw264/706/
setTimeout(function() {
document.getElementById(imgLink).innerHTML = '<a target="_blank" href="' + theGraphs.getChart().getImageURI() + '">Printable Graph</a>';
}, 100);
theGraphs.draw();

Related

How do I return my DHTMLX Gantt data using a loop?

I'm using DHTMLX Gantt Chart. As part of my project, I create a lot of custom layers to work with the chart but sadly, it's making my code very bloated having to declare each task to be rendered as a layer. My initial thought was to add the tasks using a loop and an object array. When I attempted this approach, it didn't work and nothing was rendered.
Here is my code so far:
//Gantt Initilisation
document.addEventListener("DOMContentLoaded", function (event) {
// Specifying the date format
gantt.config.xml_date = "%Y-%m-%d %H:%i";
gantt.config.readonly = true;
// Initializing gantt
gantt.init("positionsReport");
// Initiating data loading
gantt.load("/api/data/get");
// Initializing dataProcessor
var dp = new gantt.dataProcessor("/api/");
//Convert date to a usable format
gantt.attachEvent("onTaskLoading", function (task) {
task.options_start = gantt.date.parseDate(task.options_start, "xml_date");
task.options_end = gantt.date.parseDate(task.options_end, "xml_date");
task.f2_onhire = gantt.date.parseDate(task.f2_onhire, "xml_date");
task.f2_offhire = gantt.date.parseDate(task.f2_offhire, "xml_date");
task.f2_options_start = gantt.date.parseDate(task.f2_options_start, "xml_date");
task.f2_options_end = gantt.date.parseDate(task.f2_options_end, "xml_date");
task.f3_onhire = gantt.date.parseDate(task.f3_onhire, "xml_date");
task.f3_offhire = gantt.date.parseDate(task.f3_offhire, "xml_date");
return true;
});
This is the basic setup before any work with layers, I initialise the Gantt, convert some date times to a format it understands and then define my API and required data.
//Fixture Layers
gantt.addTaskLayer(function draw_planned(task) {
var fixtureArray = [
{
onhire: task.start_date,
offhire: task.end_date,
charterer: task.current_charterer
},
{
onhire: task.f2_onhire,
offhire: task.f2_offhire,
charterer: task.f2_charterer
},
{
onhire: task.f3_onhire,
offhire: task.f3_offhire,
charterer: task.f3_charterer
}
];
For the purposes of this question, I've created a short array based on the task data I've required which is limited to three items.
for (i = 0; i < fixtureArray.length; i++) {
var sizes = gantt.getTaskPosition(fixtureArray, fixtureArray.onhire, fixtureArray.offhire);
var el = document.createElement('div');
el.className = 'gantt_task_content';
return el;
}
return false;
});
I then attempt to create the required layers for each item in my array.
gantt.config.columns = [
{ name: "text", tree: false, width: 150, resize: true, align: "left", label: "Vessel" },
{ name: "start_date", align: "center", width: 150, resize: true, align: "left", label: "Onhire" },
{ name: "duration", align: "center", width: 70, resize: true, hide: true }
];
//Month format
gantt.config.scale_unit = "month";
gantt.config.date_scale = "%M";
//View
gantt.config.start_date = new Date(2019, 01, 01);
gantt.config.end_date = new Date(2020, 01, 01);
// and attaching it to gantt
dp.init(gantt);
// setting the REST mode for dataProcessor
dp.setTransactionMode("REST");
});
Then I finish off with a date range, scale and rest mode settings, and the final initilisation statement.
The Gantt chart works fine and there are no error messages, but, it only renders one item from my array loop, the rest never appear. Is my approach correct for this setup?
You can have only one return value from a function, your code exits addTaskLayer function on the first iteration.
If you want it to display several elements, you can create an additional 'wrapper' element, append your items to it and return the wrapper element from the function:
var wrapper = document.createElement('div'); //!!!
for (i = 0; i < fixtureArray.length; i++) {
var sizes = gantt.getTaskPosition(fixtureArray, fixtureArray.onhire, fixtureArray.offhire);
var el = document.createElement('div');
el.className = 'gantt_task_content';
wrapper.appendChild(el); //!!!
}
if(wrapper.children.length){
return wrapper; //!!!
}
return false;
For example, you can look at this demo:
https://docs.dhtmlx.com/gantt/samples/04_customization/18_subtasks_displaying.html - check Task #2 row, there are multiple elements added by addTaskLayer function using the very same approach.

Getting Lost in Prototype Method Calls

apologies I realise there are a number of other threads that describe similar issues however I have not been able to find one that answers my question fully (at least in a way that I understand).
I use the following code to create an Object which manages the UI interactions for the various complicated instances of an input Form across my site.
Using Prototypes, I effectively end up with an object called categoryForm with various methods contained within:
- categoryForm.addEventListeners
- categoryForm.handlers
- categoryForm.validation
The last two are objects containing a number of different Methods.
The below code is a cut down version of my finished code, however should be sufficient to get the problem across, as the rest is variations on a similar theme.
The Issue I have is that, in the below example:
- I click '.addNewItems' on my table
- This triggers my listener, which calls the 'addNewTableItem' handler method.
- The handler then attempts to Loop through the Inputs, passing them through the 'validation.checkInputVal' method to validate each input before proceeding.
However, by the time we're in this loop, the scope of this has totally changed (as expected) and I have no idea how to refer to my categoryForm Object and call the 'validation.checkInputVal' method. I just get an error saying that this is not a function.(again expected)
Here's the code:
function categoryFormFuncs(){
// VARIABLES
var _this = this;
var table = $('table');
// EVENT LISTENER FUNCTIONS
this.addEventListeners = function(){
// Listen for the AddItemButton on Tables and call the addNewTableItem function
table.on('click', '.addNewItems', function(e){
// Find new ItemGroup and Collect Inputs into an Array
var newItemGroup = $(this).parents('.newItemGroup')[0];
// Send New Item Group and Table to be updated to the Handler
_this.handlers.addNewTableItem(newItemGroup);
});
}
};
// HANDLER FUNCTIONS
categoryFormFuncs.prototype.handlers = {
// Function to Create a NewItemGroup table row
addNewTableItem: function (inputGroup){
var validationcheck;
// Get all the Inputs
var inputs = $(inputGroup).find('input');
// Check Inputs are valid and Highlight them if not
for(var i = 0; i < inputs.length; i++){
validationcheck = validation.checkInputVal(inputs[i]);
if(!validationcheck.passed){
$(inputs[i]).addClass('input-inValid')
return
} else {
$(inputs[i]).removeClass('input-inValid')
}
};
// If Valid, turn each input into a Table Cell and clear the original Input value
var rowCells = ""
for(var i = 0; i < inputs.length; i++){
rowCells += "<td>" + $(inputs[i]).val() + "</td>";
$(inputs[i]).val("");
}
// Construct the new Table row and update the DOM
var newRow = "<tr class='itemGroup'>" + rowCells + "<td><span class='float-right remove-item fa fa-minus-circle'></span></td></tr>";
$(inputGroup).before(newRow);
}
}
// VALIDATION CHECKS
categoryFormFuncs.prototype.validation = {
checkInputVal: function(input){
if($(input).val()){
return { passed: true }
} else {
return { passed: false, message: "Input with no Value"}
}
}
}
var categoryForm = new categoryFormFuncs();
categoryForm.addEventListeners();
I have found one way to make this work which is to provide the validation method as an argument to the Handler:
function categoryFormFuncs(){
// VARIABLES
var _this = this;
var table = $('table');
// EVENT LISTENER FUNCTIONS
this.addEventListeners = function(){
// Listen for the AddItemButton on Tables and call the addNewTableItem function
table.on('click', '.addNewItems', function(e){
// Find new ItemGroup and Collect Inputs into an Array
var newItemGroup = $(this).parents('.newItemGroup')[0];
// Send New Item Group and Table to be updated to the Handler
_this.handlers.addNewTableItem(newItemGroup, _this.validation.checkInputVal);
});
}
};
// handlers
categoryFormFuncs.prototype.handlers = {
// Function to Create a NewItemGroup table row
addNewTableItem: function (inputGroup, checkInputVal){
var validationcheck;
// Get all the Inputs
var inputs = $(inputGroup).find('input');
// Check Inputs are valid and Highlight them if not
for(var i = 0; i < inputs.length; i++){
validationcheck = checkInputVal(inputs[i]);
if(!validationcheck.passed){
$(inputs[i]).addClass('input-inValid')
return
} else {
$(inputs[i]).removeClass('input-inValid')
}
};
// If Valid, turn each input into a Table Cell and clear the original Input value
var rowCells = ""
for(var i = 0; i < inputs.length; i++){
rowCells += "<td>" + $(inputs[i]).val() + "</td>";
$(inputs[i]).val("");
}
// Construct the new Table row and update the DOM
var newRow = "<tr class='itemGroup'>" + rowCells + "<td><span class='float-right remove-item fa fa-minus-circle'></span></td></tr>";
$(inputGroup).before(newRow);
}
}
// VALIDATION CHECKS
categoryFormFuncs.prototype.validation = {
checkInputVal: function(input){
if($(input).val()){
return { passed: true }
} else {
return { passed: false, message: "Input with no Value"}
}
}
}
var categoryForm = new categoryFormFuncs();
categoryForm.addEventListeners();
Alternatively I could also pass a reference to _this from the listener to the handler in order to access _this.validation.
Either way these all feel like very messy and clunky solutions.
My question is:
a) Is there a way, using my original deisgn to access the validation methods?
b) Is there a better established pattern for this type of Lister / Handler / Validation scenario that I should know about?
(Disclaimer: I am very new to programming (6 months) so apologies if my description is incorrect in any way.

Javascript array shows in console, but i cant access any properties in loops

I really try my damndest not to ask, but i have to at this point before I tear my hair out.
By the time the js interpreter gets to this particular method, I can print it to the console no problem, it is an array of "event" objects. From FireBug I can see it, but when I try to set a loop to do anything with this array its as if it doesn't exist. I am absolutely baffled......
A few things:
I am a newbie, I have tried a for(var index in list) loop, to no avail, I have also tried a regular old for(var i = 0; i < listIn.length; i++), and I also tried to get the size of the local variable by setting var size = listIn.length.
As soon as I try to loop through it I get nothing, but I can access all the objects inside it from the FireBug console no problem. Please help, even just giving me a little hint on where I should be looking would be great.
As for the array itself, I have no problems with getting an array back from PHP in the form of: [{"Event_Id":"9", "Title":"none"}, etc etc ]
Here is my code from my main launcher JavaScript file. I will also post a sample of the JSON data that is returned. I fear that I may be overextending myself by creating a massive object in the first place called content, which is meant to hold properties such as DOM strings, settings, and common methods, but so far everything else is working.
The init() function is called when the body onload is called on the corresponding html page, and during the call to setAllEvents and setEventNavigation I am lost.
And just to add, I am trying to learn JavaScript fundamentals before I ever touch jQuery.
Thanks
var dom, S, M, currentArray, buttonArray, typesArray, topicsArray;
content = {
domElements: {},
settings: {
allContent: {},
urlList: {
allURL: "../PHP/getEventsListView.php",
typesURL: "../PHP/getTypes.php",
topicsURL: "../PHP/getTopics.php"
},
eventObjArray: [],
buttonObjArray: [],
eventTypesArray: [],
eventTopicsArray: []
},
methods: {
allCallBack: function (j) {
S.allContent = JSON.parse(j);
var list = S.allContent;
for (var index in list) {
var event = new Event(list[index]);
S.eventObjArray.push(event);
}
},
topicsCallBack: function(j) {
S.eventTopicsArray = j;
var list = JSON.parse(S.eventTopicsArray);
topicsArray = list;
M.populateTopicsDropDown(list);
},
typesCallBack: function(j) {
S.eventTypesArray = j;
var list = JSON.parse(S.eventTypesArray);
typesArray = list;
M.populateTypesDropDown(list);
},
ajax: function (url, callback) {
getAjax(url, callback);
},
testList: function (listIn) {
// test method
},
setAllEvents: function (listIn) {
// HERE IS THE PROBLEM WITH THIS ARRAY
console.log("shall we?");
for(var index in listIn) {
console.log(listIn[index]);
}
},
getAllEvents: function () {
return currentArray;
},
setAllButtons: function (listIn) {
buttonArray = listIn;
},
getAllButtons: function () {
return buttonArray;
},
setEventNavigation: function(current) {
// SAME ISSUE AS ABOVE
var l = current.length;
//console.log("length " + l);
var counter = 0;
var endIndex = l - 1;
if (current.length < 4) {
switch (l) {
case 2:
var first = current[0];
var second = current[1];
first.setNextEvent(second);
second.setPreviousEvent(first);
break;
case 3:
var first = current[0];
var second = current[1];
var third = current[2];
first.setNextEvent(second);
second.setPreviousEvent(first);
second.setNextEvent(third);
third.setPreviousEvent(second);
break;
default:
break;
}
} else {
// do something
}
},
populateTopicsDropDown: function(listTopics) {
//console.log("inside topics drop");
//console.log(listTopics);
var topicsDropDown = document.getElementById("eventTopicListBox");
for(var index in listTopics) {
var op = document.createElement("option");
op.setAttribute("id", "dd" + index);
op.innerHTML = listTopics[index].Main_Topic;
topicsDropDown.appendChild(op);
}
},
populateTypesDropDown: function(listTypes) {
//console.log("inside types drodown");
//console.log(listTypes);
var typesDropDown = document.getElementById("eventTypeListBox");
for(var index2 in listTypes) {
var op2 = document.createElement("option");
op2.setAttribute("id", "dd2" + index2);
op2.innerHTML = listTypes[index2].Main_Type;
typesDropDown.appendChild(op2);
}
}
},
init: function() {
dom = this.domElements;
S = this.settings;
M = this.methods;
currentArray = S.eventObjArray;
buttonArray = S.buttonObjArray;
topicsArray = S.eventTopicsArray;
typesArray = S.eventTypesArray;
M.ajax(S.urlList.allURL, M.allCallBack);
//var tempList = currentArray;
//console.log("temp array length: " + tempList.length);
M.setAllEvents(currentArray);
M.testList(currentArray);
M.setEventNavigation(currentArray);
//M.setEventNavigation();
M.ajax(S.urlList.topicsURL, M.topicsCallBack);
M.ajax(S.urlList.typesURL, M.typesCallBack);
}
};
The problem you have is that currentArray gets its value asynchronously, which means you are calling setAllEvents too soon. At that moment the allCallBack function has not yet been executed. That happens only after the current running code has completed (until call stack becomes emtpy), and the ajax request triggers the callback.
So you should call setAllEvents and any other code that depends on currentArray only when the Ajax call has completed.
NB: The reason that it works in the console is that by the time you request the value from the console, the ajax call has already returned the response.
Without having looked at the rest of your code, and any other problems that it might have, this solves the issue you have:
init: function() {
dom = this.domElements;
S = this.settings;
M = this.methods;
currentArray = S.eventObjArray;
buttonArray = S.buttonObjArray;
topicsArray = S.eventTopicsArray;
typesArray = S.eventTypesArray;
M.ajax(S.urlList.allURL, function (j) {
// Note that all the rest of the code is moved in this call back
// function, so that it only executes when the Ajax response is
// available:
M.allCallBack(j);
//var tempList = currentArray;
//console.log("temp array length: " + tempList.length);
M.setAllEvents(currentArray);
M.testList(currentArray);
M.setEventNavigation(currentArray);
//M.setEventNavigation();
// Note that you will need to take care with the following asynchronous
// calls as well: their effect is only available when the Ajax
// callback is triggered:
M.ajax(S.urlList.topicsURL, M.topicsCallBack); //
M.ajax(S.urlList.typesURL, M.typesCallBack);
});
}

updating the array that is being passed to highcharts in meteor app

I'm using meteor to build a dashboard that uses highcharts to build the charts. Current set up is as follows. The page loads to show a drop down menu. When the user clicks the drop down menu, the client.js queries the database based on the selection, pushes the results to a few global arrays followed by a blaze.render of the template which has the chart. The chart uses the global variables as the series data and the xAxis categories. Problem is that when the user selects a different option from the drop down, the arrays should get updated and the chart re-rendered. But I noticed that the array does not get populated with new values. Instead new values get appended to the array.
Code below:
Template.districtDropdown.events({
'change #selectDistrict' : function(event, template){
// productValues2.length = 0;
// productUsage2.length = 0;
// productFidelity2.length = 0;
// productNames2.length = 0;
event.preventDefault();
var selectedValue = template.$("#selectDistrict").val();
console.log("You Selected " + selectedValue);
var filter = {
find: {
'School District' : selectedValue
}
};
$(filter).ready(function() {
Meteor.subscribe('aggByDistrict', filter, function() {
productUsage2 = [], productValues2 = [], productFidelity2 = [];
productNames2 = _.uniq(CombinedData.find().map( function(doc) { return doc.Product; }));
for(var i = 0; i < productNames2.length; i++) {
productValues2.push(CombinedData.find({'Product' : productNames2[i]}).count());
productUsage2.push(CombinedData.find({'Product' : productNames2[i], 'Usage' : {$gt:0}}).count());
productFidelity2.push(CombinedData.find({'Product' : productNames2[i], 'Fidelity' :1 }).count());
};
console.log(productNames2, productUsage2, productValues2, productFidelity2);
// Renders the highchart that shows the various aggs for selected district
Blaze.render(Template.LicensesDistributedByDistrict, $("#LicensesByDistrictCharts")[0]);
});
});
}
});
If I were to uncomment the productValues2.length = 0 then the chart gets empty arrays.
I know this is probably a a horrible implementation, but I'm trying to learn how to use meteor / highcharts.
It's appending the values because you're not clearing out its existing values.
// productValues2.length = 0;
// productUsage2.length = 0;
// productFidelity2.length = 0;
// productNames2.length = 0;
The code above clears the arrays
Okay, I think I figured out the answer. With the code posted by me earlier, the client side minimongo is populated by the subscribe command when the client makes the first choice from the drop down menu. When the client makes a second choice from the drop down, the server uses the new filter, and published the new documents to the already live collection on the client side minimongodb. To fix this, I modified the code by adding a .stop() at the end, after the chart has been rendered. This stops / clears the client side collection, and the whole process starts again when the client selects a second option from the drop down. Fixed code below:
//Handle the selection event from district dropdowns
Template.districtDropdown.events({
'change #selectDistrict' : function(event, template){
event.preventDefault();
var selectedValue = $(event.target).val();
var filter = {
find: {
'School District' : selectedValue
}
};
// console.log(filter);
$(filter).ready(function() {
var filteredCollection = Meteor.subscribe('aggByDistrict', filter, function() {
productNames2 = [], productUsage2 = [], productValues2 = [], productFidelity2 = [];
console.log(productNames2, productUsage2, productValues2, productFidelity2);
productNames2.length = 0, productUsage2.length = 0, productValues2.length = 0, productFidelity2 .length = 0;
productNames2 = _.uniq(CombinedData.find().map( function(doc) { return doc.Product; }));
for(var i = 0; i < productNames2.length; i++) {
// productValues2 = CombinedData.find({'Product' : productNames2[i]}).map()
productValues2.push(CombinedData.find({'Product' : productNames2[i]}).count());
productUsage2.push(CombinedData.find({'Product' : productNames2[i], 'Usage' : {$gt:0}}).count());
productFidelity2.push(CombinedData.find({'Product' : productNames2[i], 'Fidelity' :1 }).count());
};
console.log(productNames2, productUsage2, productValues2, productFidelity2);
// Renders the highchart that shows the various aggs for selected district
Blaze.render(Template.LicensesDistributedByDistrict, $("#LicensesByDistrictCharts")[0]);
filteredCollection.stop();
});
});
}
});

javascript/jQuery getJSON very slow with a inner loop

I'm using OpenLayers to render DOM for layerswitcher, with about 1200 divs. The problem can be simplified as follows.
This is very fast (3 seconds or so):
var allLayers = [layerMapnik, layerCycleMap];
for(var ii = 0; ii < 1200; ++ii) {
console.log("ii = " + ii.toString());
var groupName = "wms/Transit/railroad/image/" + ii;
var layName = "CycleMap" + ii.toString();
var oneLayer = new OpenLayers.Layer.XYZ(layName, "http://whatever/tile/${z}/${y}/${x}", {visibility: false, group:groupName});
allLayers.push(oneLayer);
}
console.log("adding layers");
map.addLayers(allLayers);
$.getJSON("sampleArray.json", {}, function( doc ) {
console.log(doc.length);
});
But once the for-loop is put inside the getJson callback, it's painfully slow and freezes the browser, even when the for-loop is not reading the 'doc' at all!
var allLayers = [layerMapnik, layerCycleMap];
$.getJSON("sampleArray.json", {}, function( doc ) {
console.log(doc.length);
for(var ii = 0; ii < 1200; ++ii) {
console.log("ii = " + ii.toString());
var groupName = "wms/Transit/railroad/image/" + ii;
var layName = "CycleMap" + ii.toString();
var oneLayer = new OpenLayers.Layer.XYZ(layName, "http://whatever/tile/${z}/${y}/${x}", {visibility: false, group:groupName});
allLayers.push(oneLayer);
}
console.log("adding layers");
map.addLayers(allLayers);
});
No matter how simple the json file is sampleArray.json:
[
{
"a": 123,
"b": 456
},
{
"c": "ccc",
"d": "ddd"
}
]
Did I use the getJSON or callback wrong? Or it is an OpenLayer thing?
I also tried $.each instead of for-loop, no help.
Answering my own questions, for future reference.
The adding LayerSwitcher should be inside the callback function too. Because the whole LayerSwitcher div should be generated BEFORE adding it to the main html, otherwise the redraw for every added layer in the switcher slows things down and freezes the browser.
Also, it is helpful to have the $.ajax call outside the $(document).ready(), to speed things up, since the data fetching does not need to wait for the $(document)

Categories

Resources