Can't fetch image URL and use it as CSS background image - javascript

I have a Unordered List wist List Items that are created using the Fetch method. The data is JSON data and needs to be relayed to an HTML document
I want the created list items to have a CSS-background based on the JSON data URL. It looks something like this;
fetch(url).then(function(response){
return response.json();
JSON.stringify(data);
}).then(function(data){
for(var i=0;i<data.length;i++){
var createListItem = document.createElement("li");
createListItem.className = "listItem";
var listItem = document.querySelector(".listItem");
var createHeaderItem = document.createElement("h3");
createHeaderItem.innerHTML = data[i].title;
ul.appendChild(createListItem);
createListItem.appendChild(createHeaderItem);
if (data[i] && data[i].media[0] && data[i].media[0].url) {
listItem.style.backgroundImage = "url('"+""data[i].media[0].url""+"')";
} else {
listItem.style.backgroundImage = "none";
}
}
});
I can't get the list item style background to relay the URL. What is the correct way of grabbing the data.media.url and using it as CSS list-item property?

It looks like you have excessive quotes
Try removing the quote after the first + and before the second +
listItem.style.backgroundImage = "url('"+""data[i].media[0].url""+"')";
To
listItem.style.backgroundImage = "url('"+ data[i].media[0].url +"')";
Edited:
for(var i=0;i<data.length;i++){
var createListItem = document.createElement("li");
createListItem.className = "listItem";
var createHeaderItem = document.createElement("h3");
createHeaderItem.innerHTML = data[i].title;
ul.appendChild(createListItem);
createListItem.appendChild(createHeaderItem);
var listItem = document.querySelector(".listItem");
if (data[i] && data[i].media[0] && data[i].media[0].url) {
listItem.style.backgroundImage = "url('"+ data[i].media[0].url +"')";
} else {
listItem.style.backgroundImage = "none";
}
What has changed?
You never declared
var ul = document.createElement("ul");
I'm going to assume this is global variable and declared outside this scope otherwise you have additional problems that needs addressing.
I've moved the following code (see below) further down the code. You attempted to access a DOM element using document.querySelector() on an element that has not yet been added to the DOM. As a result of this you always got null returned.
var listItem = document.querySelector(".listItem");
This excessive quote I raised earlier still stands.

As I have mentioned in my comments listItem is null. This is due to the fact that createListItem has yet to be added to the DOM and .querySelector(..) checks the current DOM tree when the function is called.
There is no need for listItem to exist since you're manipulating and appending to createListItem. So rather than trying to assign .style.backgroundImage = ".." to listItem add it to createListItem:
if (data[i] && data[i].media[0] && data[i].media[0].url) {
createListItem.style.backgroundImage = "url('"+data[i].media[0].url+"')";
} else {
createListItem.style.backgroundImage = "none";
}
Additionally if .listItem, the CSS class, does not have a default background-image of some sort you can omit the above else clause as it is unnecessary:
if (data[i] && data[i].media[0] && data[i].media[0].url) {
createListItem.style.backgroundImage = "url('"+data[i].media[0].url+"')";
}

Related

How would I use local storage for a to do list?

I am being asked to have a to do list and save each task (that the user supplies as well as original) through local storage. My teacher did a very simple demo on something completely different and I spent a few hours trying to figure it out. When I looked at the solution, I honestly cannot figure it out. It looks really complicated, and I don't even know where to start. If anyone can give me any hints, that would be awesome!
My code:
let ul = document.querySelector('ul');
let newItem = document.querySelector('input[type=text]');
let checkbox = document.createElement('input');
checkbox.setAttribute('type', 'checkbox');
function output() {
let newTodo = document.createElement('li');
newTodo.innerText = newItem.value;
newTodo.classList.add('todo');
let ulAppend = ul.append(newTodo);
ul.append(newTodo);
let checkboxAppend = newTodo.append(checkbox);
newTodo.append(checkbox);
newItem.value = '';
}
let button = document.querySelector('.btn');
button.addEventListener('click', output);
ul.addEventListener('click', function(e) {
if (e.target.tagName === 'LI') {
e.target.remove();
} else if (e.target.tagName === 'INPUT') {
e.target.parentElement.classList.toggle('finished');
}
});
My teacher's code/local storage solution:
const todoForm = document.getElementById("newTodoForm");
const todoList = document.getElementById("todoList");
// retrieve from localStorage
const savedTodos = JSON.parse(localStorage.getItem("todos")) || [];
for (let i = 0; i < savedTodos.length; i++) {
let newTodo = document.createElement("li");
newTodo.innerText = savedTodos[i].task;
newTodo.isCompleted = savedTodos[i].isCompleted ? true : false;
if (newTodo.isCompleted) {
newTodo.style.textDecoration = "line-through";
}
todoList.appendChild(newTodo);
}
todoForm.addEventListener("submit", function(event) {
event.preventDefault();
let newTodo = document.createElement("li");
let taskValue = document.getElementById("task").value;
newTodo.innerText = taskValue;
newTodo.isCompleted = false;
todoForm.reset();
todoList.appendChild(newTodo);
// save to localStorage
savedTodos.push({ task: newTodo.innerText, isCompleted: false });
localStorage.setItem("todos", JSON.stringify(savedTodos));
});
todoList.addEventListener("click", function(event) {
let clickedListItem = event.target;
if (!clickedListItem.isCompleted) {
clickedListItem.style.textDecoration = "line-through";
clickedListItem.isCompleted = true;
} else {
clickedListItem.style.textDecoration = "none";
clickedListItem.isCompleted = false;
}
// breaks for duplicates - another option is to have dynamic IDs
for (let i = 0; i < savedTodos.length; i++) {
if (savedTodos[i].task === clickedListItem.innerText) {
savedTodos[i].isCompleted = clickedListItem.isCompleted;
localStorage.setItem("todos", JSON.stringify(savedTodos));
}
}
});
Even though my code is more simpler (at least from what I can tell), it works exactly as his code does.
Local storage saves a JSON object to the user's computer. You should create an array of todos, append that array with every new todo, then set that item to local storage.
let ul = document.querySelector('ul');
const savedTodos = JSON.parse(localStorage.getItem("todos")) || []; // Retrieves local storage todo OR creates empty array if none exist
let newItem = document.querySelector('input[type=text]');
let checkbox = document.createElement('input');
checkbox.setAttribute('type', 'checkbox');
function output() {
let newTodo = document.createElement('li');
newTodo.innerText = newItem.value;
newTodo.classList.add('todo');
ul.append(newTodo);
newTodo.append(checkbox);
savedTodos.push({task: newItem.value, isCompleted: false}); // Appends the new todo to array
localStorage.setItem("todos", JSON.stringify(savedTodos)); //Converts object to string and stores in local storage
newItem.value = '';
}
I've annotated the solution you posted with some comments to help you step through it.
// Retrieve elements and store them in variables
const todoForm = document.getElementById("newTodoForm");
const todoList = document.getElementById("todoList");
// Get data stored in localStorage under the key "todos".
// The data type will be a string (local storage can only store strings).
// JSON is a global object that contains methods for working with data represented as strings.
// The `||` syntax is an OR operator and is used here to set an empty array as a fallback in case `localStorage` is empty
const savedTodos = JSON.parse(localStorage.getItem("todos")) || [];
// Create a loop the same length as the list of todos
for (let i = 0; i < savedTodos.length; i++) {
// Create an <li> element in memory (does not appear in the document yet)
let newTodo = document.createElement("li");
// Set the inner text of that new li with the contents from local storage.
// The savedTodos[i] is accessing data in the localStorage array.
// The [i] is a different number each loop.
// The `.task` is accessing 'task' property on the object in the array.
newTodo.innerText = savedTodos[i].task;
// Create a new property on the element called `isCompleted` and assign a boolean value.
// This is only accessible in code and will not show up when appending to the DOM.
newTodo.isCompleted = savedTodos[i].isCompleted ? true : false;
// Check the value we just set.
if (newTodo.isCompleted) {
// Create a style for the element if it is done (strike it out)
newTodo.style.textDecoration = "line-through";
}
// Actually append the new element to the document (this will make it visible)
todoList.appendChild(newTodo);
}
// `addEventListener` is a function that registers some actions to take when an event occurs.
// The following tells the browser - whenever a form is submitted, run this function.
todoForm.addEventListener("submit", function(event) {
// Don't try to send the form data to a server. Stops page reloading.
event.preventDefault();
// Create a <li> element in memory (not yet visible in the document)
let newTodo = document.createElement("li");
// Find element in the document (probably a input element?) and access the text value.
let taskValue = document.getElementById("task").value;
// Set the text of the <li>
newTodo.innerText = taskValue;
// Set a property on the <li> call `isCompleted`
newTodo.isCompleted = false;
// Empty out all the input fields in the form
todoForm.reset();
// Make the new <li> visible in the document by attaching it to the list
todoList.appendChild(newTodo);
// `push` adds a new element to the `savedTodos` array. In this case, an object with 2 properties.
savedTodos.push({ task: newTodo.innerText, isCompleted: false });
// Overwrite the `todos` key in local storage with the updated array.
// Use the JSON global object to turn an array into a string version of the data
// eg [1,2,3] becomes "[1,2,3]"
localStorage.setItem("todos", JSON.stringify(savedTodos));
});
// This tells the browser - whenever the todoList is clicked, run this function.
// The browser will call the your function with an object that has data about the event.
todoList.addEventListener("click", function(event) {
// the `target` of the event is the element that was clicked.
let clickedListItem = event.target;
// If that element has a property called `isCompleted` set to true
if (!clickedListItem.isCompleted) {
// update the styles and toggle the `isCompleted` property.
clickedListItem.style.textDecoration = "line-through";
clickedListItem.isCompleted = true;
} else {
clickedListItem.style.textDecoration = "none";
clickedListItem.isCompleted = false;
}
// The code above changes the documents version of the data (the elements themselves)
// This loop ensures that the array of todos data is kept in sync with the document
// Loop over the array
for (let i = 0; i < savedTodos.length; i++) {
// if the item in the array has the same text as the item just clicked...
if (savedTodos[i].task === clickedListItem.innerText) {
// toggle the completed state
savedTodos[i].isCompleted = clickedListItem.isCompleted;
// Update the localStorage with the new todos array.
localStorage.setItem("todos", JSON.stringify(savedTodos));
}
}
});
Keep in mind, there are 2 sources of state in your todo list. One is how the document looks, and the other is the array of todos data. Lots of challenges come from making sure these 2 stay in sync.
If somehow the document showed one of the list items as crossed out, but your array of data shows that all the todos are not completed, which version is correct? There is no right answer here, but state management will be something you might consider when designing apps in the future. Redux is a good js library with a well understood pattern that helps solve this problem. Hope this last comment doesn't confuse too much. Best of luck!
The important part is in (de)serializing the data. That means:
reading from localStorage (JSON.parse(localStorage.getItem("todos")) || [])
We add the default [] because if the todos key does not exist, we will get null and we expect a list
saving to localStorage (localStorage.setItem("todos", JSON.stringify(savedTodos)))
We need JSON.parse and its complementary operation JSON.stringify to parse and save strings because localStorage can store only strings.
In your case you need to read the data from localStorage and render the initial list. To save it to localStorage, again, you have to serialize the data. See the below snippets (link to working JSFIDDLE, because the below example does not work in the StackOverflow sandbox environment):
let ul = document.querySelector('ul');
let newItem = document.querySelector('input[type=text]');
const Store = {
serialize () {
return [].slice.call(document.querySelectorAll("li")).map(c => {
return {
text: c.textContent,
finished: c.querySelector("input").checked
}
})
},
get () {
return JSON.parse(localStorage.getItem("todos")) || []
},
save () {
return localStorage.setItem("todos", JSON.stringify(Store.serialize()))
}
}
const firstItems = Store.get()
firstItems.forEach(it => {
output(it.text, it.finished)
})
function output(v, finished) {
let newTodo = document.createElement('li');
newTodo.innerText = v || newItem.value;
newTodo.classList.add('todo');
let ulAppend = ul.append(newTodo);
ul.append(newTodo);
// Create a checkbox for each item
let checkbox = document.createElement('input');
if (finished) {
checkbox.checked = true
}
checkbox.setAttribute('type', 'checkbox');
let checkboxAppend = newTodo.append(checkbox);
newTodo.append(checkbox);
newItem.value = '';
}
let button = document.querySelector('.btn');
button.addEventListener('click', () => {
output()
Store.save()
});
ul.addEventListener('click', function(e) {
if (e.target.tagName === 'LI') {
e.target.remove();
} else if (e.target.tagName === 'INPUT') {
e.target.parentElement.classList.toggle('finished');
}
// Update the value in localStorage when you delete or add a new item
Store.save()
});
<ul></ul>
<input type="text" /> <button class="btn">Submit</button>
I have added the Store variable to simplify the way you get and set the data in localStorage.
The serialize method will read the TODOs from the list. document.querySelectorAll("li") returns a NodeList, but by doing [].slice.call(...) we convert it to an Array.

Add value for DOM nodes in list

I am trying to create a list of links using DOM nodes from the data in the database. Instead of having a link for each DOM element. The whole list only have one link. I want the link to be separate because I want to add value to it so I can identify which link the user clicks.
This is the link in HTML
This is my Javascript code in which I take data from database. I want to put each corresponding in database as the value for the corresponding link in the HTML code.
window.onload = async function outsong() {
var selected = localStorage.getItem("category")
document.getElementById("result").innerHTML = selected;
var result = [];
if(selected == "Popular") {
await db.collection("Song").doc("Popular").collection("songs").get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
result.push(doc.data());
});
});
console.log(result.length);
for(a = 0; a < result.length; a++) {
var node = document.createElement("li");
var textnode = document.createTextNode(result[a].song_name);
node.appendChild(textnode);
document.getElementById("songlist").appendChild(node);
var anchor = document.getElementById("songlist");
var att = document.createAttribute("value");
att.value = result[a].song_name;
anchor.setAttributeNode(att);
}
}
In the image, I want to separate the link, not one as the whole.
Within your loop, you'll need to create a new <a> element within each <li>, and add the song link to that a element. Modifying your code:
const songList = document.getElementById("songlist");
for(let a = 0; a < result.length; a++) {
var node = document.createElement("li");
var link = document.createElement('a');
link.setAttribute('href', result[a].song_link);
link.innerText = result[a].song_name;
node.appendChild(link);
songList.appendChild(node);
}
I don't see where you're getting the song_link from, so for the sake of the example, I guess it was in the result[a] object, alongside song_name.

