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
Deferred
s. 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).
3.129.42.243