Forum Moderators: open

Message Too Old, No Replies

Proper Use of getElementById()

         

SeanF

1:24 pm on Dec 6, 2020 (gmt 0)

5+ Year Member Top Contributors Of The Month



Hi:

I am having trouble setting and reading element values by ID in an html form using Javascript and Ajax. Perhaps I'm going about it all wrong or perhaps I'm misinterpreting the getElementById() method: I will try to describe the situation without posting reams of code but I have also included abbreviated code at the bottom of this post if anyone has time to read it.

Intent:
- I have an HTML form (in PHP) which is used to calculate the price of a product based on three variables
- The price is based on area and is different depending on which product is selected
- There are multiple lines in the form, allowing the user to select multiple items
- The user inputs length and width and selects a product, The form will calculate area (length x width), look up unit price and calculate cost (area x price)

Function:
- The user inputs length and width in two input fields as such:
<input type='text' name='boat_loa[$i]' id='boat_loa$i' size=8 maxlength=8 placeholder='Feet' onkeyup='calcBoatCost($i)' />
<input type='text' name='boat_beam[$i]' id='boat_beam$i' size=6 maxlength=6 placeholder='Feet' onkeyup='calcBoatCost($i)' />
<input type='text' name='boat_area[$i]' id='boatAreaResult$i' />

The $i is a unique identifier for each line of product(s) selected. boatAreaResult$i is returned from the calcBoatCost($i) function

- The user selects a product from a <select> field in which the options are populated by a MySQL lookup as such:
<select name='product' id='prod_id$i' onchange='calcBoatCost($i)'>
// iterate on each product in the query:
<option value=$product_id>$product_id $product_name $standard_price</option


- The javascript/ajax call should calculate 'area" and "cost' and return to the following fields:
<div id='uPrice$i'>$i 0.00</div>
<input type='text' name='boat_cost[$i]' id='boatCostResult$i' />


Current Result:
- Each of the form fields calls the calcBoatCost($i) as expected
- If both boat_loa$i and boat_beam$i are numbers (!isNaN) area is returned correctly
- if a product is selected from the <select> field, 'iPrice' is returned correctly.
- this value is assigned to a new variable as such:
var unitPrice = document.getElementById('uPrice' + rowNumber).value;
alert ('UP: ' + unitPrice );

- But the alert shows "UP: undefined" so the subsequent calculation (var boatCost = boatArea * parseFloat(unitPrice) ;
) fails
- This seems to be where I am falling down, but perhaps my error is elsewhere.

Code:
I have tried to summarize with only snippets above but if that is not clear and someone has time or inclination to look at more: here is some longer but still abbreviated code:

Form (partial):
<form method='post' action='$_SERVER[PHP_SELF]'>
// snip
<table>
<tr><th>LOA</th><th>Beam</th><th>Draft</th><th>product</th><th>test</th><th>Cost</th></tr>
// loop for multiple lines
<td><input type='text' name='boat_loa[$i]' id='boat_loa$i' size=8 maxlength=8 placeholder='Feet' onkeyup='calcBoatCost($i)' /></td>
<td><input type='text' name='boat_beam[$i]' id='boat_beam$i' size=6 maxlength=6 placeholder='Feet' onkeyup='calcBoatCost($i)' /></td>
<td><input type='text' name='boat_area[$i]' id='boatAreaResult$i' /></td>
<td><select name='product' id='prod_id$i' onchange='calcBoatCost($i)'>
// MySQL call to get products and loop through each found, $i is the counter
<option value=$product_id>$product_name</option>
// end of options loop
<td><div id='uPrice$i'><b>$i 0.00</b></div></td>
<td><input type='text' name='boat_cost[$i]' id='boatCostResult$i' /></td>
</table>


JavaScript Code (abbreviated):
<script type=\"text/javascript\" src=\"//code.jquery.com/jquery-1.10.2.js\"></script>
<script type=\"text/javascript\">
function calcBoatCost(rowNumber) {
var boat_loa = document.getElementById('boat_loa' + rowNumber).value;
var boat_beam = document.getElementById('boat_beam' + rowNumber).value;
var prod_id = document.getElementById('prod_id' + rowNumber).value;
alert ('Prod ID: ' + prod_id );

var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById(\"uPrice\" + rowNumber).innerHTML = this.responseText;
}
};
var str = prod_id;
alert ('STR: ' + str );
xmlhttp.open(\"GET\",\"get_product_price.php?q=\"+str,true);
xmlhttp.send();

var boatArea = parseFloat(boat_loa) * parseFloat(boat_beam);
alert ('Area: ' + boatArea );
if (!isNaN(boatArea)) {
document.getElementById('boatAreaResult' + rowNumber).value = boatArea;
}

var unitPrice = document.getElementById('uPrice' + rowNumber).value;
alert ('UP: ' + unitPrice );

var boatCost = boatArea * parseFloat(unitPrice) ;
if (!isNaN(boatCost)) {
document.getElementById('boatCostResult' + rowNumber).value = boatCost;
}
var numRows = '$numRows';
var x = 1;
var BoatTotal = 0;
while( x <= numRows) {
var boat_cost = document.getElementById('boatCostResult' + x).value;
if (!isNaN(parseFloat(boat_cost))) {
BoatTotal += parseFloat(boat_cost);
}
x++;
document.getElementById('boatTotal').value = BoatTotal;
var grandTotal = BoatTotal;
document.getElementById('grandTotal').innerHTML = '$'+grandTotal.toFixed(2);
}
}
</script>


