"Odd" behavior when clicking on li elements - javascript

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.

Related

Count number of lis and save to an html element

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().

Saving To-Do list data to local storage with HTML

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.

Menu navigation not working with jquery datepicker

I have database generated menu with child menu items in my MVC4 web application which was working fine until I didn't implemented Jquery datepicker. As soon as I included datetimepicker.js and jqueryui bundle, my menu stopped expending. I had tried to change order of scripts and style but no success. I am fairly new to css so I am not be able to figure out whats going wrong here.
Here is my _Layout.cshtml:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>#ViewBag.Title</title>
<link href="~/Images/Company-logo/logo.ico" rel="shortcut icon" type="image/x-icon" />
<meta name="viewport" content="width=device-width" />
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/modernizr")
#Scripts.Render("~/bundles/jqueryui")
#Styles.Render("~/Content/themes/base/css","~/Content/themes/base", "~/Content/css")
<script src="~/Scripts/DatePickerReady.js"></script>
<link href="~/Content/indexView.css" rel="stylesheet" />
</head>
<body>
<header>
<div>
<img src="~/Images/Company-logo/header_bar.png" style="width:100%"/>
</div>
<div class="content-wrapper">
<div class="float-right">
#Html.Partial("Menu")
</div>
</div>
</header>
<div id="body">
#RenderSection("featured", required: false)
<section class="content-wrapper main-content clear-fix">
#RenderBody()
</section>
</div>
<footer>
</footer>
<script src="#Url.Content("~/Scripts/dbGeneratedMenu.js")" type="text/javascript"></script>
<link href="#Url.Content("~/Content/dbGeneratedMenu.css")" rel="stylesheet" type="text/css" />
#RenderSection("scripts", required: false)
</body>
<script type="text/javascript">
$m = jQuery.noConflict();
$m(document).ready(function () {
$m('.menu').dbGeneratedMenu();
});
</script>
</html>
Following is my css for menu:
.menu, .menu ul
{
margin: 0;
padding: 0;
list-style-type: none;
position: relative;
line-height: 2.5em;
}
.menu a
{
text-decoration: none;
}
.menu > li
{
margin-left: 15px;
}
.menu > li:first
{
margin-left:0px!important;
}
.menu > li > a
{
padding: 0px 10px;
margin: 0;
width: 100%;
text-decoration: none;
color: #005D7C;
font-weight: bold;
}
div.box
{
position: absolute;
z-index: -1;
background-color: #75CDD2;
left: 0;
top: 0;
border-radius: 4px 4px 0px 0px;
-moz-border-radius: 4px 4px 0px 0px;
-webkit-border-radius: 4px 4px 0px 0px;
}
li.pull-down
{
padding-right:6px;
}
li.pull-down > a
{
background-image: url(/Images/menu/darrow.png);
background-position: 96% 75%;
background-repeat: no-repeat;
padding-right: 20px;
}
li.right-menu > a
{
background-image: url(/Images/menu/rarrow.png);
background-position: 97% 45%;
background-repeat: no-repeat;
}
.menu a.selected
{
background-color: #75CDD2;
border-radius: 0px 4px 4px 4px;
-moz-border-radius: 0px 4px 4px 4px;
-webkit-border-radius: 0px 4px 4px 4px;
}
.menu li
{
float: left;
position: relative;
}
.menu ul
{
position: absolute;
display: none;
width: 200px;
top: 2.5em; /*padding-right: 10px;*/
background-color: #B8C402; /*-moz-opacity: .50; filter: alpha(opacity=50); opacity: .50;*/
border-radius: 0px 4px 4px 4px;
-moz-border-radius: 0px 4px 4px 4px;
-webkit-border-radius: 0px 4px 4px 4px;
}
.menu li ul a
{
width: 180px;
height: auto;
float: left;
color: #FFFFFF;
padding: 0 10px;
}
.menu li ul li
{
padding: 0;
margin: 0;
}
.menu ul ul
{
top: auto;
}
.menu li ul ul
{
left: 198px; /*margin: 0px 0 0 10px;*/
}
.menu-item-selected > a
{
background-color: #000000;
-moz-opacity: .50;
filter: alpha(opacity=50);
opacity: .50;
font-weight: bold;
}
a:hover {
background-color: #B8C402;
color: #FFFFFF !important;
}
.menu-item-selected > a:hover
{
background-color: #000000;
color: #FFFFFF !important;
}
This is JQuery fiddle for database generated menu:
(function ($) {
$.fn.extend({
dbGeneratedMenu: function () {
return this.each(function () {
//add class .drop-down to all of the menus having drop-down items
var menu = $(this);
var timeoutInterval;
if (!menu.hasClass('menu')) menu.addClass('menu');
$("> li", menu).each(function () {
if ($(this).find("ul:first").length > 0)
$(this).addClass('pull-down');
});
$("> li ul li ul", menu).each(function () {
$(this).parent().addClass('right-menu');
});
$("li", menu).mouseenter(function () {
var isTopLevel = false;
//if its top level then add animation
isTopLevel = $(this).parent().attr('class') === 'menu';
if (isTopLevel) {
clearTimeout(timeoutInterval);
var w = $(this).outerWidth();
// if ($(this).hasClass('pull-down')) w += 10;
var h = $(this).outerHeight();
var box = $('<div/>').addClass('box');
$('> li', menu).removeClass('selected');
$('>li div.box', menu).remove();
$('>li ul', menu).css('display', 'none').slideUp(0);
$(this).prepend(box);
$(this).addClass('selected');
box.stop(true, false).animate({ width: w, height: h }, 100, function () {
if ($(this).parent().find('ul:first').length == 0) {
timeoutInterval = setTimeout(function () {
box.stop(true, false).animate({ height: '+=5' }, 300, function () {
box.parent().find('ul:first').css('display', 'block').css('top', box.height()).stop(true, false).slideDown(100);
});
}, 10);
}
else {
timeoutInterval = setTimeout(function () {
box.stop(true, false).animate({ height: '+=0' }, 0, function () {
box.parent().find('ul:first').css('display', 'block').css('top', box.height()).stop(true, false).slideDown(100);
});
}, 10);
}
});
}
else {
$(this).find('ul:first').css('display','block').stop(true, false).slideDown(100);
}
}).mouseleave(function () {
isTopLevel = $(this).parent().attr('class') === 'menu';
if (isTopLevel) {
$(this).parent().find('div.box').remove();
}
$(this).find('ul').slideUp(100, function () {
$(this).css('display', 'none');
});
});
$('> li > ul li a', menu).hover(function () {
$(this).parent().addClass('menu-item-selected');
}, function () {
$(this).parent().removeClass('menu-item-selected');
});
});
}
});
})(jQuery);
Partial view for date:
#model DateTime?
#{
var value = "";
if (Model.HasValue) {
value = String.Format("{0:d}", Model.Value.ToString("dd-MM-yyyy"));
}
}
#Html.TextBox("", value, new { #class = "datefield", type = "date" })
model:
public class LEAVE_REQUEST
{
[Display(Name = "Id"), Required, DatabaseGenerated(DatabaseGeneratedOption.None)]
public long ALLOTMENT_REQUEST_ID { get; set; }
[Display(Name = "Request date"), Required]
public Nullable<System.DateTime> REQUEST_DATE { get; set; }
[Display(Name = "Leave start date"), Required(ErrorMessage="Leave start date not entered")]
[DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:dd-mm-yyyy}")]
public Nullable<System.DateTime> LEAVE_START_DATE { get; set; }
}
View where I am implementing datetimepicker
#model TESTAPP.Models.LEAVE_REQUEST
#{
ViewBag.Title = "Create";
}
<script src="~/Scripts/jquery-ui-1.8.24.min.js"></script>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
<fieldset>
<legend>LEAVE_REQUEST</legend>
<div class="editor-label">
#Html.LabelFor(model => model.LEAVE_START_DATE)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LEAVE_START_DATE)
#Html.ValidationMessageFor(model => model.LEAVE_START_DATE, "*")
</div>
#Html.ValidationSummary(false)
<p>
<input type="submit" value="Submit" class="submitButton"/>
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
I really appreciate any help from you guys. Thanks
I finally solved my problem. I think something is conflicting in JQuery bundle with js file I'd used for menu navigation. I used jquery-1.8.2.min.js only instead of rendering entire bundle. and now its working as expected. However, I am still not be able to find what conflict with what :(

