Why do some functions recognize this global variable but others don't? - javascript

So I have the following JS function that adds rows to a table based on a global list, events. Events starts out empty and I have another function pushing dict object into it. Items are pushed to the list successfully, however, when events reaches fillTable(), it's empty. In the code below, the first console.log(events) (inside fillTable()), prints an empty list but the second console.log(events) prints the data as expected. events isn't being defined anywhere else so I am lost.
events = []
function otherFunction(repo, type, url) {
events.push({'repo': repo, 'type': type, 'url': url})
}
function fillTable() {
console.log(events); // {}
console.log(events.length); // 0
var table = document.getElementById("table")
for (let i = 0; i < events.length; i++) {
let event = events[i];
const repo = document.createElement('td')
repo.appendChild(document.createTextNode(event['repo']));
const type = document.createElement('td')
type.appendChild(document.createTextNode(event['type']));
const url = document.createElement('td')
url.appendChild(document.createTextNode(event['url']));
const row = document.createElement('tr')
row.appendChild(repo);
row.appendChild(type);
row.appendChild(url);
table.appendChild(row);
}
}
otherFunction('a', 'b', 'c');
console.log(events); // {'repo': 'a', 'type': 'b', 'url': 'c'}
console.log(events.length); // 1
fillTable();

This is a problem with your usage of async functions.
events = []
getGithubActivity();//this function makes an xmlHttp request
fillTable();//this function is called straight after. There has been no chance for a return of the xmlHttp request.
i suggest placing fillTable like this
request.onreadystatechange = function () {
if (request.readyState == 4 && request.status == 200) {
try {
//add events
fillTable();
}
catch (e) {
console.log('getGithubActivity() - Error: ' + e);
}
}
};
When you log an object in console. It will update upon being opened, this is why it appears filled to you even though at the time of logging the length was 0. By the time you open it in console, the request has returned.
Also I noticed that eventList isn't defined anywhere, could this possibly be a typo?

Related

Invalid calling object in IE at the start of a for loop

