JQuery/JavaScript: Iterate through form fields, perform calculation, then submit - javascript

Right now my code is very "hard-coded" and repetitive. I'd like to know if there is a cleaner way to do the following. Ideally, I want to iterate through my forms fields with a loop and calculate the results with one statement, but I'm struggling to figure out how best to do so.
Summary: I have ten form fields, each with a distinct decimal value that a user may or may not supply. When the user hits submit, it should add the value in the input field with a value being displayed on the current HTML page, then insert into the DB.
First, I grab that value from the form input field and convert it into a number with two decimal places. I then grab the current total from the HTML and add the two numbers together. After that I inject that total back into the form input field so that it can be stored in $_POST and inserted into a database.
How can I make my code more DRY (ie, Don't Repeat Yourself)? Below are just two examples but they are exactly the same except for the element calls:
var subtotal = Number($("#housing").val());
subtotal = (subtotal).toFixed(2);
var currentTotal = $('#output-housing').html().replace("$", "");
var total = Number(subtotal) + Number(currentTotal);
$('#housing').val(total);
var subtotal = Number($("#utilities").val());
subtotal = (subtotal).toFixed(2);
var currentTotal = $('#output-utilities').html().replace("$", "");
var total = Number(subtotal) + Number(currentTotal);
$('#utilities').val(total);
I would like to iterate through my input fields like so, but I'm trying to figure out how I could display the logic inside:
var input = $('.form-expenses :input');
input.each(function() {
// Insert switch statement here?? Some other construct??
});
HTML: (Uses Bootstrap 3 classes)
FORM:
<form class="form-expenses form-horizontal" role="form" method="post" action="/profile/update">
<div class="form-group">
<label for="housing" class="control-label col-sm-3">Housing</label>
<div class="input-group input-group-lg col-sm-9">
<span class="input-group-addon">$</span>
<input type="text" class="form-control" name="housing" id="housing" />
</div>
</div>
<div class="form-group">
<label for="utilities" class="control-label col-sm-3">Utilities</label>
<div class="input-group input-group-lg col-sm-9">
<span class="input-group-addon">$</span>
<input type="text" class="form-control" name="utilities" id="utilities" />
</div>
</div>
...
<button class="btn btn-lg btn-primary btn-block" id="update-expenses" type="submit"> Update</button>
</form>
OUTPUT:
<tr>
<td>Housing</td>
<td id="output-housing">$<?php echo $total['housing']?></td>
</tr>
<tr>
<td>Utilities</td>
<td id="output-utilities">$<?php echo $total['utilities']?></td>
</tr>

Something like this should work. Assumes the same prefixing relationship of output/input ID's
$(function() {
$('form.form-expenses').submit(function() {
updateValues();
return false/* prevent submit for demo only*/
})
})
function updateValues(){
$('.form-expenses :input').not('#update-expenses').each(function(){
var $input=$(this), inputId=this.id;
var curr=$('#output-'+inputId).text().replace("$", "");
$input.val(function(i,val){
return (1*(val ||0) + 1*curr).toFixed(2);
})
});
}
DEMO
From a UI perspective, this seems very counter intuitive to change values that user just input.
To create ajax data object instead of updating the display values:
function getAjaxData(){
var ajaxData={}
$('.form-expenses :input').not('#update-expenses').each(function(){
var $input=$(this), inputId=this.id;
var curr=$('#output-'+inputId).text().replace("$", "");
ajaxData[this.name] =(1*(val ||0) + 1*curr).toFixed(2);
});
return ajaxData
}
/* in submit handler*/
$.post('path/to/server', getAjaxData(), function(response){/*do something with reponse*/})

"if I allow a user to add/remove fields, then this could get a bit sticky"
In that case, give your fields a class name. As long as that exists on added fields, they will all be calculated.
<input type="text" class="form-control calculate-me" name="housing" id="housing" />
And iterate though all, using their ids as a reference
$(".calculate-me").each(function(){
var ref=this.id;
var subtotal = Number($("#" + ref).val());
subtotal = (subtotal).toFixed(2);
var currentTotal = $('#output-' + ref).html().replace("$", "");
var total = Number(subtotal) + Number(currentTotal);
$('#' + ref).val(total);
});

Related

Google Sheets script UserForm - Send Data from Form to Spreadsheet- userform fields not handled correctly

I am new to google scripting. I have followed some tutorials online and created a user form which has 4 input:
company name, qty, agent and comment. The only goal here is to copy data from user form to spread sheet. I have written the following html and functions but data does not get populated after button add is clicked.
I know the addRowData function is working when correct input gets to it. So either I am not population rowData correctly or EventListener does not work correctly. Can anybody please help me find where the issue is?
function addNewRow(rowData) {
const currentDate=new Date();
const ss=SpreadsheetApp.getActiveSpreadsheet();
const ws=ss.getSheetByName("Results");
ws.appendRow([currentDate, rowData.companyName,rowData.qty,rowData.agentName,rowData.commentText]);
return true;
}
<div class="container">
<div>
<div class="form-group">
<label for="company-name">Company</label>
<input type="text" class="form-control" id="company-name">
</div>
<div class="form-group">
<label for="number-boxes">Number of Boxes</label>
<input type="Text" class="form-control" id="number-boxes">
</div>
<div class="form-group">
<label for="agent-name">Agent</label>
<input type="text" class="form-control" id="agent-name">
</div>
<div class="form-group">
<label for="comment-text">Comment</label>
<input type="Text" class="form-control" id="comment-text">
</div>
<button class="btn btn-primary" id="mainButton">Add</button>
</div>
</div>
<script>
function afterButtonClicked(){
var companyName = getElementById("company-name");
var qty = getElementById("number-boxes");
var agentName = getElementById("agent-name");
var commentText = getElementById("comment-text");
var rowData={companyName: companyName.value,qty: qty.value,agentName: agentName.value,commentText: commentText.value};
google.script.run.withSuccessHandler(afterSubmit).addNewRow(rowData);
}
function afterSubmit(e){
var qty = getElementById("number-boxes");
qty.value="";
}
document.getElementById("mainButton").addEventListener("click",afterButtonClicked());
</script>
</body>
I would like to propose the following modification.
Modification points:
At document.getElementById("mainButton").addEventListener("click",afterButtonClicked());, the function is run by () of afterButtonClicked() at the load of HTML. In this case, please remove ().
About getElementById("###"), in this case, please add document like document.getElementById("###").
When above points are reflected to your script, it becomes as follows. I think that your Google Apps Script works.
Modified script:
In this case, please modify your Javascript as follows.
<script>
function afterButtonClicked(){
var companyName = document.getElementById("company-name");
var qty = document.getElementById("number-boxes");
var agentName = document.getElementById("agent-name");
var commentText = document.getElementById("comment-text");
var rowData={companyName: companyName.value,qty: qty.value,agentName: agentName.value,commentText: commentText.value};
google.script.run.withSuccessHandler(afterSubmit).addNewRow(rowData);
}
function afterSubmit(e){
var qty = document.getElementById("number-boxes");
qty.value="";
}
document.getElementById("mainButton").addEventListener("click",afterButtonClicked);
</script>
References:
EventTarget.addEventListener()
Document.getElementById()

jQuery table rows won't render in within table element

I am using jQuery to add and remove table rows for a collection of forms within another form in Symfony 4. This was not easy, but eventually made it work. With a macro in Twig I can get this rendered result:
<table>
<div id="document-list" data-prototype="
<tr>
<td>
<fieldset class="form-group">
<div id="program_programDocument___name__" novalidate="novalidate">
<div class="form-group"><input type="text" id="program_programDocument___name___name" name="program[programDocument][__name__][name]" required="required" class="form-control"/>
</div>
</div>
</fieldset>
</td>
<td>
<button type="button"class="remove-collection-widget"data-list="#remove-collection-widget">Remove</button>
</td>
</tr>" data-widget-tags="<div></div>">
</div>
</table>
<button type="button" class="add-another-collection-widget" data-list="#document-list">Add document</button>
I cleaned up this code as much as possible to make it readable. All the HTML within data-prototype="...." is how it should be. My code works (ish) together with some jQuery:
jQuery('.add-another-collection-widget').click(function(e) {
var list = jQuery(jQuery(this).attr('data-list'));
// Try to find the counter of the list or use the length of the list
var counter = list.data('widget-counter') | list.children().length;
// grab the prototype template
var newWidget = list.attr('data-prototype');
// replace the "__name__" used in the id and name of the prototype
// with a number that's unique to your emails
// end name attribute looks like name="contact[emails][2]"
newWidget = newWidget.replace(/__name__/g, counter);
// Increase the counter
counter++;
// And store it, the length cannot be used if deleting widgets is allowed
list.data('widget-counter', counter);
// create a new list element and add it to the list
var newElem = jQuery(list.attr('data-widget-tags')).html(newWidget);
newElem.appendTo(list);
});
$(function() {
$(document).on("click", ".remove-collection-widget", function() {
$(this).closest("tr").remove();
});
});
The problem is, the rendered result when added more form rows is that they don't actually end up within the table. You can see for yourself (JSFiddle) the result looks alright, but in reality it's not.
I am pretty sure it has to do with my jQuery, but I am stuck now and hope some of you can point out what is wrong.
Putting a div as a direct child of a table isn't proper HTML, which is what's tripping it up.
Move id="document-list" data-prototype="... to table element
Get rid of div inside table
Change data-widget-tags to tr instead of div
Remove wrapping tr from data-prototype
Solution
jQuery('.add-another-collection-widget').click(function(e) {
var list = jQuery(jQuery(this).attr('data-list'));
var counter = list.data('widget-counter') | list.children().length;
var newWidget = list.attr('data-prototype');
newWidget = newWidget.replace(/__name__/g, counter);
counter++;
list.data('widget-counter', counter);
var newElem = jQuery(list.attr('data-widget-tags')).html(newWidget);
newElem.appendTo(list);
});
$(function() {
$(document).on("click", ".remove-collection-widget", function() {
$(this).closest("tr").remove();
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="document-list" data-prototype="
<td>
<fieldset class="form-group">
<div id="program_programDocument___name__" novalidate="novalidate">
<div class="form-group"><input type="text" id="program_programDocument___name___name" name="program[programDocument][__name__][name]" required="required" class="form-control"/>
</div>
</div>
</fieldset>
</td>
<td>
<button type="button"class="remove-collection-widget"data-list="#remove-collection-widget">Remove</button>
</td>" data-widget-tags="<tr></tr>">
</table>
<button type="button" class="add-another-collection-widget" data-list="#document-list">Add document</button>
Documentation
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table (see Permitted Content)

Add input to form if button is clilcked

I'm not really sure the best way to go about this but I've laid the framework.
Basically, I would like to add the functionality so that when my #moreItems_add button is clicked and calls the moreItems function, I simply want to add a new Input field below it, and so on. I want to limit this to 10 fields though, so I show that in the function.
The only trick is, I will be submitting all fields via ajax to save to the database, so I need to try and keep track of an ID with each.
What's the best way to continue the javascript here so that I can append an input field on button press and keep track of IDs for each?
<div class="modal-body">
<form id="Items">
<label id="ItemLabel">Item 1:</label>
<input type="text" name="Items[]">
<button id="moreItems_add" onclick="moreItems()" id="moreItems">More Items</button>
</form>
</div>
<div class="modal-footer">
<input type="submit" name="saveItems" value="Save Items">
</div>
<!-- modal JS -->
<script type="text/javascript">
function moreItems(){
var MaxItems = 10;
//If less than 10, add another input field
}
</script>
You can use the jQuery .insertBefore() method to insert elements right before "more items" button. Below is the code representing this:
var maxItems = 1;
function moreItems() {
if (maxItems < 10) {
var label = document.createElement("label");
label.id="ItemLabel"+maxItems;
label.innerHTML = "Item "+(maxItems+1)+": ";
var input = document.createElement("input");
input.type='text';
input.name = 'item'+maxItems;
$('<br/>').insertBefore("#moreItems_add");
$(label).insertBefore("#moreItems_add");
$(input).insertBefore("#moreItems_add");
maxItems++;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="modal-body">
<form id="Items">
<label id="ItemLabel">Item 1:</label>
<input type="text" name="Items[]">
<button type="button" id="moreItems_add" onclick="moreItems()" id="moreItems">More Items</button>
</form>
</div>
<div class="modal-footer">
<input type="submit" name="saveItems" value="Save Items">
</div>
Something like this should do the trick:
<!-- modal JS -->
<script type="text/javascript">
var MAX_ITEMS = 10,
added = 0;
function moreItems(){
if (added >= MAX_ITEMS) return;
var newField = document.createElement('input');
newField.type = 'text';
// TODO: Any field setup.
document.getElementById('items').appendChild(newField);
added++;
}
</script>
In terms of tracking each field with an ID, that should always be done by the back-end for data integrity and safety reasons.
some years ago I've wrote an article about making a repeated section using jQuery.
The live example is available on jsFiddle.
In the example you can find that both "add" and "remove" button are available, however you can set just the "add" button for your purpose.
The idea to limit to specific number of repeated boxes is to watch the number of repeatable elements just created in the context. The part of code to change in the live example is rows 13-18:
// Cloning the container with events
var clonedSection = $(theContainer).clone(true);
// And appending it just after the current container
$(clonedSection).insertAfter(theContainer);
There you should check if the number of repeated elements is less than <your desired number> then you will allow the item to be created, else you can do something else (like notice the user about limit reached).
Try this:
const maxItens = 10,
let itensCount = 0;
function moreItems() {
if (itensCount++ >= maxItens) {
return false;
}
let newInput = document.createElement('input');
// use the iterator to make an unique id and name (to submit multiples)
newInput.id = `Items[${itensCount}]`;
newInput.name = `Items[${itensCount}]`;
document.getElementById('items').appendChild(newInput);
return false;
}
Add type "button" to stop submiting the page (also, your button have two ID):
<button id="moreItems_add" onclick="moreItems();" type="button">More Items</button>
The submit button must be inside the form to work:
<form>
<div class="modal-body">
<div id="Items">
<label id="ItemLabel">Item 1:</label>
<input type="text" name="Items[]">
</div>
<button id="moreItems_add" onclick="moreItems()" id="moreItems">More Items</button>
</div>
<div class="modal-footer">
<button type="submit">Save Items</button>
</div>
</form>
To append itens in sequence the button must be outside of div "itens".

Increment of counter on dynamic addition of rows

I'm trying to add the rows dynamically plus auto-increment of a counter.I want to start with 1 then 2 then 3 and so on . I have added my code on plunker ,in which every time the max value is getting in first column like 4 then 1,1,2,3.Where am i going wrong ?i Want it to be 1,2,3,4.
Here is the plunker link http://plnkr.co/edit/GuDbJ3SHOPvWkHfNfd8E?p=preview
var _counter = 0;
function Add() {
_counter++;
var oClone = document.getElementById("template").cloneNode(true);
oClone.id += (_counter + "");
document.getElementById("placeholder1").appendChild(oClone);
document.getElementById("myVal").value=_counter;
}
<div id="placeholder1">
<div id="template">
<div>
Value:<input type="text" id="myVal" placeholder="1">
Quantity:<input type="text" placeholder="Qty">
<input type="button" onClick="Add()" value="Click! ">
</div>
</div>
I think it is because you have multiple divs with the id="myVal". The id attribute should be unique on the page. If not, your page will still load, but you may have unexpected behavior.
You are changing the id of the template div, but not the myVal div.
I assume you are looking for something like this:
var _counter = 0;
function Add() {
_counter++;
var oClone = document.getElementById("template").cloneNode(true);
oClone.id += (_counter + "");
document.getElementById("placeholder1").appendChild(oClone);
oClone.getElementsByClassName("myVal")[0].value = _counter;
}
<div id="placeholder1">
<div id="template">
<div>
Value:
<input type="text" class="myVal" placeholder="1">Quantity:
<input type="text" placeholder="Qty">
<input type="button" onClick="Add()" value="Click! ">
</div>
</div>
</div>
In your original you are cloning your template with the same id for the input. So when you do document.getElementById("myVal").value=_counter;, you only get the first input. I changed it to use class instead and get the input with the appropriate class that is a child of the cloned node.

javascript add comma to total price

I have a script that works pretty good for calculating a price total from checkboxes with a starting price. I got most of this code from Stackoverflow, but I need to tweak it a little.
I would like to add a comma to separate thousands in the total dollar amount. I've seen a few functions for this but don't know where to add the thousands comma function.
Here is the code:
HTML
<form action="#" method="" name="form">
<fieldset>
<div class="build-option">
<div class="build-title">King-Dome</div>
<label class="build-label"><input type="checkbox" name="nozzle" value="1500"> $1,500</label>
</div>
<div class="build-option">
<div class="build-title">Solar Package</div>
<label class="build-label"><input type="checkbox" name="nozzle" value="1800"> $1,800</label>
</div>
<div class="build-option">
<div class="build-title">Additional Awning</div>
<label class="build-label"><input type="checkbox" name="nozzle" value="900"> $900</label>
</div>
<div class="build-option">
<div class="build-title">Generator Basket</div>
<label class="build-label"><input type="checkbox" name="nozzle" value="250"> $250</label>
</div>
</fieldset>
Javascript
<script type="text/javascript">
window.onload=function() {
document.getElementsByTagName('fieldset')[0].onclick = totalizer;
};
function totalizer(e) {
var e = e? e : window.event;
var target = e.srcElement || e.target;
if(e.type=='click' && target.type=='checkbox') {
var cost = document.getElementById('cost');
var extra = parseFloat(target.value);
cost.firstChild.data = (parseFloat(cost.firstChild.data) + ((target.checked)? extra : -extra)).toFixed(2);
var parts = e.toString().split("."); //add this for comment - didn't work
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); //add this for comment - didn't work
return parts.join("."); //add this for comment - didn't work
}
}
</script>
I also need to find a script to clear form elements by click a "Clear" form button and a script that clears the form checks if the page is refreshed. I could possibly skip the clear button if I could get the script to calculate "already" checked boxes when refreshing the page.
If you check a box now, the total will update. But if you leave the check then refresh the page, the starting total is then changed )it should be $39950. If you uncheck the box that was checked before refreshing, the total is then less than what the starting total should be.
I may need to ask a another question for this second part. Main thing is to fix the comma issue first.

Categories

Resources