I have a table on my page which is dynamically built and contains editable data fields. The table looks something like this:
<tr>
<td >1</td>
<td ><input type="number" data-id="123" value="123" /></td>
<td ><input type="number" data-id="456" value="456" /></td>
</tr>
<tr>
<td >2</td>
<td ><input type="number" data-id="321" value="321" /></td>
<td ><input type="number" data-id="654" value="654" /></td>
</tr>
I am using a jQuery .each() loop over all the tr elements, and trying to compare the values of each input to the data=id (the data-id is set at the server as equal to the initial value of the box) so I can save changed values when the user clicks a button.
My function looks something like this:
$('tr').each(function (index, element) {
var idToSave = $(element).children('.identifier').first().text();
var toSave = false;
var $cone = $(element).children('td.pup input[type=number]').first();
var $ctwo = $(element).children('td.van input[type=number]').first();
var x = $cone.text();
alert('Text: ' x);
var y = $cone.val();
alert('Val: ' y);
var z = $cone.data("id");
alert('Data: ' z);
if ($cone.text() != $cone.data("id")) {
toSave = true;
}
if (toSave) {
//Do an ajax call to the save method, passing in values
}
});
When I run my jQuery each() function, I see the identifier correctly (verified by alerts I've since removed) but the variables x, y, and z all come back as undefined. I have confirmed that the variable names are unique within my page (they are NOT x,y,z in my actual page, just this simplified version) and I have experimented with many versions of the code, including using .attr("data-id") and .dataset.id to pull the data off of my input. I feel like I'm missing something simple and obvious.
Cany anyone give any advice?
CodePudding user response:
The main problems here seems:
.children()
only travels a single level down the DOM tree (docs)
this was causing theundefined
values, since starting fromtr
you can only search for direct children (I.E. sometd
) but nottd
children asinput
.- Using
.data('id')
returns type number the first time
(because it parse as a number fromdata-id="123"
) - missing
.pup
and.van
classes in td (td.pup
,td.van
selectors are missing; there are notd
with this classes, so no inputs will be found)
Solution
- Update your
.children(selector)
method with.find(selector)
to find deep nested elements - Concat
''
to your.data('id')
to make it a string and to check against your input value. - Update your toBeSaved value by checking all the conditions you need
In the following example i used a listener for input.keyup()
to run the .each()
function on each input change
// I'm setting up a keyup listener to run our .each() every time some input get a keyup event
$('input').keyup(function() {
console.clear()
// I'm using 'tbody tr' as selector to exlude the first 'tr' (the header tr aka 'thead tr')
$('tbody tr').each(function(index, element) {
// Use .find() to search for children element
// this is because .children() method differs from .find()
// in that .children() only travels a single level down the DOM tree
var idToSave = $(element).find('.identifier').first().text();
var c1 = $(element).find('td.c1 input[type=number]').first();
var c2 = $(element).find('td.c2 input[type=number]').first();
// Check if some value needs to be saved (using .attr() to get 'data-id' value)
var toSave = c1.val() !== c1.data("id") '' || c2.val() !== c2.data('id') '';
if (toSave) {
// Optionally build queryString params for your fetch
/// Note that to do this, i added 'name' attribute in our input
const rowSerialized = $(element).find('input').filter(function(el) {
// Including only fields with updated value
const res = ($(this).data('id') '') !== $(this).val();
// Note that .data() will return number the first time
return res;
}).serialize();
// Optionally build a diff object from the serialized string
const rowDiffsObject = parseSerializedRow(rowSerialized)
console.log("Saving Row With Id: ", idToSave, "\nDiff Object: ", rowDiffsObject);
// Just for this example i'm faking save by updating the data-id
c1.data('id', c1.val());
c2.data('id', c2.val());
}
});
})
// just convert from serialized string
// to diffs object
function parseSerializedRow(rowSerialized) {
let rowDiffs = {}
rowSerialized.split('&').forEach((field) => {
const split = field.split('=');
const fieldName = split[0];
const fieldValue = split[1];
rowDiffs[fieldName] = fieldValue;
});
return rowDiffs;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table>
<thead>
<tr>
<th>Id</th>
<th>Data 1</th>
<th>Data 2</th>
</tr>
</thead>
<tbody>
<tr>
<td >1</td>
<td ><input type="number" name="data1" data-id="123" value="123" /></td>
<td ><input type="number" name="data2" data-id="456" value="456" /></td>
</tr>
<tr>
<td >2</td>
<td ><input type="number" name="data1" data-id="321" value="321" /></td>
<td ><input type="number" name="data2" data-id="654" value="654" /></td>
</tr>
</tbody>
</table>
<p>Update the values to see when some row needs to update</p>
This way you will be able to perform a save each time one of your lines is updated.
Please note that using .data() to get the initial value (data-id="123"
) will return a number instead of string. This is because i concat ''
every time i check the .data('id')