Why is the .remove() not working inside the callback function?

What I want to happen is when I click the x button, the item will be removed when the slideUp() method is done. But with the code below, it will remove all the items at once and when I refresh the page, it has undefined on it.
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Simple Todo List</title>
<!-- CSS -->
<link rel="stylesheet" href="styles.css">
<!-- jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="index.js"></script>
</head>
<body>
<div class="container">
<h1>Things I've Gotta Get Done</h1>
<ul id="list-items">
<!-- Here's where out todo list items will go! -->
</ul>
<form class="add-items">
<input type="text" class="form-control" id="todo-list-item" placeholder="What do you need to do today?">
<button class="add" type="submit">Add to List</button>
</form>
</div>
</body>
</html>
CSS
.container {
margin: 0 auto;
min-width: 500px;
width: 45%;
text-align: left;
color: #3b3b3b;
font-weight: bold;
font-family: 'Helvetica', 'Arial', sans-serif;
}
form {
outline: 0;
height: 36px;
margin-top: 5%;
border: 3px solid #3b3b3b;
}
input[type="text"] {
outline: 0;
width: 55%;
height: 32px;
border: none;
font-size: 18px;
font-weight: normal;
padding-left: 10px;
}
.add {
outline: 0;
float: right;
width: 34%;
height: 36px;
color: #fff;
font-size: 18px;
border: none;
cursor: pointer;
background-color: #3b3b3b;
}
ul {
padding: 0;
text-align: left;
list-style: none;
}
hr {
border-bottom: 0;
margin: 15px 0;
}
input[type="checkbox"] {
width: 30px;
}
.remove {
float: right;
cursor: pointer;
}
.completed {
text-decoration: line-through;
}
JS
$(document).ready(function () {
$("#list-items").html(localStorage.getItem("listItems"));
$(".add-items").submit(function(event) {
event.preventDefault();
var item = $.trim( $("#todo-list-item").val() );
if (item.length > 0) {
if (item === "exercise" || item === "EXERCISE" || item === "Exercise"){
$("#list-items").append("<li><input class='checkbox' type='checkbox' /><img src='assets/images/exercise.gif' alt='exercise'><a class='remove'>x</a><hr></li>");
localStorage.setItem("listItems", $("#list-items").html());
$("#todo-list-item").val("");
} else {
$("#list-items").append("<li><input class='checkbox' type='checkbox' />" + item + "<a class='remove'>x</a><hr></li>");
localStorage.setItem("listItems", $("#list-items").html());
$("#todo-list-item").val("");
}
}
});
$(document).on("change", ".checkbox", function() {
if ($(this).attr("checked")) {
$(this).removeAttr("checked");
} else {
$(this).attr("checked", "checked");
}
$(this).parent().toggleClass("completed");
localStorage.setItem("listItems", $("#list-items").html());
});
$(document).on("click", ".remove", function() {
$(this).parent().slideUp("slow", function() {
$(this).parent().remove();
localStorage.setItem("listItems", $("#list-items").html());
});
});
});
Since you are calling slideUp() on the li element, Inside the callback, this refers to the the li element, so when you say $(this).parent().remove() it is removing the ul element that is why all the elements are removed.
So you can remove the element referred by this, no need to call .parent()
$(document).on("click", ".remove", function() {
$(this).parent().slideUp("slow", function() {
$(this).remove();
localStorage.setItem("listItems", $("#list-items").html());
});
});

