I'm struggling with a to-do app. I want to cross out a LI element and add a span with an x sign. It's easy by toggling class. However, when I "untoggle" the class by clicking again. The "x" remains and when I click on the item again the "x" is duplicated. How can I prevent adding another "x" or make the "x" disappear when the items is "untoggled".
const addButton = document.querySelector("#add");
const input = document.querySelector("input[name='input-item'");
const ul = document.querySelector("ul");
const allItems = document.querySelectorAll("li");
for (let i = 0; i < allItems.length; i++) {
allItems[i].addEventListener("click", myList);
}
function myList() {
let temp = this.classList.toggle("red");
if (temp) {
let span = document.createElement("span");
span.innerHTML = "×";
span.addEventListener("click", function() {
this.parentElement.remove();
});
this.appendChild(span);
} else if (this.classList.contains("red")) {
this.getElementByTagName("span").remove();
}
}
.red {
text-decoration: line-through;
color: red;
}
span {
background-color: white;
padding: 0 0.3rem;
color: black;
margin: 0 0.2rem;
display: inline-block;
}
<div class="container">
<ul>
<li>banana</li>
<li>orange</li>
<li>grapes</li>
</ul>
<input type="text" name="input-item" placeholder="Enter a new item" /><button id="add">Add Item</button>
</div>
Related
I'm creating a tab menu like this:
function clear_selected() //sets all columns color black
{
var parent = document.querySelector("#container")
var items = document.querySelectorAll(".item")
var n = items.length;
for (var i = 0; i < n; i++)
items[i].style.backgroundColor = "";
}
function plus(itself) //adds another column
{
var parent = itself.parentElement;
var n = parent.childElementCount;
clear_selected();
var n = parent.querySelectorAll(".item").length;
var page = document.createElement("button");
page.className = "item";
page.style.backgroundColor = "blue";
page.textContent = "column"
page.onclick = function() {
clear_selected();
this.style.backgroundColor = "blue";
};
var temp = document.createElement("span");
temp.className = "del"
temp.innerHTML = "×"
temp.onclick = function() { //it's suppose to remove a column and color default as blue
document.querySelector("#main_item").style.backgroundColor = "blue" //THIS LINE ISN'T WORKING
this.parentElement.remove();
};
page.appendChild(temp);
parent.insertBefore(page, parent.childNodes[n]);
}
function see(el) {
clear_selected();
el.style.backgroundColor = "blue";
}
#container {
display: flex;
width: 100%;
height: 50px;
text-align: center;
background-color: yellow;
}
.item {
background-color: black;
color: white;
border: none;
outline: none;
cursor: pointer;
margin: 0.1rem;
padding: 0.1rem;
max-width: 100%;
}
.del {
background-color: red;
display: inline-block;
cursor: pointer;
border-radius: 50%;
width: 0.7rem;
margin-left: 2rem;
}
<div id="container">
<button class="item" id="main_item" style="background-color:blue;" onclick="see(this)">default column </button>
<button class="item" onclick="plus(this)">+</button>
</div>
but when I press the 'x' to remove a column, I want the default column to color blue, but the line of code which is suppose to achieve that isn't working
document.querySelector("#main_item").style.backgroundColor = "blue"
Before pressing 'x':
After pressing 'x' on the last column:
What it SHOULD look like:
I've losing sleep over this, can someone PLEASE tell me why isn't it working?
When you click on the "X", both of your onclick handlers are getting called, including the one that runs clear_selected, which sets the background color to "".
You can fix this by using stopPropagation on the event passed into the onclick function for the "x". That will stop the click event from going up the chain to the parent element of the "x".
temp.onclick = function(e) {
document.querySelector("#main_item").style.backgroundColor = "blue"
this.parentElement.remove();
e.stopPropagation();
};
I am creating a list of inputs and storing the values in an array when the add button is clicked.
All works well, but I have introduced the possibility to remove items in the list effectively by hiding the 'deleted' elements from the DOM.
Now I need to remove the hidden elements from the array.
To keep things simple, I thought to store in the array only the visible elements (instead of removing the hidden element from the array).
To do so I am looking at adapting the 'how to count visible elements' code in: (https://stackoverflow.com/questions/37634146/javascript-count-visible-elements but it doesn't work and a list of '[object HTMLLIElement]' is displayed when displaying the array on an alert.
This is my code:
// Create a "close" button and append it to each list item
var myNodelist = document.getElementsByTagName("LI");
var i;
for (i = 0; i < myNodelist.length; i++) {
var span = document.createElement("SPAN");
var txt = document.createTextNode("\u00D7");
span.className = "close";
span.appendChild(txt);
myNodelist[i].appendChild(span);
}
// Click on a close button to hide the current list item
var close = document.getElementsByClassName("close");
var i;
for (i = 0; i < close.length; i++) {
close[i].onclick = function () {
var div = this.parentElement;
div.style.display = "none";
};
}
// Create a new list item when clicking on the "Add" button
function addNumber() {
var li = document.createElement("li");
var inputValue = document.getElementById("number").value;
var t = document.createTextNode(inputValue);
li.appendChild(t);
if (inputValue === "") {
alert("Add at least one phone number!");
} else {
document.getElementById("myUL").appendChild(li);
}
// remove elements from form after added to list
document.getElementById("number").value = "";
var span = document.createElement("SPAN");
var txt = document.createTextNode("\u00D7");
span.className = "close";
span.appendChild(txt);
li.appendChild(span);
// without this, the element is not deleted
for (i = 0; i < close.length; i++) {
close[i].onclick = function () {
var div = this.parentElement;
div.style.display = "none";
};
}
}
// store values after submit
function submitRecords() {
divs = document.getElementsByTagName("li");
var divsArray = [].slice.call(divs);
var displayShow = divsArray.filter(function (el) {
return getComputedStyle(el).display !== "none";
});
alert(displayShow);
}
#myUL {
/* text-align: center; */
width: 40%;
float: left;
}
/* Style the list items */
ul li {
position: relative;
padding: 12px 8px 12px 40px;
list-style-type: none;
background: #eee;
border-top: lightgrey 1px solid;
border-bottom: lightgrey 1px solid;
font-size: 20px;
transition: 0.2s;
/* this allows for the borders of 2 adiacent items to overlap */
margin-top: -1px;
}
/* Style the close button */
.close {
cursor: pointer;
position: absolute;
right: 0;
top: 0;
padding: 12px 16px 12px 16px;
}
.close:hover {
background-color: red;
color: white;
}
.addBtn {
background: #eee;
border: black 1px solid;
padding: 4px;
color: black;
cursor: pointer;
}
.addBtn:hover {
background-color: #ddd;
}
<div class="inputForm">
<form>
<input
type="text"
id="number"
placeholder="Enter number"
onkeypress="return isNumberKey(event)"
/>
<span onclick="addNumber()" class="addBtn">Add</span>
<br /><br />
<input
onclick="submitRecords()"
id="send"
type="submit"
value="SEND"
/>
</form>
</div>
<!-- this is needed to generate the list of numbers -->
<ul id="myUL"></ul>
What am I doing wrong?
There is a .value in function addNumber which seems to be spurious, on this line:
divs = document.getElementsByTagName("li").value;
when this is removed the console.log shows li elements.
The code for adding onclick functionality to the span elements looks as though it is trying to step through a (non-existent?) array, and it is doing this each time a number is added. Removing this code and instead setting up the onclick as a span element is created seems to work.
Here is the altered code:
<!-- Added HTML for testing -->
<input id="number" value=999 onchange="addNumber();"/>
<button onclick="submitRecords();">submit records</button>
<ul id="myUL"></ul>
<script>
function submitRecords() {
var divs = (document.getElementsByTagName("li"));// REMOVED .value
var divsArray = [].slice.call(divs);
var displayShow = divsArray.filter(function (el) {
return getComputedStyle(el).display !== "none";
});
var numbers=[];
var keys = [];
for (var i=0;i<displayShow.length;i++) {
numbers[i]=displayShow[i].childNodes[0].textContent;
console.log(numbers[i]);
}
}
function addNumber() {
var li = document.createElement("li");
var inputValue = document.getElementById("number").value;
var t = document.createTextNode(inputValue);
li.appendChild(t);
if (inputValue === "") {
alert("Add at least one phone number!");
} else {
document.getElementById("myUL").appendChild(li);
}
// close button
var span = document.createElement("SPAN");
var txt = document.createTextNode("\u00D7");
span.className = "close";
span.onclick = function () { //ADDED so that an li element's span child gets this onclick function as soon as it is created
var div = this.parentElement;
div.style.display = "none";
};
span.appendChild(txt);
li.appendChild(span);
/* REMOVED // hide element
for (i = 0; i < close.length; i++) {
close[i].onclick = function () {
var div = this.parentElement;
div.style.display = "none";
};
*/
}
</script>
Your idea to get only visible items is just fine, and you can do it like this:
function submitRecords() {
const displayShow = [];
//declare array
[...document.querySelectorAll('#myUL li:not([style="display: none;"])')].forEach(element => {
//get all li items and for each of them:
displayShow.push(element.textContent.slice(0, -1))
//push its text Content into array, and also just remove last x (button) character
});
console.clear();
console.log(displayShow);
}
Ps: li element does not have .value, you can red content with: textContent
Just remove onsubmit event from form, and add back onkeypress="return isNumberKey(event)" to input i made modifications just for demonstration:
EXAMPLE:
// Create a "close" button and append it to each list item
var myNodelist = document.getElementsByTagName("LI");
var i;
for (i = 0; i < myNodelist.length; i++) {
var span = document.createElement("SPAN");
var txt = document.createTextNode("\u00D7");
span.className = "close";
span.appendChild(txt);
myNodelist[i].appendChild(span);
}
// Click on a close button to hide the current list item
var close = document.getElementsByClassName("close");
var i;
for (i = 0; i < close.length; i++) {
close[i].onclick = function () {
var div = this.parentElement;
div.style.display = "none";
};
}
// Create a new list item when clicking on the "Add" button
function addNumber() {
var li = document.createElement("li");
var inputValue = document.getElementById("number").value;
var t = document.createTextNode(inputValue);
li.appendChild(t);
if (inputValue === "") {
alert("Add at least one phone number!");
} else {
document.getElementById("myUL").appendChild(li);
}
// remove elements from form after added to list
document.getElementById("number").value = "";
var span = document.createElement("SPAN");
var txt = document.createTextNode("\u00D7");
span.className = "close";
span.appendChild(txt);
li.appendChild(span);
// without this, the element is not deleted
for (i = 0; i < close.length; i++) {
close[i].onclick = function () {
var div = this.parentElement;
div.style.display = "none";
};
}
}
// store values after submit
function submitRecords() {
const displayShow = [];
[...document.querySelectorAll('#myUL li:not([style="display: none;"])')].forEach(element => {
displayShow.push(element.textContent.slice(0, -1))
});
console.clear();
console.log(displayShow);
}
#myUL {
/* text-align: center; */
width: 40%;
float: left;
}
/* Style the list items */
ul li {
position: relative;
padding: 12px 8px 12px 40px;
list-style-type: none;
background: #eee;
border-top: lightgrey 1px solid;
border-bottom: lightgrey 1px solid;
font-size: 20px;
transition: 0.2s;
/* this allows for the borders of 2 adiacent items to overlap */
margin-top: -1px;
}
/* Style the close button */
.close {
cursor: pointer;
position: absolute;
right: 0;
top: 0;
padding: 12px 16px 12px 16px;
}
.close:hover {
background-color: red;
color: white;
}
.addBtn {
background: #eee;
border: black 1px solid;
padding: 4px;
color: black;
cursor: pointer;
}
.addBtn:hover {
background-color: #ddd;
}
<div class="inputForm">
<form onsubmit="event.preventDefault();">
<input
type="text"
id="number"
placeholder="Enter number"
/>
<span onclick="addNumber()" class="addBtn">Add</span>
<br /><br />
<input
onclick="submitRecords()"
id="send"
type="submit"
value="SEND"
/>
</form>
</div>
<!-- this is needed to generate the list of numbers -->
<ul id="myUL"></ul>
I made only small modification to your code to get the desired values from the <li> element.
function submitRecords() {
divs = document.getElementsByTagName("li");
var divsArray = [];
var displayShow = Object.values(divs).filter(function (el) {
return getComputedStyle(el).display !== "none";
});
displayShow.forEach(function(item){let ind=item.innerHTML.indexOf('<span');
divsArray.push(item.innerHTML.substr(0,ind))});
alert(divsArray);
}
I am trying to create a text font colour drop down button where it gives you an option of multiple colour to pick from and then it would change the colour of the text. I am not sure on how to approach this and I am not meant to use jQuery. Any help would be appreciated. In the code below it shows other examples of other button where they change the user input entered into the contenteditable. I want the font colour button to do the same but just change the colour of the text
const TAB_KEY = 9;
const ENTER_KEY = 13;
const SHIFT_KEY = 16
const editor = document.querySelector('.editor');
editor.appendChild(document.createElement('li'));
editor.addEventListener('keydown', (e) => {
let code = e.keyCode || e.which;
if (code == TAB_KEY) {
e.preventDefault();
let parent = e.target;
let ul = document.createElement('ul');
let li = document.createElement('li');
ul.appendChild(li);
parent.appendChild(ul);
moveCursorToEnd(li);
} else if (code == ENTER_KEY) {
e.preventDefault();
let parent = e.target;
let li = document.createElement('li');
parent.appendChild(li);
moveCursorToEnd(li);
} else if (code == TAB_KEY * TAB_KEY){
e.preventDefault();
let parent = e.target;
let ol = document.createElement('ol');
let li = document.createElement('li');
ol.appendChild(li);
parent.appendChild(ol);
moveCursorToEnd(li);
}
});
function moveCursorToEnd(el) {
el.focus();
document.execCommand('selectAll', false, null);
document.getSelection().collapseToEnd();
}
/*editor.addEventListener('click', (x) => {
x = document.getElementById("b");
if(x.style.fontWeight == "bolder"){
x.style.fontWeight = "normal";
} else {
x.style.fontWeight = "bolder";
}
});*/
function bold(){
if(document.execCommand("bold")){
document.execCommand("normal");
}else{
document.execCommand("bold");
}
}
/*function underline(){
let x = document.getElementById("text");
if(x.style.textDecoration == "underline"){
x.style.textDecoration = "none";
}else{
x.style.textDecoration = "underline";
}
}*/
function underline(){
if(document.execCommand("underline")){
document.execCommand("none");
}else{
document.execCommand("underline");
}
}
/*Turns the font of the text to Italic*/
function italic(){
if(document.execCommand("italic")){
document.execCommand("normal");
}else{
document.execCommand("italic");
}
}
function highlighSelectedText(){
let sel = window.getSelection().getRangeAt(0);
let selText = sel.extractContents();
let span = document.createElement("span");
span.style.backgroundColor = "yellow";
span.appendChild(selText);
sel.insertNode(span);
}
/*function printPage(){
let printButton = document.getElementById("ul");
printButton.style.visibility = 'hidden';
window.print();
printButton.style.visibility = 'visible';
}*/
body{
margin-top:1em;
margin-bottom: 10em;
margin-right: 1em;
margin-left: 1em;
border: solid;
border-color: #0033cc;
background-color: #f6f6f6;
}
div button{
padding: 1em 2em;
color: white;
background-color: #0000cc;
}
div input{
padding: 1em 2em;
color: white;
background-color: #0000cc;
}
div{
list-style-type:square;
list-style-position: inside;
margin-left: 0.25em;
margin-bottom: 5em;
}
section {
padding: 1em 2em;
color: white;
background-color: #0000cc;
}
.editor {
font-weight: normal;
}
div contenteditable{
margin-bottom: 10em;
}
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<head>
<title>Outliner</title>
<link href="style.css" rel="stylesheet" title="Style">
<div>
<button id="b" onclick="bold()"> B </button>
<button onclick="underline()"> U </button>
<button onclick="italic()"> I </button>
<input type="button" onclick="highlighSelectedText()" value="Highlight"/>
<div id="text" class="editor" contenteditable="true" draggable="true"></div>
</div>
<section>
<input id="saveAs"></input>
<button onclick="saveTextFile()">Download</button>
<input type="file" id="load"/>
<button onclick="loadFile()">Load</button>
</section>
<section>
<button class="btn btn-primary" onclick="saveChanges()">Save Text</button>
<button class="btn btn-warning" onclick="clearStorage()">Reset</button>
</section>
</head>
<script type= "text/javascript" src='setting.js'></script>
</body>
First off we will use a CSS variable. Lets declare a value at :root
:root {
--font-color: #000;
}
Now we will use that value to style the font color of our P tags.
p {
color: var(--font-color);
}
Now when somebody clicks one of the color names, we want to change the value of --font-color. (Notice we are using the data- attribute model to store the color we want to change too).
document.documentElement.style.setProperty('--font-color', target.dataset.color);
And presto we can now change color easily. This works for other values also.
Here is a great article
document.addEventListener('click', ({ target }) => {
if(target.matches('p')) {
document.documentElement.style.setProperty('--font-color', target.dataset.color);
}
});
:root {
--font-color: #000;
}
p {
width: 30%;
border: 2px solid #00000030;
border-radius: 7px;
margin: 0.25rem;
padding: 0.25rem;
color: var(--font-color);
}
<h2>Click a color</h2>
<p data-color="#f00">Red</p>
<p data-color="#0f0">Green</p>
<p data-color="#00f">Blue</p>
<p data-color="#000">Reset</p>
You can manipulate the style variable:
<div id="text">
Choose a color
</div>
<input id="color" type="color">
<button onclick="document.getElementById('text').style.color = document.getElementById('color').value;">Change Color</button>
I am trying to build a vertical tabs, but the list items should be connected to the divs seperately. All the divs are hidden with display: none; and when someone clicks on for example tab2, the second div should be turned into display: block. In other words, the className "selected" will be added to that specific div. Got the following error in console.log (Cannot read property 'classList' of null at HTMLUListElement.). Anyone here who knows how to write the JavaScript so the list items are connected to the divs.
Only in plain JavaScript please.
document.getElementById("verticalUl").addEventListener("click", function(e) {
var e = e || window.event,
elements = document.getElementById("verticalUl").children,
content = document.querySelectorAll(".support-box");
if(e.target && e.target.nodeName == "LI") {
var attribute = e.target.getAttribute("id");
for(var i = 0; i < elements.length; i++) {
elements[i].classList.remove("active-support");
}
for (var i = 0; i < content.length; i++) {
content[i].classList.remove("selected");
};
document.querySelector("." + attribute + "-box").classList.add("selected");
e.target.classList.add("active-support");
}
});
.group {
display: none;
}
.selected {
display: block;
}
.vertical-navbar {
float: left;
width: 20%;
}
.vertical-navbar li {
list-style-type: none;
border: 1px solid red;
margin: 5px;
}
.support-box {
width: 60%;
float: right;
height: 300px;
border: 1px solid red;
}
<ul class="vertical-navbar" id="verticalUl">
<li id="tab1" class="support-tabs-label active-support">Hot Topics</li>
<li id="tab2" class="support-tabs-label">Account</li>
<li id="tab3" class="support-tabs-label">Product</li>
<li id="tab4" class="support-tabs-label">Order and Shipping</li>
</ul>
<div class="content-support1 support-box tab1-box group selected">
Hot Topics
</div>
<div class="content-support2 support-box tab2-box group">
Account
</div>
<div class="content-support3 support-box tab3-box group">
Product
</div>
<div class="content-support4 support-box tab4-box group">
Order and Shipping
</div>
On the assumption that all of the support-box elements have tabx-box, where tabx matches the clicked li element, you can simply use:
document.querySelector("." + attribute + "-box").classList.add("selected");
The Solution
document.getElementById("verticalUl").addEventListener("click", function(e) {
var e = e || window.event,
elements = document.getElementById("verticalUl").children,
content = document.querySelectorAll(".support-box");
for (var i = 0; i < content.length; i++) {
content[i].classList.remove("selected");
}
if (e.target && e.target.nodeName == "LI") {
var attribute = e.target.getAttribute("id");
for (var i = 0; i < elements.length; i++) {
elements[i].classList.remove("active-support");
}
e.target.classList.add("active-support");
document.querySelector("." + attribute + "-box").classList.add("selected");
}
});
https://jsfiddle.net/j8k09r9s/1/
I'm trying to have a bgcolor change for an element on mouseover, mouseout, and onclick. The problem is Javascript overwrites my onclick with mouseout, so I can't have both. So is there any way to have mouseover reset after mouseout?
function init() {
document.getElementById('default').onmouseover = function() {
tabHoverOn('default', 'grey')
};
document.getElementById('default').onmouseout = function() {
tabHoverOff('default', 'yellow')
};
document.getElementById('section2').onmouseover = function() {
tabHoverOn('section2', 'grey')
};
document.getElementById('section2').onmouseout = function() {
tabHoverOff('section2', 'yellow')
};
document.getElementById('section3').onmouseover = function() {
tabHoverOn('section3', 'grey')
};
document.getElementById('section3').onmouseout = function() {
tabHoverOff('section3', 'yellow')
};
}
function tabHoverOn(id, bgcolor) {
document.getElementById(id).style.backgroundColor = bgcolor;
}
function tabHoverOff(id, bgcolor) {
document.getElementById(id).style.backgroundColor = bgcolor;
}
var current = document.getElementById('default');
function tab1Highlight(id) {
if (current != null) {
current.className = "";
}
id.className = "tab1highlight";
current = id;
}
function tab2highlight(id) {
if (current != null) {
current.className = "";
}
id.className = "tab2highlight";
current = id;
}
function tab3highlight(id) {
if (current != null) {
current.className = "";
}
id.className = "tab3highlight";
current = id;
}
window.onload = init();
body {
width: 900px;
margin: 10px auto;
}
nav {
display: block;
width: 80%;
margin: 0 auto;
}
nav > ul {
list-style: none;
}
nav > ul > li {
display: inline-block;
margin: 0 3px;
width: 150px;
}
nav > ul > li > a {
width: 100%;
background-color: #ffff66;
border: 1px solid #9b9b9b;
border-radius: 12px 8px 0 0;
padding: 8px 15px;
text-decoration: none;
font-weight: bold;
font-family: arial, sans-serif;
}
main {
display: block;
width: 80%;
margin: 0 auto;
border: 1px solid #9b9b9b;
padding: 10px;
}
main > h1 {
font-size: 1.5em;
}
.tab1highlight {
background-color: #339966;
color: white;
}
.tab2highlight {
background-color: #ff6666;
color: white;
}
.tab3highlight {
background-color: #6600ff;
color: white;
}
main img {
border: 5px solid #eeefff;
width: 80%;
margin-top: 20px;
}
<body>
<nav>
<ul>
<li>Section 1</li>
<li>Section 2</li>
<li>Section 3</li>
</ul>
</nav>
<main>
<h1>Exercise: Navigation Tab #5</h1>
<ul>
<li>
Combine the navigation tab exercises #1, #3, and #4 in one file, including <br>
<ul>
<li>temporarily change the background color of a tab when the cursor is hovering on it.</li>
<li>set the foreground and background color of the tab being clicked.</li>
<li>change the background color of the main element based on the selected tab.</li>
</ul>
<p>
To test, click on a tab and then move your mouse around. For example, the third tab is clicked, the tab background color is switched to blue. Then hover the mouse over the third tab, the background color of the tab should be switch to light green and then back to blue after the mouse moves out.
</p>
<img src="menu_tab5.jpg">
</li>
</ul>
</main>
It's generally a good idea to keep CSS out of JavaScript completely if you can help it. A better strategy for solving the hover problem is to use the CSS pseudo selector :hover rather than coding the color changes in JavaScript. If you give all your tabs the same class, you only have to write the CSS once:
.tab {
background-color: yellow;
}
.tab:hover {
background-color: grey;
}
Once you've done that, you can also relegate the click styling to CSS by creating an event handler that adds and removes a special class each time a tab is clicked.
In the CSS file:
.tab.clicked {
background-color: blue;
}
And then in JavaScript, something like:
var tabs = document.getElementsByClassName('tab');
for (i = 0; i < tabs.length; i ++) {
tabs[i].onclick = function (ev) {
for (i = 0; i < tabs.length; i ++) {
tabs[i].classList.remove('clicked');
}
ev.currentTarget.classList.add('clicked');
};
}
I've created a JSFiddle to illustrate.
Try updating a Boolean variable.
var Ele = document.getElementById('default');
var clicked = false;
Ele.onclick = function(){
clicked = true;
// add additional functionality here
}
Ele.onmouseover = function(){
clicked = false;
// add additional functionality here
}
Ele.onmouseout = function(){
if(!clicked){
// add additional functionality here
}
}