Delivering an Order by Clicking a Row

You are almost there! CoffeeRun’s users can fill out the form to add orders. When they submit the form, it adds the order information to the application’s database and draws a checklist item for the order.

Next, users should be able to check off the checklist items. When a checklist item is clicked, meaning that the order has been delivered, the order information should be deleted from the database and the checklist item should be removed from the page. Figure 11.7 shows this process.

Figure 11.7  Sequence diagram: clicking a checklist item

Sequence diagram: clicking a checklist item

First, you will create the functionality for removing the checklist item from the page.

Creating the CheckList.prototype.removeRow method

When you create a Row, the value of the <input> is set to the customer’s email address. removeRow will use the email address argument to find the right CheckList item to remove from the UI. It will do that by creating an attribute selector to find the <input> whose value attribute matches the email address.

When it has found the matching element, it will move up the DOM until it finds the [data-coffee-order="checkbox"].

This is the <div> that wraps around all of the elements that are part of a row. Finally, with that <div> selected using jQuery, its .remove method can be called, removing the element from the DOM and also cleaning up any event listeners that were attached to any element in that DOM subtree.

Add the removeRow method in checklist.js and specify an emailAddress parameter. Use the $element instance property to search for any descendant elements whose value attribute matches the email parameter. From that matching element, add a call to closest to search for an ancestor whose data-coffee-order attribute is equal to "checkbox". Finally, call remove on that element. (You will notice some new syntax in this code, which we will explain after you enter it.)

