Handling Events for Many Elements - optimalizing my solution - javascript

I am building a simple shop website (just to practise) and even though my current solution works, from what I read it is not optimal and could be improved through creating event listener on the parent element (here it would be cart-items instead of on every anchor element. The problem is that when I attach event handler to it, then only the first input is changed if there are two or more elements in the basket, no matter which increase button I click (the one from product one, two, ect.).
My question is: in such case is attaching event listener to the parent element really the best option, and if yes, how can I properly refactor my code to make increase/decrease button work on their closest input value, not only on the first one from the rendered list?
Below I attach my current JS Code:
const qtyBoxes = document.querySelectorAll('.cart-qty-box');
qtyBoxes.forEach((box) => {
const increase = box.querySelector('.increase');
const decrease = box.querySelector('.decrease');
const currQty = box.querySelector('.currQty');
increase.addEventListener('click', function(e) {
e.preventDefault();
currQty.value++;
$('#przelicz').click(); //uptades UI
});
decrease.addEventListener('click', function(e) {
e.preventDefault();
if(currQty.value > 0) {
currQty.value--;
}
$('#przelicz').click(); //updates UI
});
});
HTML:
<div class="cart-items">
///... Item 1 code ...
<div class="qty-box">
<div class="qty-ctl">
<a class="incr-btn decrease" data-action="decrease" href="#"></a>
</div>
<input id="qnt" class="qty currQty input-text" type="text" value="{$poz->count}"/>
<div class="qty-ctl">
<a class="incr-btn increase" data-action="increase" href="#"></a>
</div>
</div>
///... Item 2 code ...
<div class="qty-box">
<div class="qty-ctl">
<a class="incr-btn decrease" data-action="decrease" href="#"></a>
</div>
<input id="qnt" class="qty currQty input-text" type="text" value="{$poz->count}"/>
<div class="qty-ctl">
<a class="incr-btn increase" data-action="increase" href="#"></a>
</div>
</div>
</div>
Here I paste a link to the image if the description of what I am trying to build is not clear:
screenshot of basket

Yes, it is better to attach the event listener to the parent, because you have only one listener instead of multiple listeners (one for every button).
You can achieve this by checking to which box the target of the click-event (e.target) belongs:
const click_parent = e.target.closest('.qty-box');
and if it's an increase- or decrease-button, for example:
if (e.target.classList.contains('increase')) {
The rest works like in your snippet.
Working example:
document.querySelector('.cart-items').addEventListener('click', function(e) {
e.preventDefault();
const click_parent = e.target.closest('.qty-box');
const currQty = click_parent.querySelector('.currQty');
if (e.target.classList.contains('increase')) {
currQty.value++;
$('#przelicz').click(); //uptades UI
} else if (e.target.classList.contains('decrease')) {
if (currQty.value > 0) {
currQty.value--;
}
$('#przelicz').click(); //uptades UI
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="cart-items">
///... Item 1 code ...
<div class="qty-box">
<div class="qty-ctl">
<a class="incr-btn decrease" data-action="decrease" href="#">decrease</a>
</div>
<input id="qnt" class="qty currQty input-text" type="text" value="2" />
<div class="qty-ctl">
<a class="incr-btn increase" data-action="increase" href="#">increase</a>
</div>
</div>
///... Item 2 code ...
<div class="qty-box">
<div class="qty-ctl">
<a class="incr-btn decrease" data-action="decrease" href="#">decrease</a>
</div>
<input id="qnt" class="qty currQty input-text" type="text" value="3" />
<div class="qty-ctl">
<a class="incr-btn increase" data-action="increase" href="#">increase</a>
</div>
</div>
</div>

Related

Thymeleaf attribute and modal

Edited:
Just figured out that i need JS. Plase, help me with this. I have a th:attr="data-object-id=${department.id}" who store Department id, which i need to put in the modal in the <input id="ids" name="ids" type="text" class="validate">.
How will JS or JQuery looks like? I am trying to write, but all time null or undefined.
<tr th:each="department : ${departments}">
<td class="dep_id" th:text="${department.id}">1</td>
<td th:text="${department.name}"></td>
<td>
<div class="dep_edit">
<a class="settings_dep" th:href="#{'/departments/' + ${department.id} + '/'}"><i class="material-icons">settings</i></a>
<a class="edit_dep modal-trigger" href="#modal3" th:attr="data-object-id=${department.id}"><i
class="material-icons">edit</i></a>
<form method="post" th:action="#{'/departments/' + ${department.id} + '/delete'}" class="inline">
<button type="submit" value="" class="link-button delete_dep">
<i class="material-icons">delete</i>
</button>
</form>
</div>
</td>
</tr>
<div id="modal3" class="modal modal-departments">
<div class="modal-dep modal-content">
<h4>Update Department</h4>
<a href="#" class="dep-modal-close modal-close"><i
class="material-icons icon_close">close</i></a>
<p>Update Department name</p>
</div>
<div class="dep-modal">
<form id="dep-update" class="col s12" th:action="#{'/departments/update'}" method="POST">
<div class="row-modal-dep">
<div class="input-field col s6">
<input id="depName" name="name" type="text" class="validate">
<input id="ids" name="ids" type="text" class="validate">
<label for="name">Department Name</label>
<i class="edit-dep-marker material-icons prefix">mode_edit</i>
</div>
</div>
</form>
<div class="modal-footer">
<input class="modal-close waves-green green btn-dep btn" type="submit" form="dep-update">
</div>
</div>
</div>
I need to give ID value of department to MODAL, so i can update it
My Departments class is easy. Only ID and name;
The 2 tricks to achieve the same (PS - I'm not a Frontend expert):-
1.) Is to create a hidden html-element on your html page & set the value and then get the value of that element using jquery on your modal.
2.) create a function on that html-element and pass the dynamic value to it and then implement your modal hide/show code inside that function, something like this :- -
<a class="edit_dep modal-trigger" th:onclick="'javascript:showFunctionModal(\'' + ${department.id} +'\');'"><i
class="material-icons">edit</i></a>
and your function would be something like this :-
function showFunctionModal(id) {
//logic to hide & show function
}
You can listen to the show.bs.modal event and capture the department id as shown below:
$('#modal3').on('shown.bs.modal', function (e) {
var target = e.relatedTarget;
var departmentId = $(target).data('object-id');
$("#ids").val(departmentId);
});
Problem solved. Huge thanks #Sumit.
th:onclick="'javascript:showFunctionModal(\'' + ${department.id} +'\');'"> on the field which id i want to fetch and then in ready modal function set id.
function showFunctionModal(id) {
$(document).ready(function () {
$('.modal3').modal();
$("#ids").val(id);
});

selecting all class within this in query

well right now I am doing something like this to find all textbox values which has the same class name.
function generalBottom(anchor) {
var sends = $('input[data-name^="noValues"]').map(function(){
$(this).attr('value',$(this).val());
return $(this).val();
}).get();
}
and i call this function on a onclick of submit button like generalBottom(this)
and I have something like as per my requirement
When I click submit button of User I call a general function passing this as a parameter, but the above code gives me the text values of client as well
["perfect", "hyperjack", "julie", "annoying", "junction", "convulated"], which is undesired, I want only ["annoying", "junction", "convulated"] using my anchor params.
How to do this via my this parameter, I thought to traverse through my tags using children(), parent() but I won't be knowing how many fields user have added as its all dynamic, user can add as many values(text boxes).
I tried this
1) $(anchor).find('.rightAbsNo')
2) $(anchor).find('input[data-name^="noValues"]')
3) $(anchor).find('.rightAbsNo').map(function () {
console.log($(this).find('. showNoButton')); })
None of this worked for me.
My html is somewhat like this, code
<div id="attach0" class="leftbottomno">
<div style="overflow: hidden" class="leftbottomAbsolute" id="">
<form>
<span class="absno"><input type="text" required=""
id="absdelete" data-inputclass="leftbottomAbsoluteNo_class"
value="" class="leftbottomabsolutenotest keys" data-value=""
style="margin-bottom:4px; color: #1c1c1c;"> </span>
<a onclick="addAbsoluteValues(this);" style="margin-left: 50px">
<i class="fa fa-plus-circle color-blue-grey-lighter"></i> </a>
</form>
<a onclick="deleteAbsoluteEmpty(this);> </a><br>
<div class="rightAbsNo" id="yesValueattach">
<div class="rightAbsNoValue">
<input type="text" id="nonattach" placeholder="values"
data-name="noValues" data-inputclass="absYes_class" value="annoying"
class="showNoButton showActivity value" data-value="">
<button type="button" onclick="generalBottom(this);">
<i class="glyphicon glyphicon-ok"></i></button>
</div>
</div>
<div class="rightAbsNo" id="yesValueattach">
<div class="rightAbsNoValue" id=""> <input type="text"
data-name="noValues" data-inputclass="absYes_class" subattribute=""
value="" class="showNoButton showActivity value" data-value="">
<button type="button" onclick="generalBottom(this);">
<i class="glyphicon glyphicon-ok"></i></button>
</div>
</div>
<div class="rightAbsNo" id="yesValueattach">
<div class="rightAbsNoValue" id="">
<input type="text" data-name="noValues"
data-inputclass="absYes_class" placeholder="values" subattribute=""
value="junction" class="showNoButton showActivity value"
data-value="" >
<button type="button" style="display: none;"
onclick="generalBottom(this);">
<i class="glyphicon glyphicon-ok"></i>
</button>
</div>
</div>
</div>
</div>
First of all, you need to define a container to the groups with something like :
<div class="container">input groups</div>
<div class="container">input groups</div>
and change <button type='submit'> to <button type='button'> to prevent submitting the form.
Then change your function to this:
function generalBottom(anchor) {
var all_inputs = $(anchor).parent(".container").find("input");
var input = $(anchor).siblings('input').first();
all_inputs.each(function(){
$(this).val(input.val());
});
}
Here's Jsfiddle
first $(anchor).siblings(input) find inputs
then go through each element from first step and return their value
function generalBottom(anchor) {
var input = 'input[data-name^="noValues"]'
var values = $.map($(anchor).siblings(input), (elemnt, index)=>{
return elemnt.value
})
$('#shows').val(values.join())
}
$('button').on('click',function(){
generalBottom(this)
})
hope this helps

check and uncheck check in click of li

How to check and unchecked a checkbox in JavaScript when click whole li. I want the checkbox checked if checked and click then unchecked.
function handleClick(cb) {
if(jQuery('related-products-field')){
document.getElementById('related-checkbox-'+cb).checked = true;
numArray.push(cb);
//alert(numArray);
//var allele = document.getElementById("dialog").innerHTML = numArray;
document.getElementById('related-products-field').value = numArray.join(",");
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<li class="item odd" onclick="handleClick(338);" id="">
<span class="input-holder"><input type="checkbox" id="related-checkbox-338" class="checkbox related-checkbox" name="related_products[]" value="338">ADD</span>
<div class="product" id="disbaledtoclick">
<a disabled="" href="" title="" class="product-image"><img src="https://www.example.com/image.jpg" width="100px" height="100"></a>
<div class="product-details">
<p class="product-name"> Small Teddy Bear (6")</p>
<div class="price-box">
<span class="regular-price" id="product-price-2-related">
<span class="price" disabled="">279.0000</span></span>
</div>
</div>
</div></li>
If you want to switch the checked state of the checkbox, you can assign the opposite value to it.
function changeCheckbox(){
let cbox = document.getElementById('cBox');
cbox.checked = !(cbox.checked);
}
<label>checkbox <input type="checkbox" id="cBox" /></label>
<button onclick="changeCheckbox();">Change Checkbox</button>
Since you seem to be using jQuery you can do the following:
function handleClick(cb) {
if(jQuery('related-products-field')){
// since you are using this checkbox twice in the function, it's a good idea to define it as a variable.
var checkbox = jQuery('#related-checkbox-'+cb);
// The is(':checked') function checks if the checkbox is checked
var isChecked = checkbox.is(':checked');
// We then assign the opposite of the current state to the 'checked' attribute.
checkbox.prop('checked', !isChecked);
// do the rest of the function
}
}
Here is a jQuery code which makes the job in one line :
$(document).ready(function(){
$('li.item.odd').on('click', function(){
$(this).find('input[type="checkbox"]').prop("checked", !$(this).find('input[type="checkbox"]').prop("checked"));
});
});
copy / paste this full code to see the result :
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<li class="item odd">
<span class="input-holder"><input type="checkbox" id="related-checkbox-338" class="checkbox related-checkbox" name="related_products[]" value="338">ADD</span>
<div class="product" id="disbaledtoclick">
<a disabled="" href="" title="" class="product-image"><img src="https://www.example.com/image.jpg" width="100px" height="100"></a>
<div class="product-details">
<p class="product-name"> Small Teddy Bear (6")</p>
<div class="price-box">
<span class="regular-price" id="product-price-2-related">
<span class="price" disabled="">279.0000</span></span>
</div>
</div>
</div></li>
<script type="text/javascript">
$(document).ready(function(){
$('li.item.odd').on('click', function(){
$(this).find('input[type="checkbox"]').prop("checked", !$(this).find('input[type="checkbox"]').prop("checked"));
});
});
</script>

Doesn't work click event on input by native js

I have html markup
<div id="site">
<header id="masthead">
<h1>Winery</h1>
</header>
Go to Shopping Cart <span></span>
<div id="content">
<div id="products">
<ul data-bind="foreach: products">
<li >
<div class="product-image">
<img data-bind="attr:{src: img}" alt="" />
</div>
<div class="product-description">
<h3 class="product-name"><span data-bind="text: name" /></h3>
<span class="product-id">ID <span data-bind="text: id" /></span></span>
<p class="product-price">€ <span data-bind="text: price" /></p>
<label for="qty-1">Quantity</label>
<span class="down" ></span>
<input type="text" data-bind="value: qty" class="qty" />
<span class="up"></span><br>
<input type="submit" value="Add to cart" class="btn" id="add" />
</div>
</li>
</ul>
</div>
<div id="browser">
</div>
</div>
and I want to hang a click event on input by native js, but event doesn't work on input
document.getElementById('add').onclick = function() {
document.getElementById('browser').remove();
}
If assign a handler through attribute html tag it's works
<input type="submit" value="Add to cart" class="btn" id="add" onclick="addToBlock()"/>
Make sure the document is loaded before assigning event handlers:
window.onload = function() {
document.getElementById('add').onclick = function() {
document.getElementById('browser').remove();
}
};
If you use jQuery, you could have it execute potentially earlier than with the above code, as it does not wait for images to be loaded:
$(function() {
$('#add').click(function () {
$('#browser').remove();
});
});
Executing it straight away, but by moving the code at the bottom of your document, like after the close of your body tag will work in most cases, although I have had deeply nested DOM hierarchies where this could fail:
...
</body>
<script>
document.getElementById('add').onclick = function() {
document.getElementById('browser').remove();
}
</script>
</html>
If you go for that last option, but want to add some precaution, but the code in a setTimeout with delay 0, which will make it run asynchronously allowing the DOM to be completely built:
setTimeout(function() {
document.getElementById('add').onclick = function() {
document.getElementById('browser').remove();
}
}, 0);
Problem was in ID, I have a few elements with the same ID and onclick work only for first element. I get by class
//get all input by class
var buttons = document.getElementsByClassName('btn');
//add onclick to each
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function() {
}
}

Bootstrap collapse with wrong behavior

My problem is that I have 2 blocks in modal and when I click on .emailInbound checkbox it toggle .in-serv-container open, but when I click on .accordion-heading to open comment part it makes .in-serv-container collapse.
What can be a reason?
HTML:
<label class="checkbox">
<input class="emailInbound" type="checkbox" onclick="toggleInServ(this)">Использовать Email для регистрации обращений
</label>
<div id='in-serv-container'>
<div><strong>Настройка входящей почты</strong></div>
<div>
<input class="emailOutserver" type="text" placeholder="Сервер входящей почты">
<input class="emailOutserverPort" type="text" placeholder="Порт">
</div>
<div>
<select class="emailOutProtocol half" style="width: 49%!important;">
<option value='usual'>Обычный</option>
<option value='ssl'>SSL</option>
</select>
<input class="emailInFolder half" type="text" placeholder="Папка входящей почты">
</div>
<div class="modal-body-container">
<input type="text" placeholder="Опции">
</div>
<button class="btn btn-link">Проверить подключение</button>
<hr>
</div>
<div class="accordion" id="comment-wrapper">
<div class="accordion-heading" data-toggle='collapse' data-target='#emailComment' onclick='toggleChevron(this)'>
<strong>Комментарий</strong> <i class='icon-chevron-right'></i>
</div>
<div id="emailComment" class="collapse" data-parent="#comment-wrapper">
<textarea class="emailComment"></textarea>
</div>
</div>
JS:
function toggleInServ(box){
var checked = $(box).prop('checked');
if(checked){
$('#in-serv-container').css('height', '170px');
}else{
$('#in-serv-container').css('height', '0px');
}
}
function toggleChevron(o){
var icon = $(o).children('[class*=icon]');
if(icon.hasClass('icon-chevron-right')){
icon.removeClass('icon-chevron-right');
icon.addClass('icon-chevron-down');
}else{
icon.removeClass('icon-chevron-down');
icon.addClass('icon-chevron-right');
}
}
If I'm in the right track at this, you want each dropdown to stay on opened if the checkbox is ticked? What ever is the case here, please provide us with your CSS-styling as well. Would be best if you'd give us JSFiddle of your case.
Changing just the
height
attribute of your CSS seems like a bad idea. Instead of that, you could try using
display: block;
and
display: none;
So it would really be a hidden field before it gets selected. Not the answer to the question itself, but just something to note.
It was because of 'bubbling'. I added e.stopPropoganation() and it help.

Categories