So the short version of this is: Can I traverse only the elements within the matched element of the selectors before the each()? Or is there a simpler way of getting what I want without an each() loop?
I thought this would be much easier, which makes me think I'm just missing some fundamental principle of element traversing with jquery.
So here's the scenario:
I have a table (and it is appropriate in this case), where each cell has a text input. The last input is read-only and is supposed to be the total sum of the other values entered on that row. I have a really messy js script for finding both the totals of each row and then the grand total of each row total.
Here's the basic HTML:
<table>
<thead>
<tr><th>Col 1</th><th>Col 2</th><th>Col 3</th><th>Total</th></tr>
</thead>
<tbody>
<tr id="row1"><td><input type="text" /></td><td><input type="text" /></td><td><input type="text" /></td><td class="total"><input type="text" readonly="readonly" /></td></tr>
<tr id="row2"><td><input type="text" /></td><td><input type="text" /></td><td><input type="text" /></td><td class="total"><input type="text" readonly="readonly" /></td></tr>
<tr id="row3"><td><input type="text" /></td><td><input type="text" /></td><td><input type="text" /></td><td class="total"><input type="text" readonly="readonly" /></td></tr>
</tbody>
</table>
The javascript will validate that the data entered is numerical, just to be clear.
So I have a event listener for each input for onchange that updates the total when the user enters data and moves to the next cell/input. Then I have a function called updateTotal that currently uses for loops to loop through each row and within that loop, each cell, and finally sets the input in the total cell to sum.
Quick note: I have included the code below to show that I'm not just looking for a hand out and to demonstrate the basic logic of what I have in mind. Please feel free to skim or skip this part. It works and doesn't need any debugging or critique.
This is what that looks like:
function updateTotal() {
table = document.getElementsByTagName("tbody")[0];
allrows = table.getElementsByTagName("tr");
grandtotal = document.getElementById("grand");
grandtotal.value = "";
for (i = 0; i < allrows.length; i++) {
row_cells = allrows[i].getElementsByTagName("input");
row_total = allrows[i].getElementsByTagName("input")[allrows.length - 2];
row_total.value = "";
for (ii = 0; ii < row_cells.length - 1; ii++) {
row_total.value = Number(row_total.value) + Number(row_cells[i][ii].value);
grandtotal.value = Number(grandtotal.value) + Number(row_cells[i][ii].value);
}
}
}
Now I am trying to re-write the above with jquery syntax, but I'm getting stuck. I thought the best way to go would be to use each() loops along the lines of:
function findTotals() {
$("tbody tr").each(function() {
row_total = 0;
$($(this) + " td:not(.total) input:text").each(function() {
row_total += Number($(this).val());
});
$($(this) + " .total :input:text").val(row_total);
});
}
But using $(this) doesn't seem to work in the way I thought. I read up and saw that the use of $(this) in an each loop points to each matched element, which is what I expected, but I don't get how I can traverse through that element in the each() function. The above also leaves out the grand_total bit, because I was having even less luck with getting the grand total variable to work. I tried the following just to get the row_totals:
$($(this).attr("id") + " td:not(.total) input:text").each(function() {
with some success, but then managed to break it when I tried adding on to it. I wouldn't think I'd need each row to have an id to make this work, since the each part should point to the row I have in mind.
So the short version of this is: Can I use the each loop to traverse only the elements within the matches, and if so, what is the correct syntax? Or is there a simpler way of getting what I want without an each loop?
Oh, one last thought...
Is it possible to get the numerical sum (as opposed to one long string) of all matched elements with jquery? I'll research this more myself, but if anyone knows, it would make some of this much easier.
You are trying to set your context incorrectly try this:
function findTotals() {
$("tbody tr").each(function() {
row_total = 0;
$("td:not(.total) input:text",this).each(function() {
row_total += Number($(this).val());
});
$(".total :input:text",this).val(row_total);
});
}
For more information about the context check out the jquery docs: http://docs.jquery.com/Core/jQuery#expressioncontext
There can be two parameters for a selector. The second parameter is the context htat that the search is to take place in. Try something like the following:
$('#tableID tbody tr).each(function(){
//now this is a table row, so just search for all textboxes in that row, possibly with a css class called sum or by some other attribute
$('input[type=text]',this).each(function(){
//selects all textbosxes in the row. $(this).val() gets value of textbox, etc.
});
//now outside this function you would have the total
//add it to a hidden field or global variable to get row totals, etc.
});
Related
I have a big table that takes inputs, then I use Javascript to take the values from those inputs - I give them the save Tag so that I can collect all of them and get them into an array. Then I do some calculations and end up with another array with data that I want to add to my table. My question is: Is there any easy way to add this data to the TDs that are next to the input TDs? I was looking for something simple like when I use document.getElementsByTagName() but reversed so it actually adds values to all the elements that have that tag.
Not sure if I was explicit enough...
(Disclaimer: I'm not even sure if the way i'm doing these calculations is correct)
<head>
<title>calculadora v2.0</title>
<meta charset="UTF-8">
<script type="text/javascript">
var Nivel= [];
var Pontos_Base=[10,16,20,24,10,10,42,512,19,0,24,10,6,6,6,5,6,5,8];
var Pontos_Finais=[];
function asseb_calc(){
var Nivel = document.getElementsByTagName("asseb_edificios");
for (var i = 0; i < Nivel.length; i++) {
var x= Math.round(Pontos_Base[i]*Math.pow(1.2,Nivel[i]-1));
Pontos_Finais.push(x);
}
}
</script>
<body>
<table><tr><th>Edifício</th><th>Nível</th><th>Pontos</th><th >Fazenda Ocupada</th></tr>
<tr><td> Edificio Principal</td><td><input onchange="asseb_calc()" type="number" class="asseb_edificios"></td><td class="asseb1" class="center">0</td><td class="center">0</td></tr>
<tr><td> Quartel</td><td><input onchange="asseb_calc()" type="number" class="asseb_edificios"></td><td class="asseb1" class="center">0</td><td class="center">0</td></tr>
<tr><td> Estábulo</td><td><input onchange="asseb_calc()" type="number" class="asseb_edificios"></td><td class="asseb1" class="center">0</td><td class="center">0</td></tr>
<tr><td> Oficina</td><td><input onchange="asseb_calc()" type="number" class="asseb_edificios"></td><td class="asseb1" class="center">0</td><td class="center">0</td></tr></table>
Tried erasing all the CSS from it and some extra rows it had.
The idea was for the values from Pontos_Finais to go to TDs that have class="asseb1" as their class.
Sorry for bad coding I'm trying learning on my own :p
onchange="asseb_calc()" is not the best way to handle an event but using this approach you can do the following onchange="asseb_calc(this)" where this will represent the html element of the event i.e. your input tag. You have to change your function definition to:
function asseb_calc(element){
// your calculating code here
// ...
// you have your input element so you can change it,
// or change its parent, or find the parent `tr`
// i.e you have your reference point
}
Yes. You use the parentNode and "nextSibling" attributes to go up to the input's TD parent, and then to the next TD.
function asseb_calc(){
var Nivel = document.getElementsByTagName("asseb_edificios");
for (var i = 0; i < Nivel.length; i++) {
var x= Math.round(Pontos_Base[i]*Math.pow(1.2,Nivel[i]-1));
Pontos_Finais.push(x);
Nivel[i].parentNode.nextSibling.innerHTML = x;
}
}
There is a table with some input and select fields in a row. I want to check if all input and select fields of an row have a value. This is how I would think to do that, but do I have to use closest and find? I think this is not optimal.
HTML
<table>
<tr>
<td><select><option></option><option>Select anything</option></td>
<td><input type="text" name="field1"></td>
<td><input type="text" name="field2"></td>
</tr>
<tr>
<td><select><option></option><option>Select something</option></td>
<td><input type="text" name="field3"></td>
<td><input type="text" name="field4"></td>
</tr>
</table>
JS
'change #table input, change #table select': function(event) {
var $this = $(event.currentTarget),
$row = $this.closest('tr'),
$elements = $row.find('input, select');
var empty = false;
$elements.each(function(index) {
if (!$(this).val()) empty = true;
});
if (empty)
console.log('some value is missing')
else {
console.log('valide');
// do something with values
}
}
There are really two questions here:
Most optimal method to select all inputs in a table row
Ensure all the inputs have a value
For the first question there is a subliminal side to that. Ensure that it IS an input and then select it within the context of the current row of the changed input.
First off, jQuery uses the Sizzle (https://sizzlejs.com/) engine under the covers for selection. One thing to be aware of is the "right to left" processing of the selector string by that engine.
Thus the most optimal selection is somewhat browser specific but the fastest way to select is an ID followed in modern browsers by a class. Some older browsers do not select by class as well but let's leave that for your research.
Selection: Bad way to do stuff
So given that, let's look at a complex selector that you might use:
'div.mycontainer div.mytablecontainer>table#mytable.mytableclass tr td select, div.mycontainer div.mytablecontainer>table#mytable.mytableclass tr td input'
First off DO NOT USE THAT. Now to explore why not: Remember we talked about the "right to left" selector processing? For discussion let us narrow down out selector to the last part:
"div.mycontainer div.mytablecontainer>table#mytable.mytableclass tr td input"
What this does then in starting on the right:
"find all the inputs in the DOM",
use that list of those inputs, "find all the inputs in a td element
use those td elements, find all those in a tr
find all those tr in a .mytableclass element
find all those in an element with an id of mytable (remember this ID MUST be unique)
Now keep going, find that single element id that is a table element
That is an immediate child of an element with classmytablecontainer
That is a DIV element div
That is a child of an element with class mycontainer
That is a DIV element div
Whew that's a lot of work there. BUT we are NOT DONE! We have to do the same thing for the OTHER selector in there.
Selection: Better way to do stuff
NOW let's do this better; first off let's leverage the modern browser class selector by adding a class to all our "scoped" inputs - things we want to check for entry.
<input class="myinput" />
It does really need a type="" attribute but ignore that for now. Let's use that.
$('#mytable').find('.myinput');
What this does is:
Select the element with ID of 'mytable' which is the FASTEST selector in all browsers; we have already eliminated those 47 other tables in our DOM.
Find all the elements with a class of class="myinput"; within that table; in modern browsers this is also very fast
DONE. WOW! that was SO much less work.
Side note on the .find() instead of "#mytable input"
Remember our right to left once again? Find all inputs in the DOM, then narrow to those inputs we found that are in that table NO STOP THAT right now.
Or (better likely) "#mytable .myinput"
SO our "rules" of selecting a group of elements are:
Use an ID to limit scope to some container if at all possible
Use the ID by itself NOT part of a more complex selector
FIND elements within that limited scope (by class if we can)
Use classes as modern browsers have great selection optimization on that.
When you start to put a space " " or ">" in a selector be smart, would a .find() or .children() be better? In a small DOM perhaps maintenance might be easier, but also which is easier to understand in 4 years?
Second question: not specific but still there
You cannot simply globally use !$(this).val() for inputs.
For a check box that is invalid. What about radio buttons? What about that <input type="button" > someone adds to the row later? UGH.
SO simply add a class to all "inputs" you DO wish to validate and select by those:
<input type="text" class="validateMe" />
<select class="validateMe" >...
Side note you MIGHT want to sniff the TYPE of the input and validate based upon that: How to get input type using jquery?
EDIT: Keep in mind your validation input MIGHT have a "true/false" value so then this might fail: !$(this).val() (radio buttons, checkbox come to mind here)
Some code and markup:
<table id="mytable">
<tr>
<td>
<select class="myinput">
<option></option>
<option>Select anything</option>
</select>
</td>
<td>
<input class="myinput" type="text" name="field1" />
</td>
<td>
<input class="myinput" type="text" name="field2" />
</td>
</tr>
<tr>
<td>
<select class="myinput">
<option></option>
<option>Select something</option>
</select>
</td>
<td>
<input class="myinput" type="text" name="field3" />
</td>
<td>
<input class="myinput" type="text" name="field4" />
</td>
</tr>
</table>
<div id="results">
</div>
probably NOT want a global (namespace the "selectors")
var selectors = '.myinput';
$('#mytable').on('change', selectors, function(event) {
var $this = $(event.currentTarget),
$row = $this.closest('tr'),
$elements = $row.find(selectors);
var $filledElements = $elements.filter(function(index) {
return $(this).val() || this.checked;
});
var hasEmpty = $filledElements.length !== $elements.length
var rowIndex = $row.index();
$('#results').append("Row:" + rowIndex + " has " + $filledElements.length + ' of ' + $elements.length + ' and shows ' + hasEmpty + '<br />');
if (hasEmpty)
console.log('some value is missing');
else {
console.log('valide');
// do something with values
}
});
AND something to play with: https://jsfiddle.net/MarkSchultheiss/fqadx7c0/
If you're only selecting on particular element with knowing which parent to select with, you should try using .filter() to filter out only element that did't have a value like following :
$('button').click(function() {
var h = $('table :input').filter(function() {
return $(this).val() == "" && $(this);
}).length;
alert(h);
});
DEMO
I did this plunk
https://plnkr.co/edit/q3iXSbvVWEQdLSR57nEi
$(document).ready(function() {
$('button').click(function() {
var table = $('table');
var rows = table.find('tr');
var error = 0;
for (i = 0; i < rows.length; i++) {
var cell = rows.eq(i).find('td');
for (a = 0; a < cell.length; a++) {
var input = cell.eq(a).find(':input');
if (input.val() === "") {
input.css("border", "solid 1px red");
error++;
} else {
input.css("border", "solid 1px rgb(169, 169, 169)");
}
}
}
if (error > 0){
alert('Errors in the form!')
return false;
} else {
alert('Form Ok!')
return true;
}
})
})
Simple Jquery validation, searching all the inputs (including selects), if it's null, increment the error counter and change class. If the error counter is > 0, alert error and return false;
Maybe isn't the best solution, but it sure can help get started.
I have a table of items available for purchases which I am displaying on the site. I am using mysql to fetch all the items and display them in a table. Among others, the table contains this:
<input type="hidden" name="price" id="price'.$id.'"> //id is one part of MySQL query results
<input type="text" name="count_'.$id.'">
All this is displayed for around 200 items with ID being not completely in sequence (I found some JavaScript code that used for (i = 0; i < rows.length; i++) {}, however, with my IDs not being in a sequence, this is not a good option for me).
I would like to display a total of an order using JavaScript and I am not experienced when it comes to JS. I would be very thankful for your advices.
You coul duse jQuery:
function orderTotal()
{
var total=0;
$('input[name="price"]').each(function(){
var price = parseFloat($(this).val());
var amount = parseFloat($('input[name="count_'+$(this).attr('name').substring(5)+'"]').val());
total += price+amount;
});
return total;
}
Consider adding a class to each element that you want to count and see the answer below on stackoverflow. You should be able to have a counter for each occurrence of the class and show this variable in the html
How to getElementByClass instead of GetElementById with Javascript?
<div class="item"> ... <your inputs> ... </div>
I suggest you wrap them in another element, lets use div. Add a class to that, lets say moneyline
<div class="moneyline">
<input class="price" type="hidden" name="price" id="price'.$id.'"> //id is one part of MySQL query results
<input class="quantity" type="text" name="count_'.$id.'">
</div>
Im going to give the example with jQuery, and some button to trigger it:
$('#someButton').on('click', function(){
var total = 0;
$('.moneyline').each(function(){
var price = parseInt($(this).find('.price'), 10);
var quantity = parseInt($(this).find('.quantity'), 10);
total+= quantity*price;
});
alert( total );
});
Please help me out on this. I have Javascript like the following:
function calc() {
var table = document.getElementById(tableNum);
var rowCount = table.rows.length;
for (var i = 0; i < rowCount; i++) {
var totalNum[i] = document.formNum.txt1[i].value * document.formNum.txt2[i].value;
document.getElementById('totalCalc[' + i + ']').innerHTML = totalNum;
}
}
And HTML like this:
<table id="tableNum">
<form name="formNum" action="" id="formNum">
<tr>
<td><input type="text" name="txt1[]" onkeyup="calc()"/></td>
<td><input type="text" name="txt2[]" onkeyup="calc()"/></td>
<td><span id="totalCalc[]"></span></td>
</tr>
</form>
</table>
The number of input fields is unknown. No error, but totalCalc field is empty. Please tell me what I have done wrong. Thanks.
EDIT: I'm sorry, I forgot to mention both the input fields are in a table. Please check the edited code. Thanks.
EDIT: I'm actually working on a demo which the number of table row is defined by user, by clicking insert row button.
EDIT: Thanks Travis for the code. After a few changes, the code is working now. But only the first row is working. I'm thinking to get the length of the row and to use for loop for the text fields. <input type="text" name="txt1[<?php echo $rowLength;?>]" onkeyup="calc()"/> Does anyone have other ideas? Thanks.
The first thing seems wrong is
document.getElementById(tableNum);
should be
document.getElementById("tableNum");
Secondly,
var totalNum[i] =
should be
var totalNum =
Also, its not working, you can find it out quickly by debugging through firebug or chrome's integrated developer tool. Which will help you for syntax verification as well.
Here is what is going on.
HTML first
If you are going to reference these by indices, then use proper indices, like this
name="txt1[0]"
name="txt2[0]"
<span id="totalCalc[0]">
Javascript
document.getElementById(tableNum);
getElementsById expects a string, so this should be
document.getElementById("tableNum");
Since you are iterating, you only need one of these variables since it is immediately used (not a whole array):
var totalNum = instead of var totalNum[i]
When you access the form using dot notation, the brackets in the name messes that up, you need to do it like this:
document.formNum["txt1["+i+"]"].value instead of document.formNum.txt1[i].value
Vuala
When you make these minor changes, the code you used will actually produce proper results :) See this working demo: http://jsfiddle.net/69Kj7/ , also, here is a demo with 2 rows: http://jsfiddle.net/69Kj7/1/
For reference, this is the code in the demo:
html:
<table id="tableNum">
<form name="formNum" action="" id="formNum">
<tr>
<td><input type="text" name="txt1[0]" onkeyup="calc()"/></td>
<td><input type="text" name="txt2[0]" onkeyup="calc()"/></td>
<td><span id="totalCalc[0]"></span></td>
</tr>
</form>
</table>
js:
function calc() {
var table = document.getElementById("tableNum");
var rowCount = table.rows.length;
for (var i = 0; i < rowCount; i++) {
var totalNum = document.formNum["txt1["+i+"]"].value * document.formNum["txt2["+i+"]"].value;
document.getElementById('totalCalc[' + i + ']').innerHTML = totalNum;
}
}
if you wants to work with pure java script and here is the logical code
html
<form name="formNum" id="formNum" action="" >
<input type="text" name="foo[]" onkeyup="calc()" value="5"/>
<input type="text" name="foo[]" onkeyup="calc()" value="12"/>
<span id="totalCalc"></span>
</form>
js
var inputs = formNum["foo[]"];
var total = 1;
alert(inputs.length);
for (var i = 0; i < inputs.length; i++) {
total *= inputs[i].value;
}
alert(total);
working DEMO
I've figured out how to solve the problem. Just insert array after totalCalc, but not within totalCalc.
Thank you guys so much for helping me out :)
I am taking user input from a form and trying to create a table from that input. The input should determine the number of rows and columns. I have tried the following but I am getting nothing. I am a bit stumped. Any help appreciated.
JS
function makeChart(){
var table = document.createElement("table");
var taskName = document.getElementById("taskname").value + "</br>";
var numDays = document.getElementById("days").value + "</br>";
var howOften = document.getElementById("times").value + "</br>";
var rows=table.insertRow(howOften);
var cols=rows.insertCell(numDays);
document.getElementById("holdTable").appendChild(table);
table.appendChild(rows);
table.appendChild(cols);
}
HTML
<div id="holdTable">
<form id="chartInput">
<label for="taskname">Task</label>
<input id="taskname" type="text" placeholder="Enter the task name here"> <br>
<label for="days">How many days</label>
<input id="days" name="days" type="number" min="1" max="7"> <br>
<label for="times">How many times a day</label>
<input id="times" name="times" type="number" min="1" max="4"> <br>
<input id="createChart" type="button" value="Make the chart" onClick="makeChart();"> <br>
</form>
</div>
I think you need to use loop.
for(var i = 1;i<=howOften;i++)
{
var row = table.insertRow(-1)
for(var i = 1;i<=numDays;i++)
{
row.insertCell(-1)
}
table.appendChild(row);//edited
}
Try using this in place of this 2 lines:
var rows=table.insertRow(howOften);
var cols=rows.insertCell(numDays);
EXPLANATION
insertRow function inserts new table row(<tr>) in at index position specified in brackets. It inserts only one row. So if index is 0 <tr> will be appended to the beggining of the table as a first row. Similarly -1 appends <tr> as the last row in the table.
insertCell function inserts cell(<td>) in at index position of the row(<tr>). Same as above -1 means it adds new cell at the last position in the row.
Each row than has to be appended to the table or stored in an array for later use therefore I added table.appendChild(row); which adds each consecutive row to the array.
table.insertRow and tableRow.insertCell take, as their parameters, the index at which you wish to do the insertion. You are, instead, passing a string which contains your field values appended to an HTML BR tag (a malformed one at that).
Furthermore, to add text to an element you need to create text nodes with document.createTextNode and append those nodes to the element in question.
Finally, cols should be a child of rows, not of table. So your line, table.appendChild(cols) is in error as well.
Beyond those comments, I cannot go further. I am not sure what exactly you're looking for in the outputted table structure. It seems to me you are wanting one row with two columns, but Dharman's answer shows that he reads it as you wanting multiple rows. Consider updating your question with some markup showing the desired table.
Edit: Using your comment for more information, I wrote the following code demo for you to try: http://jsfiddle.net/Kkb7n/ . For the sake of demonstration, I have it use the "task name" as the text content of the created cells.
Modified JS in case JSFiddle is down:
var makeChart = function () {
var table = document.createElement('table'),
taskName = document.getElementById('taskname').value,
numDays = document.getElementById('days').value, //columns
howOften = document.getElementById('times').value, //rows
row,
r,
col,
c;
for (r = 0; r < howOften; r++) {
row = table.insertRow(-1);
for (c = 0; c < numDays; c++) {
col = row.insertCell(-1);
col.appendChild(document.createTextNode(taskName));
}
}
document.getElementById('holdTable').appendChild(table);
};