get_product_price.php (abbreviated):
$price_query = "SELECT product_prices.*,
FROM show_product_prices
WHERE product_id = $q
";
$price_result = query($price_query);
$PriceArray = fetch_array($price_result);
$standard_price = $PriceArray['standard_price'];
echo "$standard_price";

SeanF

1:16 pm on Dec 8, 2020 (gmt 0)

5+ Year Member Top Contributors Of The Month



OK... I learn best by "trial and error". Soooo... after about 4 hours of trial and error I go this running the way I wanted it to. There were a number of errors: Confusion between 'innerHTML" and "value", misinterpreting that "readystate" clause executes after the page loads, which requires reordering where other lines go... and on and on and on...

But I got there!

Case solved

jpmmedia

3:35 pm on Dec 9, 2020 (gmt 0)

5+ Year Member Top Contributors Of The Month



getElementById() allows you to access a document element

For example, to check if the element exist in the DOM:

if(document.getElementById('someid')){
// do something
}

For example, you can use it to set and get a element's attributes

// set
document.getElementById('someid').setAttribute('value','somevalue');
document.getElementById('someid').value = 'somevalue';

// get
var inputValue = document.getElementById('someid').getAttribute('value');
var inputValue = document.getElementById('someid').value;

NickMNS

5:55 pm on Dec 9, 2020 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



@SEanF
As you have determined by your trial an error, there is nothing wrong with how you are using this getElementbyID, but you are using it too much.

The way the it works is that each time it is called, it loops through the DOM searching for the id. Now calling it once is perfect, you need to select the element. Calling it a second, third or more times, will start a new search each time and this can become wasteful. If you have a big page with many elements, calling it many times can become noticeable. The key is "document." each time you are searching the entire document, but if you have found your first "input" the second is likely close by, for example in the same "form". So instead you search for the "form" using getElementByID and saving the reference to a var, then you can restrict future searches to the Form node. By doing something like:

var formNode = document.getElementByID('myForm')
formNode.getElementByID('myInput'). addEventListener(..... //or whatever code you want


The other issue more generally with getElementByID() is that it selects a node based a unique identifier. Now you may think, "wait no, that is good thing!" But as I explained above it is neither good nor bad, it is good when you specifically need it. That is when you need to select a very specific element, but it becomes bad when you start attaching unique id to every element because that is the only method you know to use for selections. Writing code in this way also prevents you from reusing it as every call becomes dependent on some unique identifier.

In my example above, once you have selected the Form node, thus you already have access to all its children, all you need to do is use the aptly named parentNode.children method to select each one individually. As such you will no longer need to to call getElementID for each element.

var formNode = document.getElementByID('myForm')
var formChildren = formNode.children
for (let i = 0; i < formChildren.length; i++) {
console.log(formChildren.children[i].tagName);
}


more info here:
[developer.mozilla.org...]

I prefer using querySelector and querySelectorAll over the getElementID() method. querySelector is more versatile, you can select elements by ID just pass it "#myID", but you can also pass it a tag name "form", this will return the first instance of "form" that it finds. If you use querySelectorAll and pass it "form" it will find every instance of "form" as a node list that you can loop through as in the example above. You can also select based on class names ".myClass" and it will find every instance of that class. Finally you can combined the two:


var selectNodes = document.querySelectorAll('select .prod_id') // selects select tags with a class name .prod_id
for (let i = 0; i < selectNodes.length; i++) {
selectNodes[i].addEventListener('change', calcBoatCost)
}


This code attaches the onChange event to select elements and calls the calcBoatCost function on the "change", and it does it in one place without out any counters. Pair this with my comments in your previous post here:
[webmasterworld.com...]
and you should be good to go.

I have a few more remarks to add, unrelated to your direct question, but I will add those a later time, when I find a free moment.

jpmmedia

6:40 pm on Dec 9, 2020 (gmt 0)

5+ Year Member Top Contributors Of The Month



I usually build out with document first and then replace them later with more direct targeting of elements. Less coding in the beginning ;) Optimization process. But agree with nick here, calling less is important.

var div = document.getElementById('someid');
div.style.display = '';
div.style.backgroundColor = '';
div.style.border = '';
div.style.position = '';
div.style.top = '';
div.style.left = '';

jpmmedia

2:16 am on Dec 10, 2020 (gmt 0)

5+ Year Member Top Contributors Of The Month



Just a heads up, using it like below will give you a error:

var formNode = document.getElementById('myForm');
formNode.getElementById('myInput').addEventListener("focus",function(){
console.log(this.value);
});

formNode.getElementById is not a function
Also function names are case sensitive