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>
Related
I was unsure how exactly to phrase this in the title. I've made a todo list and I'm working on making the todo items editable. Through the displayTodo function, I've been able to make the li items editable in the DOM, but I would like this change to be reflected in the todoList array as well when I hit the save button. I'm unsure of how exactly I would be able to make this work. I was thinking of the splice method, but I don't know how that would work in this situation since I would need to pass in the index.
// Global Variables
const input = document.querySelector('.input');
const addBtn = document.querySelector('.add-btn');
const removeBtn = document.querySelector('.remove-btn');
const todos = document.querySelector('.todos');
// Event Listeners
addBtn.addEventListener('click', addTodo);
removeBtn.addEventListener('click', removeTodo);
const todoList = [
]
function addTodo() {
// Push user input to array
let inputValue = input.value;
todoList.push(inputValue);
displayTodo();
input.value = '';
console.log(todoList);
}
function removeTodo() {
let listItems = document.querySelectorAll('.todos li');
// Remove last todo from array
todoList.splice(-1, 1);
// Remove last todo from ul
todos.removeChild(listItems[listItems.length - 1]);
//console.log(todoList);
}
function displayTodo() {
// Create li and display it
let newTodo = document.createElement('li');
newTodo.textContent = input.value;
todos.appendChild(newTodo);
// Create edit button and display it
let editButton = document.createElement('button');
editButton.textContent = 'Edit';
newTodo.appendChild(editButton);
editButton.addEventListener('click', function() {
// Create edit input and save button
editButton.style.opacity = 0;
editButton.style.visibility = 'hidden';
let editInput = document.createElement('input');
newTodo.appendChild(editInput);
let saveButton = document.createElement('button');
newTodo.appendChild(saveButton);
saveButton.textContent = 'Save';
saveButton.addEventListener('click', function() {
newTodo.textContent = editInput.value;
console.log(todoList);
})
});
}
<!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" type="text/css" href="style.css">
<title>Todo List</title>
</head>
<body>
<div class="container">
<h1>Todo List</h1>
<input class="input" type="text" placeholder="Add A Task" autocomplete="off" required>
<button class="add-btn">Add Task</button>
<button class="remove-btn">Remove Task</button>
<ul class="todos">
</ul>
</div>
</body>
</html>
First of all, In editButton.addEventListener('click', function() {}, get the text content of the <li> element that has to be edited. The text content has the word 'Edit' appended to the list element and hence removing it in the second line. Get the index of the array element whole value is liValue using indexOf property.
let liValue = newTodo.textContent;
liValue = liValue.replace('Edit', '');
let liIndex = todoList.indexOf(liValue);
In saveButton.addEventListener('click', function () {}, after updating the DOM, use splice() to update the array list.
todoList.splice(liIndex, 1, editInput.value);
I have added the function in which the changes are done. The parts of the code that has to be added are commented down below.
editButton.addEventListener('click', function () {
// Create edit input and save button
editButton.style.opacity = 0;
editButton.style.visibility = 'hidden';
/* first part of the code starts here */
let liValue = newTodo.textContent;
liValue = liValue.replace('Edit', '');
let liIndex = todoList.indexOf(liValue);
/* first part of the code ends here */
let editInput = document.createElement('input');
newTodo.appendChild(editInput);
let saveButton = document.createElement('button');
newTodo.appendChild(saveButton);
saveButton.textContent = 'Save';
saveButton.addEventListener('click', function () {
newTodo.textContent = editInput.value;
/* second part of the code starts here */
todoList.splice(liIndex, 1, editInput.value);
/* second part of the code ends here */
})
});
Link to codepen: https://codepen.io/geekyquentin/pen/LYQdjXw
I have a large datasets which i am retrieving via tableau API call. im using async await to call the data and storing this as txt extension.
How i am retrieving the data is by using this script below, script is working as expected and the logic i came out with is
Retrieve data records via api call
Append data into div element
Once data is fully loaded to div, use file streamer to save records as txt file
script used -
<!DOCTYPE html>
<html>
<head>
<title>getData() Basic Example</title>
<script type="text/javascript" src="https://public.tableau.com/javascripts/api/tableau-2.min.js"></script>
<script type="text/javascript">
var viz, sheet, table;
function initViz() {
var containerDiv = document.getElementById("vizContainer"),
url = "http://public.tableau.com/views/RegionalSampleWorkbook/Storms",
options = {
hideTabs: true,
hideToolbar: true,
onFirstInteractive: function () {
document.getElementById('getData').disabled = false; // Enable our button
}
};
viz = new tableau.Viz(containerDiv, url, options);
}
async function savefile(data){
const newHandle = await window.showSaveFilePicker();
const writableStream = await newHandle.createWritable();
await writableStream.write(data)
await writableStream.close();
}
function getUnderlyingData(){
sheet = viz.getWorkbook().getActiveSheet().getWorksheets().get("Storm Map Sheet");
sheet.getUnderlyingDataAsync().then(function(dataTable){
let _tmpdata = ''
for(let i = 0; i < dataTable.getData().length;i++){
for(let a = 0; a < dataTable.getColumns().length;a++){
_tmpdata = dataTable.getData()[i][a].formattedValue;
document.getElementById('storage').innerHTML += _tmpdata
}
}
let whatisthis = document.getElementById('storage').innerHTML
savefile(whatisthis)
});
}
</script>
</head>
<body onload="initViz();">
<div class="page-header">
<button id="getData" onclick="getUnderlyingData()" class="btn" disabled>Get Data</button>
<div id="storage"></div>
</div>
<div id="vizContainer" style="width:600px; height:600px;"></div>
<div id="dataTarget"></div>
</body>
</html>
This is working as expected but what worries me is when i have super large volume of data, the alternative logic i have in mind which i tried to implement is as follow
create streamer inside getunderlyingdata function
append data directly in for loop
New logic i tried, ets say saveFile() does not exist and writableStream are directy implemented in getUnderlyingData, this is script i tried
async function getUnderlyingData(){
// save file to location
const newHandle = await window.showSaveFilePicker();
const writableStream = await newHandle.createWritable();
sheet = viz.getWorkbook().getActiveSheet().getWorksheets().get("Storm Map Sheet");
sheet.getUnderlyingDataAsync().then(async function(dataTable){
let _tmpdata = ''
for(let i = 0; i < dataTable.getData().length;i++){
for(let a = 0; a < dataTable.getColumns().length;a++){
_tmpdata = dataTable.getData()[i][a].formattedValue;
// Write data to stream
await writableStream.write(_tmpdata)
}
}
});
// Close Sream
await writableStream.close();
}
It was not able to capture the data is because the page get reloaded as soon as i tried to save to a location . Is it possible to disable the page reload when a location is selected to save the file ?
I'm a beginner web dev learner and I'm trying to create a to-do list app by vanilla JS.
I created some main functions but now I'm stucking in 2 points.
1st.
I created an "edit" button. this button adds input textbox element into li element that surrounds each to-do task by using a map method.However, I can't come up with a proper way to replace an original wrtten tasks with a text contents in input textboxes when I finish editing.
So, I would like you to tell me how to write a "compEdit" function in the source code below.
2nd
When I add several tasks and push an edit button of other than 1st task, too many input text boxes are created.Probably, the number of created textboxes is as same amount as the element in arrayItems.
I suppose using map method itself is a wrong aproach.But I can't come up with a good alternative.
I'll be glad if someone tell me a proper way and why the bug in 2nd question happens.
Source code is here
//grab the elements
const todoText = document.getElementsByClassName('todo-text')[0];
const todoBtn = document.getElementsByClassName('todo-btn')[0];
const inputTask = document.getElementsByClassName('input-task')[0];
const arrayItems = [];
//function to add tasks
const addTask = (task) => {
const listItem = document.createElement('li');
const showItem = inputTask.appendChild(listItem);
showItem.innerHTML = task;
listItem.classList.add('list-item');
arrayItems.push(listItem);
//create a delete button
const deleteBtn = document.createElement('button');
deleteBtn.innerHTML = 'delete';
listItem.appendChild(deleteBtn);
//call a function when the button will be clicked
deleteBtn.addEventListener('click', () => {
deleteTask(deleteBtn);
});
//create an edit button
const editBtn = document.createElement('button');
editBtn.innerHTML = 'edit';
listItem.appendChild(editBtn);
//call a function when the button will be clicked
editBtn.addEventListener('click', () => {
editTask(arrayItems, listItem);
});
};
const deleteTask = (deleteBtn) => {
const chosenItem = deleteBtn.closest('li');
inputTask.removeChild(chosenItem);
};
const editTask = (els = [], inputTask) => {
//create a textbox into list items
inputTask.innerHTML = els.map((el, i) => {
return `
<input type="text" class="editing-text[${i}]" name="item[${i}]" required>
<input type="submit" class="complete-btn" value="complete">
`
});
//grab the elements of "edit button" and text into it
const editingText = document.getElementsByClassName('editing-text')[0];
const compBtn = document.getElementsByClassName('complete-btn')[0];
//the function to complete editing
const compEdit = () => {
}
compBtn.addEventListener('click', compEdit);
}
todoBtn.addEventListener('click', () => {
const task = todoText.value;
if(task == ''){
return;
}
addTask(task);
todoText.value = '';
});
<!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">
<title>Document</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<div class="wrap">
<header class="header">
<h1>Welcome!</h1>
</header>
<section class="add-todo">
<div class="list-title">
<h2>Add your task</h2>
</div>
<div class="contents-wrapper">
<div class="list-contents">
<input type="text" name="todo" class="todo-text">
<input type="submit" value="add" class="todo-btn">
</div>
</div>
</section>
<section class="current-tasks">
<div class="current-tasks__title">
<h3>current tasks</h3>
</div>
<div class="tasks-wrapper">
<ul class="input-task"></ul>
</div>
</section>
<footer></footer>
</div>
<script src="/main.js"></script>
</body>
</html>
When I add several tasks and push an edit button of other than 1st task, too many input text boxes are created
This is happening because as you add tasks, the number of elements are increasing, so the inputs are created on the basis of the number of li elements currently in the document. You can avoid this by using event.target which can be used to target each task element. I have shown an example with slight modification in your code.
//grab the elements
const todoText = document.getElementsByClassName("todo-text")[0];
const todoBtn = document.getElementsByClassName("todo-btn")[0];
const inputTask = document.getElementsByClassName("input-task")[0];
//function to add tasks
const addTask = (task) => {
const listItem = document.createElement("li");
const showItem = inputTask.appendChild(listItem);
const taskElem = document.createElement("span");
taskElem.innerHTML = task;
listItem.appendChild(taskElem);
listItem.classList.add("list-item");
//create a delete button
const deleteBtn = document.createElement("button");
deleteBtn.innerHTML = "delete";
listItem.appendChild(deleteBtn);
//call a function when the button will be clicked
deleteBtn.addEventListener("click", () => {
deleteTask(deleteBtn);
});
//create an edit button
const editBtn = document.createElement("button");
editBtn.innerHTML = "edit";
// add a class to identify
editBtn.classList.add("edit");
listItem.appendChild(editBtn);
//call a function when the button will be clicked
// editBtn.addEventListener("click", () => {
// // editTask(arrayItems, listItem);
// });
};
const deleteTask = (deleteBtn) => {
const chosenItem = deleteBtn.closest("li");
inputTask.removeChild(chosenItem);
};
inputTask.addEventListener("click", function(e) {
const target = e.target.classList.contains("edit"),
update = e.target.classList.contains("update");
if (target) {
let val = e.target.parentElement.firstChild.innerHTML;
// alert(val);
e.target.parentElement.innerHTML = `
<input type="text" name="todo" class="todo-text" value="${val}">
<input type="submit" value="update" class="todo-btn update">
`;
}
if (update) {
let updatedValue = e.target.previousElementSibling.value;
e.target.parentElement.innerHTML = `
<li class="list-item"><span>${updatedValue}</span><button>delete</button><button class="edit">edit</button></li>
`;
}
});
todoBtn.addEventListener("click", () => {
const task = todoText.value;
if (task == "") {
return;
}
addTask(task);
todoText.value = "";
});
<div class="wrap">
<header class="header">
<h1>Welcome!</h1>
</header>
<section class="add-todo">
<div class="list-title">
<h2>Add your task</h2>
</div>
<div class="contents-wrapper">
<div class="list-contents">
<input type="text" name="todo" class="todo-text">
<input type="submit" value="add" class="todo-btn">
</div>
</div>
</section>
<section class="current-tasks">
<div class="current-tasks__title">
<h3>current tasks</h3>
</div>
<div class="tasks-wrapper">
<ul class="input-task"></ul>
</div>
</section>
<footer></footer>
</div>
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 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.