How to display HTML class of current node in JS

I have variables which:
display the result (result), and
reference the current node (thisNode).
What do I need to change in my code so that it would display the HTML class?
var thisNode = document.body.firstChild;
var result = document.getElementById("resultOfButton");
result.InnerHTML = thisNode.;
/* Here, in JS are there any ways like displaying the class name,
like nodeClass */
Please give recommendations for my code. There may be some errors. Thank you.
var thisNode = document.body.firstChild;
var result = document.getElementById("resultOfButton");
var block = false;
function buttonDown()
{
if(block == true)
{
thisNode = thisNode.parentElement.firstChild;
block = false;
}
thisNode = thisNode.nextSibling;
result.innerHTML = thisNode.nodeName;
if(thisNode == thisNode.parentNode.lastChild)
{
block = true
}
}
function buttonUp()
{
// not done now...
}
function buttonEnter()
{
thisNode = thisNode.firstChild;
result.innerHTML = thisNode.c;
}
function buttonBack()
{
// not done now...
}
I think you're asking for the className attribute. I copied your first sample and added some code so you can run it on this page. You'll get the second emoji replaced by the class name of the inserted element.
var thisNode = document.getElementById("thisNode"); // document.body.firstChild;
var result = document.getElementById("resultOfButton");
result.innerHTML = thisNode.className; /*Here, in JS are there any ways like displaying the class name, like nodeClass*/
<div id="thisNode" class="sample-class">🙂</div>
<div id="resultOfButton">🙃</div>
Quoting MDN:
"The className property of the Element interface gets and sets the value of the class attribute of the specified element."