...
  CheckList.prototype.addRow = function (coffeeOrder) {
    ...
  };

  CheckList.prototype.removeRow = function (email) {
    this.$element
      .find('[value="' + email + '"]')
      .closest('[data-coffee-order="checkbox"]')
      .remove();
  };

  function Row(coffeeOrder) {
    ...

Here, you have chained several method calls together. jQuery is designed so that you can write multiple method calls for an object like a list of steps. You only include the semicolon at the end of the very last method call.

The requirement for chaining is that a method must return a jQuery-wrapped selection in order to have another method call chained to it. find returns a jQuery-wrapped selection, as does closest. This allows you to chain the three method calls together.

Notice that you used this.$element.find. This does a scoped selection: Instead of searching the entire DOM, it only searches the descendants of the checklist, which you have a reference to with this.$element.

Removing overwritten entries

Save your file and switch to the browser. Using your form, enter two orders for the same email address. Make the coffee for the first “order 1” and for the second “order 2.” After submitting both orders, call myTruck.printOrders in the console. Figure 11.8 shows the result.

Figure 11.8  Orders for same email address remain in UI

Orders for same email address remain in UI

You decided early on to allow only one open order per customer. Because you are using a simple key/value store for your data, any subsequent orders for the same customer email address overwrite the existing one. So, as the console shows, “order 2” is the only pending order. “Order 1” has been overwritten.

But the checklist does not reflect this – it still shows rows for both “order 1” and “order 2.” When you add a row for an order, you need to make sure that any existing rows associated with the same customer email address are removed.

Now that you can remove rows based on the customer’s email address, this is straightforward. In checklist.js, update the addRow prototype method so that the first thing it does is call removeRow, passing in the email address of the customer.

...
  CheckList.prototype.addRow = function (coffeeOrder) {
    // Remove any existing rows that match the email address
    this.removeRow(coffeeOrder.emailAddress);

    // Create a new instance of a row, using the coffee order info
    var rowElement = new Row(coffeeOrder);

    // Add the new row instance's $element property to the checklist
    this.$element.append(rowElement.$element);
  };
...

Save checklist.js and verify in the browser that the first order’s checklist item is removed when a second order with the same email is submitted.

Now that you can remove a checklist row from the UI, turn your attention to handling the checklist click event.

Writing the addClickHandler method

To handle clicks to the checklist, you will use the same event handler registration technique that you used with FormHandler.

FormHandler.prototype.addSubmitHandler accepts a function argument, fn, and then registers an anonymous function to handle the submit event of this.$formElement. Inside of that anonymous function, fn is invoked. Here is that method definition for reference:

  FormHandler.prototype.addSubmitHandler = function (fn) {

    console.log('Setting submit handler for form');
    this.$formElement.on('submit', function (event) {
      event.preventDefault();

      var data = {};
      $(this).serializeArray().forEach(function (item) {
        data[item.name] = item.value;
        console.log(item.name + ' is ' + item.value);
      });
      console.log(data);

      fn(data);
      this.reset();
      this.elements[0].focus();
    });
  };

This makes FormHandler.prototype.addSubmitHandler flexible, because it can be passed any function that needs to run when the form is submitted. This way, FormHandler.prototype.addSubmitHandler does not need to know the details of that function or what steps it takes.

You will add a prototype method to CheckList called addClickHandler that will work the same way as FormHandler’s addSubmitHandler. That is, it will:

  1. Accept a function argument.

  2. Register an event handler callback.

  3. Invoke the function argument inside the event handler callback.

CheckList.prototype.addClickHandler differs from FormHandler.prototype.addSubmitHandler in that it will listen for a click event and bind the callback to the CheckList instance.

In checklist.js, add the addClickHandler method and specify a parameter named fn. Listen for a click event using jQuery’s on method.

Inside the event handler function, declare a local variable named email and assign it event.target.value, which is the customer’s email address. Then call removeRow, passing it email. After that, invoke fn and pass it email also. Make sure to use bind(this) to set the context object of the event handler function.

...
  function CheckList(selector) {
    ...
  }

  CheckList.prototype.addClickHandler = function (fn) {
    this.$element.on('click', 'input', function (event) {
      var email = event.target.value;
      this.removeRow(email);
      fn(email);
    }.bind(this));
  };

  CheckList.prototype.addRow = function (coffeeOrder) {
    ...

When you registered the event handler callback with this.$element.on, you specified click as the event name. But you also passed in a filtering selector as the second argument. The filtering selector tells the event handler to run the callback function only if the event was triggered by an <input> element.

This is a pattern called event delegation. It works because some events, like clicks and keypresses, propagate through the DOM, meaning each ancestor element is informed about the event.

Any time you need to listen for an event on elements that are dynamically created and removed, such as the checklist items, you should use event delegation. It is easier and more performant to add a single listener to the dynamic elements’ container and then run the handler function based on what element triggered the event.

Notice that you do not call event.preventDefault inside the event handler. Why not? If you called event.preventDefault, the checkbox would not actually change its visual state to show a checkmark in the box.

Also, notice that you bind the event handler callback to this, which refers to the instance of CheckList.

Calling addClickHandler

addClickHandler needs to be connected to deliverOrder. Go to main.js to make that connection. Pass a bound version of deliverOrder to checkList.addClickHandler.

...
  var myTruck = new Truck('ncc-1701', new DataStore());
  window.myTruck = myTruck;
  var checkList = new CheckList(CHECKLIST_SELECTOR);
  checkList.addClickHandler(myTruck.deliverOrder.bind(myTruck));
  var formHandler = new FormHandler(FORM_SELECTOR);
...

Save your changes and add some coffee orders in the form. Click either the checkbox or the text of one of the checklist items, and it will be removed (Figure 11.9)!

Figure 11.9  Clicking a checklist item removes it

Clicking a checklist item removes it

You have learned how to create dynamic form elements and work with the events they generate. You were able to associate each one with a specific coffee order by using the email address as an identifier.

Using these techniques, you completed the modules that manage the UI, turning what was a console-only application into one that could be used for a real-world task.

You have completed two of the three major parts of CoffeeRun. The internal logic governs the data within the application. The form elements, FormHandler, and CheckList provide the interactive UI. The next chapters deal with preparing and exchanging data with a remote server.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.197.95