I am writing a todo app using vanilla JavaScript to learn the language without using a library.
In the app, you can add a task, complete task or un-complete task, edit and delete task. I have sample tasks that show the functionality in HTML.
I have a function that adds a task to incomplete tasks section—each task item is wrapped in an li tag and has a checkbox, edit and delete buttons. The addTask function works perfectly.
The problem I am having is in the selectbox part. The app is designed in a way that when checkbox of a task is selected, it indicates a task is completed and thus the item is shown in the completed tasks section. The function works for the items available on the page but not the ones added using the add functionality. How do I make the new task work? Thanks. Here is my HTML Code:
<h3>Todo</h3>
<ul id="incomplete-tasks">
<li><input type="checkbox"><label>Pay Bills</label><button>Edit</button><button>Delete</button></li>
</ul>
<h3>Completed</h3>
<ul id="completed-tasks">
<li><input type="checkbox" checked></input><label>See Doctor</label><button>Edit</button><button>Delete</button></li>
And here is my JS code:
var incompleteTasksHolder = document.getElementById("incomplete-tasks");
var completedTasksHolder = document.getElementById("competed-tasks");
var incompleteTextboxes = incompleteTasksHolder.querySelectorAll("input[type=checkbox]");
for (var i = 0; i < incompleteTextboxes.length; i++) {
incompleteTextboxes[i].onfocus = function() {
var item = this.parentNode;
this.setAttribute("checked", "checked");
completedTasksHolder.appendChild(item);
}
}
You are probably using some event listeners to mark the task as completed when the checkbox changes value.
I think that you just forgot the bind this listener to the new item that you created via Javascript.
When creating your new element bind your listener to it:
taskCB.addEventListener('change', toggleCompleted);
Where taskCB is the checkbox element and toggleCompleted is the name of the function that is fired when a task checkbox is checked or unchecked.
This probably happens because the incompleteTextboxes variable only gets assigned a collection of DOM nodes once. Each time you add a new task, that ends up inside incompleteTasksHolder you should also re-assign an updated collection by re-running the assignment: incompleteTextboxes = incompleteTasksHolder.querySelectorAll("input[type=checkbox]");.
That could look something like this:
var incompleteTasksHolder = document.getElementById("incomplete-tasks"),
completedTasksHolder = document.getElementById("competed-tasks");
// Initialize variable, but create a function for the assignment:
var incompleteTextboxes;
// This function searches the DOM for all checkboxes inside incompleteTasksHolder, and assigns the collection of found elements to incompleteTextboxes:
function refreshIncompleteTasksCollection() {
incompleteTextboxes = incompleteTasksHolder.querySelectorAll("input[type=checkbox]");
}
// You can now re-assign all currently existing checkboxes by calling this function:
refreshIncompleteTasksCollection();
Now, each time there are new elements inside incompletetextboxes, you can run that function to add those to your collection. Note, you'll probably want to re-run your for-loop as well, for binding the focus event handler to your new elements.
A whole different (and in my opinion cleaner) approach would be to use event delegation for your onfocus handlers. Since incompletetextboxes only contains checkboxes inside #incomplete-tasks (by the way you set up your variables), you could use that element to delegate the event handling to. This can be done with addEventListener. You can read more about that here: https://davidwalsh.name/event-delegate.
The main reason I bring this up is because this would solve the problem of missing event handlers for your new elements. What event delegation basically means is, instead of saying:
listen at each incompleteTextbox for a focus event (which means that, when you add new elements you have to say that again for each new element)
you now say:
listen at incompleteTasksHolder for a focus event, and then determine whether that event was fired by a incompleteTextbox. That is possible because your checkboxes live INSIDE #incomplete-tasks. That could look something like this:
incompleteTasksHolder.addEventListener("focus", function(eventObject) {
if (eventObject.target && eventObject.target.matches("input[type=checkbox]")) {
// eventObject.target is the checkbox you want to work with
var checkbox = eventObject.target;
// YOUR CODE:
var item = checkbox.parentNode;
checkbox.setAttribute("checked", "checked");
completedTasksHolder.appendChild(item);
}
});
That means only one listener, that will always work, even for incompleteTextboxes that are inserted after that listener was set.
Related
I'm trying to get specific data depending on which node element the user is clicking. I have 4 elements that I have targeted using the querySelectorAll code. What I want to accomplish is that if I click the first element I will console.log that specific data, and if I select the third element I will get that data logged. I've tried a couple of things, but haven't got it to work yet.
function selectedSplit() {
var macroSplits = document.querySelectorAll(".card");
console.log(macroSplits[0].childNodes[3].childNodes[1].innerHTML);
}
It's unclear where you are using selectedSplit - Wether or not it is being used as the event listener return function. But using an onClick event listener, you're return function will be passed the information you need.
If you want to accomplish this in the markup, you could do -
<div class='card' onClick="selectedSplit"></div>
Then you can simply access it via event.target
function selectedSplit(event) {
var thisCard=event.target;
console.log(thisCard.innerHTML);
}
event.target has the clicked element:
d.onclick = e => console.log(e.target)
<div id=d>
<button><b>b</b></button>
<button><i>i</i></button>
<button><u>u</u></button>
<button><s>s</s></button>
</div>
HTML:
<h2>CHECK AS MANY AS YOU CAN</h2>
<form id="boxone">
</form>
JS:
$boxone = $("#boxone");
$boxone.html('<input type="checkbox" class="fourthboxes">');
$fourthboxes = $(".fourthboxes");
for(var i=0; i <341; i++) {
$fourthboxes.clone(true, true).appendTo($boxone);
}
$fourthboxes.change(function() {
alert('yo');
});
The rest of the checkboxes don't alert when I click on them, only the original one does
I even tried $fourthboxes.on('click'... instead and still nothing.
I took a look at this question and tried the solution but it didn't work.
jQuery clone() not cloning event bindings, even with on()
Use .on():
$(document).on('change', '.fourthboxes', function() {
alert('yo');
});
This makes your event handler work for current elements, but also future added elements, that match the .fourthboxes selector. This uses the principle of delegated events.
From the documentation:
When a selector is provided, the event handler is referred to as delegated. The handler is not called when the event occurs directly on the bound element, but only for descendants (inner elements) that match the selector. jQuery bubbles the event from the event target up to the element where the handler is attached (i.e., innermost to outermost element) and runs the handler for any elements along that path matching the selector.
Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time.
The issue is because although you use clone(true, true) you're cloning the elements before you add the change event handler to them. You just need to swap the logic around:
$boxone = $("#boxone");
$boxone.html('<input type="checkbox" class="fourthboxes">');
$fourthboxes = $(".fourthboxes");
$fourthboxes.change(function() {
alert('yo');
});
for(var i=0; i <341; i++) {
$fourthboxes.clone(true, true).appendTo($boxone);
}
However, it would be much better to use a single delegated event handler, like this:
var $boxone = $("#boxone").on('change', '.fourthboxes', function() {
alert('yo');
});
var $fourthboxes = $('<input type="checkbox" class="fourthboxes">').appendTo('#boxone');
for (var i = 0; i < 341; i++) {
$fourthboxes.clone().appendTo($boxone);
}
Working example
Since you assign $(".fourthboxes") to the variable $fourthboxes before you add the 340 other checkboxes, the variable still holds only that one checkbox when you add the change-function.
Put the change-function in front of the for-loop and everything works as expected.
It only selects the original because you never select the new elements. The selector only has the original, it is not a live collection. So you need to reselect them.
$(".fourthboxes").on("change", ...)
You can use event delegation so you are not selecting all the checkboxes. Listen for the change event on the form.
$("#boxone").on("change", ".fourthboxes", function(){});
When you clone an object, only the objects are cloned. And not their events. Because the events are bound to the original object (based on the jQuery selector used) before creating the clone.
As #trincot mentioned in his answer, you need to have an event at the document level.
For eg. Let's say my DOM contains three input checkboxes
<input type="checkbox" class="fourthboxes">
<input type="checkbox" class="fourthboxes">
<input type="checkbox" class="fourthboxes">
Now when you bind events using the jQuery selector like this,
$(".fourthboxes").change(function() {
alert('yo');
});
Things to note, is that this jQuery selector returns an array of DOM elements which are present on the page, at that instant of time. And then the onchange event is registered on each of them. It is equivalent to binding the event to each of the existing DOM.(In this case, three checkboxes)
I have requirement to remove some element and get it back when required. I have red about the .detach() which do almost I required but the things is I have to done it with javascript.
<div id="get">a</div>
document.getElementById("get").addEventListener('click',function(){
alert(0)
})
If you need to keep the binding on the node as maybe you are appending it later on why not store the node with its event bindings?
http://jsfiddle.net/w4699vm8/
var myDiv = document.getElementById("get"),
wrapper = document.getElementById("wrapper");
myDiv.addEventListener('click',function(){
alert(0);
});
wrapper.removeChild(myDiv);
wrapper.appendChild(myDiv); //myDiv still has listener
I showed 5 markers on google map with infowindow. Each contents has checkbox.
I am adding div contents to Compare list when user click on each.There is Remove button to remove them back.I want to UnCheck it on remove.Complete code is here JSFIDDLE
I have two issues now
On Each check i want to keep their ids in hidden fields,I tried this code which is not working
var value = [];
var count = 0;
$('#map-canvas input:checked').each(function() {
value+=$(this).attr('value')+',';
count++;
});
$('#cmpIds').val(value);
On Remove button click I want to uncheck each checkbox and hide it.I have this function which is not working for each popup onclick="removeAdd(this);"
There are two main issues with your code. Firstly, you are constructing an array incorrectly. In order to construct an array based on the checkbox value, you do not construct it literally (i.e. by inserting , between values). Instead, you use .push(), i.e.:
var value = [],
count = 0;
$('#map-canvas input:checked').each(function() {
value.push($(this).attr('value'));
count++;
});
$('#cmpIds').val(value);
Secondly, you should also avoid using inline JS for the delete function. Use .on() if you want to bind event handlers to dynamically added elements. Therefore, for the injected markup, simply remove the inline JS reference:
<a class="text-success">Remove</a>
Also, I have used $(this).closest('.media') to find the .parent().parent(), as it is more verbose. You can cache this selector so jQuery wouldn't have to comb through the DOM repeatedly within the same click event:
$(document).on('click', '.text-success', function() {
var $parent = $(this).closest('.media');
// Remove listing
$parent.remove();
// Uncheck associated textbox
var parentID = $parent.attr('id'),
checkboxID = parentID.split('_');
$('#'+checkboxID).prop('checked', false);
});
Working fiddle here: http://jsfiddle.net/teddyrised/z0Lkbmyh/4/
Additional notes: your value array and count variable are reset every time a change event is registered on your checkbox. I suspect, although I cannot confirm, that this is not the desired behavior — I believe you want to keep track of checked properties on the go. Therefore, you should declare both of them outside the .change() handler:
var value = [],
count = 0;
$(document).on('change', '.wrapmap-gist input:checkbox', function() {
$('#map-canvas input:checked').each(function() {
value.push($(this).attr('value'));
count++;
});
$('#cmpIds').val(value);
// Rest of your code here
});
$(document).on('click', '#button-id', function() {
// your code here
});
When you specify a function (with or without parameters) in the DOM via onChange, you are limited to static paramters. E.g.
<select id="blah" onChange="doSomething(3);">
However, jQuery lets you dynamically define and assign functions to event handlers, such as 'change'.
See the example below:
function addRow(customObject) {
<< Create TR containing a select named "#someSelect + customObject.id" >>
// Demo
$("#someSelect" + customObject.id).change(function() {
changeSomething(customObject)
});
}
My question is - essentially - will this work? I am using a specific instance o
f an object in my jQuery change event handler. Will that particular instance of customObject (wherever it lies) always be bound to this event handler callback?
customObject is initialized at load, and is one of many. For that particular select box, I always want it to be linked to that object.
New to jQuery. Thanks in advance.
EDIT:
I have several rows of items. Each is represented by an object. I just want to make sure that when someone modifies one of the rows via HTML, that the underlying object (bound to the callback function) is properly updated.
I am looking for something like this, conceptually a mapping between:
Row 1 - customObject1
Row 2 - customObject2
Row 3 - customObject3
... such that if I modify one of the HTML elements (e.g. change the select box) in row 3, that behind the scenes, customObject3 is modified
What you have will work in case customObject.id is defined before adding the listener and you intent the listener itself to be bound to that specific element without moving it around.
From your question I understand that customObject.id is dynamic as in can change its value and depending on this value, the listener can listen on different element. In this case, you'd have to use a bit more general selector and check the value inside the listener itself:
$('select').on('change', function(e) {
if(this.id === 'someSelect' + customObject.id)
return myCallback(e);
});
Edit:
The problem lies in accessing the customObject inside the listener. It uses the current content, not the content it used to have when you added the listener.
Easiest solustion I know is:
var select = $("#someSelect" + customObject.id);
select.customObject = customObject; /* save customObject to the select itself */
select.on('change', function() {
changeSomething(this.customObject); /* don't use global object but its own */
});