Using Deferreds with Callback-Only APIs

Sometimes you will need to coordinate your Deferred-based code with callback-only APIs, such as event listeners.

Currently, formHandler.addSubmitHandler resets the form and focuses on the first element – no matter what happens with the Ajax request. However, you only want those things to happen if the Ajax request is successful. Put another way, you only want those things to happen if the Deferred is fulfilled.

How can you know whether the Deferred is fulfilled? Your function argument to addSubmitHandler can return the Deferred, and inside of addSubmitHandler you can chain a .then call to the Deferred.

In main.js, add a return keyword to the callback.

...
  formHandler.addSubmitHandler(function (data) {
    return myTruck.createOrder.call(myTruck, data)
      .then(function () {
        checkList.addRow.call(checkList, data);
      });
  });
...

Save main.js. Next, open formhandler.js, find the addSubmitHandler method, and locate the call to the anonymous function, fn. Because that anonymous function now returns a Deferred, you can chain a call to .then on the end of it. Use .then to register a callback that resets the form and focuses on the first element.

...
  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);
      .then(function () {
        this.reset();
        this.elements[0].focus();
      });
    });
  };
...

Before, you had three sequential statements: invoke the callback, reset the form, and focus on the first form element. Now, you have one statement that depends on the result of the previous statement. You invoke the callback and – if the promised work finishes execution normally, without encountering an exception – then you reset the form and focus on the first form element.

There is just one concern. When you register a callback function with .then, that callback function has a new scope. You need to .bind that anonymous function so that the value of this is set to the FormHandler instance.

Make this change in formhandler.js.

...
  FormHandler.prototype.addSubmitHandler = function (fn) {
    ...
      fn(data)
        .then(function () {
          this.reset();
          this.elements[0].focus();
        }.bind(this));
    });
  };
...

Save formhandler.js.

Similarly, you only want to remove an item from the checklist if the call to Truck.prototype.deliverOrder is successful.

Chain a call to .then off the function passed to addClickHandler in checklist.js. Remember to .bind the value of this for the anonymous function.

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

Save checklist.js. Recall that you invoke addClickHandler in main.js:

checkList.addClickHandler(myTruck.deliverOrder.bind(myTruck);

There is no need to make any changes to this method call. Because Truck.prototype.deliverOrder is returning the Deferred, addClickHandler will work as written.

All of your data is remote, which means you need to load it and draw checklist items for each coffee order. You can use the Truck.prototype.printOrders method along with the CheckList.prototype.addRow method to do this.

You will make two changes to printOrders. First, you will update printOrders to work with Deferreds. Then, you will add a function argument to printOrders which it will call as it iterates through the data to print.

In truck.js, your code for Truck.prototype.printOrders currently looks like this:

...
  Truck.prototype.printOrders = function () {
    var customerIdArray = Object.keys(this.db.getAll());

    console.log('Truck #' + this.truckId + ' has pending orders:');
    customerIdArray.forEach(function (id) {
      console.log(this.db.get(id));
    }.bind(this));
  };
...

Update this implementation to call and return this.db.getAll, chaining a .then to it. Pass an anonymous function to .then and set its this keyword using .bind:

...
  Truck.prototype.printOrders = function () {
    return this.db.getAll()
      .then(function (orders) {
        var customerIdArray = Object.keys(this.db.getAll());

        console.log('Truck #' + this.truckId + ' has pending orders:');
        customerIdArray.forEach(function (id) {
          console.log(this.db.get(id));
        }.bind(this));
      }.bind(this));
  };
...

Your anonymous function expects to receive an object containing all of the coffee order data retrieved from the server. Extract the keys from that object and assign them to the variable named customerIdArray.

...
  Truck.prototype.printOrders = function () {
    return this.db.getAll()
      .then(function (orders) {
        var customerIdArray = Object.keys(this.db.getAll()); orders);

        console.log('Truck #' + this.truckId + ' has pending orders:');
        customerIdArray.forEach(function (id) {
          console.log(this.db.get(id));
        }.bind(this));
      }.bind(this));
  };
...

Likewise, change the console.log statement so that it does not call this.db.get(id). It should use the allData object, which already has all of the coffee orders. You should not make an extra Ajax call for each item that needs to be printed.

...
  Truck.prototype.printOrders = function () {
    return this.db.getAll()
      .then(function (orders) {
        var customerIdArray = Object.keys(orders);

        console.log('Truck #' + this.truckId + ' has pending orders:');
        customerIdArray.forEach(function (id) {
          console.log(this.db.get(id)); orders[id]);
        }.bind(this));
      }.bind(this));
  };
...

printOrders should take an optional function argument. You need to check whether it was passed in and, if it was, invoke it. When you invoke it, you will pass it the current coffee order allData[id].

...
  Truck.prototype.printOrders = function (printFn) {
    return this.db.getAll()
      .then(function (orders) {
        var customerIdArray = Object.keys(orders);

        console.log('Truck #' + this.truckId + ' has pending orders:');
        customerIdArray.forEach(function (id) {
          console.log(orders[id]);
          if (printFn) {
            printFn(orders[id]);
          }
        }.bind(this));
      }.bind(this));
  };
...

Save truck.js. In main.js, invoke printOrders and pass it checkList.addRow. Make sure that addRow is bound to the CheckList instance.

...
  formHandler.addInputHandler(Validation.isCompanyEmail);

  myTruck.printOrders(checkList.addRow.bind(checkList));

  webshim.polyfill('forms forms-ext');
  webshim.setOptions('forms', { addValidators: true, lazyCustomMessages: true });

})(window);

Save and return to the browser. CoffeeRun should show the existing coffee orders in the checklist. Manually reload to confirm that the checklist is repopulated each time. Inspect the network panel and see that Ajax requests are taking place (Figure 14.5).

Figure 14.5  Drawing orders on page load

Drawing orders on page load
..................Content has been hidden....................

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