**screenshot of the error
Hello everyone,
I am a complete beginner in coding and have been coding along with a webinar when I received the error: Uncaught SyntaxError: Unexpected end of input. I included a screenshot of the error. This happened I think when I added the inputChange function. The instructor of the webinar did not have any errors, and my code (so far) is identical to his code during the video.
Anybody who can help me understand and solve this issue?
Here is my code:
//Variables, Arrays, and Objects, dotNotation, bracketNotation
//Dom manipulation
let items = [
{
name: 'Ironhack T',
price: 10,
image: 'https://miro.medium.com/max/5190/1*aVsUjp1zvlRb1799gDjbLA#2x.jpeg'
},
{
name: 'Ironhack Hoodie',
price: 15,
image: 'https://m.media-amazon.com/images/I/B1i3u9-Q-KS._AC_CLa%7C2140%2C2000%7CB1wqstnnTfS.png%7C0%2C0%2C2140%2C2000%2B0.0%2C0.0%2C2140.0%2C2000.0_UL1500_.png'
},
{
name: 'Ironhack Sticker',
price: 2,
image:'https://e7.pngegg.com/pngimages/887/803/png-clipart-ironhack-web-development-job-startup-company-design-blue-user-interface-design-thumbnail.png'
},
{
name: 'Ironhack Mug',
price: 8,
image: 'https://d0bb7f9bf11b5ad1a6b2-6175f06f5e3f64e15abbf67415a276ec.ssl.cf1.rackcdn.com/product-images/designlab/11-oz-traditional-ceramic-coffee-mugs-7102-white1582888132.jpg'
},
];
let list = document.querySelector('ul');
items.forEach((item, i) =>{
console.log(item.name);
list.innerHTML += `<li>
<div>Name: ${item.name}</div>
<div>Price: $${item.price}</div>
<img src="${item.image}" />
<input type='number' placeholder='quantity' onchange='inputChange(${i}, '${item.name}', '${item.price}')' />
<button>Buy item</button>
</li>`
});
function inputChange(i, name, price){
console.log('I want to buy the ',i,' item named, ',name,' that costs $',price);
};
*{
transition: all 1s;
}
body{
padding: 10px;
background-color: lightseagreen;
}
section{
display: flex;
flex-direction: row;
}
img {
width: 50px;
}
#cart{
background-color:salmon;
}
#cart, #items{
width: 50vw;
}
h1{
color:#7c32ff;
}
/*selecting a tag*/
p{
color:green;
text-decoration: line-through;
}
/*Ids have hashtags*/
#two {
background-color:rebeccapurple;
}
/*classes have dots*/
.example {
border: 5px dashed purple;
margin: 5px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!--Now my CSS is linked to my html-->
<link href="/favicon.ico" type="image/x-icon" rel="icon" />
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<section>
<div>
<h2>Items</h2>
<ul id="items">
</ul>
</div>
<div>
<h2>Cart <span id="grandTotal">$0</span></h2>
<ul id="cart">
</ul>
</div>
</section>
<!-- <div class="example">
This is a div
</div>
<div class="example" id="two">
This is a div 2
</div>
<div class="example">
This is a div 3
</div>
<i>This will be italic text</i>
<b>This will be bold text</b>
<h1>This is a certain size</h1>
<p>This is for paragraphs</p> -->
<script src="./script.js"></script>
</body>
</html>
Edit: Fixing the Single (') and Double (") Quotes in the following code is enough
Notice the double quotes wrapping the variables passed to inputChange. And notice the single quotes wrapping the complete value passed to the onchange='...' attribute
// Inside the forEach loop
list.innerHTML += `<li>
<div>Name: ${item.name}</div>
<div>Price: $${item.price}</div>
<img src="${item.image}" />
<input type='number' placeholder='quantity' onchange='inputChange("${i}", "${item.name}", "${item.price}")' />
<button>Buy item</button>
</li>`;
Edit: Alternative solution which logs the current value of input additionally
While debugging, I took another solution, which also logs the current value of the input elements (i.e. how much the customer wants to buy).
Adding only an input element for each item, with an id property for getting it later when the change event fires.
Adding event listeners with .addEventListener("change", inputChange) - inputChange will receive an event object each time the input element is changed.
inputChange is the event handler function. It extracts the input element's id, and uses it to find the data (object in items array) with the Array.prototype.find() (More on this prototype https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)
// script.js
//Variables, Arrays, and Objects, dotNotation, bracketNotation
//Dom manipulation
let items = [
{
name: "Ironhack T",
price: 10,
image: "https://miro.medium.com/max/5190/1*aVsUjp1zvlRb1799gDjbLA#2x.jpeg",
},
{
name: "Ironhack Hoodie",
price: 15,
image:
"https://m.media-amazon.com/images/I/B1i3u9-Q-KS._AC_CLa%7C2140%2C2000%7CB1wqstnnTfS.png%7C0%2C0%2C2140%2C2000%2B0.0%2C0.0%2C2140.0%2C2000.0_UL1500_.png",
},
{
name: "Ironhack Sticker",
price: 2,
image:
"https://e7.pngegg.com/pngimages/887/803/png-clipart-ironhack-web-development-job-startup-company-design-blue-user-interface-design-thumbnail.png",
},
{
name: "Ironhack Mug",
price: 8,
image:
"https://d0bb7f9bf11b5ad1a6b2-6175f06f5e3f64e15abbf67415a276ec.ssl.cf1.rackcdn.com/product-images/designlab/11-oz-traditional-ceramic-coffee-mugs-7102-white1582888132.jpg",
},
];
/* Adds id property to objects inside items array */
items = items.map((item) => ({
...item,
id: item.name.replace(" ", "-").toLowerCase(),
}));
/* inputChange receives event object when the input changes */
function inputChange(event) {
// for inspection - log the event object
// console.log(event);
const { id } = event.target; // the id I gave to the input
const number = event.target.value; // the current value of the input
// find the object with the corresponding data in the items array
const data = items.find((item) => item.id === id);
console.log(
`I want to by ${number} of the item named, ${data.name} that costs $${data.price}`
);
}
let list = document.querySelector("ul");
items.forEach((item, i) => {
/* prettier-ignore */
list.innerHTML += `<li>
<div>Name: ${item.name}</div>
<div>Price: $${item.price}</div>
<img src="${item.image}" />
<input type='number' placeholder='quantity' id=${item.id} />
<button>Buy item</button>
</li>`;
});
/* Get all inputs */
const inputs = document.querySelectorAll("input");
inputs.forEach((inputEl) => inputEl.addEventListener("change", inputChange));
Additionally, one word concerning file paths in src property of script tag:
Either solution is fine in this case:
<script src="script.js"></script>
<script src="./script.js"></script>
your code ./script.js are incorrect.
this is some example of HTML File Paths:
<srcipt src="script.js"> The "script.js" file is located in the same folder as the current page
<srcipt src="js/script.js"> The "script.js" file is located in the js folder in the current folder
<srcipt src="/js/script.js"> The "script.js" file is located in the js folder at the root of the current web
<srcipt src="../script.js"> The "script.js" file is located in the folder one level up from the current folder
Related
I am practising on a todolist. I would like to remove the items from the list with a button in one go . When I click the button Clear the second if of containerList.addEventListener manages to delete all the items from the local storage. Unfortunately I can't manage to target the <li> in the DOM and remove the same items there too, unless I refresh...
How to target and remove them? I have also tried to target `list.children.remove()' but there is an error saying "it is not a function". Plese help. Thank you
const addForm = document.querySelector('.add');
const list = document.querySelector('.todos');
const clearAll = document.querySelector('.bottomForm');
const containerList = document.querySelector('.containerList');
// salvato gli items dal local storage in una variabile
let storedItems = localStorage.getItem('tasks');
const generateTemplate = todo => {
const html = `
<li class="newList">
<div class="tick">
<input type="checkbox" id="" name="" value="">
</div>
<div class="content">
<span>${todo}</span>
<i class="fas fa-times delete"></i>
</div>
</li>`
list.innerHTML += html;
}
if (!storedItems) {
storedItems = [];
} else {
storedItems = JSON.parse(storedItems);
storedItems.forEach(item => {
generateTemplate(item);
});
}
addForm.addEventListener('submit', e => {
const todo = addForm.add.value.trim();
e.preventDefault();
if (todo.length) {
generateTemplate(todo);
storedItems.push(todo);
localStorage.setItem('tasks', JSON.stringify(storedItems))
addForm.reset();
console.log(`${todo} has been added to html list`)
console.log(`Local storage now contains ${storedItems}`)
document.getElementById("numberItems").innerHTML = `${storedItems.length} item(s) `; //counting element when added
console.log(`aftr adding, now the items are ${storedItems.length}`)
}
});
/*Removing item*/
containerList.addEventListener('click', e => {
console.log(e.target);
if (e.target.classList.contains('delete')) {
e.target.parentElement.parentElement.remove();
let removedItem = e.target.parentElement.firstElementChild.innerText;
//console.log(`${removedItem} has been removed from the html list`);
//console.dir(e.target.parentElement.firstElementChild.innerText);
//console.log(storedItems)
const newArr = storedItems.filter(item => item !== removedItem)
//console.log(newArr)
storedItems = newArr
localStorage.setItem('tasks', JSON.stringify(storedItems))
document.getElementById("numberItems").innerHTML = `${storedItems.length} item(s) `; //counting element when deleted
//console.log(`Local storage now contains ${storedItems} `)
//console.log(`after removing, now the items are ${storedItems.length}`)
}
if (e.target.classList.contains('clears')){
window.localStorage.removeItem('tasks');
console.log('clear button has been pushed')
document.getElementsByClassName("newList").innerHTML = ''
}
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- displays site properly based on user's device -->
<link rel="icon" type="image/png" sizes="32x32" href="./images/favicon-32x32.png">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<title>Frontend Mentor | Todo app</title>
<!-- Feel free to remove these styles or customise in your own stylesheet 👍 -->
<!-- <style>
.attribution { font-size: 11px; text-align: center; }
.attribution a { color: hsl(228, 45%, 44%); }
</style> -->
<link rel="stylesheet" href="style.css">
</head>
<body>
<!--
Todo
items left
All
Active
Completed
Clear Completed
Drag and drop to reorder list -->
<div class="mainTitle">
<h1>TO DO</h1>
</div>
<form action="" class="add">
<div class="inputE">
<input type="text" id="name" name="add" placeholder="Enter name here">
</div>
<div class="containerList">
<ul class="todos"></ul>
<div class="bottomForm">
<div class="itemsLeft">
<p><span id="numberItems"></span> left</p> <!--use if for items or item !-->
</div>
<div class="allItems">
<button onclick="myFunction()">All</button>
</div>
<div class="activeItems">
<button onclick="myFunction()">Active</button>
</div>
<div class="completedItems">
<button onclick="myFunction()">Completed</button>
</div>
<div class="clear">
<button onclick="" class="clears">Clear</button>
</div>
<div class="completedItems">
<button onclick="myFunction()">Completed</button>
</div>
</div>
</div>
</div>
</form>
<!-- <div class="attribution">
Challenge by Frontend Mentor.
Coded by Your Name Here.
</div> -->
<script src="app.js"></script>
</body>
</html>
You were close with list.children.remove()! The problem is, that the children properties holds a HTMLCollection of elements, which does not support remove() itself. You can iterate over that HTMLCollection with a loop and call remove() on every child element:
for (const child of list.children) {
child.remove()
}
Alternativly you could convert it to a array and use the typical array functions, if you are more familiar with those:
[...list.children].forEach(child => child.remove())
The challenge was to target and remove all the children of .list. list.children.remove() did not work because
the children properties holds a HTMLCollection of elements, which does not support remove()
as #Julian Adler stated.
So I have used childNodes property in the for loop to remove all the items of the list (
in reality all the nodes, including white spaces and <i>):
`for (let i= list.childNodes.length -1; i>=0; i--) {
list.childNodes[i].remove()
}`
const addForm = document.querySelector('.add');
const list = document.querySelector('.todos');
const containerList = document.querySelector('.containerList');
let storedItems = localStorage.getItem('tasks');
const generateTemplate = todo => {
const html = `
<li class="newList">
<div class="tick">
<input type="checkbox" id="" name="" value="">
</div>
<div class="content">
<span>${todo}</span>
<i class="fas fa-times delete"></i>
</div>
</li>`
list.innerHTML += html;
}
if (!storedItems) {
storedItems = [];
} else {
storedItems = JSON.parse(storedItems);
storedItems.forEach(item => {
generateTemplate(item);
});
}
addForm.addEventListener('submit', e => {
const todo = addForm.add.value.trim();
e.preventDefault();
if (todo.length) {
generateTemplate(todo);
storedItems.push(todo);
localStorage.setItem('tasks', JSON.stringify(storedItems))
addForm.reset();
console.log(`${todo} has been added to html list`)
console.log(`Local storage now contains ${storedItems}`)
document.getElementById("numberItems").innerHTML = `${storedItems.length} item(s) `; //counting element when added
console.log(`aftr adding, now the items are ${storedItems.length}`)
}
});
/*Removing item*/
containerList.addEventListener('click', e => {
console.log(e.target);
if (e.target.classList.contains('delete')) {
e.target.parentElement.parentElement.remove();
let removedItem = e.target.parentElement.firstElementChild.innerText;
const newArr = storedItems.filter(item => item !== removedItem)
storedItems = newArr
localStorage.setItem('tasks', JSON.stringify(storedItems))
document.getElementById("numberItems").innerHTML = `${storedItems.length} item(s) `; //counting element when deleted
${storedItems.length}`)
}
if (e.target.classList.contains('clears')){
window.localStorage.removeItem('tasks');
console.log('clear button has been pushed')
for (let i= list.childNodes.length -1; i>=0; i--) {
list.childNodes[i].remove()
}
}
})
I'm currently making a program for my players in an RPG to be able to select their character skills from drop-down menus which will 1. Display the title of the skill under the results section and also in the Selected Advantages section(top right). 2. Display the description of the skill under its title.
It is working mostly as intended except for a bug I have discovered. I can select the desired advantage and it will appear as expected. It will also remove the selection when I click on the delete button under the Selected Advantages section at the top right of the page.
The problem is that if I reselect the same advantage it will display the advantage description twice at the bottom of the page, if I do the same again it will display three times, and so on.
When I delete a selection the container is removed but when they are selected again the subsequent descriptions are also contained in a single container instead of creating a new one.
Here is my code so far.
Note: I have only included the object for Absolute Direction but have tested with others and get the same duplication result.
const createDivElement = document.createElement('div');
//Creates containers for the selected advantage descriptions.
function createContainer(advantageName) {
const placeAdvantageDescription = document.getElementById('place-text');
placeAdvantageDescription.appendChild(createDivElement);
createDivElement.setAttribute("id", `container-${advantageName}`);
}
//Creates and adds the text for the selected advantages title, points and description.
//should I make this three seperate functions???
function addAdvantageText(advantageObject) {
const createTitleElement = document.createElement('p');
const createPointsElement = document.createElement('p');
const createDescriptionElement = document.createElement('p');
//creates Title
createDivElement.append(createTitleElement);
createTitleElement.classList.add("title");
createTitleElement.innerText = advantageObject.title;
//creates points
createDivElement.append(createPointsElement);
createPointsElement.classList.add("points");
createPointsElement.innerText = `${advantageObject.points}.`;
//creates description
createDivElement.append(createDescriptionElement);
createDescriptionElement.classList.add("description-text");
createDescriptionElement.innerHTML = advantageObject.description;
}
//creates sub catagories for selected advantage and adds the extra text if applicable.
function addSubCategories(advantageObject) {
if (advantageObject.subCategory1) {
let createSubCategory1 = document.createElement('p');
createDivElement.append(createSubCategory1);
createSubCategory1.innerHTML = `<strong>${advantageObject.subCategory1.name}:</strong> ${advantageObject.subCategory1.text}<br><strong><em>${advantageObject.subCategory1.sub1Points}</em></strong>.`;
}
if (advantageObject.subCategory2) {
let createSubCategory2 = document.createElement('p');
createDivElement.append(createSubCategory2);
createSubCategory2.innerHTML = `<strong>${advantageObject.subCategory2.name}:</strong> ${advantageObject.subCategory2.text}<br><strong><em>${advantageObject.subCategory2.sub2Points}</em></strong>.`;
}
if (advantageObject.subCategory3) {
let createSubCategory3 = document.createElement('p');
createDivElement.append(createSubCategory3);
createSubCategory3.innerHTML = `<strong>${advantageObject.subCategory3.name}:</strong> ${advantageObject.subCategory3.text}<br><strong><em>${advantageObject.subCategory3.sub3Points}</em></strong>.`;
}
if (advantageObject.subCategory4) {
let createSubCategory4 = document.createElement('p');
createDivElement.append(createSubCategory4);
createSubCategory4.innerHTML = `<strong>${advantageObject.subCategory4.name}:</strong> ${advantageObject.subCategory4.text}<br><strong><em>${advantageObject.subCategory4.sub4Points}</em></strong>.`;
}
if (advantageObject.extraText) {
let createExtraText = document.createElement('p');
createDivElement.appendChild(createExtraText);
createExtraText.innerHTML = advantageObject.extraText;
}
}
//adds name of chosen advantages in Selected Advantages column and a delete button for each.
function addToSelectedColumn(advantageName) {
const hr = document.createElement('hr');
const button = document.createElement('button');
const createSelectedAdvantageDiv = document.createElement('div');
const placeSelectedText = document.getElementById('selected-items');
placeSelectedText.appendChild(createSelectedAdvantageDiv);
createSelectedAdvantageDiv.append(advantageName.toUpperCase());
createSelectedAdvantageDiv.setAttribute("id", `selected-items-container-${advantageName}`);
createSelectedAdvantageDiv.append(button);
button.setAttribute("id", `delete-${advantageName}`);
createSelectedAdvantageDiv.append(hr);
}
function removeAdvantageText(removeThis) {
let advantageDescriptionToRemove = document.getElementById(`container-${removeThis}`);
let selectedAdvantageToRemove = document.getElementById(`selected-items-container-${removeThis}`);
advantageDescriptionToRemove.remove();
selectedAdvantageToRemove.remove();
}
function removeAdvantage(advantageToDelete) {
document.getElementById(`delete-${advantageToDelete}`).addEventListener("click", function() {
removeAdvantageText(advantageToDelete);
})
};
function addAdvantage(advantageInString, advantageObjectName) {
createContainer(advantageInString);
addToSelectedColumn(advantageInString);
addAdvantageText(advantageObjectName);
addSubCategories(advantageObjectName);
}
//Event listeners for when users select advantage. They will check for matching selection then call addAdvantage() with arguments.
//AAAAAAAAAAAAAAAA
document.getElementById("submit-advantage-a").addEventListener("click", function(e) {
e.preventDefault();
const userAdvantageA = document.getElementById('user-advantages-a').value;
if (userAdvantageA === 'absolute direction') {
addAdvantage('absolute direction', absoluteDirection);
removeAdvantage('absolute direction');
} else if (userAdvantageA === 'absolute timing') {
addAdvantage('absolute timing', absoluteTiming);
removeAdvantage('absolute timing');
} else if (userAdvantageA === 'acute senses') {
addAdvantage('acute senses', acuteSenses);
removeAdvantage('acute senses');
}
});
let absoluteDirection = {
title: "Absolute Direction",
points: "5 or 10 points",
description: "You have an excellent sense of direction.This ability comes in two levels:",
subCategory1: {
name: "Absolute Direction",
text: "You always know which way is north, and you can always retrace a path you have followed within the past month, no matter how faint or confusing. This ability does not work in environments such as interstellar space or the limbo of the astral plane, but it does work underground, underwater, and on other planets. This gives +3 to Body Sense and Navigation (Air, Land, or Sea).<br> (Note: The navigational sense that guides migratory creatures to their destination is too crude to qualify; treat it as a 0-point feature.)",
sub1Points: "5 points"
},
subCategory2: {
name: "3D Spatial Sense",
text: "As above, but works in three dimensions. This ability is useful in deep space – although it does not help you if you travel across dimensions. You get the skill bonuses to Piloting and +2 to Aerobatics, Free Fall, and Navigation (Hyperspace or Space).",
sub2Points: "10 points"
}
}
.container {
border-bottom: solid 2px black;
padding: 1rem 2rem;
}
.title {
font-size: 1.5rem;
font-weight: bolder;
text-align: center;
}
.points {
font-weight: bold;
}
#selected-items-container {
float: right;
background-color: gray;
color: white;
text-align: center;
width: 20rem;
margin-right: 4rem;
}
.forms {
margin-left: 2rem;
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/style.css">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<script type="text/javascript" src="/script.js" async></script>
<title>GCRC</title>
</head>
<body>
<a id="home-nav" href="*">Home</a>
<div class="make-choice">
<div id="selected-items-container"><strong>SELECTED ADVANTAGES</strong>
<hr>
<div id="selected-items"></div>
</div>
<div class="forms">
<h1>Select your characters advantages</h1>
<p>A.</p>
<form method="GET">
<select name="user-advantages-a" id="user-advantages-a">
<option value="absolute direction">Absolute Direction</option>
<option value="absolute timing">Absolute Timing</option>
<option value="acute senses">Acute Senses</option>
</select>
<input id="submit-advantage-a" type="submit" />
</form>
<p>B.</p>
<form method="GET">
<select name="user-advantages-b" id="user-advantages-b">
<option value="binding">Binding</option>
<option value="blessed">Blessed</option>
<option value="brachiator">Brachiator</option>
</select>
<input id="submit-advantage-b" type="submit" />
</form>
<p>C.</p>
<form method="GET">
<select name="user-advantages-c" id="user-advantages-c">
<option value="catfall">Catfall</option>
<option value="chameleon">Chameleon</option>
<option value="channeling">Channeling</option>
</select>
<input id="submit-advantage-c" type="submit" />
</form>
<p>D.</p>
<form method="GET">
<select name="user-advantages-d" id="user-advantages-d">
<option value="damage resistance">Damage Resistance</option>
<option value="danger sense">Danger Sense</option>
<option value="daredevil">Daredevil</option>
</select>
<input id="submit-advantage-d" type="submit" />
</form>
</div>
</div>
<hr>
<div class="display-choice">
<p>Advantages</p>
<div class="advantages">
<div id="place-text"></div>
</div>
</div>
</body>
</html>
I've created a todo app, and I want to save the todo's in localStorage.
I was thinking to add the localStorage property like this:
addForm.addEventListener("submit", (e) => {
e.preventDefault();
let todo = addForm.add.value.trim();
if (todo.length) {
localStorage.setItem(todo);
generateTemplate(todo);
addForm.reset();
}
}
But, it's not working.
So, my question is, in which part of the code is it best practice to add the property and in which part is it best to also add the getItem method?
Here is the whole code without the localStorage property:
const addForm = document.querySelector(".add");
const list = document.querySelector(".todos");
const search = document.querySelector(".search input");
// generate new toDo's
const generateTemplate = (todo) => {
let html = ` <li class="list-group-item d-flex justify-content-between align-items-center">
<span>${todo}</span><i class="far fa-trash-alt delete"></i>
</li>`;
list.innerHTML += html;
};
// submit the todo
addForm.addEventListener("submit", (e) => {
e.preventDefault();
let todo = addForm.add.value.trim();
if (todo.length) {
generateTemplate(todo);
addForm.reset();
}
});
// delete todo's
list.addEventListener("click", (e) => {
if (e.target.classList.contains("delete")) {
e.target.parentElement.remove();
}
});
// filter the toDo's
const filterTodos = (term) => {
Array.from(list.children)
.filter((todo) => !todo.textContent.toLowerCase().includes(term))
.forEach((todo) => todo.classList.add("filtered"));
Array.from(list.children)
.filter((todo) => todo.textContent.toLowerCase().includes(term))
.forEach((todo) => todo.classList.remove("filtered"));
};
search.addEventListener("keyup", () => {
const term = search.value.trim().toLowerCase();
filterTodos(term);
});
body {
background-color: #352f5b;
}
.container {
max-width: 400px;
}
input[type="text"],
input[type="text"]:focus {
color: #fff;
border: none;
background: rgba(0, 0, 0, 0.2);
max-width: 400px;
}
.todos li {
background: #423a6f;
}
.delete {
cursor: pointer;
}
.filtered {
display: none !important;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>toDoList</title>
<script src="https://kit.fontawesome.com/fa5117c01c.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous" />
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<header class="text-center text-light my-4">
<h1 class="text-ligth mb.4">thingsToDo</h1>
<form action="" class="search"><input type="text" class="form-control m-auto" name="search" placeholder="Search toDo's"></form>
</header>
<ul class="list-group todos mx-auto text-light"></ul>
<form class="add text-center my-4">
<label class="text-light">Add new toDo...</label>
<input type="text" class="form-control m-auto" name="add">
</form>
</div>
<script src="index.js"></script>
</body>
</html>
Storages object (what you use with LocalStorage) allows to save Strings (DOMStrings to be specific), so if you want to save a javascript object, you should convert it to string first (eg. with JSON.stringify)
On your code, also, the syntax for setItem is incorrect. You need to specify a key that identifies the object
localStorage.setItem('todo', todo);
Notice that this will only save a todo. Therefore, when you get todo's back you will need to do localStorage.getItem('todo') and this of course will only return the last todo that you have saved.
A solution for this might be to have a storage for your todos:
var todos = []
Everytime you create a new todo you add that todo to this object
todos.push(todo)
and saves it to localstorage
localStorage.setItem('todos', JSON.stringify(todos))
When you get back that list, you will need to parse the string back to get the actual object:
var todosString = localStorage.getItem('todos');
var todos = JSON.parse(todosString);
Take into account that this will only get your todos data into that var. Then you need to repopulate your dom.
This can be done by reusing your generateTemplate function.
Eg.
todos.forEach(todo => generateTemplate(todo));
I'd also recommend you to take a look at the MDN page for LocalStorage: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
And the Using the Web Storage API page for a full working example: https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
I've read up on inserting css rules using Javascript and have managed to get it working (after some trial & error). So I have 2 questions:
Q.1 Why is an index < 1 not working - see Mozilla example (and many others) below:
// push a new rule onto the top of my stylesheet - doesn't work...
myStyle.insertRule("#blanc { color: white }", 0); // returns Uncaught DOMException: Failed to execute 'insertRule' on 'CSSStyleSheet': Failed to insert the rule.
// change the index and it works!
myStyle.insertRule("#blanc { color: white }", 1);
This article by David Walsh (very helpful), explains that the default for index is -1. He uses 1 in his example, which is what worked for me. Anything less than 1, ie 0 or -1 (as per the default) threw the following errors:
Index -1 error:
Uncaught DOMException: Failed to execute 'insertRule' on 'CSSStyleSheet': The index provided (4294967295) is larger than the maximum index (2071).
Index 0 error:
Uncaught DOMException: Failed to execute 'insertRule' on 'CSSStyleSheet': Failed to insert the rule.
It's not a huge problem, but it does mean I can't control specificity. I can use !important or rework the css/inserted rule so that it overrides any existing styles, but as I'm just learning JavaScript, I'd really like to know why it's not working as expected. Does anyone have any ideas?
Q.2 Having got it to work using index 1, I now want to pull in values dynamically. I have the item names in an array, which is used to create multiple objects, inside which are the property values I want to use for the individual style rules for that object.
Basically what I'm trying to output is this (which works):
styleSheet.insertRule("#item { border-top-color: #000000; border-right-color: #ffffff; }", 1);
But using variables, something like this:
styleSheet.insertRule("[itemName] { border-top-color: [itemName.value1]; border-right-color: [itemName.value2]; }", 1); // itemName.valueX being the object's array item
I've tried heaps of things, but I can't get the array item bit to work, ie colour and colour4 should actually be itemName.value1/2 or a var that equals the same. This is the closest I've got so far...
styleSheet.insertRule("#" + name + " { border-top-color: " + colour + "; border-right-color: " + colour4 + " !important; }", 1); // 1st rule works, 2nd doesn't show anything
It all works lovely if I write it manually (as per the 1st example), but how to do it dynamically? I've found info on insertRule, but not using dynamic values - can anyone help/point me in the right direction?
Many thanks in advance!
Expanded WIP for more clarity:
function itemColours() {
for (i = 3; i < itemsArray.length; i++) {
let name = itemsArray[i];
let colour = #000000;
console.log(item1.value); // returns the hex value I want to use in the rule
styleSheet.insertRule("#" + name + " { border-top-color: " + colour + "; border-right-color: " + name + ".value !important; }", 1);
// rule 1 works, rule 2 doesn't...
}
Update 2
This demo:
can accept user data to use insertRule() and deleteRule().
has an Add Set button which will create and append a clone of the <form> part of the document
has 3 styleSheets Bootstrap [0], CSSOM, and the <style> tag.
Demo 4
// Just for demo
.as-console-wrapper {
width: 170px;
max-height: 40px;
transform: translateX(340px)
}
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' title='Bootstrap-3.3.7'>
<link href='https://glpjt.s3-us-west-1.amazonaws.com/ir/cssom.css' rel="stylesheet" title='CSSOM'>
<style title='Tag'>
body {
max-width: 96%;
visibility: hidden;
font: 400 16px/1.2 Verdana
}
ul.x-list.x-list {
margin-left: 0;
padding-left: 1em;
text-indent: -1em;
list-style-type: none;
}
li.x-item.x-item {
list-style: none;
line-height: 1.5;
}
.x-flex.x-flex {
display: flex;
justify-content: center;
align-items: center;
}
.x-col {
display: flex;
flex-direction: column
}
option::before {
content: attr(name)
}
#idx {
max-width: 6ch;
}
label {
margin: 10px auto 10px -15px
}
#add {
position: fixed;
z-index: 1;
top: 50px;
left: 20px
}
</style>
</head>
<body>
<main class='container' style='padding:50px 0 20px 20px'>
<form id='cssom' class='row x-col'>
<div class='btn-group col-sm-12'>
<input id='add' class='btn-lg btn-success' type='button' value='Add Set'>
</div>
<section class='row'>
<!--=================================[0]-->
<fieldset class='set0 col-sm-12'>
<hr>
<div class='row x-flex'>
<!--=================================[1]-->
<label class='form-control-label col-sm-2'>CSS Rule</label>
<textarea id='rul0' class='form-control col-sm-10' rows='2'>li.x-item.x-item::before {content: '\1f539\00a0';list-style:none;font-size:small;position:relative;bottom:2px}</textarea>
</div>
<div class='form-inline row'>
<label class='form-control-label col-sm-2'>Stylesheet</label>
<select id='sht0' class='form-control col-sm-4'>
<optgroup label="LINK">
<!--================================[3]-->
<option value='0' selected name='Bootstrap-3.3.7'> [0]</option>
<option value='1' name='CSSOM'> [1]</option>
</optgroup>
<optgroup label="STYLE">
<option value='2' name='Tag'> [2]</option>
</optgroup>
</select>
<label class='form-control-label col-sm-1'>Rule Index</label>
<input id='idx0' class='form-control col-sm-1' type='number' min='-1' value='0'>
<!--==========[4]-->
<div class="btn-group col-sm-4">
<!--=====[5]-->
<input id='ins0' class='btn btn-primary' type='button' value='Insert Rule' onclick='modRule(this)'>
<!--======[6]-->
<input id='del0' class='btn btn-danger' type='button' value='Delete Rule' onclick='modRule(this)'>
</div>
</div>
</fieldset>
</section>
<hr><br>
</form>
<hgroup class='x-inline'>
<!--====================================[hgroup.x-inline]-->
<h1 class='h1'>CSSStyleSheet</h1>
<h2 class='h2'>.insertRule()</h2>
</hgroup>
<article class='text-primary'>
<blockquote class='blockquote'>
<h3 id="Restrictions" class='h3'>Restrictions</h3>
<p>CSS stylesheet rule-lists have a number of intuitive and not-so-intuitive <a class="external" href="https://drafts.csswg.org/cssom/#insert-a-css-rule">restrictions</a> affecting how and where rules can be inserted. Violating these will likely
cause an error raised as a <a title="The DOMException interface represents an abnormal event (called an exception) which occurs as a result of calling a method or accessing a property of a web API." href="https://developer.mozilla.org/en-US/docs/Web/API/DOMException"><code>DOMException</code></a> </p>
<!--==========================[ul.x-list | li.x-fade]-->
<ul id='list' class='list-group-flush x-list'>
<li class='list-group-item-text fade x-fade x-item'>If index > number of rules in the stylesheet (the <a title="A CSSRuleList is an (indirect-modify only) array-like object containing an ordered collection of CSSRule objects." href="https://developer.mozilla.org/en-US/docs/Web/API/CSSRuleList"><code>CSSRuleList</code></a>.length),
then aborts with IndexSizeError.</li>
<li class='list-group-item-warning x-item'>If rule cannot be inserted at index 0 due to some CSS constraint, then aborts with HierarchyRequestError.</li>
<li class='list-group-item-danger x-item'>If more than one rule is given in the rule parameter, then aborts with SyntaxError</li>
<li class='list-group-item-text x-fade x-item'>If trying to insert an #import at-rule after a style rule, then aborts with HierarchyRequestError.</li>
<li class='list-group-item-text x-fade x-item'>If rule is #namespace at-rule and list has more than just #import at-rules and/or #namespace at-rules, then aborts with InvalidStateError.</li>
</ul>
</blockquote>
<footer class='blockquote-footer'>
<!--===============================[[cite.x-cite]-->
<cite class='x-cite'><a href='https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/insertRule#Restrictions'>CSSStyleSheet.insertRule() - Wed APIs #Restrictions | MDN</a></cite> </footer>
</article>
</main>
<script>
var cnt = 0;
var form = document.forms[0];
var add = document.getElementById('add');
function modRule(ID) {
var e = window.event;
var i = ID.id.split('').pop();
console.log('ruleIndex: ' + i);
var sheets = document.styleSheets;
var sheet = document.getElementById('sht' + i);
var rulez = document.getElementById('rul' + i);
var index = document.getElementById('idx' + i);
var vSht = parseInt(sheet.value, 10);
var vIdx = parseInt(index.value, 10);
var vRul = rulez.value;
if (e.target.value === 'Delete Rule') {
switch (vSht) {
case 0:
sheets[0].deleteRule(vIdx);
break;
case 1:
sheets[1].deleteRule(vIdx);
break;
case 2:
sheets[2].deleteRule(vIdx);
break;
default:
sheets[0].deleteRule(vIdx);
break;
}
} else if (e.target.value === 'Insert Rule') {
switch (vSht) {
case 0:
sheets[0].insertRule(vRul, vIdx);
break;
case 1:
sheets[1].insertRule(vRul, vIdx);
break;
case 2:
sheets[2].insertRule(vRul, vIdx);
break;
default:
sheets[0].insertRule(vRul, vIdx);
break;
}
} else {
return;
}
}
add.addEventListener('click', addSet, false);
function addSet(e) {
cnt++;
var set = document.querySelector('.set0');
var opt = document.options
var dupe = set.cloneNode(true);
dupe.className = 'set' + cnt;
var fields = Array.from(dupe.querySelectorAll('[id]'));
var ids = fields.map(function(ID, idx) {
var zero = ID.id.lastIndexOf("0");
ID.id = ID.id.slice(0, zero);
ID.id = ID.id + cnt;
if (ID.id === 'rul' + cnt) {
ID.textContent = 'p {color:red}';
}
console.log('id: ' + ID.id + ' val: ' + ID.value);
return ID.value;
});
form.appendChild(dupe);
}
</script>
</body>
</html>
Update
If the stylesheet is big, wouldn't it be more efficient to dynamically create a new stylesheet for them?
Yes, but not an external <link> it's far more efficient and a ton more easier to dynamically manipulate a <style> block at the bottom of the </head>.
Not only is it simple, it's powerful since it's at the bottom of the cascade it'll override anything from an external <link>.
Also another thing to consider is that a HTTP request isn't needed unlike an external file which requires it and that' adds to your website's latency.
Demo 3 features 3 functions:
injectCSS() will be called mere microseconds before it's counterpart injectJS() at window.onload. It will create a <style> block within the </head> along with whatever styles we want initially.
injectJS() loads after injectCSS() because as a general rule style should always load before script. It will create a <script> tag and append it as the last child of the <body> tag specifically right before the closing </body> tag. Just like injectCSS() it may have anything within its tags that's script.
inject() calls both injectCSS() and injectJS() asynchronously to ensure that the former will always load before the latter.
As far as this demo relates to the OP, injectCSS() is the function that we should concern ourselves with as was already explained previously.
Details are commented in the demo
For faster loading time, please review the PLUNKER instead.
Demo 3
<!DOCTYPE html>
<html>
<head>
<style>
html {
font: 400 100%/1 Consolas;
}
html,
body {
height: 100%;
width: 100%;
}
main {
height: auto;
width: 100%;
padding: 10px;
}
section {
height: auto;
width: 100%;
padding: 10px
}
fieldset {
min-width: 70%;
margin: 20px 0;
padding: 10px;
}
var {
background: rgba(0, 0, 0, .7);
color: lime;
}
</style>
</head>
<body>
<h1></h1>
<main>
<p><var>injectCSS =</var> All paragraphs will be red</p>
<section>
<form id='test0'>
<fieldset>
<legend>Test Log</legend>
<label for='msg0'>injectCSS()...:
<output id='msg0'></output></label>
<br>
<label for='msg1'>injectJS()....:
<output id='msg1'></output></label>
</fieldset>
</form>
</section>
<section>
</section>
</main>
<footer>
<p>Review this page with Firebug/DevTools and we'll see an extra <style> tag in the <head> and we'll see an extra <script> tag right before the closing <\body> tag.</p>
</footer>
<script>
// HTMLFormControlsCollection★
var x0 = document.forms[0].elements;
var m0 = x0.msg0;
var m1 = x0.msg1;
// Input strings of styles and scripts that are to be injected
var css = "p {color:red}";
var js = "document.querySelector('h1').innerHTML = '<var>injectJS =</var> H1 HTML'";
/* This function manages injectCSS() and injectJS() functions by using
|| the async/await★ keywords. Times are provided by
|| performance.now()★ method.
*/ //* ✎ Delete/add the first * to disable/enable this version of inject().
// The proceeding edit ✎ must be done as well.
var inject = async function() {
var wait0 = injectCSS.call(this, css);
var wait1 = injectJS.call(this, js);
m0.value = performance.now();
var init1 = await wait1;
m1.value = performance.now()
return false;
};
/*/// ✎ Delete/add the first / to enable/disable this version of inject().
// The previous edit ✎ must be done as well.
var inject = function() {
injectCSS.call(this, css);
m0.value = performance.now();
injectJS.call(this, js);
m1.value = performance.now()
return false;
};
/* These 2 functions do the same thing but with different content.
|| They could be refactored into one function but I made them
|| separately to show injectCSS() sepatately for QA SO46985099.
|| Both creates a tag, then writes the code in it, and then appends
|| it to DOM.
*/
function injectCSS(style) {
var sty = document.createElement("style");
sty.innerHTML = style;
document.querySelector('head').appendChild(sty);
}
function injectJS(script) {
var scr = document.createElement("script");
scr.innerHTML = script;
document.body.appendChild(scr);
}
/* The main function inject() is called at window.load. This is the last
|| loading event possible in which we are able call our function. This
|| ensures that specific CSS is loaded before specific JS is called.
|| This is the last step in the loading process, therefore there should be
|| no more styles to render or script that blocks.
*/
window.onload = inject;
</script>
</body>
</html>
Demo Outline
Collects all <link> and <style> into a styleSheetList using document.stylesheets
Converts it into an array called sheetArray with Array.from()
The target is the 3rd <link> which is sheetArray[2]
Then there are 2 rules inserted at index 0 and index 1 successfully.
This is done through a for loop and arrays as the dynamic parameters and interpolation of Template Literals.
This demo does not function on SO, go to PLUNKER for a functioning demo.
Note: In the demo content is an excerpt from MDN that defines the restrictions of insertRule(). The highlighted items may apply to your specific errors.
Demo 1 - index.html [Review PLUNKER for a working demo]
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css' rel='stylesheet'>
<link href="style.css" rel="stylesheet">
<link href='cssom.css' rel="stylesheet">
</head>
<body>
<main class='container'>
<hgroup class='x-inline'>
<!--====================================[hgroup.x-inline]-->
<h1 class='h1'>CSSStyleSheet</h1>
<h2 class='h2'>.insertRule()</h2>
</hgroup>
<article class='text-primary'>
<blockquote class='blockquote'>
<h3 id="Restrictions" class='h3'>Restrictions</h3>
<p>CSS stylesheet rule-lists have a number of intuitive and not-so-intuitive <a class="external" href="https://drafts.csswg.org/cssom/#insert-a-css-rule">restrictions</a> affecting how and where rules can be inserted. Violating these will likely
cause an error raised as a <a title="The DOMException interface represents an abnormal event (called an exception) which occurs as a result of calling a method or accessing a property of a web API." href="https://developer.mozilla.org/en-US/docs/Web/API/DOMException"><code>DOMException</code></a> </p>
<!--==========================[ul.x-list | li.x-fade]-->
<ul class='list-group-flush x-list'>
<li class='list-group-item-text x-fade'>If index > number of rules in the stylesheet (the <a title="A CSSRuleList is an (indirect-modify only) array-like object containing an ordered collection of CSSRule objects." href="https://developer.mozilla.org/en-US/docs/Web/API/CSSRuleList"><code>CSSRuleList</code></a>.length),
then aborts with IndexSizeError.</li>
<li class='list-group-item-warning'>If rule cannot be inserted at index 0 due to some CSS constraint, then aborts with HierarchyRequestError.</li>
<li class='list-group-item-danger'>If more than one rule is given in the rule parameter, then aborts with SyntaxError</li>
<li class='list-group-item-text x-fade'>If trying to insert an #import at-rule after a style rule, then aborts with HierarchyRequestError.</li>
<li class='list-group-item-text x-fade'>If rule is #namespace at-rule and list has more than just #import at-rules and/or #namespace at-rules, then aborts with InvalidStateError.</li>
</ul>
</blockquote>
<footer class='blockquote-footer'>
<!--===============================[[cite.x-cite]-->
<cite class='x-cite'><a href='https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/insertRule#Restrictions'>CSSStyleSheet.insertRule() - Wed APIs #Restrictions | MDN</a></cite> </footer>
</article>
</main>
<script>
var sheets = document.styleSheets;
var sheetArray = Array.from(sheets);
var sel = ['ul.x-list', 'li::before'];
var dec = [`margin-left: 0; padding-left: 1em; text-indent: -1em;`, `content: '🔹';`];
var idx = [0, 1];
var qty = idx.length;
for (let r = 0; r < qty; r++) {
sheetArray[2].insertRule(`${sel[r]} {${dec[r]}}`, idx[r]);
}
</script>
</body>
</html>
Deno 2 - cssom.css
/*0*/
hgroup.x-inline {display:flex; justify-content:center; align-items: center;}
/*1*/
ul.x-list.x-list {list-style: none;}
/*2*/
li.x-fade.x-fade {color:rgba(0,0,0,.3);}
/*3*/
cite.x-cite.x-cite {position:relative; left:60%}
/*4*/
cite.x-cite.x-cite::before {content:"\2014 \00A0"}
/*5*/
.blockquote-footer::before {content: '';}
/*6*/
li.list-group-item {line-height:1.5}
/*7*/
a {text-shadow: 2px 5px 3px rgba(192,192,192,.8)}
I'm new at AngularJS. I do know some javascript, but AngularJS seems hard to learn (maybe it's just me).
My problem is as follows...
I have a list of players and I would like to make it possible for a user (coach or whoever) to add their players to the list. I have tried couple of method for these past few days, and I just can't figure it out.
Code is below, and u can check out my plunkr here:
HTML
<!DOCTYPE html>
<html >
<head>
<!-- CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link href="style.css" rel="stylesheet" />
<!-- Scripts -->
<script data-require="angular.js#1.2.17" data-semver="1.2.17"
src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.17/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-app="app">
<div ng-controller="MoveCtrl" class = "container">
<div class = "row">
<div class = "col-xs-4 left-space">
<!--Forgot to add this block of code for input-->
<label>Player name: </label> <input #playerName/>
<button (click) = "add(playerName.value); playerName.value = ''">
Add
</button>
<!--Rest is the same-->
<label class="left-space-title" for="aplayers">Available Players</label>
<select class="left-space-container" size="5" multiple ng-model="available"
ng-options="player as player.name for player in availableplayers">
</select>
</div>
<div class = "col-xs-2 mid-space ">
<input id="moveright" type="button" value=">>"
ng-click="moveItem(available, availableplayers,selectedplayers)" />
<input id="moveleft" type="button" value="<<"
ng-click="moveItem(selected, selectedplayers,availableplayers)" />
</div>
<div class = "col-xs-4 right-space">
<label class="right-space-title" for="splayers">Selected Players</label>
<select class="right-space-container" size="5" multiple ng-model="selected"
ng-options="player as player.name for player in selectedplayers">
</select>
</div>
</div>
</div>
</div>
</body>
</html>
CSS
.mid-space {
margin-top: 30px;
}
.left-space__title,
.right-space__title {
width: 100%;
}
.left-space-container,
.right-space-container {
width: 100%;
min-height: 500px;
}
#moveright,
#moveleft {
width: 100%;
}
Javascript
angular.module('app', []).controller('MoveCtrl', function($scope) {
$scope.moveItem = function(items, from, to) {
items.forEach(function(item) {
var idx = from.indexOf(item);
if (idx != -1) {
from.splice(idx, 1);
to.push(item);
}
});
};
$scope.selectedplayers = [];
$scope.availableplayers = [
{
id: 1,
name: 'foo'
},
{
id: 2,
name: 'boo'
},
{
id: 3,
name: 'goo'
}
];
});
Plunkr is here if you would like to fiddle with the code directly
The plunker has been updated with your working code changes to add the new player to the available players list
add new player plunker
$scope.addItem = function(){
var numberOfplayers = $scope.availableplayers.length;
var newPlayer = {
id : numberOfplayers + 1,
name : $scope.newPlayerName
}
console.log($scope.newPlayerName);
$scope.availableplayers.push(newPlayer);
}
<label>Player name: </label> <input ng-model="newPlayerName"/>
<button ng-click = "addItem(playerName);">
Add
</button>
code changes done .
1.set the ng-model for the input field .
2.get the value of the
input fiend in controllers add player method.
3.create the new
players object with the generated id value.
4.Insert the new player
object to the availableplayers scope variable which inturn pops up
the value in the box.