This keyword issue? - javascript

I asked this question earlier but the formatting wasnt too helpful so im reposting. The logic that I wanted to implement was everytime the button is clicked, the value from the input boxes goes to the addInventory function, creates a new Album, and pushed the newly created object to the products array. At the end of the logic, there is a FOR OF loop that invokes the gridChild function and creates a new div box for every element in the array.
The issue is that whenever I click the button, nothing happens and also the THIS keyword is undefined. The new object doesnt get pushed into the array and only works when the addInventory function is a called manually.
"use strict";
// DIV NESTING USING ONLY JS
const app = document.querySelector(`.app`);
const div = `<div class="grid-child">
</div>`;
const gridContainer = `<div class="grid-container"></div>`;
// New products will be pushed in here
let products = [];
const gridParent = function() {
// app.insertAdjacentHTML(`afterbegin`, gridContainer);
app.insertAdjacentHTML(`afterbegin`, gridContainer);
}
gridParent();
const gridContainerDiv = document.querySelector(`.grid-container`);
const gridChild = function() {
gridContainerDiv.insertAdjacentHTML(`afterbegin`, div);
}
// UI logic for product form
// Will create a new album object
// Need to use a prototype and class inheritance so the component is reusable and applicable for different
// types of products.
class Album {
constructor(title, artist, price) {
this.title = title;
this.artist = artist;
this.price = price;
}
}
const addInventory = (title, artist, price) => {
const newAdd = new Album(title, artist, price);
products.push(newAdd);
console.log(this);
};
// THIS WORKS, ADDS TO THE PRODUCT ARRAY NO PROBLEM
//
addInventory(`Internet`, `Donald Glover`, 15);
addInventory(`Black Pumas`, `Black Pumas`, 31);
const productName = document.getElementById(`product-name`);
console.log(productName);
const productPrice = document.getElementById(`product-price`);
console.log(productPrice);
const inputBtn = document.getElementById(`inputBtn`);
console.log(inputBtn);
// THIS DOESNT WORK, THE THIS KEYWORD IS UNDEFINED AND DOES NOT CALL THE ADDINVENTORY FUNCTION
inputBtn.addEventListener(`click`, (e) => {
e.preventDefault();
addInventory(productName.value, ` `, +productPrice.value);
console.log(this);
})
console.log(products);
// Product list mutation needs to happen before the loop function
// so that data will be the most updated version everytime
// A div box will be created for every element in the product array
for (const everyElement of products) {
gridChild();
console.log(everyElement);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Product div creator</title>
<link rel="stylesheet" href="css/style.css">
<script type="module" defer src="script.js"></script>
</head>
<body>
<div class="app">
</div>
<div class="inventory-gui">
<input type="text" id="product-name" name="product-name" data-product="info"><br>
<input type="number" id="product-price" name="product-price" data-product="info"><br><br>
<button type="submit" id="inputBtn">Make a new item</button>
</div>
</body>
</html>

The script is doing what it is supposed to do.
const addInventory = (title, artist, price) => {
const newAdd = new Album(title, artist, price);
products.push(newAdd);
console.log(this);
};
addInventory(`Internet`, `Donald Glover`, 15);
In the above script the this keyword will be undefined because addInventory is an arrow function and you are using strict mode.
inputBtn.addEventListener(`click`, (e) => {
e.preventDefault();
addInventory(productName.value, ` `, +productPrice.value);
console.log(this);
})
The callback of event listener has access to the addInventory function so it will be called when the button is clicked. this keyword will be undefined here because it is an arrow function.
`console.log(products);`
I think you are expecting the above line to execute again when you click on the button. When you click on the button only the callback given to event listener gets executed not the whole script. If you need to access the updated products array you can in the callback like after calling the addInvetory function.
See this sandbox for what I'm trying to say.

Related

Creating a soundtrack app in JavaScript to search and display tracks

I'm trying to build a small JavaScript program which allows the users to enter a search query, and then find the results of these query and display them through an asynchronous request. Below you can find what I tried so far, but it doesn't display anything other than a search box. Could you please help with implementation? Thanks in advance!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Podcasts</title>
<script>
// TODO 1: Ensure that the main part of the script is executed when the DOM is ready.
document.addEventListener('DOMContentLoaded', function (event) {
})
// TODO 2: Set the constant input to the input field from the HTML body
const input = document.getElementById("input");
const outputDiv = document.getElementById("output");
// Attach the event handler to the input
// Event handler function for when the user types
// TODO 3: Register the inputHandler function as event listener on the input
function InputHandler() {
input.addEventListener('submit', function (event) {
// TODO 4:
// While the user types in the field, the event listener inputHandler
// shall create an asynchronous request to access the API to search
// for soundtracks by title.
async function asynchronousRequest () {
const id = await document.getElementsByName('input');
return document.getElementById(id);
}
});
}
function responseHandler (soundtracks) {
outputDiv.innerHTML = '';
const tb1 = document.createElement('table');
// TODO 5: Process the result
soundtracks.forEach(soundtrack => {
let tr = document.createElement('tr');
let td1 = document.createElement('td');
tr.append(td1);
let td2 = document.createElement('td');
tr.append(td2);
let td3 = document.createElement('td');
tr.append(td3);
})
const tr = tb1.insertRow();
const td1 = tr.insertCell();
// TODO 6: Create an additional audio element and set source to soundtracks' audioSrc.
const audio = document.getElementById("audioSrc").src;
outputDiv.appendChild(tb1);
outputDiv.appendChild(td1);
outputDiv.appendChild(audio);
}
</script>
</head>
<body>
<input type="text" class="user-input">
<div id="output"></div>
</body>
</html>

localStorage not working properly/localStorage overwriting itself

I'm attempting to create a simple to-do list and I've encountered two problems:
After refreshing the page, all the created elements are no longer visible on the page despite being in local storage.
After refreshing the page and submitting new values to the input, localStorage overwrites itself.
Despite that, the items displayed from the input fields are from the previous localStorage, which no longer exists (I really hope this makes sense).
const inputEl = document.getElementById("inputEl")
const submitBtn = document.getElementById("submit")
const clearBtn = document.getElementById("clearBtn")
const todoListContainer = document.getElementById("todoList")
const taskContainer = document.querySelector(".task")
const cancelBtn = document.querySelector(".cancelBtn")
const doneBtn = document.querySelector(".doneBtn")
const errorMsg = document.querySelector(".error")
let localStorageContent = localStorage.getItem("tasks")
let tasksItem = JSON.parse(localStorageContent)
let tasks = []
function createTask() {
if (inputEl.value.length != 0) {
const newDiv = document.createElement("div")
newDiv.classList.add("task")
const newParagraph = document.createElement("p")
const newCancelBtn = document.createElement("button")
newCancelBtn.classList.add("cancelBtn")
newCancelBtn.textContent = "X"
const newDoneBtn = document.createElement("button")
newDoneBtn.classList.add("doneBtn")
newDoneBtn.textContent = "Done"
todoListContainer.appendChild(newDiv)
newDiv.appendChild(newParagraph)
newDiv.appendChild(newCancelBtn)
newDiv.appendChild(newDoneBtn)
//^^ Creating a container for a new task, with all its elements and assigning the classes^^
tasks.push(inputEl.value)
inputEl.value = ""
for (let i = 0; i < tasks.length; i++) {
localStorage.setItem("tasks", JSON.stringify(tasks))
newParagraph.textContent = JSON.parse(localStorageContent)[i]
}
errorMsg.textContent = ""
} else {
errorMsg.textContent = "You have to type something in!"
errorMsg.classList.toggle("visibility")
}
}
submitBtn.addEventListener("click", () => {
createTask()
})
clearBtn.addEventListener("click", () => {
localStorage.clear()
})
HTML code below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/style.css">
<script src="/script.js" defer></script>
<title>To-do list</title>
</head>
<body>
<h2 class="error visibility"></h2>
<div id="todoList">
<h1>To-Do List</h1>
<input type="text" name="" id="inputEl" placeholder="Add an item!">
<button type="submitBtn" id="submit">Submit</button>
<button id="clearBtn">Clear list</button>
<div class="task">
</div>
</div>
</body>
</html>
After refreshing the page, all the created elements are no longer visible on the page despite being in local storage
That is because you are rendering the HTML only after the click event and not on page load. To render the HTML for existing tasks stored in the localStorage you have to write a code that loops over your existing tasks in the tasksItem and applies the rendering logic to it.
I would suggest splitting the rendering code from your createTask() function and create a new function for it (for example renderTask()), then you can use it inside a loop on page load and also call the function once a new task is created in the createTask() function.
window.addEventListener('load', (event) => {
// Your read, loop and render logic goes here
})
After refreshing the page and submitting new values to the input, localStorage overwrites itself.
That's because you are actually overriding the tasks in the localStorage. To keep existing tasks, you have to use your tasksItem variable instead of the blank tasks array to create your tasks in and save them to the localStorage.
So, instead of:
tasks.push(inputEl.value)
You would use:
tasksItem.push(inputEl.value)
The same goes for:
for (let i = 0; i < tasksItem.length; i++) {
localStorage.setItem("tasks", JSON.stringify(tasksItem))
// …
}

I am having trouble using bind to change the this statement to point to my controller Javascript MVC

I am trying to implement model view controller pattern in a simple print hello world program. I can get everything to work properly except at the end after I click the button in my program. I am trying to bind my function to the controller so that way the function uses the this statements in the controller to access my model and view instances in the controller. When I click the button the this statements in my function are referencing my button object instead of the controller. I am having trouble using bind to change what the this statements point to. Please, any assistance would be greatly appreciated. thank you.
<!DOCTYPE html>
<html lang="en">
<head>
<title> Hello World MVC </title>
<link rel="stylesheet" href="css file name">
</head>
<body>
<div id="container">
<input type=text id="textBox">
<button id="displayButton">Display</button>
</div>
<script src="mainbind.js"></script>
</body>
</html>
function Model(text) {
this.data = text;
};
function View() {
this.displayButton = document.getElementById('displayButton');
this.textBox = document.getElementById('textBox');
this.initialize = function(displayButtonProcess) {
this.displayButton.addEventListener('click', displayButtonProcess);
}
};
function Controller(text) {
this.model = new Model(text);
this.view = new View;
this.buttonClick = function(event) {
// process the button click event
this.view.textBox.value = this.model.data;
};
this.view.initialize(this.buttonClick);
};
let x = new Controller("Hello World");
x.buttonClick = x.buttonClick.bind(x);
The problem is that you are changing controller instance property after you have already used unbinded version as a callback.
You can fix it by binding directly when creating a controller. Or you should better use arrow functions instead.
this.buttonClick = () => this.view.textBox.value = this.model.value
function Model(text) {
this.data = text;
};
function View() {
this.displayButton = document.getElementById('displayButton');
this.textBox = document.getElementById('textBox');
this.initialize = function(displayButtonProcess) {
this.displayButton.addEventListener('click', displayButtonProcess);
}
};
function Controller(text) {
this.model = new Model(text);
this.view = new View;
this.buttonClick = function(event) {
// process the button click event
this.view.textBox.value = this.model.data;
};
this.view.initialize(this.buttonClick.bind(this));
};
let x = new Controller("Hello World");
// x.buttonClick = x.buttonClick.bind(x);
<div id="container">
<input type=text id="textBox">
<button id="displayButton">Display</button>
</div>

Button Event-listener is unresponsive

I'm following a tutorial and I made a button to show some content. However this button doesn't work and I'm at my wits end unable to figure out what could be causing this.
Can someone show why this doesn't work?
const users = document.querySelector('#user');
const getUsers = document.getElementById('getUsers');
getUsers.addEventListener('click', loadUsers);
var loadUsers = () => {
console.log('hello button clicked')
let xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.github.com/users', true);
xhr.onload = () => {
if (this.status == 200) {
let gusers = this.responseText
console.log(gusers);
}
}
xhr.send()
}
console.log(getUsers)
<h1>USER</h1>
<button id="getUsers">Get Users</button>
<div id="users"></div>
Order of your variable declarations matters in this scenario due to hoisting - move the loadUsers definition above the call.
JavaScript only hoists declarations, not initializations. If a
variable is declared and initialized after using it, the value will be
undefined.
The block-quote above from MDN explains why function declarations can be defined after they are called (reading code from top-to-bottom), but variables that are initialized after they are used would have a value of undefined.
const users = document.querySelector('#user');
const getUsers = document.getElementById('getUsers');
const loadUsers = () => {
console.log('Load users..');
}
getUsers.addEventListener('click', loadUsers);
<head>
<meta charset="UTF-8">
<title>Testing AJAX</title>
</head>
<body>
<h1>USER</h1>
<button id="getUsers">Get Users</button>
<div id="users"></div>
</body>
Or you could keep the function at the bottom but use a function declaration which will be hoisted:
const users = document.querySelector('#user');
const getUsers = document.getElementById('getUsers');
getUsers.addEventListener('click', loadUsers);
function loadUsers() {
console.log('Load users..');
}
<head>
<meta charset="UTF-8">
<title>Testing AJAX</title>
</head>
<body>
<h1>USER</h1>
<button id="getUsers">Get Users</button>
<div id="users"></div>
</body>
In addition to the correct answer have a look at your code that I have refactored below. Hope this helps.
// Get Elements
const usersList = document.querySelector('#usersList');
const usersBtn = document.querySelector('#usersBtn');
// Bind listener to usersButton
usersBtn.addEventListener('click', () => {
// XHR Request function
const xhr = new XMLHttpRequest();
xhr.open('GET','https://api.github.com/users')
xhr.send()
xhr.onload = function() {
if (xhr.status == 200) {
// Convert the response to JSON and assign it to data
const data = JSON.parse(xhr.responseText)
// Loop throug through data
for(let i = 0; i <data.length; i++) {
// Create LI element and append the user name
const listItem = document.createElement('li');
usersList.appendChild(listItem).innerHTML = data[i].login
}
}
}
})
<h1>USERS</h1>
<button id="usersBtn">Get Users</button>
<ul id="usersList"></ul>

What is the error i'm making that is stopping me from accessing object properties?

I've run into a problem that I can't figure out myself. My assignment is to:
create onload event handler init()
in init(), create 3 javascript objects each with the same 6 properties (pertinent one is Price)
initialize the values of the properties of each of the 3 objects
create an HTML page with 3 buttons (one for each type of object). these buttons will each have an onclick event activating an event handler called "getPrice"
create a div for output text
in the JS event handler, code it up so that the output text will show the name of the button clicked and the corresponding price
My major problem is that I can't understand how to pull the Price property from the objects. Here is my HTML:
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
<script type="text/javascript" src="Scripts/Script.js"></script>
</head>
<body onload="init()">
<input id="btnKoalas" type="button" name="Koalas" value="Koalas" onclick="getPrice()"/>
<input id="btnTulips" type="button" name="Tulips" value="Tulips" onclick="getPrice()"/>
<input id="btnPenguins" type="button" name="Penguins" value="Penguins" onclick="getPrice()"/>
<br />
<p id="divText">This is the output text</p>
</body>
</html>
and here is my javascript
function init()
{
function Koala(ProdID, SupplierCode, Description, PictureName, QtyOnHand, Price) {
this.ProdID = ProdID;
this.SupplierCode = SupplierCode;
this.Description = Description;
this.PictureName = PictureName;
this.QtyOnHand = QtyOnHand;
this.Price = Price;
}
function Tulip(ProdID, SupplierCode, Description, PictureName, QtyOnHand, Price) {same as above}
function Penguin(ProdID, SupplierCode, Description, PictureName, QtyOnHand, Price) {same as above}
}
objKoala = new Koala('1002', 'S1001', 'Koalas', 'Koala.jpg', '9', '119.95');
objTulip = new Tulip('1003', 'S1002', 'Tulips', 'Tulip.jpg', '9', '7.95');
objPenguin = new Penguin('1004', 'S1003', 'Penguins', 'Penguin.jpg', '9', '127.95');
function getPrice()
{
var output = objKoala.Price;
//i know this ^ isn't right.. i'm floundering trying to directly pull the data from my object above and it's still not displaying it
document.getElementById('divText').innerHTML = output;
}
i thought i had established my objects as global but when i click my button, nothing happens. How can i pass parameters to the getPrice() function to pull the properties through? Am i making a mistake in the way i am creating my objects?
Try this instead of that body onload. I moved the event listener into the js itself. And this will also work for all animals. Notice the this.name in the price function. That should take the button's name attribute and access it's respective property without you having to sent it as a parameter.
var animals = {};
function init() {
// The event listeners for each button
document.getElementById('btnKoalas').addEventListener('click', getPrice);
document.getElementById('btnTulips').addEventListener('click', getPrice);
document.getElementById('btnPenguins').addEventListener('click', getPrice);
// We don't need to do it in here, we can also put it outside.
animals.Koala = new Koala('1002', 'S1001', 'Koalas', 'Koala.jpg', '9', '119.95');
animals.Tulip = new Tulip('1003', 'S1002', 'Tulips', 'Tulip.jpg', '9', '7.95');
animals.Penguin = new Penguin('1004', 'S1003', 'Penguins', 'Penguin.jpg', '9', '127.95');
}
function Koala(ProdID, SupplierCode, Description, PictureName, QtyOnHand, Price) {
this.ProdID = ProdID;
this.SupplierCode = SupplierCode;
this.Description = Description;
this.PictureName = PictureName;
this.QtyOnHand = QtyOnHand;
this.Price = Price;
}
function Tulip(ProdID, SupplierCode, Description, PictureName, QtyOnHand, Price) {same as above}
function Penguin(ProdID, SupplierCode, Description, PictureName, QtyOnHand, Price) {same as above}
// This gets called when the user clicks the button
function getPrice() {
// 'this' here is the element that was clicked, which can be any of the 3 buttons
// And we can dynamically access the animal based on the name attribute of the currently clicked button
var output = animals[this.name].Price;
document.getElementById('divText').innerHTML = output;
}
// We wait for the window to load, inline event listeners like '<body onload="">' are a bad idea
window.addEventListener('DOMContentLoaded', init);
And the html
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
<script type="text/javascript" src="Scripts/Script.js"></script>
</head>
<body>
<input id="btnKoalas" type="button" name="Koala" value="Koalas"/>
<input id="btnTulips" type="button" name="Tulip" value="Tulips"/>
<input id="btnPenguins" type="button" name="Penguin" value="Penguins"/>
<br />
<p id="divText">This is the output text</p>
</body>
</html>
The functions Koala, Tulip and Penguin are scoped inside the init function, and can not be accessed outside it. The init function doesn't really do anything in particular so you could just as well move these outside.
(Also, the code inside the init() function only runs after the page has loaded, but new Koala etc. runs immediately as the script is loading, i.e., before the Koala function has been defined inside init(). But as I said, since Koala isn't accessible from outside init(), this doesn't even matter here.)
When you drop the init() function, your code should work fine:
// NOTE: no init() here, so Koala, Tulip, Penguin are globally declared
function Koala(ProdID, SupplierCode, Description, PictureName, QtyOnHand, Price) {
this.ProdID = ProdID;
this.SupplierCode = SupplierCode;
this.Description = Description;
this.PictureName = PictureName;
this.QtyOnHand = QtyOnHand;
this.Price = Price;
}
// etc.
objKoala = new Koala('1002', 'S1001', 'Koalas', 'Koala.jpg', '9', '119.95');
// etc.
function getPrice()
{
var output = objKoala.Price;
document.getElementById('divText').innerHTML = output;
}
<input id="btnKoalas" type="button" name="Koalas" value="Koalas" onclick="getPrice()"/>
<!-- etc. -->
<br />
<p id="divText">This is the output text</p>

Categories