Cannot read attribute of null using array

I am trying to change the the classes of element that already exist but are hidden . This problem is whenever the code is executed a null exception is thrown. Tried everything but can't seem to get what's wrong
function changeMenu(code) {
var i, id = ["'demo'", "'drill'", "'alert'"];
for (i = 0; i <= 2; i += 1) {
if (document.getElementById(id[i]) !== null) {
if (i !== code) {
document.getElementById(id[i]).setAttribute('class', 'row hidden');
} else {
document.getElementById(id[i]).setAttribute('class', 'row');
}
} else {window.alert("error");}
}
}
The script is already loaded in the end of document and the id also exists.
The problem here is that you are storing the ids with the ' in your strings, so when you will call document.getElementById("'demo'") it won't find the element and return null.
Change your id array declaration to:
var i, id = ["demo", "drill", "alert"];
The script is already loaded in the end of document and the id also exists.
Yes the id exists but they don't have ' on it, just remove it from your array.
There are several issues with quotes and tests
I believe you actually meant to do this:
var ids = ["demo", "drill", "alert"];
function changeMenu(code) {
for (var i = 0; i < ids.length; i++) {
var elm = document.getElementById(id[i]);
if (elm) { // element exists
elm.classList.toggle("hidden",ids[i] !== code);
}
else {
window.alert("error");
}
}
}

