I have a function that generates the inputs all of class "priceInput" within a table.
And then a function that sums the values of that class.
I've tested the summing equation and I know it works if the "priceInput" class elements are in my html, but not when I have them created by the user. Please help!
function newItem() {
var tab;
tab = document.createElement("div");
tab.innerHTML = `
<table><tbody>
<tr>
<td class="descriptionLabel top priceContainer">Price: $</td>
<td class="descriptionPrice"><input type="number" class="priceInput"></td>
</tr>
</tbody></table>`;
document.getElementById("addtable").appendChild(tab);
}
$(".priceInput").on("keyup", function () {
let sum = 0;
$(".priceInput").each(function () {
sum += $(this).val() / 1;
});
$("#specialTotal").text(sum);
});
<div class="buttonContainer">
<button class="newItemButton" onclick="newItem()">Add New item</button>
</div>
<div id="addtable"></div>
<div class="total" id="specialTotal"></div>
I am guessing this is happening because of a common problem. What I believe is happening is that when $(".priceInput").on("keyup",... is run, it attaches the event listener to the elements with .priceInput that exist at the time of execution of this command.
As a result, any dynamically created elements with .priceInput do not have an event listener attached to them. There are two ways to fix this:
Attach the event listener each time to any new element you create.
Attach the listener to the whole document and verify if the event.target is the element that you wish it to be. If it is an element with .priceTable then do your summing.
Fiddle applying method 1: https://jsfiddle.net/a8b9whez/
Fiddle applying method 2: https://jsfiddle.net/t8e0obyk/3/
Related
I have been using oneclick methods forever and am rewriting my website and want to switch to a modern of doing things (event listeners)
I understand how to add an event listener.
Const button = document.getElementById('button');
button.addEventListener('click');
I could loop through all the buttons but how would I properly event delegate?
Basically I have a table. And I want to target every button in it that has a specific class “edit-user” and listen for any of those buttons being clicked.
Thanks, but confused at best way to event delegate and target specific elements and having one event listener for entire table. Seems bad to add 50 different listeners for each button.
Event Delegation works on this way:
( more easy with the use of element.matches )
<table id="my-table">
...
<tr>
<td>
<button class="edit-user">aaa</button>
...
<tr>
<td>
<button class="edit-user">bbb</button>
...
<tr>
<td>
<button class="other-class">ccc</button>
...
const myTable = document.querySelector('#my-table')
myTable.onclick = e =>
{
if (!e.target.matches('button.edit-user')) return // reject other buttons
console.log( e.target.textContent) // show "aaa" or "bbb"
// ...
}
Here is an example, note that you can add new buttons dynamically and it still works the way that's not possible when you add an event listener to each element, so in my example there is a button "add more buttons" that adds more buttons dinamically to demonstrate that all are clickable the same way.
var butCont = document.querySelector("#buttons-container");
butCont.onclick = function(e) {
if(e.target.nodeName === "BUTTON") {
//to prevent hiding the snippet with the console
console.clear();
console.log(e.target.textContent);
}
};
for(var i = 0;i < 50; i++) {
butCont.innerHTML += `<button>${i + 1}</button>`;
}
//don't worry the .querySelector will get the first button which is the add more buttons one and not other button
document.querySelector("button").onclick = function() {
butCont.innerHTML += `<button>${i += 1}</button>`;
}
#buttons-container {
width: 300px;
}
button {
width: 30px;
height: 30px;
}
<button style="width: 300px;">add more buttons</button>
<div id="buttons-container">
</div>
I have an ol element with some li's. Inside each li is also an input with an oninput function. When the li is clicked on, it's cloned and added to a separate column. The cloning works fine, except for the inputs. Only one of the inputs seems to actually read any values once it's cloned (I assume it's the original).
Any help would be greatly appreciated.
let exerciseBuilder = document.getElementById("exercise-builder");
let exerciseList = document.getElementById("list-of-exercises");
let repsLabel = document.querySelectorAll(".reps-label");
let ETA = document.getElementById("ETA");
// $(".reps").hide();
$(repsLabel).hide();
// This is the function that clones and copies over the li Element. I assume the problem is somewhere in here.
function addToBuilder(e) {
let target = e.target;
if (target.matches('li')) {
exerciseBuilder.insertBefore(e.target, exerciseBuilder.firstChild);
let clone = e.target.cloneNode(true)
exerciseList.appendChild(clone)
$(e.target.children).show();
}
}
// This is the function simply removes the li Element from the right column. This seems to work OK.
function removeFromBuilder(e) {
let target = e.target;
if (target.matches('li')) {
e.target.remove();
}
}
// This is the oninput function that I have linked in the HTML of the very first input.
function estimatedTimeOfCompletion() {
let repsValue = document.getElementById("back-squat-reps").value;
console.log(repsValue);
}
exerciseList.addEventListener("click", function(e) {
addToBuilder(e);
})
exerciseBuilder.addEventListener("click", function(e) {
removeFromBuilder(e);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- Here is the start of the left column, where the input seems to work correctly. -->
<div id="list-of-exercises-container">
<ol id="list-of-exercises">
<li class="exercise" id="back-squat">Back Squat
<!-- I have have the oninput function on one of the inputs at the moment for testing purposes, but the plan is to put on all. -->
<input class="reps" type="text" id="back-squat-reps" oninput="estimatedTimeOfCompletion()"><label class="reps-label">Reps</label>
</li>
<li class="exercise" id="power-clean">Power Clean
<input class="reps"><label class="reps-label">Reps</label>
</li>
<li class="exercise" id="bench-press">Bench Press
<input class="reps"><label class="reps-label">Reps</label>
</li>
<li class="exercise" id="box-jump">Box Jump
<input class="reps"><label class="reps-label">Reps</label>
</li>
</ol>
</div>
<!-- Here is the start of the right column. When the li is cloned over here, the input stops working. -->
<div id="exercise-builder">
<ul>
</ul>
<p id="ETA">Estimated Time Of Completion: </p>
</div>
This is by design per the MDN documentation. cloneNode() does not copy event handlers.
You'll have to add the event listener to each element created.
let clone = e.target.cloneNode(true)
clone.addEventListener("click", <function>);
exerciseList.appendChild(clone)
You can pass the event object to the input event handler and read the value of the current <input> with e.target.value (alternatively, you could pass the element itself to the function with this). However, you should consider using addEventListener instead of inline event handlers.
<input class="reps" type="text" oninput="estimatedTimeOfCompletion(event)">
function estimatedTimeOfCompletion(e) {
let repsValue = e.target.value;
console.log(repsValue);
}
It would be better to give the <input> a specific class and use event delegation. Note that cloning an element with the id attribute causes there to be duplicate ids in the document.
The problem:
I have 134 elements which must have an onclick event attached.
I am doing this by now, on eeeeeeevery single one of them (and they have an ondbclick event attached too!):
<div id="id1" class="name" onclick="functionName(this.id)"></div>
<div id="id2" class="name" onclick="functionName(this.id)"></div>
<div id="id3" class="name" onclick="functionName(this.id)"></div>
but read in Eloquent Javascript (chapter 14) that this is considered bad practice, since it mixes html and javascript.
So I thought I could find a way to attach the onclick event to all of them together. I searched a few hours and tried a few things, like this: (from 'How do you set a JavaScript onclick event to a class with css' here on stackoverflow)
window.onload = function() {
var elements = document.getElementsByClassName('nameOfTheClass');
for(var i = 0; i < elements.length; i++) {
var OneElement = elements[i];
OneElement.onclick = function() {
//do something
}
}
}
Which got the click to work on the elements, but not my function.
My original function was receiving two values, the id and the innerHTML of the element that got clicked, and now I cannot find a way to access that information.
I tried OneElement.id and OneElement.innerHTML just to find out that it gets the id and innerHTML of the last of the elements in the document.
Any clues? Any help very much appreciated! :)
When an event is triggered in JavaScript, JavaScript passes an event object to the callback function. Pass that into the signature and you will gain access to element properties.
window.onload = function() {
const elements = document.getElementsByClassName('nameOfTheClass');
for (const element of elements) {
element.addEventListener("click", e => {
console.log("element was clicked", e.target.id, e.target.innerHTML);
})
}
}
<div id="id1" class="name">first</div>
<div id="id2" class="name">second</div>
<div id="id3" class="name">third</div>
<script type="text/javascript">
var nodes = document.querySelectorAll('.name');
Array.from(nodes).forEach(function (node) {
node.addEventListener('click', function (event) {
alert('you clicked' + event.target.textContent + ' with id: ' + event.target.getAttribute('id'));
// you might also use event.target.innerHTML here
});
});
</script>
There are two DOM apis I would recommend you use:
document.querySelector and document.querySelectorAll and
element.addEventListener('click', event => {/* ... */});
Those are my gotos for "vanilla js" dom manipulation.
See the example below for what you wanted.
Array.from(document.querySelectorAll('.name')).forEach(element => {
// for each element that matches the querySelector `.name`
element.addEventListener('click', clickEvent => {
// call your function when the element is clicked
// see the properties of a Dom Element here:
// https://developer.mozilla.org/en-US/docs/Web/API/Element
yourFunction(element.id, element.innerHTML);
});
})
function yourFunction(id, innerHtml) {
// use the values!
console.log({id, innerHtml});
}
<div id="id1" class="name">[click me] inner html 1</div>
<div id="id2" class="name">[click me] inner html 2</div>
<div id="id3" class="name">[click me] inner html 3</div>
In the template I have a input element.
<input class='qq-edit-caption-selector qq-edit-caption kk-editing' placeholder='Enter Caption here ...' onkeypress='captionUpdate();'>
I want to track the change in the input value,and enable the Update button.Tried below options:
onkeypress='captionUpdate();'
Tried jquery change or click on the class
$('.qq-edit-caption').change(function() {
alert('qq-edit-caption');
});
Both options does not get fired up!!not sure I have anything issues with my setup or Fine Uploader does not allow that? Please see my screenshot:
Any way to solve this problem with FU?
If you are simply adding this inline event handler directly to the template element, then it's not surprising that it's never triggered. Fine Uploader templates are quite primitive in that the template is interpreted as an HTML string and then used to create DOM elements inside of your container element (the element referenced as your element option).
You really should never use inline event handlers. There are quite a few disadvantages to this approach. I talk about this in more depth in my book - Beyond jQuery. And the method of attaching event handlers is not necessary at all in your case, as far as I can tell. Instead, after constructing a new instance of Fine Uploader, simply attach an event handler of your choice to the input element using addEventListener. For example if your <input> element is given a CSS class name of 'qq-edit-caption', you can attach a "change" event handler like this:
var uploadContainer = document.querySelector('#my-uploader')
var uploader = new qq.FineUploader({
element: uploadContainer
...
})
uploadContainer.querySelector('.qq-edit-caption')
.addEventListener('change', function(event) {
// event handler logic here...
})
...and if you are creating this input element for each file and need to attach a "change" handler to all of these input elements, you should attach a single delegated event handler to the container element, and react based on the element that initially triggered the event (look at the target property of the event). You can determine the ID of the file by looking at the CSS class of the parent <li> of the event.target, or you can look for a 'qq-file-id' attribute on the parent <li> of the target element (the value will be the file ID). That code might look something like this:
var uploadContainer = document.querySelector('#my-uploader')
var uploader = new qq.FineUploader({
element: uploadContainer
...
})
uploadContainer.addEventListener('change', function(event) {
if (event.target.className.indexOf('qq-edit-caption') >= 0) {
var fileId = parseInt(event.target.getAttribute('qq-file-id'))
// ...
}
})
This might get you started:
$('.inp input').keyup(function(){
if (this.value.length > 0) {
$(this).closest('.row').find('.cell.btn button.upload').prop('disabled', false);
}else{
$(this).closest('.row').find('.cell.btn button.upload').prop('disabled', true);
}
});
* {position:relative;box-sizing:border-box;}
.row{overflow:hidden;}
.cell{float:left;height:40px;}
.pic{width:82px;}
.inp{width:230px;}
.inp input{font-size:1rem;padding:2px 5px;}
.btn{width:60px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="row">
<div class="cell pic">
<img src="http://lorempixel.com/80/40">
</div>
<div class="cell inp">
<input class='qq-edit-caption-selector qq-edit-caption kk-editing' placeholder='Enter Caption...'>
</div>
<div class="cell btn">
<button class="upload" disabled>Upload</button>
</div>
<div class="cell btn">
<button class="del" disabled>Delete</button>
</div>
</div><!-- .row -->
You can enable and disable the state of button by the input value.Using the Keyup function
$('.qq-edit-caption').keyup(function() {
if(this.value.length > 0){
$("#edit").prop('disabled', false);
}
else {
$("#edit").prop('disabled', true);
}
});
Have you tried the onchange event? Does it work?
<input class='qq-edit-caption-selector qq-edit-caption kk-editing' placeholder='Enter Caption here ...' onchange='captionUpdate();'>
I have a table with around 30 columns and the idea is to let the user select which columns to be hidden or shown. The reason for this is to let them select which columns will be visible when they print.
To tackle this problem, I have assigned a class name to each column and i'm using jQuery's toggle function. This works fine, but I was wondering if there is a better way to go about it that is more efficient and cleaner than what I am currently using. I have a separate function for each column and my code looks like this:
jQuery
function tablecolumn1toggle(){
$(".column1").toggle();
}
function tablecolumn2toggle(){
$(".column2").toggle();
}
function tablecolumn3toggle(){
$(".column3").toggle();
}
HTML Simplified
toggle column 1
toggle column 2
toggle column 3
<table class="table table-bordered" id="points_table">
<tbody>
<tr>
<th class="column1>Route</th>
<th class="column2">Location</th>
<th class="column3>Track</th>
</tr>
</tbody>
</table>
and so on..
I am using a button to call each toggle function, I will use checkboxes once I have the basic code working. So, is there a way for me to cut down the amount of code?
EDIT: Thank you all for your answers, it was really hard to pick a single answer but i'm grateful for all your input.
If you want to do it dynamically using checkboxes, add a data property to the checkbox
<input class='toggleColumns' type="checkbox" data-target="column1" />
<input class='toggleColumns' type="checkbox" data-target="column2" />
<input class='toggleColumns' type="checkbox" data-target="column3" />
<input class='toggleColumns' type="checkbox" data-target="column4" />
then add a change event on the checkbox:
$('.toggleColumns').on('change', function (e) {
// get the target for this checkbox and toggle it
var tableColumn = $(e.currentTarget).data('target');
$('.' + tableColumn).toggle();
});
Here is working fiddle: https://jsfiddle.net/9Ls49w97/
A bit of a late addition, but to add one more alternative: if you have multiple setups of this kind and you don't want to add classes each time, you can show or hide a column with something like $('tr *:nth-child(' + (ColumnIndex + 1) + ')', table).toggle();. Especially if you change the column order in the future, the class approach can come to bite you.
One step further, is not to define the checkboxes beforehand, but have JQuery create them on the fly. This is also easier in maintaining the page and with the added benefit that you can assign the column index while creating the input objects and don't have to add any special attributes in design time.
All in all, the html would be as light as possible (no classes or properties) and doesn't have to be maintained. An example where the checkboxes are added in a div:
var table = $('table'); //add an id if necessary
var cols = $('th', table); //headers
var div = $('<div>'); //new div for checkboxes
cols.each(function(ind){
$('<label>').text($(this).text()).append(
$('<input type="checkbox" checked=true>') //create new checkbox
.change(function(){
$('tr *:nth-child(' + (ind + 1) + ')', table).toggle();
})
).appendTo(div);
});
table.before(div); //insert the new div before the table
Fiddle
/* number is a parameter now */
function tablecolumntoggle(i){
$(".column"+i).toggle();
}
/* example to iteratly call */
for (var i = 1; i <= 3; i++) {
tablecolumntoggle(i);
};
Here's one way to make it simpler.
Give each button a data-col value and the matching column element(s) the same data-col value, then they can be paired in a simple function:
<button data-col='column1'>toggle</button>
<button data-col='total'>toggle</button>
<button data-col='other'>toggle</button>
<div class="col" data-col="column1">
column 1
</div>
<div class="col" data-col="total">
total column
</div>
<div class="col" data-col="other">
other
</div>
and code
$(function() {
$("button[data-col]").on("click", function() {
var col = $(this).data("col");
$(".col[data-col='" + col + "']").toggle();
});
})
Simple fiddle demo: https://jsfiddle.net/bb1mm0cp/
You Pass number 1,2,3 function
Try this
function tablecolumn1toggle(id){
$(".column"+id).toggle();
}
function call like this
tablecolumn1toggle(1); or
tablecolumn1toggle(2); or
tablecolumn1toggle(3);
OR
function tablecolumn1toggle(colum_name){
$(colum_name).toggle();
}
function call like this
tablecolumn1toggle(column1); or
tablecolumn1toggle(column2); or
tablecolumn1toggle(column3);