Tutorial: More State Input
Now that we've seen how to dynamically update the state_exemptions input field, we're ready to tackle the handling of two more input fields for those states which need additional information.
In order to accomodate the different requirements of various taxing jurisdictions, we include 'miscellaneous' and 'auxiliary' input fields. These are generic floating point parameters which are sometimes needed when a tax formula requires extra information.
For example, the Alabama Income Tax withholding formula allows the employee to optionally claim a 'Personal Exemption'. This is separate from the State Exemptions field used previously, which for Alabama represents the number of dependents other than the employee's spouse.
So for Alabama, the 'miscellaneous' field is used to indicate personal exemption preferences. However, we need to convey the intent for the input field to the user. Rather than just including a "Miscellaneous:" label, we'll adjust the terminology and title to reflect the usage. In the rarer case where two additional input values are needed, we use the 'auxiliary' field in the same way.
index.html:
<!DOCTYPE html>
<html>
<body>
<form>
<label for="earnings">Earnings:</label>
<br>
<input type="text" value="1000.00" id="earnings">
<br>
<label for="payperiod">Pay Period:</label>
<br>
<select id="payperiods">
<option value="1">Yearly</option>
<option value="4">Quarterly</option>
<option value="12">Monthly</option>
<option value="24">2x monthly</option>
<option value="26">Bi-weekly</option>
<option value="52" selected="selected">Weekly</option>
<option value="260">Daily</option>
</select>
<br>
<label for="filingstatus">Filing Status:</label>
<br>
<select id="filingstatus">
<option value="0" selected="selected">Single</option>
<option value="1">Head of Household</option>
<option value="2">Qualified Widow(er)</option>
<option value="3">Married Filing Jointly</option>
<option value="4">Married Filing Separately</option>
<option value="5">Married Both Employed</option>
</select>
<br>
<label for="state">State:</label>
<br>
<select id="state"></select>
<br>
<div id="state_div">
<label for="state_exemptions" id="state_exemptions_label">
State Exemptions:</label>
<br>
<input type="text" value="0" id="state_exemptions">
<br>
<div id="state_miscellaneous_div">
<label for="state_miscellaneous"
id="state_miscellaneous_label">
Miscellaneous:</label>
<br>
<input type="text" value="0" id="state_miscellaneous">
<br>
</div>
<div id="state_auxiliary_div">
<label for="state_auxiliary" id="state_auxiliary_label">
Auxiliary:</label>
<br>
<input type="text" value="0" id="state_auxiliary">
<br>
</div>
</div>
<button onclick="compute(); return false;">Compute</button>
<hr>
<h5>Output</h5>
<br>
<label for="out_federal">Federal:</label>
<br>
<input type="text" id="out_federal" value="0.00" disabled>
</form>
</body>
<script>
: (Javascript goes here)
</script>
</html>
Now we've added two fields, with corresponding labels: state_miscellaneous and state_auxiliary .
Each of these will be dynamically hidden if not needed, and if shown will be given a proper label and title.
Javascript:
document.addEventListener("DOMContentLoaded", function() {
loadStates();
});
function compute() {
var earnings = getFloat('earnings');
var filingstatus = getInt('filingstatus');
var payperiods = getInt('payperiods');
getFederal(earnings, filingstatus, payperiods);
}
function getFederal(earnings, filingstatus, payperiods) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4) {
let res = JSON.parse(this.responseText);
if (res.status == "ok") {
var out_federal = document.getElementById('out_federal');
out_federal.value = res.value.toFixed(2);
}
}
};
var uri = "http://localhost:8000/taxamount/federal income tax"
+ "?earnings=" + earnings.toString()
+ "&filingstatus=" + filingstatus.toString()
+ "&payperiods=" + payperiods.toString();
xhttp.open("GET", uri, true);
xhttp.send();
}
function loadStates() {
var select = document.getElementById('state');
var states = [["AL", "Alabama"], ["AK", "Alaska"], ["AZ", "Arizona"],
["AR", "Arkansas"], ["CA", "California"], ["CO", "Colorado"],
["CT", "Connecticut"], ["DE", "Delaware"], ["FL", "Florida"],
["GA", "Georgia"], ["HI", "Hawaii"], ["ID", "Idaho"],
["IL", "Illinois"], ["IN", "Indiana"], ["IA", "Iowa"],
["KS", "Kansas"], ["KY", "Kentucky"], ["LA", "Louisiana"],
["ME", "Maine"], ["MD", "Maryland"], ["MA", "Massachusetts"],
["MI", "Michigan"], ["MN", "Minnesota"], ["MS", "Mississippi"],
["MO", "Missouri"], ["MT", "Montana"], ["NE", "Nebraska"],
["NV", "Nevada"], ["NH", "New Hampshire"], ["NJ", "New Jersey"],
["NM", "New Mexico"], ["NY", "New York"], ["NC", "North Carolina"],
["ND", "North Dakota"], ["OH", "Ohio"], ["OK", "Oklahoma"],
["OR", "Oregon"], ["PA", "Pennsylvania"], ["RI", "Rhode Island"],
["SC", "South Carolina"], ["SD", "South Dakota"],
["TN", "Tennessee"], ["TX", "Texas"], ["UT", "Utah"],
["VT", "Vermont"], ["VA", "Virginia"], ["WA", "Washington"],
["WV", "West Virginia"], ["WI", "Wisconsin"], ["WY", "Wyoming"]];
states.forEach(function(state) {
var option = document.createElement('option');
option.setAttribute('value', state[0]);
option.appendChild(document.createTextNode(state[1]));
select.appendChild(option);
});
select.addEventListener("change", function() {
updateStateDiv(select);
});
updateStateDiv(select);
}
function updateStateDiv(state_select) {
var div = document.getElementById("state_div");
getProperties(state_select.value, function (response) {
if (response.status == "ok") {
var tax_props = response.value;
var div = document.getElementById("state_div");
if (!tax_props.has_tax) {
div.style.display = "none";
state_select.setAttribute("title", "No state tax");
} else {
div.style.display = "block";
state_select.setAttribute("title", "");
updateStateInput(tax_props);
}
} else {
alert(response.value);
}
});
}
function updateStateInput(tax_props) {
var lbl = document.getElementById("state_exemptions_label");
var input = document.getElementById("state_exemptions");
var misc_div = document.getElementById("state_miscellaneous_div");
var misc_lbl = document.getElementById("state_miscellaneous_label");
var misc = document.getElementById("state_miscellaneous");
var aux_div = document.getElementById("state_auxiliary_div");
var aux_lbl = document.getElementById("state_auxiliary_label");
var aux = document.getElementById("state_auxiliary");
if (tax_props.stateexemptions_tag == ""){
lbl.innerHTML = "State Exemptions:";
} else {
lbl.innerHTML = "State " + tax_props.stateexemptions_tag + ":";
}
if (tax_props.stateexemptions_instructions == ""){
input.setAttribute("title", "");
} else {
input.setAttribute("title", tax_props.stateexemptions_instructions);
}
if (tax_props.miscellaneous_tag == ""){
misc_div.style.display = "none";
} else {
misc_div.style.display = "block";
misc_lbl.innerHTML = tax_props.miscellaneous_tag + ":";
}
if (tax_props.miscellaneous_instructions == ""){
misc.setAttribute("title", "");
} else {
misc.setAttribute("title", tax_props.miscellaneous_instructions);
}
if (tax_props.auxiliary_tag == ""){
aux_div.style.display = "none";
} else {
aux_div.style.display = "block";
aux_lbl.innerHTML = tax_props.auxiliary_tag + ":";
}
if (tax_props.auxiliary_instructions == ""){
aux.setAttribute("title", "");
} else {
aux.setAttribute("title", tax_props.auxiliary_instructions);
}
}
function getProperty(taxname, property, callback) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4) {
let res = JSON.parse(this.responseText);
callback(res);
}
};
var uri = "http://localhost:8000/tax/" + taxname
+ "?field=" + property;
xhttp.open("GET", uri, true);
xhttp.send();
}
function getFloat(id) {
var elem = document.getElementById(id).value;
var f = parseFloat(elem);
if (isNaN(f)) {
return 0.00;
} else {
return f;
}
}
function getInt(id) {
var elem = document.getElementById(id).value;
var f = parseInt(elem);
if (Number.isInteger(f)) {
return f;
} else {
return 0;
}
}
We've renamed updateExemptionsLabel to the now more appropriate updateStateInput function.
In addition, we've added logic to updateStateInput which dynamically handles miscellaneous and auxiliary properties when needed.
Note that we can determine whether either miscellaneous or auxiliary is required for a tax by checking its corresponding '_tag' or '_instructions' property. If either has a non-empty string value, the field is needed.
Result:
Now when you select different states, you'll see additional fields changing.