I have a search function that grabs some 1700 items from an XML list and will search through them by the user's query. Beyond that the user can further filter their results by selecting various filters.
It works flawlessly in Chrome but when testing in IE, anytime I click a filter I get an error "invalid calling object" which references the function sortList() specifically the line for(var i=0; i < loadedList.length; i++) as shown in more detail below.
Again, Chrome has no problem with this, its a total IE thing. Setting watches in Chrome, loadedList is an HTMLCollection and can have a .length method applied but for some reason in IE this does not work.
This script is fairly lengthy but I've tried to include the relevant functions below.
So mapping this out conceptually:
var results = [];
var loadedList;
window.onLoad = loadList();
// actual function
function loadList() {
var items = new XMLHttpRequest();
items.onreadystatechange = function() {
//puts all the xml into the loadedList variable
if (this.readyState == 4 && this.status == 200) {
var xmlDoc = this.responseXML;
loadedList = xmlDoc.getElementsByTagName("itm");
sortList();
}
};
items.open("GET", "item-list-file.xml", true);
items.send();
};
function sortList() {
for (var i = 0; i < loadedList.length; i++) {
for (var j = 0; j < loadedList.length; j++) {
if (boxed[j].checked && boxed[j].id.substr(0, 3) == getElement("category", i).toLowerCase().substring(0, 3)) {
// getElement is just a function with a catch statement to handle any missing info on the xml list
//pushes any relevant results into results array
results.push("<li>" + getElement("title", i) + "</li>");
}
}
}
}
};
That's the gist of what happens when the page loads. There is a search() function which just grab's the user's query and passes it to a returnSearch() function. There are no problems with any of that.
The problem arises after the user has searched once and wants to narrow the search by selecting one or more filters.
There is a function, updateURL(), which both updates the window.location.href (so we can link to the search with specific filters already selected), and then runs sortList() again.
function updateURL(searchType) {
//this resents the results array so that
results = [];
//resorts results based on new criteria
sortList();
//runs search again so that the filters are applied asynchronously
search();
};
From what I could tell for some reason IE did not like having the variable loadedList declared outside of the loadList() function. I don't know why but after the items initially loaded there would always be problems with loadedList whenever sortList() was called again.
I ended up removing the function loadList() and just having the xml in the body (not in a function).
var items = new XMLHttpRequest();
items.onreadystatechange = function() {
//puts all the xml into the loadedList variable
if (this.readyState == 4 && this.status == 200) {
var xmlDoc = this.responseXML;
loadedList = xmlDoc.getElementsByTagName("itm");
sortList();
}
};
items.open("GET", "item-list-file.xml", true);
items.send();
function sortList() {
Still not sure what IE's issue with this was but it works now.

A function in JavaScript that returns an array. I am able to print the items in the array when the function is called, but not each item in the array

I am trying to create a Movie object from a Json output I got from an API. After each movie object is created, I add them an array of movies. All of this is inside a function and it returns the array of movies. When the function is called, I was able to console log the movies; however, when trying to get a specific item using an index, it returns undefined. Am I missing something in the code? Any help is greatly appreciated. Thanks!
function Movie(title, description, director, producer) {
this.title = title;
this.description = description;
this.director = director;
this.producer = producer;
}
var connectedToAPI = false;
function retrieveMovies() {
var movies = [];
// Create a request variable and assign a new XMLHttpRequest object to it.
var request = new XMLHttpRequest();
// Open a new connection, using the GET request on the URL endpoint
request.open('GET', 'https://ghibliapi.herokuapp.com/films', true);
request.onload = function() {
// Begin accessing JSON data here
var data = JSON.parse(this.response);
if (request.status >= 200 && request.status < 400) {
var x = 0;
data.forEach(movie => {
// var title = movie.title;
// var description = movie.description;
// var director = movie.director;
// var producer = movie.producer;
var film = new Movie(movie.title, movie.description, movie.director, movie.producer);
movies[x] = film;
x++;
});
} else {
console.log('error');
}
}
request.send();
connectedToAPI = true;
return movies;
}
var films = retrieveMovies();
if (connectedToAPI == true) {
console.log(films);
console.log(films.length);
console.log("THIS IS MOVIE NUMBER 3: ");
console.log(films[1]);
}
The console prints out:
[] //->this contains the movies when expanded
0 //->the length is zero
THIS IS MOVIE NUMBER 3:
undefined //->retrieving specific item returns udefined
Your code is running BEFORE the request has come back. This is known as a "Race condition". What you want is to pass a callback to your function or promise.
Put an argument in retrieveMovies like so
function retrieveMovies(callback)
Then pass the value of movies into your callback as an arg:
callback(movies)
But do that right after your forEach completes. Your main function won't have a value immediately.
Finally, before you call your api declare a variable onFinished and set it to a function that logs your results:
var onFinished = function(films){
console.log(films);
console.log(films.length);
console.log("THIS IS MOVIE NUMBER 3: ");
console.log(films[1]);
}
retrieveMovies(onFinished);
Note that you don't set the value of retrieveMovies or check for api connection state if you use this technique.
Also, you will note that you do not need to scope your movies array so far away from your loop. Or even at all...
The better technique would be to just invoke the callback with Array.map to avoid more clutter.
Simply invoke like this (delete your forEach and var movies entirely):
callback(data.map(m=> new Movie(m.title, m.description, m.director, m.producer));
A slick one-liner :)
forEach is not modifying variable 'x'. It works on copies, producing undesirable effects in this case. Use a traditional for loop in cases when an index is to be accessed/used

Associative array not filling up in server-sent-event environement

I think I'm on the wrong track here:
I have an event source that gives me updates on the underlying system oprations. The page is intended to show said events in a jquery powered treetable. I receieve the events perfectly but I realized that there were a case I did not handle, the case where an event arrives but is missing it's parent. In this case I need to fetch the missing root plus all potentially missing children of that root node from the database. This works fine too.
//init fct
//...
eventSource.addEventListener("new_node", onEventSourceNewNodeEvent);
//...
function onEventSourceNewNodeEvent(event) {
let data = event.data;
if (!data)
return;
let rows = $(data).filter("tr");
rows.each(function (index, row) {
let parentEventId = row.getAttribute("data-tt-parent-id");
let parentNode = _table.treetable("node", parentEventId);
// if headless state is not fully
// resolved yet keep adding new rows to array
if (headlessRows[parentEventId]) {
headlessRows[parentEventId].push(row);
return;
} else if (parentEventId && !parentNode) { // headless state found
if (!headlessRows[parentEventId])
headlessRows[parentEventId] = [];
headlessRows[parentEventId].push(row);
fetchMissingNodes(parentEventId);
return;
}
insertNode(row, parentNode);
});
}
function fetchMissingNodes(parentEventId) {
let url = _table.data("url") + parentEventId;
$.get(url, function (data, textStatus, request) {
if (!data)
return;
let rows = $(data).filter("tr");
//insert root and children into table
_table.treetable("loadBranch", null, rows);
let parentNode = _table.treetable("node", parentEventId);
let lastLoadedRow = $(rows.last());
let headlessRowsArray = headlessRows[parentEventId];
while (headlessRowsArray && headlessRowsArray.length > 0) {
let row = headlessRowsArray.shift();
let rowId = row.getAttribute("data-tt-id");
if (rowId <= lastLoadedRow) // already loaded event from previous fetch
continue;
insertNode(row, parentNode);
let pendingUpdatesArray = pendingUpdates[rowId];
// shouldn't be more than one but who know future versions
while (pendingUpdatesArray && pendingUpdatesArray.length > 0) {
let updateEvent = headlessRowsArray.shift();
updateNode(updateEvent)
}
delete pendingUpdates[rowId]; // <- something better here?
}
delete headlessRows[parentEventId]; // <- something better here too?
});
}
The problem is around the line if (headlessRows[parentEventId]).
When I run it step by step (putting a debugger instruction just before) everything works fine, the headless array is created and filled correctly.
But as soon as I let it run full speed everything breaks.
The logs I printed seems to indicate that the array is not behaving in the way I was expecting it to. If I print the array with a console.log it shows as follow :
(2957754) [empty × 2957754]
length : 2957754
__proto__ : Array(0)
It seems to be missing any actual data. whereas it shows as follow when I execute it step by step:
(2957748) [empty × 2957747, Array(1)]
2957747:[tr.node.UNDETERMINED]
length:2957748
__proto__:Array(0)
I'm missing something but it is still eluding me.
your code is async, you do http request but you treat him as synchronized code.
try this fix
//init fct
//...
eventSource.addEventListener("new_node", onEventSourceNewNodeEvent);
//...
async function onEventSourceNewNodeEvent(event) {
let data = event.data;
if (!data)
return;
let rows = $(data).filter("tr");
rows.each(function (index, row) {
let parentEventId = row.getAttribute("data-tt-parent-id");
let parentNode = _table.treetable("node", parentEventId);
// if headless state is not fully
// resolved yet keep adding new rows to array
if (headlessRows[parentEventId]) {
headlessRows[parentEventId].push(row);
return;
} else if (parentEventId && !parentNode) { // headless state found
if (!headlessRows[parentEventId])
headlessRows[parentEventId] = [];
headlessRows[parentEventId].push(row);
await fetchMissingNodes(parentEventId);
return;
}
insertNode(row, parentNode);
});
}
function fetchMissingNodes(parentEventId) {
return new Promise((resolve,reject) =>{
let url = _table.data("url") + parentEventId;
$.get(url, function (data, textStatus, request) {
if (!data){
resolve()
return;
}
let rows = $(data).filter("tr");
//insert root and children into table
_table.treetable("loadBranch", null, rows);
let parentNode = _table.treetable("node", parentEventId);
let lastLoadedRow = $(rows.last());
let headlessRowsArray = headlessRows[parentEventId];
while (headlessRowsArray && headlessRowsArray.length > 0) {
let row = headlessRowsArray.shift();
let rowId = row.getAttribute("data-tt-id");
if (rowId <= lastLoadedRow) // already loaded event from previous fetch
continue;
insertNode(row, parentNode);
let pendingUpdatesArray = pendingUpdates[rowId];
// shouldn't be more than one but who know future versions
while (pendingUpdatesArray && pendingUpdatesArray.length > 0) {
let updateEvent = headlessRowsArray.shift();
updateNode(updateEvent)
}
delete pendingUpdates[rowId]; // <- something better here?
}
delete headlessRows[parentEventId]; // <- something better here too?
resolve()
});
})
}

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);
});
}

