Example Page HTML Source JavaScript Source

Suggested reading: The section on functions in chapter 3 of the JavaScript text.

The exercise calculator

Today's example implements a calculator that computes calories burned for several different activities.

Using Bootstrap to style forms

In HTML a form a region of the page that contains input elements along with a button to launch a calculation. Since you may also wish to use Bootstrap in the first JavaScript assignment, this is an opportune moment to discuss Bootstrap form styling classes.

One widely used structure for forms is to enclose all of the form elements in an HTML <fieldset> element. The first element inside the <fieldset> is typically a <legend> element, which gives the form a title.

<fieldset>
  <legend>---Title for form---</legend>
  <!-- Form elements go here -->
</fieldset>

Bootstrap then uses divs with the form-group and row classes to implement rows of controls and labels.

<div class="form-group row">
  <!-- A row of labels and controls goes here -->
</div>

You will want to use the Bootstrap column classes to control the width of the individual components in a form group. In some cases you will apply the column classes directly to an element, while in other cases you will need to place the element inside a div and then apply the column classes to the div itself. Here is a typical example:

<div class="form-group row">
  <label for="activity" class="col-form-label col-sm-2">Activity:</label>
  <div class="col-sm-3">
    <select class="form-control" id="activity">
      <option value="walking">Walking</option>
      <option value="cycling">Cycling</option>
      <option value="swimming">Swimming</option>
      <option value="running">Running</option>
    </select>
  </div>
  <div class="col-sm-3">
    <input type="text" class="form-control" id="duration">
  </div>
  <label for="duration" class="col-form-label col-sm-1">Minutes</label>
</div>

Individual elements of a form, such as labels and input elements, also need to have appropriate Bootstrap classes such as col-form-label or form-control applied to them to ensure that they are styled properly.

Buttons will need to have the btn class applied. If you also want the button colored you will need to apply one of the button color classes, such as button-primary or button-default.

<button class="btn btn-primary" id="compute">Compute</button>

Calling functions

In the previous two examples we saw one very important use for JavaScript functions. Functions serve as bundles of code that can be invoked in response to events in the page. Starting with today's example we are going to see another use for functions. This use is closer to the traditional concept of a function in mathematics: a function is something that takes one or more inputs, does a computation using those inputs, and returns a result. For example, here is a function that converts a temperature in Fahrenheit to its Celsius equivalent:

This is the code we would write for a JavaScript function that does this computation:

function celsius(temperature) {
  var result;

  result = 5*(temperature - 32)/9;

  return result;
}

The inputs to a function are called parameters. These will appear in the parentheses after the function name as a comma separated list. This example takes a single input parameter, temperature. Functions that are meant to produce a result return that result via the use of a JavaScript return statement. This is typically the last statement in the body of the function.

To make use of this function in a JavaScript program we call the function whenever we want to use it. For example, here is a snippet of code that uses this function to print some weather information in a page.

var temp, weatherSpan, text;

temp = 57; // The temperature now in Fahrenheit

weatherSpan = document.getElementById('weather');
text = 'The temperature is '+temp+'F or '+celsius(temp)+'C.';
weatherSpan.textContent = text;

Note that when we call a function we don't have to use the same name for the parameter that we used when defining the function. When we call a function, the variable we use for the parameter provides a value. That value gets copied into the parameter that appears in the function definition.

The caloriesPerHour function

One very important use for functions in a JavaScript program is to isolate particularly messy portions of a computation. In today's example program we need to compute calories burned in exercise. A particularly messy part of that computation is mapping an activity and an intensity to a rate of calories burned per hour in that activity. Assuming for the moment that we can construct a function called caloriesPerHour that inputs the name of an activity and an intensity level and returns the calories per hour you would burn in that activity, the computation we need to do in the calculator simplifies down to just one simple line of JavaScript:

calories = caloriesPerHour(activity,intensity)*duration/60;

This computation is very simple because we have isolated the messiest part of the computation in a function that computes a key quantity. A little later in the notes I will show how that function does its job.

Local variables, parameters, and global variables

JavaScript programs use three different types of variables.

These three variable types differ in both scope and initialization. The scope of a variable determines which portion of a program the variable can be used in. Variables with local scope, such as local variables or parameters in a function, can only be used in the function in which they appear. Variables with global scope can be used anywhere in a program.

Local and global variables have to be initialized with assignment statements. One common practice is to combine the variable declaration with the initialization into a single statement:

var subtotal = 24.95;

Parameters get initialized when you call the function they appear in.

The practice I will follow in most examples is to make almost all of my variables local. If I am planning to use a variable in just one function, it makes sense to declare and use that variable in that function alone. As the programs we write grow in size, the number of variables involved will grow. To manage this large number of variables, it makes the most sense for almost all variables to be declared and used locally.

More about if-else statements

Here is a first attempt at writing the logic for the caloriesPerHour function:

function caloriesPerHour(activity,intensity) {
  var result;

  if(activity == 'walking') {
    if(intensity == 'low')
      result = 176;
    else if(intensity == 'medium')
      result = 232;
    else
      result = 352;
  } else if(activity == 'cycling') {
    if(intensity == 'low')
      result = 422;
    else if(intensity == 'medium')
      result = 563;
    else
      result = 704;
  } else if(activity == 'running') {
    if(intensity == 'low')
      result = 704;
    else if(intensity == 'medium')
      result = 880;
    else
      result = 1126;
  } else { // 'swimming' case
    if(intensity == 'low')
      result = 422;
    else if(intensity == 'medium')
      result = 563;
    else
      result = 704;
  }

  return result;
}

