The full cafe example

In today's lecture we are going to take full advantage of the DOM manipulation capabilities that JQuery gives us to build out the cafe example into a more complete, interactive application.

We are going to build an application that adds ordering capabilities to the cafe application.

Users will be able to both view and order menu items. The View Order button will bring up a summary view that shows the customer's order and allows them to edit the order before submitting it.

The small form at the bottom of the order view will allow the user to submit the order for processing. Clicking this button brings up a confirmation message.

This version of the application will simply fake the ordering process. Next week we will learn how to hook our applications up to a server. At that point we will be able to upload orders to the server.

Single page applications

Today's example program uses an architecture that is quite common for interactive applications driven by JavaScript. Even though it looks the application consists of three distinct parts, these parts are actually all embedded in a single page of HTML backed by a single JavaScript file. To create the illusion that this one HTML page is actually three different pages we use the following structure for the page's body:

<body>
  <div id="view">
    Menu view goes here...
  </div>
  <div id="order">
    Order view goes here...
  </div>
  <div id="confirm">
    Confirmation message goes here...
  </div>
</body>

At any given point in time only one of these three view divs will be visible and the other two will be hidden from view. To acheive this effect we will use the JQuery show() and hide() methods to control which divs are visible.

Applications that use this strategy to show and hide different divs in a single page to create the illusion that one page is many pages are called single page applications.

The setUp() function for the JavaScript program contains these lines, which are responsible for hiding the order and confirm divs at startup time.

$('div#order').hide();
$('div#confirm').hide();

By clicking the View Order button in the view div or clicking the Back to Menu button in the order div the user can switch between the view and order divs. Here is the event handler for the View Order button:

function showOrder() {
  $('div#view').slideUp();
  $('div#order').show();

  displayOrders();
}

This event handler uses the JQuery slideUp() method to slide the menu view up and out of sight, and then makes the order view visible via a call to show().

The event handler for the Back to Menu button reverses this process, hiding the order view and sliding the menu view back down into view.

function showMenu() {
  $('div#order').hide();
  $('div#view').slideDown();
}

Tracking the order

The major new capability in this application is ordering. Users can now click the order buttons next to menu items to order items.

The application keeps track of which items the user has selected by setting up a global array order that will hold the objects representing the items the user has ordered.

// Order array - initially empty
var order = [];

When the user clicks an order button we will locate the object that represents the item the user wants to order and then push() that object onto the order array.

Next week when we learn how to connect our applications to a back end server we will be sending the objects in the order array to the server as soon as the user clicks the Place Order button.

Using anonymous functions as event handlers

As our applications get more complex we will naturally end up having to write a lot of functions. As a general rule, each button in the application will need an event handler. As the number of buttons increases the number of functions in the JavaScript will grow and eventually become challenging to keep track of. One way to counteract this trend is to look for opportunities to eliminate unnecessary functions.

The orginal cafe application contained this code in the setUp() function to set up event handlers for the meal view buttons.

$('button#breakfast').click(breakfastHandler);
$('button#lunch').click(lunchHandler);
$('button#dinner').click(dinnerHandler);

Here is the code for the associated event handler functions.

// Event handler for the breakfast button
function breakfastHandler() {
  displayMeal('B');
}

// Event handler for the lunch button
function lunchHandler() {
  displayMeal('L');
}

// Event handler for the dinner button
function dinnerHandler() {
  displayMeal('D');
}

Note that each one of these functions is a one-liner: it immediately turns around and calls another function to do its work.

In cases where event handlers reduce to one line of code we can replace the event handler with an anonymous function that we pass directly to the JQuery click() method.

Here is the code we use to set up the meal view buttons in today's example.

$('button#breakfast').click(function(){displayMeal('B');});
$('button#lunch').click(function(){displayMeal('L');});
$('button#dinner').click(function(){displayMeal('D');});

The code

function(){displayMeal('B');}

is an example of an anonymous function: it looks like a function definition that is missing a name. In cases where JavaScript expects us to provide a function as a parameter we can either provide the name of an existing function or quickly construct a simple anonymous function and pass that instead.

The advantage of this strategy is that it allows us to reduce the number of named functions in a program, which in turn will make it easier for us to keep track of the functions that remain.

Dynamic buttons - order buttons

The ordering mechanism is driven by Order buttons that appear next to the menu items in the first view.

These buttons are generate via JQuery code in the function that sets up the menu to display a particular meal.

