Calculation function dead end

Recognising my weaknesses in webdesign, I got someone to convert a form I had created in Excel to a webpage, it was a disaster. I tried fixing it myself with my limited knowledge and plenty of searching to no avail, so I started again but have run into problems with the calculations. If anyone could help me with one I reckon I can do the rest. None of the examples I have found seem to do what I need or am I trying to do the wrong thing?

This is my input with basepay_ being the result from the calculation.

<div class="form-row">
    <div class="col-3">
		<label for="hours_worked"><p class="font-weight-bold">Enter Hours Worked</p></label>
		</div>
	  <div class="col">
      <input type="text" class="form-control" placeholder="Enter Hours Worked">
       </div>
	  <div class="col">
      <input type="text" class="form-control">
       </div>
  </div>
	<div class="form-row">
    <div class="col-3">
		<label for="hourly_rate">Enter Hourly Rate</label> 
		</div>
    <div class="col">
      <input type="text" class="form-control" placeholder="Enter Hourly Rate">
    </div>
    <div class="col basepay_">
      <input type="text" class="form-control" placeholder="">
    </div>

This is the function.

$(function () {
	$("#hourly_rate").keyup(function () {
    var hoursworked = $("#hours_worked").val();
    //alert(hoursworked);
    var hourlyrate = $("#hourly_rate").val();
           $("#basepay_").html(hoursworked * hourlyrate);

A very basic implementation using vanilla javascript

<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <meta name='viewport' content='width=device-width, initial-scale=1.0'>
  <title>Wages</title>
</head>
<style>
  label,
  input {
    display: block;
  }
  .form-row {
    margin-bottom: .5rem;
  }
</style>
<body>
  <form action=''>
    <div class='form-row'>
      <label for='hours-worked'>Hours Worked</label>
      <input id='hours-worked' type='text' placeholder='Enter Hours Worked'>
    </div>
    <div class='form-row'>
      <label for='hourly-rate'>Hourly Rate</label>
      <input id='hourly-rate' type='text' placeholder='Enter Hourly Rate'>
    </div>
  </form>
  <div class='output'>
    <label for='base-pay'>Base Pay</label>
    <input id='base-pay' type='text'>
  </div>
<script>
  window.addEventListener('DOMContentLoaded', function(event) {

      const hourlyRate = document.getElementById('hourly-rate');
      const hoursWorked = document.getElementById('hours-worked');
      const basePay = document.getElementById('base-pay');

      hourlyRate.addEventListener('keyup', function( event ) {
        const rate = hourlyRate.value
        const worked = hoursWorked.value

        basePay.value = (rate * worked)
      })
  })
</script>
</body>
</html>

First of all you didn’t have any id tags on your input elements, so this $("#hourly_rate") would come up with nothing.

I will edit this, as I think it would be better to add the eventlistener to the form and let the handler function work out which input triggered the keyup event — hourly-rate or hours-worked.

This way you can track that both boxes have something in it before outputting a basePay total.

Edit: A bit of a re-write and taking on m3g4p0p’s comment on coercion

I have added a couple of simple helper functions getElem and addEvent, and added the eventListener to the form instead.

(function(){

  // simple helper function to get an element
  const getElem = function(selector, root = document) {
    return root.querySelector(selector)
  }

  // simple helper function to add an eventListener to an element
  const addEvent = function(elem, type, fn) {
    return elem.addEventListener(type, fn, false)
  }

  addEvent(window, 'DOMContentLoaded', function(event) {
    // Declaring our elements here saves the handler below
    // having to look through the DOM each keypress
    const hourlyRate = getElem('#hourly-rate');
    const hoursWorked = getElem('#hours-worked');
    const basePay = getElem('#base-pay');

    // add the eventListener to the parent (Form)
    // all events fired from inside the form will bubble
    // up to the Form element (#wages)
    addEvent(getElem('#wages'), 'keyup', function(event){
      /*
        event.target will be the element inside the form
        that triggered the keyup event
      */
      const currElem = event.target
      const worked = Number(hoursWorked.value) || 0 /* || means or e.g. value or 0 */
      const rate = Number(hourlyRate.value) || 0

      if (currElem.id === 'hourly-rate' || currElem.id === 'hours-worked') {
        basePay.value = rate * worked
      }
    })
  })
}())

It could do with improving, for instance the input should be validated to make sure they are valid numbers.

2 Likes

Be careful with input values though as these are always strings; in case of a multiplication they’ll get converted to numbers implicitly, but you’ll get unexpected results when adding them, for example. So it’s always good practice to explicitly convert them to numbers first:

const worked = Number(hoursWorked.value) || 0
const rate = Number(hourlyRate.value) || 0
2 Likes

Good point m3g4p0p, a bit of laziness on my part.

1 Like

Thankyou so much for your help guys, I really appreciate it.
I will report back to show my efforts. :slight_smile:

1 Like

With regards Number, this stackoverflow response (2nd down) is pretty informative

Number returns NaN for non numerical values. In theory with the use of isNaN() (isNotANumber()?) and Number, you should be able to test for a numerical input.

e.g.

Number('22') // 22
isNaN(Number('22')) // false, it is a number
Number('2abc') // NaN
isNaN(Number('2abc')) // true, it is not a number

How fool proof this is I don’t know. If you want to restrict the values given to a certain format, say to two decimal places, then regular expressions would be the next step.

I don’t think such a test is necessary – since NaN evaluates to false, it would fall back to zero here:

const worked = Number(hoursWorked.value) || 0`

Also you might actually use an input with type="number" to only allow numbers in the first place. :-)

I think my lack of knowledge is at fault here, how do I tell it to move to next calculation please?

const hourlyRate = document.getElementById('hourly-rate');
      const hoursWorked = document.getElementById('hours-worked');
      const basePay = document.getElementById('base-pay');
	  const targetMultiplier = document.getElementById('target-multiplier');
	  const actualGross = document.getElementById('actual-gross');
	  const commissionOn = document.getElementById('commission-on');
	  const commissionRate = document.getElementById('commission-rate');
	  const commissionEarned = document.getElementById('commission-earned');
	  const wage = document.getElementById('wage');
	  const holidayAccrued = document.getElementById('holiday-accrued');
	  const employersPension = document.getElementById('employers-pension');
	  const employersNi = document.getElementById('employers-ni');
	  const totalPayroll = document.getElementById('total-payroll');

      hourlyRate.addEventListener('keyup', function( event ) {
        const rate = hourlyRate.value
        const worked = hoursWorked.value

        basePay.value = (rate * worked)
		  
	  targetMultiplier.addEventListener('keyup', function( event ) {
        const targetMultiplier = targetMultiplier.value
        const basePay = basePay.value

        target.value = (basePay * targetMultiplier)

I think I’m losing the plot. Let’s scrap today:)

1 Like

Hi,

Looking at this, alarm bells ring and it seems to me a bit of a re-think is needed.

Accounts are not my strong point, and it isn’t entirely clear to me what it is you want to achieve

I would think that a sensible start point would be to create the whole form in HTML5 first — Preferably with clean semantic code. Note there is an html&css section here if you need help with that.

For instance could the form be broken down in to fieldsets? One for hours, one for commission etc.

I think with the form laid out properly, then it will give a better idea how to tackle the Javascript side of things.

I thought if I got the calculations first I could style after? This is the form that caused all the problems in the first place so the aim would be to end up with something like that. All that was really wrong was that if the commission on was negative I needed it to show 0.

So essentially something like this?

// this is a substitute for your const commissionOn element
// purely for testing
const commissionOn = {
    value : '-£5.25' // note this is a string, not a number
}

// strip out the £ symbol and convert to a number
function poundsToNumber(value) {
    return Number(value.replace('£', ''))
}

// if you want to reset the commissionOn value
if (poundsToNumber(commissionOn.value) < 0) {
    commissionOn.value = '£0.00'
}

ps. How does £150.00 x 3.5 = £630.00? 525?

1 Like

Thanks for getting back to me. :slight_smile: It was an easy calculation when I did it in Excel but the form maker software couldn’t handle it, something about double integers I think they said but that a developer could fix it. That’s when I ended up shelling out twice for no progress. :frowning:
Sorry editing to add £150.00 x 3.5 = £630.00? 525?, Vat is included in the target

The problem could be their code?

This is the layout

<div class='sfm_element_container' id='Nett_Earnings_container'>
                  <input class='sfm_textbox_common sfm_calc_field' type='text' name='Nett_Earnings' tabindex='-1' value='' readonly id='Nett_Earnings'>
                  <input type='text' name='sfm_Nett_Earnings_parsed' id='sfm_Nett_Earnings_parsed' style='display:none'>
               </div>

And this is the calculation which didn’t work, perhaps I panicked when I couldn’t get it to work and the “experts” couldn’t either and decided to start again?

$(function()
   $('form#Commission #Commission_On').formCalc("max(Nett_Earnings - Actual_Takings, >0=0)", { currency_format:true, mirror:'sfm_Commission_On_parsed' });

We are opening up a real can of worms here. Without knowing about formCalc it is hard to debug this.

A clue though if we look at the above, specifically this
>0=0

That indicates to me, larger than 0 = 0

What you want is smaller than 0 = 0

A wild stab in the dark, but does ‘<0=0’ work?

e.g.

$('form#Commission #Commission_On').formCalc("max(Nett_Earnings - Actual_Takings, <0=0)", { currency_format:true, mirror:'sfm_Commission_On_parsed' });

Thanks for giving me your time on this, I really appreciate it… :slight_smile:

I tried that but and forgive me I had forgotten when anything is put after the Actual_Takings the calculation changes and doesn’t work at all. here is the link to the live form (with out the 0 calculation) and below is a pic of the issue that kicked my problems off.

https://www.salonlogic.co.uk/forms/Commission/Commission.php

I went looking in the folder jquery.sim.FormCalc.js and found this. Would it help for you to see the whole code?

 if(value === '' || value === 0)
                {
                    calc_field.sfm_num_val = 0;

As I say I am not familiar with formCalc, and the documentation appears to be thin on the ground.

I did try various options with comparisons and booleans, but it would only evaluate a boolean 1 or 0 and not a value. e.g. ((Nett_Earnings - Actual_Takings > 0) && (Nett_Earnings - Actual_Takings)) || 0

However, what I did find in the code is a function called minu, which appears to do what you are after — Not sure if this was a bolt on from your developers.

Could you try

$('form#Commission #Commission_On').formCalc("minu( Nett_Earnings, Actual_Takings )", { currency_format:true, mirror:'sfm_Commission_On_parsed' });

Thanks again, I’ve tried the above but whilst it doesn’t show negative it remains blank.
The full formcalc code is 62,000 characters but the limit here is 32,000, I have posted it here http://plnkr.co/edit/0P4BygHvIzKv0PSa?open=lib%2Fscript.js if it’s any help.

Failing that do we return to plan B with a new form? Again many thanks for your time on this, I really appreciate it.

Okay, let’s see what can be done here. Keeping it simple, we have a form at https://www.salonlogic.co.uk/forms/Commission/Commission.php and you never want commission to dip below zero.

Sample values of 40 hours, 3.75 pay rate, 3.5 target, and 900 gross are used.
We need to find the code that’s updating the commission, and have that never go below zero.

Developer tools show me that the gross takings input field has a change event of:
function () { calculate(); return 1;} so let’s see where that is in the code. I can right-click on the function and have the developer tool take me to the function definition, which is at line 220 at the formCalc.js code.

The calculate function is at line 163, so I set a breakpoint inside of that function at line 165.

With gross takings previously being set to 90, I add a 0 to make it 900 and the breakpoint triggers. Using step over I see that the calc_callback function causes the commission to update.

        function calculate()
        {
            if(m_settings.calc_callback)
            {
                m_settings.calc_callback(evaluate_internal());
            }

Doing this again I can set a breakpoint not on the line, but on calc_internal itself which takes me to the following returned anonymous function at line 21.

            function makeCalcUpdateCallback(calc_input)
            {
                return function(value)
                        {
                           setCalcFieldValue(calc_input,value);
                        }
            }

The call to this function has a value of 750 which isn’t the right field, so I resume script execution until the breakpoint triggers again.

Doing that again, the value is -150 so I’m now at the right place to make decisions about things.

The calc_input variable references the commission rate input field, so this is a good place to place our check.

We can check if the calc_input id value matches Commission_On and if it does, and the value is less than zero, we should set the value to be zero.

            function makeCalcUpdateCallback(calc_input)
            {
                return function(value)
                        {
                           if (calc_input.id === "Commission_On" && value < 0) {
                               value = 0;
                           }
                           setCalcFieldValue(calc_input, value);
                        }
            }

I updated the code live in the browser which seems to have been successful.
Try updating the code in formCalc.js and see how it goes.

1 Like

Thanks so much, just checking I replace line 163 with your final code and nothing else?