name.forEach is not a function after button is clicked - javascript

I am trying to edit/update current data using the contenteditable attribute which I have successfully enabled onclick. My 'enter' key allows the data to be submitted. However, the console.log reads that a PUT request has been made for a particular list item but without the 'title' or 'isbn' being updated along with it.
Another prominent issue is that my console.log shows books.forEach is not a function, and I have no idea why this is the case since the code inside that function is processed.
HTML ('li' items are solely JS-Generated with a POST request)
<div id="divShowBooks">
<li id="[object HTMLParagraphElement]">
<p id="24" name="anID" placeholder="24">1</p>
<p id="TEST" name="aTitle" placeholder="TEST">TEST</p>
<p id="12345" name="anISBN" placeholder="12345" contenteditable="true">12345</p>
<button>Delete</button>
</li>
</div>
JavaScript
var book_list = document.querySelector('#divShowBooks');
book_list.innerHTML = "";
var books = JSON.parse(this.response);
books.forEach(function (book) {
// Text information to be displayed per item
var id = document.createElement('p');
id.type = 'text';
id.innerHTML = book.id;
var title = document.createElement('p');
title.type = 'text';
title.innerHTML = book.title;
var isbn = document.createElement('p');
isbn.type = 'text';
isbn.innerHTML = book.isbn;
// Defining the element that will be created as a list item
var book_item = document.createElement('li');
// Displays id, title and ISBN of the books from the database
book_item.appendChild(id);
book_item.appendChild(title);
book_item.appendChild(isbn);
// Creates an ID attribute per list item
book_item.setAttribute("id", id)
// Assigns attributes to p items within book items
id.setAttribute("id", book.id)
title.setAttribute("id", book.title)
isbn.setAttribute("id", book.isbn)
// Adding a generic name to these elements
id.setAttribute("name", "anID")
title.setAttribute("name", "aTitle")
isbn.setAttribute("name", "anISBN")
title.addEventListener('click', function (e) {
e.preventDefault();
title.contentEditable = "true";
title.setAttribute("contenteditable", true);
title.addEventListener('keypress', function (e) {
if (e.keyCode === 13) {
e.preventDefault();
xhttp.open("PUT", books_url + '/' + book.id, true);
var editTitle = new FormData() /
editTitle.append("title", document.getElementsByName("aTitle")[0].value)
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
xhttp.send(); //
}
});
});
UPDATE
I have added the following to my code. This seems to display my database items as an array in the log. But, I am now having a similar issue with Uncaught TypeError: JSON.parse(...).map is not a function:
var params = [
id = 'id',
title = 'title',
isbn = 'isbn',
createdAt = 'createdAt',
updatedAt = 'updatedAt'
];
var books = JSON.parse(this.response).map(function(obj) {
return params.map(function(key) {
return obj[key];
});
});
console.log(books);
UPDATE 2
Here is an image of what I receive in the console.log. The first part displays the original JSON content and the second is my attempt to convert each object into an array.
See Image

You have to make sure that your books variable actually contains an Array after parsing.
Alternatively, but this wouldn't make sense, just to address the "books.forEach is not a function" issue, You can use Object.assign([], this.response);. To make sure that books will contain an array, you wrap it in a try catch and make something like this:
var books = [];
try {
books = Object.assign([], this.response);
} catch (error) {
books = [];
}
books.forEach will then be expected to always work but you have to be careful because something like this could happen:
var myStringObject = "{'myProperty':'value'}";
var myArray = Object.assign([], myStringObject );
//myArray value is ["{", "'", "myProperty", "'", ":", "'", "value", "'", "}"]
Which will leave you having to check the book in your forEach callback if it is correct:
//at the topmost of your forEach callback
if(!book.id) throw BreakException; //A simple break will not work on forEach
This will leave you again with another exception to handle. Or leave you having to use the traditional for loop since you cannot short circuit Array.forEach with a break.
TLDR: make sure books always contains an Array.

You are getting books from JSON.parse(), which means books is an object and not an array.
forEach is an array method.
Try console logging books and look for an array inside of it.

Related

I'm getting the value off "undefined" inside of getting the value off my textbox

I want to be able to write something inside off my text box and then get the value off it added to my objects that i have pushed into a list "let myToDos = [];"
window.onload = function(){
// without this my site keeps realoding when adding a new item
let firstTask = new Todo ('Bädda sängen');
let secondTask = new Todo ('Hänga upp tavlorna');
let thirdTask = new Todo ('Kick back & realx');
// Adding my premade todo's into my Array that has the variable 'myToDos'
myToDos.push(firstTask);
myToDos.push(secondTask);
myToDos.push(thirdTask);
// creating a function so that the user can add a new todo
let addButton = document.getElementById('addBtn');
addButton.addEventListener('click',addNewTask);
preMadeTasks ();
console.log(myToDos);
}
let myToDos = [];
class Todo{
constructor(toDoItem){
this.toDoItem = toDoItem;
}
}
function addNewTask (e){
e.preventDefault();
let test = document.getElementById ("mySection");
let inputValue = document.getElementById('textBox').value;
if (inputValue == ""){
alert("Type in something");
}[enter image description here][1]
else{
myToDos.push(inputValue);
test.innerHTML="";
preMadeTasks();
}
}
In the addNewTask function you are pushing a string value into the myToDos array instead of an instance of the Todo class. You have to do:
myToDos.push(new Todo(inputValue));

JQuery onclick parameter passing with append

Im currently trying to append a variable amount of text to a list. Each item would need to be clickable with their own value being passed to a function. I cannot seem to get this to work and keep getting a 'Object' is not defined at HTMLAnchorElement.onclick error, where object is the name of the object in the list. Here is the code that I am using for this:
if (user) {
id = user.uid;
ref = firestore.collection("Users").doc(id);
console.log(user);
console.log(ref.get());
ref.get().then(function(doc){
nameString = doc.data().name;
console.log(nameString);
const outputHeader = document.querySelector("#headMain");
const outputInfo = document.querySelector("#genInfo");
outputHeader.innerText = "Welcome " + nameString;
outputInfo.innerText = "Create a class or choose a class from the left";
});
firestore.collection("Users").doc(id).collection("Classrooms").get().then(function(querySnapshot){
querySnapshot.forEach(function(doc){
classNameString = doc.id;
console.log(doc.id, " => ", doc.data());
$("li").append(''+doc.id+'<br/>');
});
});
}
The current testInfo function is as follows:
function testInfo(val){
console.log(val);
}
The following html code cannot work
'+doc.id+'
You are missing the double quotes, so the onclick is executed on the anchor element (the "#")
What you want to do is:
'+doc.id+'

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

How to get only one element from an array (firebase database, nodejs)

If I use limitToLast(1), then I still get an object, with one key-value pair. To get the value, I use this code:
db.ref('/myarray').limitToLast(1).once('value').then(function(snapshot) {
var result = snapshot.val();
var lastElem;
var lastKey;
for(var i in result) {
lastElem= result[i];
lastKey = i;
break;
}
...
});
It works, but I think I do it wrong. But haven't found any better solution in the docs. How should I load only one element?
The database looks like this:
When using Firebase queries to get children, use the "child_added" event:
db.ref("/myarray")
.orderByKey() // order by chlidren's keys
.limitToLast(1) // only get the last child
.once("child_added", function(snapshot) {
var key = snapshot.key;
var val = snapshot.val();
...
});

return from JS function

basic JS question, please go easy on me I'm a newb :)
I pass 2 variables to the findRelatedRecords function which queries other related tables and assembles an Array of Objects, called data. Since findRelatedRecords has so many inner functions, I'm having a hard time getting the data Array out of the function.
As it currently is, I call showWin inside findRelatedRecords, but I'd like to change it so that I can get data Array directly out of findRelatedRecords, and not jump to showWin
function findRelatedRecords(features,evtObj){
//first relationship query to find related branches
var selFeat = features
var featObjId = selFeat[0].attributes.OBJECTID_1
var relatedBranch = new esri.tasks.RelationshipQuery();
relatedBranch.outFields = ["*"];
relatedBranch.relationshipId = 1; //fac -to- Branch
relatedBranch.objectIds = [featObjId];
facSel.queryRelatedFeatures(relatedBranch, function(relatedBranches) {
var branchFound = false;
if(relatedBranches.hasOwnProperty(featObjId) == true){
branchFound = true;
var branchSet = relatedBranches[featObjId]
var cmdBranch = dojo.map(branchSet.features, function(feature){
return feature.attributes;
})
}
//regardless of whether a branch is found or not, we have to run the cmdMain relationship query
//the parent is still fac, no advantage of the parent being branch since cmcMain query has to be run regardless
//fac - branch - cmdMain - cmdSub <--sometimes
//fac - cmdMain - cmdSub <-- sometimes
//second relationship query to find related cmdMains
var relatedQuery = new esri.tasks.RelationshipQuery();
relatedQuery.outFields = ["*"];
relatedQuery.relationshipId = 0; //fac -to- cmdMain
relatedQuery.objectIds = [featObjId];
//rather then listen for "OnSelectionComplete" we are using the queryRelatedFeatures callback function
facSel.queryRelatedFeatures(relatedQuery, function(relatedRecords) {
var data = []
//if any cmdMain records were found, relatedRecords object will have a property = to the OBJECTID of the clicked feature
//i.e. if cmdMain records are found, true will be returned; and continue with finding cmdSub records
if(relatedRecords.hasOwnProperty(featObjId) == true){
var fset = relatedRecords[featObjId]
var cmdMain = dojo.map(fset.features, function(feature) {
return feature.attributes;
})
//we need to fill an array with the objectids of the returned cmdMain records
//the length of this list == total number of mainCmd records returned for the clicked facility
objs = []
for (var k in cmdMain){
var o = cmdMain[k];
objs.push(o.OBJECTID)
}
//third relationship query to find records related to cmdMain (cmdSub)
var subQuery = new esri.tasks.RelationshipQuery();
subQuery.outFields = ["*"];
subQuery.relationshipId = 2;
subQuery.objectIds = [objs]
subTbl.queryRelatedFeatures(subQuery, function (subRecords){
//subRecords is an object where each property is the objectid of a cmdMain record
//if a cmdRecord objectid is present in subRecords property, cmdMain has sub records
//we no longer need these objectids, so we'll remove them and put the array into cmdsub
var cmdSub = []
for (id in subRecords){
dojo.forEach(subRecords[id].features, function(rec){
cmdSub.push(rec.attributes)
})
}
var j = cmdSub.length;
var p;
var sub_key;
var obj;
if (branchFound == true){
var p1 = "branch";
obj1 = {};
obj1[p1] = [cmdBranch[0].Branches]
data.push(obj1)
}
for (var i=0, iLen = cmdMain.length; i<iLen; i++) {
p = cmdMain[i].ASGMT_Name
obj = {};
obj[p] = [];
sub_key = cmdMain[i].sub_key;
for (var j=0, jLen=cmdSub.length; j<jLen; j++) {
if (cmdSub[j].sub_key == sub_key) {
obj[p].push(cmdSub[j].Long_Name);
}
}
data.push(obj);
}
showWin(data,evtObj) <---this would go away
})
}
//no returned cmdRecords; cmdData not available
else{
p = "No Data Available"
obj = {}
obj[p] = []
data.push(obj)
}
showWin(data,evtObj) <--this would go away
})
})
}
I'd like to have access to data array simply by calling
function findRelatedRecords(feature,evt){
//code pasted above
}
function newfunct(){
var newData = findRelatedRecords(feature,evt)
console.log(newData)
}
is this possible?
thanks!
Edit
Little more explanation.....
I'm connecting an Object event Listener to a Function like so:
function b (input){
dojo.connect(obj, "onQueryRelatedFeaturesComplete", getData);
obj.queryRelatedFeatures(input);
console.log(arr) //<----this doesn't work
}
function getData(relatedFeatData){
var arr = [];
//populate arr
return arr;
}
So when obj.QueryRelatedFeatures() is complete, getData fires; this part works fine, but how to I access arr from function b ?
Post Edit Update:
Due to the way that this event is being hooked up you can't simple return data from it. Returning will just let Dojo call to the next method that is hooked up to onSelectionComplete.
When init runs it is long before findRelatedRecords will ever be executed/fired by the onSelectionComplete event of the well, which is why you were seeing undefined/null values. The only way to work with this sort of system is to either 1) call off to a method like you're already doing or 2) fire off a custom event/message (technically it's still just calling off to a method).
If you want to make this method easier to work with you should refactor/extract snippets of it to make it a smaller function but contained in many functions. Also, changing it to have only one exit point at the end of the findRelatedRecords method will help. The function defined inside of subTbl.queryRelatedFeatures() would be a great place to start.
Sorry, you're kind of limited by what Dojo gives you in this case.
Pre Edit Answer:
Just return your data out of it. Everywhere where there is a showWin call just use this return.
return {
data: data,
evtObj: evtObj
}
Then your newfunct would look like this.
function newfunct(){
var newData = findRelatedRecords(feature,evt);
console.log(newData);
console.log(newData.data);
console.log(newData.evtObj);
}
If you only need that "data" object, then change your return to just return data;.
Also, start using semicolons to terminate statements.

Categories

Resources