Appending objects to JSON lists

There is an exception occurring and I am not sure why because I am new to JS. As soon as I reach the line testData["beacons"].append(beacon); My code jumps to catch(e). I am assuming I cant append objects to other arrays?
JS:
$(document).ready(function() {
// Data to describe what kind of test
var testData = {
"timestamp": "",
"hive": 0,
"hdfs": 0,
// Contains a list of testData objects
"beacons":[]
};
var testRun = document.getElementById("test-form");
testRun.addEventListener('submit', function(event) {
event.preventDefault();
var selectedTest = document.querySelector('input[name=test-select]:checked');
alert(selectedTest);
var testType = selectedTest.id;
if (testType == "hdfs-test") {
testData["hdfs"] = 1;
testData["hive"] = 0;
} else if (testType == "hive-test") {
testData["hdfs"] = 0;
testData["hive"] = 1;
} else if (testType == "hdfs-hive-test") {
testData["hdfs"] = 1;
testData["hive"] = 1;
} else {
// null
}
var events = document.getElementById("event-textarea").value;
// check in valid input
var eventSource = events.replace("],[","],,,,[");
// beaconLists allows users to submit --> [{beacon1}, {beacon2}, ...], [{beacon3}, {beacon4}, ...]
var beaconLists = eventSource.split(",,,,");
for (var i = 0; i < beaconLists.length; i++) {
// inspect one list in beaconLists [{beacon1}, {beacon2}, ...]
var beaconList = beaconLists[i];
try {
// list of JSON objects
var beaconObjList = JSON.parse(beaconList);
for (var j = 0; j < beaconObjList.length; j++) {
var beaconObj = beaconObjList[j];
if (beaconObj["data"] && beaconObj["application"]) {
// successful parse to find events
// describe beacon being tested
alert("yes");
var beacon = {
"app_name": beaconObj["application"]["app_name"],
"device": beaconObj["application"]["device"],
"device_id": beaconObj["application"]["device_id"],
"os": beaconObj["application"]["os"],
"os_version": beaconObj["application"]["os_version"],
"browser": beaconObj["application"]["browser"],
"beacon": beaconObj
};
// append to testData
testData["beacons"].append(beacon);
// reset beacon so we can append new beacon later
beacon = {};
} else {
// notify event isn't in the correct format?
alert("no");
}
}
} catch (e) {
// notify bad JSON
alert("failed");
}
}
console.log(testData);
//$.ajax({
// type: "POST",
// url: "/test/",
// data: testData,
// success: function () {
// alert("yay");
// },
// failure: function () {
// alert("boo");
// }
//});
});
});
There is nothing wrong with having an array of objects. JavaScript handles that just fine. The main issue is that append is a jQuery API method for adding elements (psuedo native appendChild). push is how you add to an array.
testData["beacons"].push(beacon);
Further, this part of your code is problematic.
// reset beacon so we can append new beacon later
beacon = {};
Both the variable beacon and the one added here testData["beacons"] are the same. In JavaScript, the value of testData["beacons"]'s recent beacon is the same as the variable beacon. When the value in the variable beacon is set to {}, so is the array's value. This line of code simply needs to be removed. Inside of the variable environment set up, the use of var will set up a new variable for beacon each iteration.
You should use the push method, like:
testData["beacons"].push(beacon);

Categories

Resources