Check if team already added

I have a page where you can invite teams. Clicking "Invite teams" makes a popup box appear showing a search input. The search-function is AJAX based. When a team is found through your search word(s), you'll have to click on the team whereupon the team will be showed in a "Invited-teams"-box.
It works in a way that when you "add" the team, a hidden input field is generated containing the team's ID as a value. The problem is that with my current code, it is possible to add the same team as many times as you wish. I should be possible to check, if the team can be found in the hidden-input-data. If it already exists, it should not be possible to add the sane team.
My current javascript-code can be found beneath here. Please notice that I have tried to make the code that checks the team, but it doesn't work.
function addTeam(tid) {
// Grab the input value
var teamName = document.getElementById(tid).innerHTML;
var teamID = document.getElementById(tid).id;
// If empty value
if(!teamName || !teamID) {
alert('An error occured.');
} else {
//Tried to do the "team-adlready-added"-test, but it doesn't work
var stored_teams = $t('#store-teams').getElementsByTagName('input');
for (var i = 0; i < stored_teams.length; i++) {
var stored_team = stored_teams[i];
if(stored_team.value == teamID) {
break;
var team_already_added = 1;
}
alert(team_already_added);
}
if((team_already_added) || team_already_added != 1) {
// Store the team's ID in hidden inputs
var store_team = document.createElement('input');
store_team.type = 'hidden';
store_team.value = teamID;
// Append it and attach the event (via onclick)
$t('#store-teams').appendChild(store_team);
// Create the teams with the value as innerHTML
var div = document.createElement('div');
div.className = 'team-to-invite';
div.innerHTML = teamName;
// Append it and attach the event (via onclick)
$t('#teams').appendChild(div);
}
div.onclick = removeTeam;
}
return false;
}
Thanks in advance.
I just want to give you a hint for a possible solution without html elements.
You can create a new functional object for team:
var Team = function (id, name) {
this.name = name;
this.id = id;
}
Create an array which will contain teams:
var TeamList = [];
Add you Teams:
TeamList.push(new Team(1, "Team 1"));
TeamList.push(new Team(2, "Team 2"));
TeamList.push(new Team(3, "Team 3"));
TeamList.push(new Team(4, "Team 4"));
Write a function which loops trough the list of teams and checks with the id if a team already exists:
function containsTeam(id) {
for (var i = 0; i < TeamList.length; i++) {
if (TeamList[i].id == id) {
return true;
}
}
return false;
}
Just check it:
containsTeam(1); //returns true
containsTeam(5); //returns false
Have a look at the jsFiddle DEMO and open the console to see the output.
EDIT: In addition, to remove an element you can write a function which looks pretty much the same as the containsTeam function. Just use array.splice instead of returning true:
function removeTeam(id) {
for (var i = 0; i < TeamList.length; i++) {
if (TeamList[i].id == id) {
TeamList.splice(i, 1);
}
}
}
And remove a team:
removeTeam(3);
Your variable scope is off.
You declare team already added in the wrong spot.
Declare it with team name and team id and it will get you in the right direction

Categories

Resources