// Reloads the menu table
function displayItems(items) {
  var table, newRow, order_td, orderButton;
  var n, length;

  // Remove any existing entries from the table
  $('#menu tr').remove();

  // Add new rows for the items in the list
  length = items.length;
  for(n = 0;n < length;n++) {
    newRow = $('<tr>').html('<td class="align-middle">'+items[n].item+'</td><td class="align-middle">'+items[n].cost+'</td>');
    orderButton = $('<button>');
    orderButton.text('Order').attr('data-id',items[n].id);
    orderButton.click(orderItem);
    orderButton.addClass('btn btn-secondary align-middle');
    button_td = $('');
    button_td.append(orderButton)
    newRow.append(button_td);
    $('#menu').append(newRow);
  }
}

The statements

orderButton = $('<button>');
orderButton.text('Order').attr('data-id',items[n].id);
orderButton.click(orderItem);
orderButton.addClass('btn btn-secondary align-middle');
button_td = $('');
button_td.append(orderButton)
newRow.append(button_td);

create and set up the buttons and attach them to the table rows.

One unusual aspect of these buttons is that we will attach the same event handler function to each one of the order buttons. To make it possible for that event handler function to tell which button the user clicked, we will attach a special attribute to each button. This attribute is the id number of the menu item this button is linked to.

orderButton.attr('data-id',items[n].id);

Hiding useful data in an element via a custom attribute is a widely used programming trick. The one convention that is useful when doing this is to name the attribute with the prefix data- to make it clear that this attribute is storing application data.

Here now is the event handler that all the Order buttons will use.

function orderItem() {
  // Each of the order buttons has the index of the associated
  // object hidden in its data-id attribute. Read that attribute
  // from the button and use it as an index into the items array.
  order.push(items[$(this).attr('data-id')]);
  // The View Order button is initially hidden. As soon
  // as the user orders an item we will want to make it
  // visible.
  $('button#viewOrder').show();
}

This code takes advantage of the fact that event handlers in JQuery allow to access the element the event handler is attached to through $(this). Once we can reach the button the user clicked we can read its data-id attribute, which stores the index of the item this button is linked to. Using that index in the items array allows us to locate the object that represents the item the user wants to order. All we have to do then is to push() that object onto the order array.

Dynamic buttons - remove buttons

The order view shows the list of items the user has ordered.

Each item in this view has a dynamically generated Remove button next to it. We set up these buttons following a similar strategy.

function displayOrders() {
  var table, newRow, button_td, removeButton;
  var n, length;

  // Remove any existing entries from the table
  $('#items tr').remove();

  // Add new rows for the items in the order list
  length = order.length;
  for(n = 0;n < length;n++) {
    newRow = $('<tr>').html('<td class="align-middle">'+order[n].item+'</td><td class="align-middle">'+order[n].cost+'</td>');
    removeButton = $('<button>');
    removeButton.text('Remove').attr('data-index',n);
    removeButton.click(removeItem);
    removeButton.addClass('btn button-secondary align-middle');
    button_td = $('');
    button_td.append(removeButton);
    newRow.append(button_td);
    $('#items').append(newRow);
  }
}

Again, the key code that sets up the buttons and attaches them to rows in the table is

removeButton = $('<button>');
removeButton.text('Remove').attr('data-index',n);
removeButton.click(removeItem);
removeButton.addClass('btn button-secondary align-middle');
button_td = $('');
button_td.append(removeButton);
newRow.append(button_td);

One small change this time around is that the special attribute we attach to each of the buttons is the index of the menu item in the order array.

Here is the code for the event handler for the Remove buttons.

function removeItem() {
  order.splice($(this).attr('data-index'),1);
  displayOrders();
}

This code looks up the index of the item we want to remove in the order array by doing

$(this).attr('data-index')

and then removes that item from the order array by using the array splice() method. splice() takes two parameters: an index in the array and a number of items to remove.

Since the splice() operation changes the structure of the order array, we need to call displayOrders() again to rebuild the order table.

Focus and Blur

Finally, there is one small, cosmetic problem that we need to fix in the application.

When you click one of the order buttons in the menu view, the button will appear to 'stick' in the clicked state:

What is actually happening here is that when you click on an input item like a button it enters a special state called the focussed state. If you are using Bootstrap to style your input elements, Bootstrap will give focussed elements a distinctive appearance. In this case, the order button we clicked gets a blue border and the text in the button gets inverted.

We can use JQuery to prevent this behavior. In JavaScript the opposite of focussing an element is blurring it. The JQuery blur() method is used to blur elements and remove the focus state from them. We can easily get our order button to blur by adding the following simple statement to the end of the event handler for the order button:

$(this).blur();