The logic here uses chained if-else statements to initially categorize the activity. Within each category of activity we also need to use a nested, chained if-else statement to categorize the intensity. This is unavoidably messy, so isolating this mess in a function and then calling the function in our computation goes a long way toward making the computation less messy.

Compound tests

The logic in the caloriesPerHour function is structured the way it is because we need to ask two questions to determine what the calories per hour figure should be - we need to determine which activity we are working with and what intensity level we have.

JavaScript offers an alternative mechanism for dealing with situations that require multiple questions. This mechanism extends the structure of a test in an if-else by allowing a compound test. A compound test is a set of two or more tests connected by logical connectives. The simplest and most widely used logical connective is the logical and operator, &&.

Here is the caloriesPerHour function rewritten to use compound tests:

function caloriesPerHour(activity,intensity) {
  var result = 0;

  if(activity=='walking' && intensity=='low')
    result = 176;
  else if(activity=='walking' && intensity=='medium')
    result = 232;
  else if(activity=='walking' && intensity=='high')
    result = 352;
  else if(activity=='cycling' && intensity=='low')
    result = 422;
  else if(activity=='cycling' && intensity=='medium')
    result = 563;
  else if(activity=='cycling' && intensity=='high')
    result = 704;
  else if(activity=='running' && intensity=='low')
    result = 704;
  else if(activity=='running' && intensity=='medium')
    result = 880;
  else if(activity=='running' && intensity=='high')
    result = 1126;
  else if(activity=='swimming' && intensity=='low')
    result = 422;
  else if(activity=='swimming' && intensity=='medium')
    result = 563;
  else if(activity=='swimming' && intensity=='high')
    result = 704;

  return result;
}

In this application we have four activities and three intensity levels that each activity can take. Together, this makes for twelve separate cases. To distinguish each of these twelve cases we form a compound test that tests for a particular activity and a particular intensity. Those two separate tests get combined into a compound test through the use of the logical and operator, &&.

Another logical connective is the logical or operator, ||. (To type this operator you use the vertical bar character |, which appears right above the back-slash character \ on a US keyboard.)

Here is an example that we should have used in the Chocolate Store example. In the test to determine whether or not we need to charge sales tax, we should apply the sales tax if the order is being shipped to a customer in Wisconsin or if the customer plans to pick the order up in the store. The appropriate logic for this is

if(stateSelector.value=='WI'||shippingSelector.value=='store') {
  // Wisconsin customers get assessed a 5% sales tax
  total = 1.05*total;
}

Separating data from logic

By the end of the week we will be in a position to construct a much less verbose caloriesPerHour function. The first step toward doing this is to separate the data concerning activities from the logic that determines which activity the user is engaged in. To do this, we are going to make use of a special JavaScript construct called the JavaScript object. An object is a list of property, value pairs. Each property has a name, and values can be any standard JavaScript data type, such as text or a number.

Here is an example of a JavaScript object that describes one of the activities the calculator deals with:

{activity:'running', intensity:'low', rate: 704}

The first step toward reorganizing the logic for the caloriesPerHour function is to make a similar object for each combination of activity and intensity. We then place all of those objects in a structure called a JavaScript array. An array is essentially a list.

var data = [{activity:'walking', intensity:'low', rate: 176},
            {activity:'walking', intensity:'medium', rate: 232},
            {activity:'walking', intensity:'high', rate: 352},
            {activity:'cycling', intensity:'low', rate: 422},
            {activity:'cycling', intensity:'medium', rate: 563},
            {activity:'cycling', intensity:'high', rate: 704},
            {activity:'running', intensity:'low', rate: 704},
            {activity:'running', intensity:'medium', rate: 880},
            {activity:'running', intensity:'high', rate: 1126},
            {activity:'swimming', intensity:'low', rate: 422},
            {activity:'swimming', intensity:'medium', rate: 563},
            {activity:'swimming', intensity:'high', rate: 704}]

To access the elements of this list we use an index notation that assigns a number to each item in the array. The items are numbered consecutively starting from 0. To access the individual properties of the objects that sit in the array, we follow the index notation with a dot and the name of the property we want to access. For example, this is how we read the rate for medium intensity cycling:

r = data[4].rate

Assuming that we have set up the data array as shown above, we can now start to restructure the caloriesPerHour function:

function caloriesPerHour(activity,intensity) {
  var result = 0;

  if(activity==data[0].activity && intensity==data[0].intensity)
    result = data[0].rate;
  else if(activity==data[1].activity && intensity==data[1].intensity)
    result = data[1].rate;
  else if(activity==data[2].activity && intensity==data[2].intensity)
    result = data[2].rate;
  else if(activity==data[3].activity && intensity==data[3].intensity)
    result = data[3].rate;
  else if(activity==data[4].activity && intensity==data[4].intensity)
    result = data[4].rate;
  else if(activity==data[5].activity && intensity==data[5].intensity)
    result = data[5].rate;
  else if(activity==data[6].activity && intensity==data[6].intensity)
    result = data[6].rate;
  else if(activity==data[7].activity && intensity==data[7].intensity)
    result = data[7].rate;
  else if(activity==data[8].activity && intensity==data[8].intensity)
    result = data[8].rate;
  else if(activity==data[9].activity && intensity==data[9].intensity)
    result = data[9].rate;
  else if(activity==data[10].activity && intensity==data[10].intensity)
    result = data[10].rate;
  else if(activity==data[11].activity && intensity==data[11].intensity)
    result = data[11].rate;

  return result;
}

This does not look like much of an improvement over the original! This code does have one great advantage: it is highly redundant, doing the essentially the same thing with minor variations over and over again. By the end of the week we will be able to leverage this feature of the code to greatly reduce its size.