How to insert text anchor inside attributes onclick?

I have a jquery tab and it's working fine.
Fiddle here
$(document).ready(function() {
$('.nnntabs ul a').on('click', function(e) {
var current = $(this).attr('href');
$('.tab-content > div' + current).fadeIn('slow').show().siblings().hide();
$(this).parent('li').addClass('active').siblings().removeClass('active');
e.preventDefault();
});
});
I just wanted to add more feature. The text anchor should be dynamically inserted inside the href and id attributes to activate the tab, or maybe replaced if there is a default value... The source of the value of href and id will be the text anchor of the menu. My fiddle sample is working fine because I inserted the values manually. Please help me turn this into reality... Thank you.
I think what you are looking for is
$(document).ready(function() {
var $content = $('.nnntabs > .tab-content > div');
$('.nnntabs > ul a').each(function(i) {
$(this).data('tab', 'tab-' + i);
$content.eq(i).attr('id', 'tab-' + i)
})
$('.nnntabs ul a').on('click', function(e) {
var current = $(this).data('tab');
$('#' + current).fadeIn('slow').show().siblings().hide();
$(this).parent('li').addClass('active').siblings().removeClass('active');
e.preventDefault();
});
});
.nnntabs {
width: 100%;
}
.nnntabs ul {
width: 100%;
margin: 0;
padding: 0;
}
.nnntabs ul li {
display: inline-block;
list-style: none;
border: 1px solid #ddd;
border-bottom: 0
}
.nnntabs ul li a {
padding: 8px 10px;
display: block;
font-size: 1em;
font-weight: bold;
color: #4c4c4c;
background: #eee;
}
.nnntabs ul li.active > a {
background: #fff;
color: #4c4c4c;
margin-bottom: -1px;
padding-bottom: 9px;
}
.tab-content {
padding: 10px;
border: 1px solid #ddd;
}
.tab-content > div {
display: none;
}
.tab-content > .active {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="nnntabs">
<ul>
<li class="active">tab1</li>
<li>tab2</li>
<li>tab3</li>
</ul>
<div class="tab-content">
<div class="active">
content1
</div>
<div>
content2
</div>
<div>
cotent3
</div>
</div>
</div>

Categories