jQuery validate dynamic percentage field - javascript

I'm using the jQuery validate plugin. I'm trying to calculate the sum of all inputs fields using the class .percent. If the sum of the .percent fields isn't equal to 100%, throw validation error.
The percent rows are a part of a dynamic grid and can very in number. You'll see a sample of what I've been working on below. I'm finding the addMethod is called for every input.percent rather than a single time. I'd also like to say it's being called before submit is called.
the code.
html
<div class="row">
<input value="" class="percent"/>
</div>
<div class="row">
<input value="" class="percent"/>
</div>
js
$.validator.addClassRules({
percent: {percent:true}
});
$.validator.addMethod("percent",
function cals(value, element) {
// all the fields that start with 'coeff'
var percent = element;
var total = 0;
for (var i = 0; i < percent.length; i++) {
total += Number(percent[i].value);
}
return total == 100;
}, $.format("Percentage fields most total up to 100%"));
$("form").validate({
});
Updates
I've tried the following code with minor success
$("#modify-funding .percent").rules('add', {sum: 100});
$.validator.addMethod("sum", function (value, element, params) {
var sumOfVals = 0;
$("#modify-funding .percent").each(function () {
sumOfVals = sumOfVals + parseInt($(this).val().length ? $(this).val() : 0);
});
if (sumOfVals == params) return true;
return false;
},
$.format("Percentage fields most total up to 100%")
);
when passing the class into rules it's only passing in a single element which doesn't enable me to query for the additional percent fields. The other issue is it only adds the error class to the first .percent element and the additional elements will not release the error once the 100% criteria has been met.
Update 2
This is the closest I've come to getting percent validation working. It isn't very efficient code since it needs to loop through all the percent fields every time you loop the rules. The code still validates before a submit action has taken place plus does not clear all the percent errors on keyup when the percent has been corrected to equal 100.
$("#modify-funding .percent").each(function() {
$(this).rules('add', {sum: [".percent"]});
})
$.validator.addMethod("sum", function (value, element, params) {
var sumOfVals = 0;
$(params[0]).each(function() {
sumOfVals += parseFloat($(this).val().length ? $(this).val() : 0);
});
if (sumOfVals == 100) return true;
return false;
},
$.format("Percentage fields most total up to 100%")
);

Don't use jquery for such a simple validation. I don't really understand what all the hype is about jquery. It just makes your code look all ugly.
function validate() {
var ret = false;
var total = 0;
var elems = document.getElementsByTagName('*'), i;
for (i in elems)
if (elems[i].className == "percent")
total += parseFloat( elems[i].value);
if (total == 100)
ret = true;
return ret;
}

Related

How do I prevent this function from modifying the variable more than once?

I wrote this function to modify the text of my HTML tag when a checkbox is marked.
var price = 15
function checkPrice() {
let extraCheese = document.getElementById("extraCheese");
if (extraCheese.checked = true) {
price += 5;
// console.log(price)
document.getElementById('pizzaPrice').innerHTML = `Current Price: $${price}.00`
}
}
<input type="checkbox" value="extraCheese" onclick="checkPrice(this);" id="extraCheese" />
<label for="extraCheese"> Extra Cheese </label>
<h3 id="pizzaPrice"> Base Price: $15.00 </h3>
It works as intended and adds 5 to the current price, but the checkbox stays checked and each time it is clicked the value adds by five again. What should I do to let the user uncheck the box and reduce the value to what it originally was?
Use else to reduce the price.
There's also no need to call getElementById(), since you pass the element to the function with checkPrice(this)
checked is a boolean property, you don't need to compare it (and you had a typo using = instead of == to compare).
function checkPrice(extraCheese) {
if (extraCheese.checked) {
price += 5;
} else {
price -= 5;
}
// console.log(price)
document.getElementById('pizzaPrice').innerHTML = `Current Price: $${price}.00`
}
you can assign an extra property to your element to check if it's already been increased or not
function checkPrice(extraCheese) {
var price = 15;
function checkPrice() {
let extraCheese = document.getElementById("extraCheese");
if (
(extraCheese.checked && (typeof extraCheese.inc == "undefined" || extraCheese.inc == false))
) {
price += 5;
extraCheese.inc = true;
// console.log(price)
document.getElementById("pizzaPrice").innerHTML = `Current Price: $${price}.00`;
}
}
}
You should be watching for the change event of the checkbox, rather than the click event. Then, from the event itself, you can get a handle to the element, and use that to check whether the element is currently checked or not.
Additionally, I recommend keeping your interaction and presentation logic separate. I.e., don't declare event handlers in your HTML, and don't include too much string formatting stuff in your javascript. Keep it surgical, just modify the specific part of the HTML that needs to change -- it will help to wrap the price number in a <span> tag so that you can change the number without having to repeat the 'Base Price: ' part in your javascript. This attention to detail can help keep things clean when your code gets larger.
var currentPriceElement = document.getElementById('currentPrice');
function getCurrentPrice() {
return Number(currentPriceElement.textContent);
}
function setCurrentPrice(price) {
currentPriceElement.textContent = price.toFixed(2);
}
function onExtraCheeseChange(event) {
var target = event.target;
var price = getCurrentPrice();
if (target.checked) {
price += 5;
}
else {
price -= 5;
}
setCurrentPrice(price);
}
document.getElementById('extraCheese').addEventListener('change', onExtraCheeseChange);
<input type="checkbox" value="extraCheese" id="extraCheese" />
<label for="extraCheese"> Extra Cheese </label>
<h3 id="pizzaPrice"> Base Price: $<span id="currentPrice">15.00</span> </h3>

