JS newbie here.
Hi, I am trying to add an instance as a Node to a div element I created,
but it gives this error:
TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
Should I not do it this way? is this wrong?
I want to create div element for every GitHub user I am searching for, adding 3 tags for each under a div.
I thought doing so by building a class with methods will help with DRY.
class AddElement {
constructor(name, tag) {
this.name = name;
this.tag = tag;
}
elem() {
this.name = document.createElement(this.tag);
document.body.append(this.name);
}
elemProp(property) {
this.elem();
this.name.innerHTML = property;
}
elemError() {
this.elem();
this.name.innerHTML = this.name + ' does not exist.';
this.name.style.color = 'red';
}
elemImg(property) {
this.elem();
this.name.src = property;
this.name.height = 150;
}
}
async function getJson(name) {
let response = await fetch(`https://api.github.com/users/${name}`);
if (response.status === 404) {
new AddElement('notFound', 'h3').elemError();
throw new Error('User not found!');
} else {
let data = await response.json();
return data;
}
}
async function gitUserInfo(name) {
let jsonData = await getJson(name);
let img = jsonData.avatar_url;
let login = jsonData.login;
let bio = jsonData.bio;
let arr = [
new AddElement('userimg', 'img').elemImg(img),
new AddElement('login', 'h2').elemProp(login),
new AddElement('bio', 'h3').elemProp(bio),
]
let div = document.createElement('div');
document.body.append(div);
arr.forEach(item => div.appendChild(item))
}
let search = document.getElementById('searchUser');
search.addEventListener('keyup', function(e) {
if (e.key === 'Enter') {
gitUserInfo(search.value)
} else if (search.value === '') {
removeTag('h2');
removeTag('h3');
removeTag('img');
}
});
function removeTag(tag) {
for (let i = 0; i < document.getElementsByTagName.length; i++) {
document.getElementsByTagName(tag)[i].remove();
}
}
You are not creating an array of htmlElement's but an array of AddElements.
However you were getting the exception as you were creating an array of undefined objects because you were calling the ctor and then some properties that had no return statement so arr looked like this:
arr[ undefined, undefined, undefined ]
It is bad practice to initialize an array in this manner. Initialize your objects first, apply the jsonData, THEN add them to the array. OR simply bypass the array and just modify the dom as you go.
Also, inside elem() you are modifying the dom by adding a newly created element to <body> before you've processed the data. If it were to work as you have it written you would be appending these objects twice.
In the code below I've stripped out the async function and just created a hard-coded json object for brevity.
Also, elements created by document.createElement() contain all the properties you need so there is no reason to create your own.
class AddElement {
constructor(name, tag) {
this.obj = document.createElement(tag);
this.obj.name = name;
}
elemProp(txt) {
this.obj.innerHTML = txt;
return this.obj;
}
elemImg(imgUrl) {
this.obj.src = imgUrl;
this.obj.height = 150;
return this.obj;
}
}
let jsonData = {
avatar_url: "https://picsum.photos/200",
login: "somelogin",
bio: "Lorem ipsum dolor sit amet"
}
function gitUserInfo(data) {
let arr = [
new AddElement('userimg', 'img').elemImg(data.avatar_url),
new AddElement('login', 'h2').elemProp(data.login),
new AddElement('bio', 'h3').elemProp(data.bio),
]
let div = document.createElement('div');
arr.forEach(item => div.appendChild(item));
document.body.append(div);
}
gitUserInfo(jsonData)
Related
// New javascript objects**
let askNow = {
findingID: 234,
findingName: 'Vulnerabilities',
};
let shiftLeft = {
findingID: 456,
findingName: 'shiftVulnerabiities',
};
// Storing javascript objects in an array**
let sources = [askNow, shiftLeft];
// Adding new property**
const addNewProperty = function(src) {
src.forEach(function(srcs) {
srcs.findingStatus = 'open';
});
};
addNewProperty(sources);
console.log(sources);
// Error in Accessing the new property elsewhere**
const index = sources.findIndex(function(cur, i, arr) {
console.log(cur.findingStatus);
})
I've been dealing with this for some time. I've a list of sections in which the user checks some checkboxes and that is sent to the server via AJAX. However, since the user can return to previous sections, I'm using some objects of mine to store some things the user has done (if he/she already finished working in that section, which checkboxes checked, etc). I'm doing this to not overload the database and only send new requests to store information if the user effectively changes a previous checkbox, not if he just starts clicking "Save" randomly. I'm using objects to see the sections of the page, and storing the previous state of the checkboxes in a Map. Here's my "supervisor":
function Supervisor(id) {
this.id = id;
this.verif = null;
this.selections = new Map();
var children = $("#ContentPlaceHolder1_checkboxes_div_" + id).children().length;
for (var i = 0; i < children; i++) {
if (i % 2 == 0) {
var checkbox = $("#ContentPlaceHolder1_checkboxes_div_" + id).children()[i];
var idCheck = checkbox.id.split("_")[2];
this.selections.set(idCheck, false);
}
}
console.log("Length " + this.selections.size);
this.change = false;
}
The console.log gives me the expected output, so I assume my Map is created and initialized correctly. Since the session of the user can expire before he finishes his work, or he can close his browser by accident, I'm storing this object using local storage, so I can change the page accordingly to what he has done should anything happen. Here are my functions:
function setObj(id, supervisor) {
localStorage.setItem(id, JSON.stringify(supervisor));
}
function getObj(key) {
var supervisor = JSON.parse(localStorage.getItem(key));
return supervisor;
}
So, I'm trying to add to the record whenever an user clicks in a checkbox. And this is where the problem happens. Here's the function:
function checkboxClicked(idCbx) {
var idSection = $("#ContentPlaceHolder1_hdnActualField").val();
var supervisor = getObj(idSection);
console.log(typeof (supervisor)); //Returns object, everythings fine
console.log(typeof (supervisor.change)); //Returns boolean
supervisor.change = true;
var idCheck = idCbx.split("_")[2]; //I just want a part of the name
console.log(typeof(supervisor.selections)); //Prints object
console.log("Length " + supervisor.selections.size); //Undefined!
supervisor.selections.set(idCheck, true); //Error! Note: The true is just for testing purposes
setObj(idSection, supervisor);
}
What am I doing wrong? Thanks!
Please look at this example, I removed the jquery id discovery for clarity. You'll need to adapt this to meet your needs but it should get you mostly there.
const mapToJSON = (map) => [...map];
const mapFromJSON = (json) => new Map(json);
function Supervisor(id) {
this.id = id;
this.verif = null;
this.selections = new Map();
this.change = false;
this.selections.set('blah', 'hello');
}
Supervisor.from = function (data) {
const id = data.id;
const supervisor = new Supervisor(id);
supervisor.verif = data.verif;
supervisor.selections = new Map(data.selections);
return supervisor;
};
Supervisor.prototype.toJSON = function() {
return {
id: this.id,
verif: this.verif,
selections: mapToJSON(this.selections)
}
}
const expected = new Supervisor(1);
console.log(expected);
const json = JSON.stringify(expected);
const actual = Supervisor.from(JSON.parse(json));
console.log(actual);
If you cant use the spread operation in 'mapToJSON' you could loop and push.
const mapToJSON = (map) => {
const result = [];
for (let entry of map.entries()) {
result.push(entry);
}
return result;
}
Really the only thing id change is have the constructor do less, just accept values, assign with minimal fiddling, and have a factory query the dom and populate the constructor with values. Maybe something like fromDOM() or something. This will make Supervisor more flexible and easier to test.
function Supervisor(options) {
this.id = options.id;
this.verif = null;
this.selections = options.selections || new Map();
this.change = false;
}
Supervisor.fromDOM = function(id) {
const selections = new Map();
const children = $("#ContentPlaceHolder1_checkboxes_div_" + id).children();
for (var i = 0; i < children.length; i++) {
if (i % 2 == 0) {
var checkbox = children[i];
var idCheck = checkbox.id.split("_")[2];
selections.set(idCheck, false);
}
}
return new Supervisor({ id: id, selections: selections });
};
console.log(Supervisor.fromDOM(2));
You can keep going and have another method that tries to parse a Supervisor from localStorageand default to the dom based factory if the localStorage one returns null.
Problem:
When the user creates a book, the information that is in the input fields will be displayed. There is a remove button that the user can click and it deletes the book. However, when I use filter() I'm just returning the book parameter, so what can I change about my deleteBook() to be able to delete a book? I don't want the UI to work but I just want the library array to update.
Repl: https://repl.it/#antgotfan/library
What I've tried:
I've tried manipulating the document and whenever the user clicked on the remove then it would be deleted but not update the object to show that it was actually deleted
// Variables
const addBook = document.querySelector("#add");
let library = [];
// Event Listeners
addBook.addEventListener("click", render);
document.addEventListener("click", deleteBook);
// Constructor
function Book(title, author, pages, isRead) {
this.title = title;
this.author = author;
this.pages = pages;
this.isRead = isRead;
}
// Prototypes
Book.prototype.toggleIsRead = function() {
if (this.isRead == "Read") {
this.isRead = "Not read";
} else {
this.isRead = "Read";
}
}
function deleteBook(event) {
if (event.target.id == "remove") {
library.filter(book => {
return book;
});
}
}
// Functions
function addBookToLibrary() {
let authorOfBook = document.querySelector("#author").value;
let bookTitle = document.querySelector("#book-title").value;
let numberOfPages = document.querySelector("#pages").value;
let status = document.querySelector("#isRead").value;
let newBook = new Book(bookTitle, authorOfBook, numberOfPages, status);
library.push(newBook);
return newBook;
}
function updateStatus() {
}
function emptyInputs() {
const inputs = Array.from(document.querySelectorAll("input"));
inputs.forEach(input => input.value = "");
}
function render() {
addBookToLibrary();
emptyInputs();
let newBook = library[library.length - 1];
let table = document.querySelector("table");
let createTr = document.createElement("tr");
table.appendChild(createTr);
createTr.innerHTML = `<td>${newBook.title}</td>
<td>${newBook.author}</td>
<td>${newBook.pages}</td>
<td><button class="table-buttons" id="not-read">${newBook.isRead}</button></td>
<td><button class="table-buttons" id="remove">Delete</button></td>`;
}
Error messages:
No errors but just not having an updated object to show what was kept or deleted.
Since the books are in an array you can use a function that takes the book properties like author, title etc
and then uses that info to find the book in the array and delete it.
let books = [{ title: "book1" }, { title: "book2" }];
console.log(books);
deleteBook("book2");
console.log(books);
function deleteBook(title) {
let i = books.findIndex(b => b.title == title);
books.splice(i, 1);
}
//Outputs
[ { title: 'book1' }, { title: 'book2' } ] //before delete called
[ { title: 'book1' } ] //after delete called
This soloution worked for me:
function deleteBook(event) {
if (event.target.id == "remove") {
const table = document.querySelector('table');
const tr = event.target.parentNode.parentNode;
table.removeChild(tr);
}
}
Basically, you grab the table and then remove the child node that had the event fired on it.
You can grab the table using the querySelector() function, and you may want to consider giving that table a unique id or something down the line.
const table = document.querySelector('table');
Then, you take the target of the event, which is the <button> element, and get it's grandparent by calling parentNode twice. The first parent is the <td> element, the next one is the <tr> element, which is what we want to remove from the table.
const tr = event.target.parentNode.parentNode;
Finally, you can call removeChild() on the <table> element and have it remove the row that the button was pushed from.
table.removeChild(tr);
This is the code below, I am confused about this line.
What is this code doing? where does this title ( === title) comes from.
Plz, can anybody explain me about this?
var duplicateNote = notes.filter((note) => note.title === title);
var addNote = (title, body) => {
var notes = [];
var note = {
title,
body
}
try {
var notesstring = fs.readFileSync('notes-data.json');
notes = JSON.parse(notesstring);
} catch (e) {
}
console.log(Array.isArray(notes));
var duplicateNote = notes.filter((note) => note.title === title);
the filter function allow you to create a new array after filtering vaues of an old one
var duplicateNote = notes.filter((note) => note.title === title);
create an array duplicateNote containing entries of note where the entry's title is strictly equal to the title passed when calling the function.
It is equivalent to :
var duplicateNote = []
for (let note of notes) {
if (note.title === title) {
duplicateNote.push(note)
}
}
I'm trying to pass a pointer to a function through the parameters, so the function can update the parameters of the object. The object is bound to the view using {{tasks}} in the tasks.component.html file. Somehow, it looks like the mutations that I do in the provideAndDisplayTasks() function don't change the tasks property in tasks.component.ts at all. I've got similar code that does the same. There it correctly updates the object. I don't understand why it doesn't work in this situation.
How do I update the array, without cloning it?
tasks.component.ts:
private provideAndDisplayTasks() {
const self = this;
self.tasks.push(new Task());
console.log('self.tasks', self.tasks);
this.tasksRepository.provideTasks(self.tasks);
}
tasks-repository.ts:
public provideTasks(tasks: Task[]) {
console.log('parameter', tasks);
this.get('truelime.task/all').subscribe(
data => this.addTasksToTaskArray(tasks, data),
error => {
if (error.status === 401) {
this.router.navigateByUrl('login');
}
Logger.getInstance().logError(error);
}
);
}
private addTasksToTaskArray(tasks: Task[], tasksDto) {
const taskArray = tasksDto.truelime_tasks[0].TrueLime_Task;
for (let i = 0; i < taskArray.length; i++) {
const taskDto = taskArray[i];
const task = new Task();
task.id = taskDto.TaskID;
task.title = taskDto.TaskTitle;
task.description = taskDto.TaskDescription;
task.status = taskDto.TaskStatus;
tasks.push(task);
console.log('tasks', tasks);
}
}
Console logs: