I am making a todo page and want to count the number of todos and display them.
<div class='numtodos'>3</div>
I want to display them here in the text content of numtodos.
Trying to count the number of todo lis using jQuery (I could also use a non-jQuery answer).
I know I would need the length of <li> elements, but how should I save it to update when a new todo is added.
Here is my code pen. trying to make the large number on the right be the number of todos.
$('ul').on('click', 'li', function() {
$(this).toggleClass('completed');
})
$('ul').on('click', 'span', function(event) {
$(this).parent().fadeOut(500, function() {
$(this).remove();
})
event.stopPropagation();
});
$("input[type='text']").keypress(function(event) {
if (event.which === 13) {
//get the new todo text from inut
var todoText = $(this).val();
$(this).val('');
//create a new li and add it ul
$('ul').append('<li><i class="far fa-circle"></i> ' + todoText + ' <span>X</span></li>')
}
});
$(".fa-plus-circle").click(function() {
$("input[type='text']").fadeToggle();
});
// let lis = document.querySelectorAll('li');
// let input = document.querySelector('input');
// let numtodos = document.querySelector('.numtodos')
// input.addEventListener('change', function(){
// let howmany = lis.length;
// numtodos.textContent= howmany;
// })
$('input').change(function() {
console.log($('.numtodos'));
})
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: rgb(67, 0, 192);
font-family: 'Roboto', sans-serif;
}
.container {
background-color: whitesmoke;
width: 480px;
height: 100%;
padding: 35px 40px;
}
/* NAV */
#nav {
display: flex;
justify-content: space-between;
align-items: center;
}
.tinyburger {
cursor: pointer;
}
.line {
height: 3px;
width: 21px;
background: black;
margin: 3px;
border-radius: 1px;
}
/* header */
.header {
display: flex;
margin: 40px 0px 30px;
font-family: 'Roboto Condensed', sans-serif;
}
h1 {
margin-right: 8px;
font-size: 3em;
}
h2 {
font-size: 1.4em;
margin-top: 26px;
color: rgb(153, 153, 153);
}
/* Input */
input {
width: 100%;
padding: 10px 5px;
border: none;
font-size: 17px;
}
ul {
list-style: none;
}
li {
line-height: 1.2;
border-bottom: 1px rgb(202, 202, 202) solid;
margin: 20px 5px;
padding: 0px 0 20px 0;
font-weight: bold;
cursor: pointer;
}
li span {
float: right;
}
.fa-plus-circle {
color: rgb(67, 0, 192);
font-size: 40px;
text-align: center;
width: 100%;
margin-top: 30px;
}
.numtodos {
font-size: 12em;
position: absolute;
top: 0%;
left: 75%;
line-height: 0.7;
color: rgba(153, 153, 153, 0.2);
font-family: 'Roboto Condensed', sans-serif;
}
.fa-circle {
color: rgba(153, 153, 153, 0.4);
font-size: 20px;
}
.completed {
text-decoration: line-through;
color: grey;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css" integrity="sha512-1PKOgIY59xJ8Co8+NE6FZ+LOAZKjy+KY8iq0G4B3CyeY6wYHN3yt9PW0XpSriVlkMXe40PTKnXrLnZ9+fkDaog==" crossorigin="anonymous" />
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght#0,400;1,700&family=Roboto:wght#400;500;700;900&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<div id='nav'>
<div class='tinyburger'>
<div class='line'></div>
<div class='line'></div>
<div class='line'></div>
</div>
<i class="fas fa-search"></i>
</div>
<div class="header">
<h1>TO-DO</h1>
<h2 class="date">NOV 23</h2>
</div>
<input type="text" name='newtodo' id='newtodo' placeholder="Write a new to-do">
<ul class='todos'>
<li><i class="far fa-circle"></i> Lunch with Helena Barnes <span>X</span> </li>
<li><i class="far fa-circle"></i> Evening Workout<span>X</span></li>
<li><i class="far fa-circle"></i> Lunch with Helena Barnes <span>X</span></li>
</ul>
<i class="fas fa-plus-circle"></i>
<div class='numtodos'>3</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
https://codepen.io/laurynatlanta/pen/QWNxYeN
I think this should be fine:
// Get todos number container div element.
var numTodosContainer = document.getElementsByClassName('numtodos')[0];
// Get all todos available.
var todos = document.getElementsByClassName(todosclassname);
// Get todos length to add it to the container.
var lastTodosNum = todos.length;
// Add it to the container.
numTodosContainer.innerHTML = lastTodosNum;
// Loop to check every 10ms if the number of todos is changed.
setInterval(function(){
// This variable will change every 10ms to get the new todos number.
var newTodosNum = document.getElementsByClassName(todosclassname).length;
// Check if the new number isn't equals the old num.
if (newTodosNum != lastTodosNum) {
numTodosContainer.innerHTML = newTodosNum;
}
// Make lastTodosNum equals newTodosNum to stop the if statement above from execute the code block inside it.
lastTodosNum = newTodosNum;
}, 10);
you have todo something like that:
const numtodos = document.querySelector('div.numtodos')
numtodos.setCount =_=> {
numtodos.textContent = document.querySelectorAll('ul.todos>li:not(.completed)').length
}
then just add numtodos.setCount(); on rights places.
so, you JS become:
const numtodos = document.querySelector('div.numtodos')
numtodos.setCount =_=> {
numtodos.textContent = document.querySelectorAll('ul.todos>li:not(.completed)').length
}
$('ul').on('click', 'li', function () {
$(this).toggleClass('completed');
numtodos.setCount();
});
$('ul').on('click', 'span', function (event) {
$(this).parent().fadeOut(500, function () {
$(this).remove();
numtodos.setCount();
});
event.stopPropagation();
});
$("input[type='text']").keypress(function (event) {
if (event.which === 13) {
//get the new todo text from inut
var todoText = $(this).val();
$(this).val('');
//create a new li and add it ul
$('ul').append(`<li><i class="far fa-circle"></i>${todoText}<span>X</span></li>`);
numtodos.setCount();
}
});
$(".fa-plus-circle").click(function () {
$("input[type='text']").fadeToggle();
});
Based on your current code, I'd suggest creating the following named function, and then calling that function in the areas of your code where you're updating the the to-do list (whether adding to, or removing from, it):
// we're not planning to change the function within the code, so we
// declare using 'const' (though 'let' would be perfectly okay, but would
// potentially allow for the variable to be overwritten); we also use
// Arrow function syntax since - within the function - we have no need
// to use 'this':
const todosLength = () => {
// here we find the number of <li> elements that do not have
// the class of 'completed' that are the children of a parent
// with the class of 'todos'; and then we retrieve the length
// of the resulting jQuery collection:
const num = $('.todos > li:not(.completed)').length;
// here we set the text of the element(s) with the
// class of 'numtodos':
$('.numtodos').text(num);
// here we return the number to the calling context
// (just in case it's ever of use):
return num;
};
When the above is merged with your code it yields the following:
const todosLength = () => {
const num = $('.todos li:not(.completed)').length;
$('.numtodos').text(num);
return num;
}
$('ul').on('click', 'li', function() {
$(this).toggleClass('completed');
})
$('ul').on('click', 'span', function(event) {
$(this).parent().fadeOut(500, function() {
$(this).remove();
todosLength();
});
event.stopPropagation();
});
$("input[type='text']").keypress(function(event) {
if (event.which === 13) {
//get the new todo text from inut
var todoText = $(this).val();
$(this).val('');
//create a new li and add it ul
$('ul').append('<li><i class="far fa-circle"></i> ' + todoText + ' <span>X</span></li>');
todosLength();
}
});
$(".fa-plus-circle").click(function() {
$("input[type='text']").fadeToggle();
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: rgb(67, 0, 192);
font-family: 'Roboto', sans-serif;
}
.container {
background-color: whitesmoke;
width: 480px;
height: 100%;
padding: 35px 40px;
}
/* NAV */
#nav {
display: flex;
justify-content: space-between;
align-items: center;
}
.tinyburger {
cursor: pointer;
}
.line {
height: 3px;
width: 21px;
background: black;
margin: 3px;
border-radius: 1px;
}
/* header */
.header {
display: flex;
margin: 40px 0px 30px;
font-family: 'Roboto Condensed', sans-serif;
}
h1 {
margin-right: 8px;
font-size: 3em;
}
h2 {
font-size: 1.4em;
margin-top: 26px;
color: rgb(153, 153, 153);
}
/* Input */
input {
width: 100%;
padding: 10px 5px;
border: none;
font-size: 17px;
}
ul {
list-style: none;
}
li {
line-height: 1.2;
border-bottom: 1px rgb(202, 202, 202) solid;
margin: 20px 5px;
padding: 0px 0 20px 0;
font-weight: bold;
cursor: pointer;
}
li span {
float: right;
}
.fa-plus-circle {
color: rgb(67, 0, 192);
font-size: 40px;
text-align: center;
width: 100%;
margin-top: 30px;
}
.numtodos {
font-size: 12em;
position: absolute;
top: 0%;
left: 75%;
line-height: 0.7;
color: rgba(153, 153, 153, 0.2);
font-family: 'Roboto Condensed', sans-serif;
}
.fa-circle {
color: rgba(153, 153, 153, 0.4);
font-size: 20px;
}
.completed {
text-decoration: line-through;
color: grey;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css" integrity="sha512-1PKOgIY59xJ8Co8+NE6FZ+LOAZKjy+KY8iq0G4B3CyeY6wYHN3yt9PW0XpSriVlkMXe40PTKnXrLnZ9+fkDaog==" crossorigin="anonymous" />
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght#0,400;1,700&family=Roboto:wght#400;500;700;900&display=swap" rel="stylesheet">
<div class="container">
<div id='nav'>
<div class='tinyburger'>
<div class='line'></div>
<div class='line'></div>
<div class='line'></div>
</div>
<i class="fas fa-search"></i>
</div>
<div class="header">
<h1>TO-DO</h1>
<h2 class="date">NOV 23</h2>
</div>
<input type="text" name='newtodo' id='newtodo' placeholder="Write a new to-do">
<ul class='todos'>
<li><i class="far fa-circle"></i> Lunch with Helena Barnes <span>X</span> </li>
<li><i class="far fa-circle"></i> Evening Workout<span>X</span></li>
<li><i class="far fa-circle"></i> Lunch with Helena Barnes <span>X</span></li>
</ul>
<i class="fas fa-plus-circle"></i>
<div class='numtodos'>3</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
JS Fiddle demo.
References:
CSS:
Negation (:not()) pseudo-class.
jQuery:
text().
Related
The sidebar menu below works as expected but there are some elements that I would like to fix and improve.
#1 If you expand the 'System Admin' section the line that is displayed on the left side a in the correct place. However, if you
click on the 'Access Control' the line eon the left side is going
below the horizontal line that's pointing to that section. I tried
fixing that with css 'last-child' method but still is not working
correct. If I change percentage from 50% to 85% then the other
section will have an issue.
#2 I'm looking for a better way to expand/collapse the sections. The function show mimic the existing one, but instead of using the object
with key items I would like to use classes instead if possible.
$( document ).ready(function() {
SIDEBAR_OLD.BASE.ExpandCollapse('Auto');
});
const SIDEBAR_OLD = {};
SIDEBAR_OLD.BASE ={};
SIDEBAR_OLD.BASE.ToggleContent = function(section_id) {
let $sContents = $("#section_" + section_id);
if ( $sContents.css("display") != "none" ) {
$sContents.css("display","none");
} else { // Default to seeing the folder's contents:
$sContents.css("display","");
}
};
$("#main-page-wrapper").on("click", ".toggle-menu", function(e){
e.preventDefault();
let section_id = $(this).attr("data-id");
SIDEBAR_OLD.BASE.ToggleContent(section_id);
});
SIDEBAR_OLD.BASE.ExpandCollapse = function(action) {
let $sContents = null,
sExpand = null,
sRoot = false,
items = {1:"m",2:"sysadmin",5:"access"};
for (key in items) {
$sContents = $("#section_" + items[key]);
switch (action) {
case "Expand":
sExpand = "Yes";
break;
case "Collapse":
sExpand = "No";
break;
default:
if ( !(sExpand = $sContents.attr("data-expand")) ) sExpand = "Yes";
}
// Never close root elements automatically. Only ToggleContent(pNumber), below, can do that.
if ( sRoot = $sContents.attr("data-root") ) { // Note! Assignment! "=", not "=="!
if (sRoot == "Yes") sExpand = "Yes";
}
if ( sExpand == "No" ) {
$sContents.css("display","none");
} else { // Default to seeing the folder's contents:
$sContents.css("display","");
}
}
return true;
};
$("#main-page-wrapper").on("click", ".collapse-menu", function(e){
e.preventDefault();
let action = $(this).attr("data-action");
SIDEBAR_OLD.BASE.ExpandCollapse(action);
});
.sidebar {
position: absolute;
top: 56px;
right: 0px;
bottom: 0px;
left: 0px;
width: 180px;
background-color: #0071bc;
color: #fff;
height: calc(100vh - 98px);
overflow: auto;
font-size: 9pt !important;
white-space: nowrap;
}
.sidebar a {
color: #fff;
}
.menuitem {
color: #fff;
font-weight: bold;
text-decoration: none;
}
.menuitem:hover, .menuitem:focus {
color: #ff0;
}
#tree {
font-weight: bold;
padding: 1px;
}
#expandcollapse {
border: 1px solid white;
text-align: center;
}
.sb-row {
border: 0px;
height: 22px;
margin: 0px;
overflow: hidden;
padding: 0px 0px 0px 3px;
position: relative;
white-space: nowrap;
}
.fa-folder-open:before {
color: #DBDB2A !important;
}
.nav>li {
position: relative;
display: block;
}
.nav>li>a {
position: relative;
display: block;
padding: 7px 22px 6px;
}
.nav>li>a:focus {
text-decoration: none;
background: transparent;
background-color: transparent;
}
.nav > li > a:hover {
color: red;
background-color: transparent;
text-decoration: none;
}
.nav.side-menu>li {
position: relative;
display: block;
cursor: pointer;
}
.nav.child_menu li {
padding-left: 20px;
}
.nav.child_menu>li>a {
font-weight: 500;
font-size: 12px;
font-weight: bold;
}
li.first-level::before {
background: #fff;
bottom: auto;
content: "";
height: 1px;
left: 10px;
margin-top: 14px;
position: absolute;
right: auto;
width: 8px;
}
li.first-level::after {
border-left: 1px solid #fff;
bottom: 0;
content: "";
left: 10px;
position: absolute;
top: 0;
}
ul.nav.side-menu li.first-level:last-child::after {
bottom: 50%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<body>
<div class="container body" id="main-page-wrapper">
<div class="sidebar">
<div id="tree" role="navigation" data-expandall="Auto">
<div id="expandcollapse">
<a class="menuitem collapse-menu pr-2" href="#" data-action="Expand">Expand</a> | <a class="menuitem collapse-menu pl-2" href="#" data-action="Collapse">Collapse</a>
</div>
<div class="sb-row">
<a class="toggle-menu" title="Open/Close Folder - System Management" data-id="m">
<i class="fa fa-folder-open fa-lg"></i> System Management
</a>
</div>
<div id="section_m" class="menu-section" data-root="Yes" data-expand="Yes">
<ul class="nav side-menu">
<li class="first-level">
<a class="toggle-menu" href="#" title="System Admin" data-id="sysadmin"><i class="fa fa-folder-open"></i> System Admin</a>
<ul class="nav child_menu first" id="section_sysadmin" data-expand="No">
<li><a class="link-item" data-action="param" title="System Parameters">System Parameters</a></li>
<li><a class="link-item" data-action="schema" title="Select Schema">Select Schema</a></li>
<li><a class="link-item" data-action="item" title="Menu Item">Menu Items</a></li>
</ul>
</li>
<li class="first-level">
<a class="toggle-menu" href="#" title="Access Control" data-id="access"><i class="fa fa-folder-open"></i> Access Control</a>
<ul class="nav child_menu first" id="section_access" data-expand="No">
<li><a class="link-item" data-action="user" title="Manage User">Manage User</a></li>
<li><a class="link-item" data-action="role" title="Manage Role">Manage Role</a></li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</div>
</body>
on my website, there are 2 buttons. one button is to indent text to the left and the other button is to indent text to the right. what I am trying to achieve is that whenever I press the indent left or right button, currently selected (on focus) text should move left or right (depending on what button I press). at the same time, I want the font size of that text to go down by one level, so when compared to the text above, it's smaller to the text above.
// global variable
let onFocus = null;
// this function
function focusedText(event) {
onFocus = event.target;
}
function init() {
const newLineButton = document.querySelector('#newLine');
newLineButton.addEventListener("click", newLine);
newLine();
document.querySelector("#printButton").addEventListener("click", printPage)
}
function newLine() {
const newLine = document.createElement("p");
newLine.setAttribute("contenteditable", true);
newLine.addEventListener("keydown", handleKeyDown)
newLine.addEventListener("focus", focusedText)
newLine.classList.add("textLine")
const parent = document.querySelector('#textBox');
parent.appendChild(newLine);
newLine.focus();
}
function handleKeyDown(event) {
if (event.which === 13) {
event.preventDefault();
newLine();
}
}
// this runs the init(); function
init();
// this prints the page
function printPage() {
window.print();
}
// this fuction changes the text size
function textSize(selectTag) {
const listValue = selectTag.options[selectTag.selectedIndex].value;
onFocus.style.fontSize = listValue;
}
* {
padding-top: 0em;
padding-bottom: 0em;
margin-top: 0em;
margin-bottom: 0em;
}
*:focus {
outline: 0px solid transparent;
}
body {
margin: 0;
}
.title {
font-family: 'Assistant', sans-serif;
font-size: 2em;
text-align: center;
text-transform: uppercase;
color: white;
background: #6a0dad;
letter-spacing: 0.20em;
}
.navMenu {
display: flex;
align-items: stretch;
padding-top: 0.5em;
padding-bottom: 0.5em;
justify-content: center;
}
.navMenu button,
#dropDown {
padding: 0.3em 1.2em;
margin: 0 0.1em 0.1em 0;
border: 0.16em solid rgba(255, 255, 255, 0);
border-radius: 2em;
box-sizing: border-box;
font-family: 'Assistant', sans-serif;
font-weight: bold;
font-size: 1rem;
color: white;
text-shadow: 0 0.04em 0.04em rgba(0, 0, 0, 0.35);
text-align: center;
transition: all 0.2s;
background-color: #6a0dad;
cursor: pointer;
}
.navMenu button:hover,
#dropDown:hover {
border-color: white;
}
#dropDown option {
font-weight: bold;
}
#textBox {
display: flex;
justify-content: left;
width: auto;
flex-flow: column nowrap;
font-family: 'Assistant', sans-serif;
}
.textLine {
color: black;
width: 100%;
padding-left: 0.8em;
padding-right: 0.8em;
box-sizing: border-box;
transition: background-color 0.25s;
}
#media screen {
.textLine:hover {
background-color: #6a0dad;
color: white;
}
}
#media print {
.navMenu,
.title {
display: none;
}
}
.textLine:empty::before {
content: "Enter your text here";
color: grey;
font-family: 'Assistant', sans-serif;
pointer-events: none;
}
.textLine:hover:empty::before {
content: "Enter your text here";
color: white;
font-family: 'Assistant', sans-serif;
}
<!DOCTYPE html>
<html>
<head>
<title>Outline Editing</title>
<link rel="stylesheet" type="text/css" href="main-css.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<main>
<h1 class="title">Outline Editing</h1>
<nav class="navMenu">
<button type="button" id="indentLeft"><i class="material-icons">format_indent_decrease</i></button>
<select id="dropDown" onchange="textSize(this);" size="1">
<option value="" selected disabled hidden>Levels</option>
<option value="200%">Level 1</option>
<option value="170%">Level 2</option>
<option value="150%">Level 3</option>
<option value="130%">Level 4</option>
<option value="100%">Level 5</option>
</select>
<button type="button" id="newLine">New Line</button>
<button type="button" id="indentRight"><i class="material-icons">format_indent_increase</i></button>
<button type="button" id="printButton">Print</button>
</nav>
<div id="textBox">
</div>
<script src="main.js"></script>
</main>
</body>
</html>
You could just add "Horizontal Tab" as an HTML entity on your focused text.
First change the <p> to <pre>.
function newLine() {
const newLine = document.createElement("pre");
//...
}
Update your init function:
function init() {
var newLineButton = document.querySelector('#newLine');
newLineButton.addEventListener("click", newLine);
newLine();
document.querySelector("#printButton").addEventListener("click", printPage);
let indentRight = document.getElementById("indentRight");
indentRight.addEventListener("click", IndentRight_Click);
let indentLeft = document.getElementById("indentLeft");
indentLeft.addEventListener("click", IndentLeft_Click);
}
Then you can add the "Horizontal Tab" on the text that is focused, checking if you have already added:
function IndentRight_Click(event) {
let horizontalTab = " ";
let originalHtml = onFocus.innerHTML;
if (!originalHtml.includes("\t")) onFocus.innerHTML = horizontalTab + originalHtml;
onFocus.focus();
}
And finally, check for it again on your "Left Indentation":
function IndentLeft_Click(event) {
var originalHtml = onFocus.innerHTML;
if (originalHtml.includes("\t")) onFocus.innerHTML = originalHtml.replace("\t", "");
onFocus.focus();
}
So if I want to save all the To-Do items in Local storage and retrieve when I restart the computer or refresh the page all the items come up on the page in their original order.?
//check of spec
$("ol").on("click", "li", function(){
$(this).toggleClass("completed");
});
//click on X to delete To-DO
$("ol").on("click", "span", function(event){
$(this).parent().fadeOut(500,function(){
$(this).remove();
});
event.stopPropagation();
});
$("input[type='text'").keypress(function(event){
if(event.which === 13) {
//grabbing the text typed
var todoText = $(this).val();
$(this).val("");
//create a new LI and add to UL
$("ol").append("<li><span><i class='fa fa-trash'></i></span> " + todoText +"</li>")
}
});
$(".fa-plus").click(function(){
$("input[type='text'").fadeToggle();
});
h1 {
background: #2980b9;
color: white;
margin: 0;
padding: 10px 15px;
text-transform: uppercase;
font-size: 24px;
font-weight: normal;
}
iframe {
float: left;
}
ol {
/* THE BULLET POINTS
list-style: none;
*/
margin: 0;
padding: 0;
font-size: 18px;
}
body {
background-color: rgb(13, 168, 108);
}
li {
background: #fff;
height: 30px;
line-height: 30px;
color: #666;
}
li:nth-child(2n){
background: #d3d3d3;
}
span {
height: 30px;
width: 0px;
margin-right: 20px;
text-align: center;
color:white;
display: inline-block;
transition: 0.2s linear;
opacity:0;
background: #e74c3c
}
li:hover span {
width: 30px;
opacity: 1.0;
}
input {
font-size: 18px;
width: 100%;
padding: 13px 13px 13px 20px;
box-sizing: border-box;
border: 3px solid rgba(0,0,0,0);
color: #2980b9;
background-color: #e4e4e4;
}
input:focus {
background: white;
border: 3px solid green;
/*OUTLINE OF INPUT BOX
outlin: none; */
}
.fa-plus {
float: right;
}
#container {
width: 360px;
margin: 60px auto;
background: #d3d3d3;
box-shadow: 0 0 3px rgba(0,0,0,0.1);
}
.completed {
color: red;
text-decoration: line-through;
}
<!DOCTYPE html>
<html>
<head>
<title>ToDo List</title>
<link rel="stylesheet" type="text/css" href="utd.css">
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css">
<script type="text/javascript" src="jquery-3.2.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$( function() {
$( "#sortable" ).sortable();
$( "#sortable" ).disableSelection();
} );
</script>
</head>
<body>
<iframe src="http://free.timeanddate.com/clock/i5zr5d5i/n1991/szw110/szh110/hoc09f/hbw0/hfc09f/cf100/hnce1ead6/fas30/fdi66/mqc000/mql15/mqw4/mqd98/mhc000/mhl15/mhw4/mhd98/mmc000/mml10/mmw1/mmd98/hhc00f/hhs2/hmcf00/hms2/hsv0" frameborder="0" width="110" height="110"></iframe>
<div id="container">
<h1>To-do List <i class="fa fa-plus"></i></h1>
<form id="task-list">
<input type="text" placeholder="Add a To-Do" id="task">
</form>
<ol id="sortable">
<li><span><i class="fa fa-trash"></i></span> EXAMPLE</li>
</ol>
</div>
<script type="text/javascript" src="Utd.js"></script>
</body>
</html>
Happy Holidays!
Your coding wishes are granted. This is a gift, to you, and you will have to be a good person and post better examples and remember that people are not here to write code for you.
There was a LOT of stuff lacking from your example.
Add items to a list
Update local storage when items are added to a list
Allow list to be sorted
Update local storage when list is updated
Allow task items to be marked completed
Update local storage when items are completed
Allow task items to be deleted
Update local storage when tasks are deleted
Load locally stored tasks
I think that covers everything you wanted this script to do. Is it becoming more clear now?
Working Example: https://jsfiddle.net/Twisty/ae6oLr47/12/
HTML
<iframe src="https://free.timeanddate.com/clock/i5zr5d5i/n1991/szw110/szh110/hoc09f/hbw0/hfc09f/cf100/hnce1ead6/fas30/fdi66/mqc000/mql15/mqw4/mqd98/mhc000/mhl15/mhw4/mhd98/mmc000/mml10/mmw1/mmd98/hhc00f/hhs2/hmcf00/hms2/hsv0" frameborder="0" width="110"
height="110"></iframe>
<div id="container">
<h1>To-do List <i class="fa fa-plus"></i></h1>
<form id="task-list">
<input type="text" placeholder="Add a To-Do" id="task">
<button type="submit"></button>
</form>
<ol id="sortable">
<li id="task-EXAMPLE"><span><i class="fa fa-trash"></i></span>
<label>EXAMPLE</label>
</li>
</ol>
</div>
The first time this loads, there will be no storage, so we can read an examples from the HTML. As you will see, once you make an update, this will no longer be the case.
Q: Why the <button>?
A: <form> likes to have a submit button. It does not need it, yet having it will help a lot in ways I do not want to go into for this question.
JavaScript
$(function() {
$("#sortable").on("click", function(event) {
console.log(event.target);
var $thatItem = $(event.target).parents("li");
switch (event.target.nodeName) {
case "SPAN":
case "I":
$thatItem.fadeOut(500, function() {
$thatItem.remove();
$("#sortable").sortable("refresh");
});
break;
case "LABEL":
$thatItem.toggleClass("completed");
break;
}
setTimeout(function() {
updateLocalStorage($("#sortable"));
}, 500);
event.stopPropagation();
});
$("#task-list").submit(function(event) {
event.preventDefault();
// Grabbing the text typed
var todoText = $("#task").val();
addListItem($("#sortable"), todoText, false);
// Clear the text field
$("#task").val("");
updateLocalStorage($("#sortable"));
});
$(".fa-plus").click(function() {
$("#task-list").fadeToggle();
});
$("#sortable").sortable({
update: function(e, ui) {
updateLocalStorage($(this));
}
}).disableSelection();
function addListItem($t, s, c) {
//create a new LI
var $li = $("<li>", {
id: "task-" + s.replace(" ", "_")
});
if (c) {
$li.addClass("completed");
}
var $wrap = $("<span>").appendTo($li);
$wrap.append($("<i>", {
class: "fa fa-trash"
}));
$li.append($("<label>").html(s));
$li.appendTo($t);
$t.sortable("refresh");
}
function updateLocalStorage($list) {
var tasks = {};
$list.find("li").each(function(ind, elem) {
var task = $(elem).text().trim();
var completed = $(elem).hasClass("completed");
tasks[task] = completed;
if (typeof(Storage) !== "undefined") {
localStorage.setItem("tasks", JSON.stringify(tasks));
}
});
}
if (typeof(Storage) !== "undefined") {
// Code for localStorage/sessionStorage.
if (localStorage.getItem("tasks") !== "undefined") {
var localTasks = JSON.parse(localStorage.getItem("tasks"));
// Grab stored tasks
$("#sortable").find("li").remove();
$.each(localTasks, function(task, status) {
addListItem($("#sortable"), task, status);
});
}
} else {
// Sorry! No Web Storage support..
}
});
You might see that there is very little of your original code left in here. There was just a lot of places to improve the code. I will discuss a bit briefly.
Click Events
Since we're basically listening for click events on the same parent, but want to do different things when specific elements are clicked, that are going to be dynamically created, we can make use of the event.target from the click event. If it's the span or the i that's clicked, we do one thing, if it's the label, another.
The setTimeout is just a way to create a delay in the operations from switch to updating. Otherwise the update will execute almost on top of the switch and will not see the changes to the list, this record no changes.
Submit Event
When you hit Enter or Return you're essentially submitting the form. Part of the reason to add a submit button. Instead of catching the keypress and trying to prevent this, why not just use the submit event to our advantage. This method will help on mobile browsers and such.
Sortable Update Event
When the list is sorted, and updated, we need to update that order in the local storage. Now is the time to do that.
Functions
I think these are pretty clear. You have an operation you will repeat many times, write a function.
Get Item
The last bit of code will load when the page is all ready. It will check for localStorage and check if there are tasks stored within. It will then populate the list with them.
Q: What's with the JSON.stringify()?
A: Yes, you can store stuff locally... only as String. This creates a string version of our object for storage.
Comment if you have other questions.
//check of spec
$("ol").on("click", "li", function(){
$(this).toggleClass("completed");
});
//click on X to delete To-DO
$("ol").on("click", "span", function(event){
$(this).parent().fadeOut(500,function(){
$(this).remove();
});
event.stopPropagation();
});
$("input[type='text'").keypress(function(event){
if(event.which === 13) {
//grabbing the text typed
var todoText = $(this).val();
$(this).val("");
//create a new LI and add to UL
$("ol").append("<li><span><i class='fa fa-trash'></i></span> " + todoText +"</li>")
}
});
$(".fa-plus").click(function(){
$("input[type='text'").fadeToggle();
});
h1 {
background: #2980b9;
color: white;
margin: 0;
padding: 10px 15px;
text-transform: uppercase;
font-size: 24px;
font-weight: normal;
}
iframe {
float: left;
}
ol {
/* THE BULLET POINTS
list-style: none;
*/
margin: 0;
padding: 0;
font-size: 18px;
}
body {
background-color: rgb(13, 168, 108);
}
li {
background: #fff;
height: 30px;
line-height: 30px;
color: #666;
}
li:nth-child(2n){
background: #d3d3d3;
}
span {
height: 30px;
width: 0px;
margin-right: 20px;
text-align: center;
color:white;
display: inline-block;
transition: 0.2s linear;
opacity:0;
background: #e74c3c
}
li:hover span {
width: 30px;
opacity: 1.0;
}
input {
font-size: 18px;
width: 100%;
padding: 13px 13px 13px 20px;
box-sizing: border-box;
border: 3px solid rgba(0,0,0,0);
color: #2980b9;
background-color: #e4e4e4;
}
input:focus {
background: white;
border: 3px solid green;
/*OUTLINE OF INPUT BOX
outlin: none; */
}
.fa-plus {
float: right;
}
#container {
width: 360px;
margin: 60px auto;
background: #d3d3d3;
box-shadow: 0 0 3px rgba(0,0,0,0.1);
}
.completed {
color: red;
text-decoration: line-through;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<title>ToDo List</title>
<link rel="stylesheet" type="text/css" href="utd.css">
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css">
<script type="text/javascript" src="jquery-3.2.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$( function() {
$( "#sortable" ).sortable();
$( "#sortable" ).disableSelection();
} );
</script>
</head>
<body>
<iframe src="http://free.timeanddate.com/clock/i5zr5d5i/n1991/szw110/szh110/hoc09f/hbw0/hfc09f/cf100/hnce1ead6/fas30/fdi66/mqc000/mql15/mqw4/mqd98/mhc000/mhl15/mhw4/mhd98/mmc000/mml10/mmw1/mmd98/hhc00f/hhs2/hmcf00/hms2/hsv0" frameborder="0" width="110" height="110"></iframe>
<div id="container">
<h1>To-do List <i class="fa fa-plus"></i></h1>
<form id="task-list">
<input type="text" placeholder="Add a To-Do" id="task">
</form>
<ol id="sortable">
<li><span><i class="fa fa-trash"></i></span> EXAMPLE</li>
</ol>
</div>
<script type="text/javascript" src="Utd.js"></script>
</body>
</html>
//check of spec
$("ol").on("click", "li", function(){
$(this).toggleClass("completed");
});
//click on X to delete To-DO
$("ol").on("click", "span", function(event){
$(this).parent().fadeOut(500,function(){
// $(this).remove();
});
//event.stopPropagation();
});
$("input[type='text'").keypress(function(event){
if(event.which === 13) {
//grabbing the text typed
var todoText = $(this).val();
$(this).val("");
//create a new LI and add to UL
$("ol").append("<li><span><i class='fa fa-trash'></i></span> " + todoText +"</li>")
}
});
$(".fa-plus").click(function(){
$("input[type='text'").fadeToggle();
});
h1 {
background: #2980b9;
color: white;
margin: 0;
padding: 10px 15px;
text-transform: uppercase;
font-size: 24px;
font-weight: normal;
}
iframe {
float: left;
}
ol {
/* THE BULLET POINTS
list-style: none;
*/
margin: 0;
padding: 0;
font-size: 18px;
}
body {
background-color: rgb(13, 168, 108);
}
li {
background: #fff;
height: 30px;
line-height: 30px;
color: #666;
}
li:nth-child(2n){
background: #d3d3d3;
}
span {
height: 30px;
width: 0px;
margin-right: 20px;
text-align: center;
color:white;
display: inline-block;
transition: 0.2s linear;
opacity:0;
background: #e74c3c
}
li:hover span {
width: 30px;
opacity: 1.0;
}
input {
font-size: 18px;
width: 100%;
padding: 13px 13px 13px 20px;
box-sizing: border-box;
border: 3px solid rgba(0,0,0,0);
color: #2980b9;
background-color: #e4e4e4;
}
input:focus {
background: white;
border: 3px solid green;
/*OUTLINE OF INPUT BOX
outlin: none; */
}
.fa-plus {
float: right;
}
#container {
width: 360px;
margin: 60px auto;
background: #d3d3d3;
box-shadow: 0 0 3px rgba(0,0,0,0.1);
}
.completed {
color: red;
text-decoration: line-through;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<title>ToDo List</title>
<link rel="stylesheet" type="text/css" href="utd.css">
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css">
<script type="text/javascript" src="jquery-3.2.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$( function() {
$( "#sortable" ).sortable();
$( "#sortable" ).disableSelection();
} );
</script>
</head>
<body>
<div id="container">
<h1>To-do List <i class="fa fa-plus"></i></h1>
<input type="text" placeholder="Add a To-Do" id="task">
<ol id="sortable">
<li><span><i class="fa fa-trash"></i></span> EXAMPLE</li>
</ol>
</div>
<script type="text/javascript" src="Utd.js"></script>
</body>
</html>
After making edits in browser, simply save the page as HTML with different file name. Your selected values will be saved.
I use for a project Font Awesome for icons and because they do not show in IE8 when I use it like:
<i class="fa fa-bars fa-2x"></i>
I am forced to use the cheat sheet icons as this:
<i class="mobile-search-icon fa fa-lg"></i>
This way, the icons show OK in IE8 as well.
Now, the problem... Using the cheat sheet icons, how can I replace the value with another on toggle? Please check the example below where you see initially the search icon and on click I should see a close icon and on another click, show again the search icon.
The example below is the closest way I have found so far, and I need some help if possible. Thanks
$(".mobile-search-icon").on('click', function(e) {
$(this).siblings(".search-container").fadeToggle(500);
if ($.trim($(this).text()) === '') {
$(this).text('');
} else {
$(this).text('');
}
return false;
});
.search-bar {
display: table-cell;
vertical-align: middle;
}
.mobile-search-icon {
color: #fff;
display: block;
line-height: 50px;
width: 150px;
z-index: 1;
background-color: black;
text-align: center;
padding: 20px;
}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<div class="search-bar">
<i class="mobile-search-icon fa fa-lg"></i>
</div>
Not sure exactly what you want, but it looks neat. I made a demo that uses FA's Unicode on Snippet 1.
I re-read your question, I think I understand ... You just want to toggle the button between 2 icons. If so look at Snippet 2. Your problem is that you are using .text() method which will add non-parsed plain text. .html() will take text and parse it to HTML so it's rendered as what the text represents, and not what it is literally.
SNIPPET 1
var tgt = document.querySelector('.search-bar');
var btn = document.querySelector('.search-btn');
btn.addEventListener('click', function(e) {
var res = document.getElementById('result');
var txt = document.getElementById('inputText').value;
var mom = res.parentNode;
if (res) {
mom.removeChild(res);
}
genFA(txt);
}, false);
function genFA(x) {
var baseUni = "";
var iEle = document.createElement('i');
iEle.id = 'result';
iEle.classList.add('fa');
iEle.classList.add('fa-lg');
iEle.innerHTML = baseUni + x;
return tgt.appendChild(iEle);
}
*,
*:before,
*:after {
box-sizing: border-box;
}
html,
body {
width: 100%;
height: 100%;
font: 400 16px/1.45'Verdana';
}
.search-bar {
display: table;
position: relative;
height: 80px;
padding: 5px 15px;
width: 500px;
}
.search-btn {
color: #fff;
display: table-cell;
line-height: 50px;
width: 35%;
background-color: black;
text-align: center;
padding: 10px 20px;
}
#inputText {
display: table-cell;
width: 25%;
padding: 0 5px;
font: 400 20px/30px'Consolas';
text-align: center;
vertical-align: middle;
}
#result {
color: #000;
line-height: 33px;
width: 35%;
background-color: #fff;
text-align: center;
vertical-align: middle;
border: 1px solid black;
}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" />
<fieldset class="search-bar">
<legend>Font-Awesome Unicode</legend>
<input id="inputText">
<i class="search-btn fa fa-lg"></i>
<i id="result" class="fa fa-lg"> </i>
</fieldset>
<p>Enter: 3 digit hexidecimal number.</p>
<p>Range: 000 - 2b1</p>
<p>Font-Awesome v.4.6.3</p>
SNIPPET 2
$(function() {
$(".mobile-search-icon").on('click', function(e) {
if ($(this).hasClass('on')) {
$(this).html('').toggleClass('on');
} else {
$(this).html('').toggleClass('on');
}
});
});
.search-bar {
color: #fff;
display: table-cell;
vertical-align: middle;
line-height: 30px;
width: 150px;
background-color: black;
text-align: center;
padding: 10px;
cursor: pointer;
}
.mobile-search-icon {
width: 100%;
}
.on {
opacity: 1;
transition: opacity 1s;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css">
<div class="search-bar">
<i class="mobile-search-icon fa fa-lg on"></i>
</div>
I'm trying to add a class to dynamically generated <li> elements, but it has a very strange behavior. With a single <li> it works perfectly, but after adding an another element, the class attribute will be added without value. After adding the third <li> it will work again (basically it works only if the number of the <li> elements is odd).
var JSTasker = {
updateTaskCounter: function() {
var taskCount = $('div#tasks ul').children().not('li.completed').size();
$('span#task_counter').text(taskCount);
},
sortTasks: function() {
var taskList = $('div#tasks ul');
var allCompleted = $(taskList).children('.completed');
allCompleted.detach();
allCompleted.appendTo(taskList);
},
updatePage: function() {
this.updateTaskCounter();
this.sortTasks();
}
};
$(document).ready(function() {
$('input#task_text').focus();
$('form#add_task').on('submit', function(event) {
event.preventDefault();
var taskText = $('input#task_text').val();
var taskItem = "<li>" + taskText + "</li>";
$('div#tasks ul').append(taskItem);
$('input#task_text').val(null);
JSTasker.updatePage();
$('div#tasks ul').on('click', 'li', function(event) {
event.preventDefault();
$(this).toggleClass('completed');
JSTasker.updatePage();
});
});
});
h1,
h2,
ul,
li {
margin: 0px;
padding: 0px;
}
html {
background: #333;
font-family: 'Gill Sans Light', 'Helvetica', 'Arial';
font-size: 90%;
}
body {
width: 400px;
margin: 10px auto;
border: 1px solid #EEE;
background: #F6F6F6;
padding: 10px 20px;
}
fieldset {
border: 2px solid #CCC;
padding: 10px;
margin: 10px 0px 0px 0px;
}
input[type=text] {
width: 300px;
padding-right: 20px;
}
input[type=submit] {} #tasks {
margin: 20px 0px 0px 0px;
}
h2 {
margin-bottom: 10px;
}
h2 span#task_counter {
font-size: 80%;
color: #999;
}
#tasks ul {
list-style-type: none;
}
#tasks ul li {
padding: 6px 10px 3px 20px;
height: 1.6em;
}
#tasks ul li:hover {
background: #FFF8DC;
text-decoration: underline;
}
#tasks ul li.completed {
background: url('icons/accept.png') no-repeat 0px 5px;
text-decoration: line-through;
color: #999;
}
.trash {
float: right;
padding: 2px;
}
.trash:hover {
background: #F99;
cursor: pointer;
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Tasker</title>
<link rel='stylesheet' type="text/css" href='JSTasker.css' />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
<script src="JSTasker.js"></script>
</head>
<body>
<h1>JSTasker</h1>
<form id='add_task'>
<fieldset>
<legend>Add a Task</legend>
<input type='text' name='task_text' id='task_text' />
<input type='submit' name='add_button' value='Add' />
</fieldset>
</form>
<div id='tasks'>
<h2>Your Tasks <span id='task_counter'></span></h2>
<ul>
</ul>
</div>
</body>
</html>
This is the problematic part:
$('div#tasks ul').on('click', 'li', function(event) {
event.preventDefault();
$(this).toggleClass('completed');
JSTasker.updatePage();
});
The application is based on this tutorial.
add onclick handler outside form submit, right now your onclick event is being initialized every time you submit the form. but $().on() is for dynamic binding and you need to call it only once
$(document).ready(function() {
$('input#task_text').focus();
$('form#add_task').on('submit', function(event) {
event.preventDefault();
var taskText = $('input#task_text').val();
var taskItem = "<li>" + taskText + "</li>";
$('div#tasks ul').append(taskItem);
$('input#task_text').val("");
JSTasker.updatePage();
});
$('div#tasks ul').on('click', "li", function(event) {
event.preventDefault();
$(this).toggleClass('completed');
JSTasker.updatePage();
});
});
You are binding click event to li for every click of add button. Take this code out and it will work. It was working for odd number of elements because it will bind event odd times. lets say you added 3 lis and hence it will call click event 3 times. first time add class, then remove and then add class again. But it will fail for even number of lis as it will first add class and then remove it.
var JSTasker = {
updateTaskCounter: function() {
var taskCount = $('div#tasks ul').children().not('li.completed').size();
$('span#task_counter').text(taskCount);
},
sortTasks: function() {
var taskList = $('div#tasks ul');
var allCompleted = $(taskList).children('.completed');
allCompleted.detach();
allCompleted.appendTo(taskList);
},
updatePage: function() {
this.updateTaskCounter();
this.sortTasks();
}
};
$(document).ready(function() {
$('input#task_text').focus();
$('form#add_task').on('submit', function(event) {
event.preventDefault();
var taskText = $('input#task_text').val();
var taskItem = "<li>" + taskText + "</li>";
$('div#tasks ul').append(taskItem);
$('input#task_text').val(null);
JSTasker.updatePage();
});
$('div#tasks ul').on('click', 'li', function(event) {
event.preventDefault();
$(this).toggleClass('completed');
JSTasker.updatePage();
});
});
h1,
h2,
ul,
li {
margin: 0px;
padding: 0px;
}
html {
background: #333;
font-family: 'Gill Sans Light', 'Helvetica', 'Arial';
font-size: 90%;
}
body {
width: 400px;
margin: 10px auto;
border: 1px solid #EEE;
background: #F6F6F6;
padding: 10px 20px;
}
fieldset {
border: 2px solid #CCC;
padding: 10px;
margin: 10px 0px 0px 0px;
}
input[type=text] {
width: 300px;
padding-right: 20px;
}
input[type=submit] {} #tasks {
margin: 20px 0px 0px 0px;
}
h2 {
margin-bottom: 10px;
}
h2 span#task_counter {
font-size: 80%;
color: #999;
}
#tasks ul {
list-style-type: none;
}
#tasks ul li {
padding: 6px 10px 3px 20px;
height: 1.6em;
}
#tasks ul li:hover {
background: #FFF8DC;
text-decoration: underline;
}
#tasks ul li.completed {
background: url('icons/accept.png') no-repeat 0px 5px;
text-decoration: line-through;
color: #999;
}
.trash {
float: right;
padding: 2px;
}
.trash:hover {
background: #F99;
cursor: pointer;
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Tasker</title>
<link rel='stylesheet' type="text/css" href='JSTasker.css' />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
<script src="JSTasker.js"></script>
</head>
<body>
<h1>JSTasker</h1>
<form id='add_task'>
<fieldset>
<legend>Add a Task</legend>
<input type='text' name='task_text' id='task_text' />
<input type='submit' name='add_button' value='Add' />
</fieldset>
</form>
<div id='tasks'>
<h2>Your Tasks <span id='task_counter'></span></h2>
<ul>
</ul>
</div>
</body>
</html>
Your code works with only odd numbers of tasks because you add a new click handler to every <li> each time you submit the form. That means that when you have two <li>s, you call toggleClass twice, and get nowhere. You can confirm this by adding alert("hi") to your current code after the toggleClass call.