JavaScript increase button increases only once

I am a beginner in JS and working at a shopping cart. I have several products which are rendered in the page with ES6 template strings. Everything is working so far, you can add items to the basket and the basket and total update correctly. The only part I am having trouble with is the increase/decrease buttons: they only work once, if you click again the amount printed in the console stays the same.
I did find other SO post related to increment/decrement functions but the button keeps working only once, so I reckon the problem is related to something else in the code that I am overlooking.
Please see the code below:
this is the shopping cart that will be rendered
// select ul
const shoppingCart = document.querySelector('.cart-items');
// create a li item inside ul
let billContainer = document.createElement('li');
// attach an event listener to every li
billContainer.classList.add('list');
// create the markup for every item added to the cart
for(let j = 0; j < basket.length; j++){
const billMarkup = `
<p class="prodName">${basket[j].name}</p>
<div class="button-wrapper">
<button type="button" name="increase" class="increase">+</button>
<span class="quantity">${basket[j].quantity}</span>
<button type="button" name="decrease" class="decrease">-</button>
</div>
<p class="totPrice">£${basket[j].price}</p>
`;
// add the markup to the DOM
billContainer.innerHTML = billMarkup;
shoppingCart.appendChild(billContainer);
}
and this is the increase/decrease functionality (the event listener for the buttons is attached to their parent 'li'):
// attach an event listener to every li
const li = document.querySelectorAll('.list');
li.forEach( liItem => liItem.addEventListener('click', updateBill));
// add or remove items on click
function updateBill(e){
if(e.target.nodeName === 'BUTTON'){
// current value in the basket
let value = parseInt(this.querySelector('.quantity').textContent);
// if the user clicks on 'increase' button
if(e.target.name === 'increase'){
value++;
console.log(value);
// if the user clicks on 'decrease' button
} else if(e.target.name === 'decrease'){
value < 1 ? value = 1 : '';
value--;
console.log(value);
}
}
}
Thanks!
Problem
Plus/minus buttons inc/decremented only once then wouldn't go any further.
Explanation
Once a value has changed, it is just a number in a variable floating in the console since that is the last statement that has anything to do with the value. So only the initial change is successful but when the buttons are clicked for the second time, the function goes back to span.quantity and gets the value that's never been updated from the last click.
Solution
The easiest way to resolve the problem at hand is to update the value of span.quantity:
if (e.target.name === 'increase') {
value++;
console.log(value);
} else if (e.target.name === 'decrease') {
value--;
value = value < 1 ? 1 : value;
console.log(value);
} else {
return false;
}
this.querySelector('.quantity').textContent = value;
Because you didn't provide a functional nor a copyable demo, I didn't bother to test it nor did I attempt to spot check your code. It's less effort to rewrite the source and resolve the problem and maybe prevent problems in the future.
Demo Highlights
The Demo uses a different API for referencing form controls and alternate methods and properties that are better versions of ones that are more commonly used. Event Delegation is used. Array methods might've been overkill but I like using them. Below are line item references to the Demo, unfortunately Stack Snippets don't have line numbers. The Plunker - index.html and README.md can be read together with line numbers.
HTMLFormControlsCollection
52 Declare the <form>,
53 Referencing ALL form controls,
92-95 Create very short references to each form control,
96-99 Create references to their values and convert them to numbers,
102-103, 109-110 Simple and short expressions,
122 A grand total value
Template Literals
75-83 Improved the layout for the list items by using semantic elements. Each element is assigned a unique #id,
92-94 Flexible referencing of #ids originating from the results of 89 and 90.
Array Methods
90-91 By planning a specific naming strategy: abc-0, split('-').pop() returns the number end of an id and split('-').shift() returns the letters before the dash,
113-120 Collecting all .prc;
map() returns an array of price totals;
reduce() returns the total sum;
Event Delegation/Event Object
52 Reference the <form>,
54 Register the <form> to click events. This is the only EventListener needed, it will work for all of its children/descendants,
88-91, 100 Reference origin of event with the Event.target property and not only determine the clicked element but others as well like siblings, parents/ancestors, and children/descendants.
Miscellaneous
56-71 It looks like the basket is an array of objects? Didn't see it in the OP so I had to guess. Removed the basket[j].quantity property because it makes more sense that each item is initially a quantity of 1.
84 insertAdjacentHTML() is innerHTML on steroids.
Plunker
Demo
<!DOCTYPE html>
<html>
<head>
<style>
html,
body {
font: 400 16px/1.1 Consolas;
}
legend {
font-size: 1.3rem;
}
output,
input {
display: inline-block;
text-align: center;
}
[id^=qty] {
width: 1.5ch;
}
[id^=prc] {
min-width: 9ch;
}
[id^=prc]::before {
content: "= £";
}
[id^=bas]::before {
content: " x £";
}
#cart+label {
display: inline-block;
margin: 10px 0 0 40%;
}
#total::before {
content: " £";
}
</style>
</head>
<body>
<form id='cart'></form>
<label>Total:
<output id='total' form='cart'>0.00</output>
</label>
<script>
var cart = document.forms.cart;
var x = cart.elements;
cart.addEventListener('click', updateBill, false);
var basket = [{
name: "thing0",
price: 1.99
}, {
name: "thing1",
price: 12.99
}, {
name: "thing2",
price: 21.59
}, {
name: "thing3",
price: 0.09
}, {
name: "thing4",
price: 5.99
}];
for (let j = 0; j < basket.length; j++) {
var details = `
<fieldset id="item-${j}">
<legend>${basket[j].name}</legend>
<button id="inc-${j}" type="button">+</button>
<output id="qty-${j}">1</output>
<button id="dec-${j}" type="button">-</button>
<output id="bas-${j}">${basket[j].price}</output>
<output id="prc-${j}" class="prc">${basket[j].price}</output>
</fieldset>
`;
cart.insertAdjacentHTML('beforeend', details);
}
function updateBill(e) {
if (e.target.type === 'button') {
var ID = e.target.parentElement.id;
var idx = ID.split('-').pop();
var dir = e.target.id.split('-').shift();
var qty = x.namedItem(`qty-${idx}`);
var bas = x.namedItem(`bas-${idx}`);
var prc = x.namedItem(`prc-${idx}`);
var sum = x.total;
var quantity = parseInt(qty.value, 10);
var base = parseFloat(bas.value).toFixed(2);
var price = parseFloat(prc.value).toFixed(2);
var total = parseFloat(sum.value).toFixed(2);
if (dir === "inc") {
quantity++;
qty.value = quantity;
prc.value = quantity * base;
} else {
quantity--;
if (quantity <= 0) {
quantity = 1;
}
qty.value = quantity;
prc.value = quantity * base;
}
}
var prices = Array.from(document.querySelectorAll('.prc'));
var numbers = prices.map(function(dig, idx) {
return parseFloat(dig.value);
});
var grandTotal = numbers.reduce(function(acc, cur) {
return acc + cur;
}, 0);
x.total.value = grandTotal.toFixed(2);
}
</script>
</body>
</html>

Troubleshooting jQuery input value

I've been troubleshooting a form that allows for users to select an amount or select other amount. The other amount when click changes the amount to $5. I'm trying to get get it so that when the user changes the amount from 5 that it changes the amt = to user input.
The bit of script that changes it
function setDonrAmount(id){
var amt = id.substring(10);
if( amt == 'Other' ){ amt = 5}
var others = id.substring(0,10);
$("button[id*="+others+"]").removeClass('active');
$('button#'+id).addClass('active');
$('input#donrAmountInput').val(amt);
$('input#donrAmountInput').change();
$('#donrReviewAmount').html(amt);
}
For reference here's the actual form. Help would be greatly appreciated. https://secure.pva.org/site/c.ajIRK9NJLcJ2E/b.9381225/k.8668/FY2016_March_Congressional/apps/ka/sd/donorcustom.asp
I've made a few updates to setDonrAmount() to handle custom donations.
$("#donrAmountButtons").on("click", function() {
var amt = id.substring(10);
setDonrAmount(amt);
$('#donrAmount' + amt).addClass('active');
});
$("#donrAmountInput").on("focusout", function() {
setDonrAmount($("#donrAmountInput").val());
$('#otherAmount button').addClass('active');
});
function setDonrAmount(val) {
var amt = val || 0;
if (amt.length < 2 && amt < 5)
amt = 5;
$('input#donrAmountInput').val(amt);
$('input#donrAmountInput').change();
$('#donrReviewAmount').html(amt);
$('#donrAmountButtons .active').removeClass('active');
}
You'll want to check for a keyup event on the input field, and make the selection when that happens. Something like... (inside document.ready)
$('#donrAmountInput').on('keyup', function() {
// Probably best to strip any non-numeric characters from the field here
amt = $(this).val();
// pseudo-code: $('#donrAmountOther').select(); as I'm not sure about jQuery's handling of input group buttons. There'll be a way to select this one somehow!
});

jquery toggle depending on value of textbox

This must be very simple, but I just got stuck with this... I have a list of products with an input field for the quantity and next to it a column with the prices. The prices are displaying two values. One of them is hidden. If the value of the input field goes over a certain value, it should hide the other price.
Example:
(input: [], show price1price2 )
input: [2], show <span class=one>price1</span>
input: [5], show <span class=one>price1</span>
input: [8], show <span class=two>price2</span>
input: [9], show <span class=two>price2</span>
My code so far (example, since I show just 2 products):
<form name="formname" action="formaction" method="post">
prod 1<input type="text" value="" class="class1" size="3"><span class="one">$1.00</span><span class="two">$2.00</span>
prod 2<input type="text" value="" class="class1" size="3"><span class="one">$4.00</span><span class="two">$6.00</span>
</form>
And at the bottom in script tags:
$(document).ready(function() {
if($('input.class1').val() > 5) {
$('.one').show();
$('.two').hide();
}
});
What am I missing? The form name perhaps?
This is just the first part...
My other question would be.. how can I make it so that if the sum of all the input fields (with class1 as class) is more than 5, do the same. (So now depending on the sum of the input fields, rather than each individual one)
var inputs = $('input[class^=class]'); // get inputs which class name
// start with "class" eg: class1, class2..
// fire blur event
// you may use keyup or something else
inputs.on('blur', function(e) {
var val = 0;
// adding all inputs value
// of all inputs with class = blured input class
$('input.' + this.className).each(function() {
val += parseInt( this.value, 10);
});
// checking for condition
if (val > 5) {
$(this).next('.one').show().next('.two').hide();
} else {
$(this).next('.one').hide().next('.two').show();
}
});
Demo with blur
Demo with keyup
Note
Place you all jQuery codes within $(document).ready().
According to comment
See this update
Code
if (val > 5) {
$('.one').show();
$('.two').hide();
} else {
$('.one').hide();
$('.two').show();
}
Update after last comment
Just change
val += parseInt( this.value, 10);
to
val += parseInt( this.value || 0, 10); // default 0, if no value given
According to comment
How to implement above code for select box?
var selects = $('select[class^=class]'); // get selects which class name
// start with "class" eg: class1, class2..
// fire change event
selects.on('change', function(e) {
var val = 0;
// adding all selects value
$('select.' + this.className).each(function() {
val += parseInt( this.value, 10);
});
// checking for condition
if (val > 5) {
$(this).next('.one').show().next('.two').hide();
} else {
$(this).next('.one').hide().next('.two').show();
}
});
You are calling this on page load, you need to run it each time the value of the input fields is changed.
Consider wrapping your if block in this:
$('input.class1').blur(function() {
if($('input.class1').val() > 5) {
$('.one').show();
$('.two').hide();
}
});
Or something to that effect, consider using $.on().
If you want to sum them, assign each input a common class, and use $.each():
$('.common-class').each(function() {
sum = sum + $(this).val()
});
Hope this helps.

Sum a list of text boxes in jQuery

I have a form in which there are textbox(s) added dynamically using jquery.
The textbox ids is formed as an array, i.e. Quantity[0], Quantity[1], Quantity[2] ...
I want to add the numbers in these textbox(s) and display the value in another textbox named "total_quantity" preferably when the focus is shifted out of the array textbox.
How can I do it? I dont mind using jQuery or plain javascript, which ever is easier.
I would suggest giving a common class to your quantity fields such that:
<input type="text" class="quantity" onblur="calculateTotal();" />
Then you would be able to define the following function with jQuery:
function calculateTotal() {
var total = 0;
$(".quantity").each(function() {
if (!isNaN(this.value) && this.value.length != 0) {
total += parseFloat(this.value);
}
});
$("#total_quantity").val(total);
}
The onblur event will fire each time the focus is shifted from the quantity fields. In turn, this will call the calculateTotal() function.
If you prefer not to add a common class, you can use the $("input[id^='Quantity[']") selector, as Felix suggest in the comment below. This will match all the text boxes that have an id starting with: Quantity[
var Total = 0;
$("input[id^='Quantity[']").each(function() { Total += $(this).val()|0; });
$("#total_quantity").val(Total);
Use the following function, if one can't use document.getElementsByName(), as some MVC frameworks such as Struts and Spring MVC use the name attribute of a text to bind the element to a backend object.
function getInputTypesWithId(idValue) {
var inputs = document.getElementsByTagName("input");
var resultArray = new Array();
for ( var i = 0; i < inputs.length; i++) {
if(inputs[i].getAttribute("id") == idValue) {
resultArray.push(inputs[i]);
}
}
return resultArray;
}

Categories

Resources