This chapter covers
7.3.1 Generating requests
If server communication is supported by the framework, it may expose the XHR object directly or abstract some or all of the XHR functionality with its own proprietary objects. These custom objects act as wrappers around the XMLHttpRequest object either directly or indirectly via another library such as jQuery. They add value by hid- ing many of the tedious, repetitive tasks in making calls and processing the results.
Before you look at any MV* examples, let’s put things into perspective by using vanilla JavaScript and the XMLHttpRequest object to make a server call. If you need a refresher on XHR, refer to appendix B.
We’ll use the shopping cart again as an example. As you did earlier, you’ll update the quantity of an existing item in the cart. Because you’re updating the quantity of an item, you’ll use the PUTHTTP request method. As you learned in the preceding sec- tion, PUT is commonly used in an update situation. To keep things simple, you’ll use an abbreviated version of the cart data used in the project:
var cartObj = { cartId : 123, items : [ { productId : "madden_nfl_15", quantity : 2 } ] };
The following listing illustrates the plain JavaScript version of an update to the shop- ping cart using the XMLHttpRequest object directly.
var cartJSON = JSON.stringify(cartObj); var xhrObj = new XMLHttpRequest(); xhrObj .open("PUT","/SPA/controllers/shopping/carts",true); xhrObj .setRequestHeader("Content-Type","application/json"); xhrObj .setRequestHeader("Accept","application/json"); xhrObj.send(cartJSON);
Listing 7.1 Shopping cart update using PUT and XHR directly
Convert JS object to JSON text
Create new instance of XMLHttpRequest object
Define the call properties
Set the content type
Declare the data type you’ll accept Send the
55
Using MV* frameworks
In this example, you’re not even handling the results. You’ll tackle that in the next section. Even so, you have to deal with several of the low-level details. You have to man- ually set the content type and any other headers you need (such as Accept). Addition- ally, you have to manually convert the JavaScript cart object to JSON-formatted text.
Generally, if an MV* framework has out-of-the-box support for server communica- tion, you’ll most likely be generating requests from one of two types of objects: a model or some type of utility/service object. If the framework requires you to create an explicitly defined data model, you’ll most likely perform server operations by call- ing functions on the model itself. If the framework doesn’t have an explicit data model (the framework considers any source of data an implied model), you’ll proba- bly work through the framework’s utility/service. AngularJS, for example, provides a couple of services for server communication: $http and $resource. You’re be using $resource in the project, and you’ll see it in action a little later.
MAKINGREQUESTSVIAADATAMODEL With some frameworks (Backbone.js, for example), you explicitly define a data model by extending a built-in model object from the framework. By extending the framework’s model, you inherit many capabilities auto- matically. This includes the built-in capability to perform the full range of
CRUD (create, read, update, and delete) operations on a remote resource (see figure 7.2).
Don’t worry, though, if you need to make custom calls. Most frame- works let you override and customize their out-of-the box behavior.
Listing 7.2 extends Backbone.Model to define your shopping cart, passing in the name of the attribute you want to use as its ID. You’re also defining a base URL for all server requests. You have to do this only once, because this is just the model’s definition.
After the model has a definition, you can create new instances of it anytime you need to use it. All new instances of your shopping cart will then inherit everything you need for server communication.
var Cart = Backbone.Model.extend({ idAttribute : 'cartId',
urlRoot : 'controllers/shopping/carts/', });
var cartInstance = new Cart(cartObj); cartInstance.save();
Listing 7.2 Backbone.js version of your shopping cart update
Your model inherits the MV* framework model’s abilities. MV* framework MV* model create() read() update() delete() Your model Cart
Figure 7.2 With MV* frameworks, where your model extends those of the framework, you automatically inherit abilities from the parent, such as the ability to make server requests.
Define a model with a URL and an ID Create a
new model instance
Call its inherited save() function to initiate the request
56 CHAPTER 7 Communicating with the server
The Backbone.js code is certainly less verbose. It’s also doing several things under the covers. For starters, it assumes you’re dealing with JSON (unless you tell it otherwise) and automatically converts the object passed into its constructor to JSON-formatted text. In addition, it automatically sets the Content-type and Accept headers for JSON. Finally, it can automatically decide whether to use PUT or POST based on whether the object of the request has an ID yet. Again, any of these features can be customized or overridden.
MAKINGREQUESTSTHROUGHDATASOURCEOBJECTS
The other manner in which MV* frameworks make requests to the server is through a separate data source object. This is typical when a framework, such as AngularJS, allows you to use anything you want as a data model. With no parent to extend, there are no canned abilities to inherit. When this is the case, the framework provides a data source object that you’ll pass your model into when making a call (see figure 7.3).
Let’s see an example of this alternative MV* approach. Listing 7.3 uses an
AngularJS$resource object to perform your shopping cart update. I mentioned ear- lier that $resource is one of AngularJS’s services that can be used when communicat- ing with the server. It has many features for easily modeling requests and dealing with the server’s response. When you get to this chapter’s project, you’ll delve into the use of $resource in detail to understand the example. For now, let’s see this style of MV*
code as a comparison with your original, vanilla JavaScript server call.
var CartDataSrc = $resource(
"controllers/shopping/carts", null, {updateCart : {method : "PUT"} }
);
CartDataSrc.updateCart(cartObj);
Listing 7.3 AngularJS version of your shopping cart update
Data sources are utility-like objects you pass your data into for the call. No prescribed
model structure
MV* framework
MV* data source object
create() read() update() delete() Your data (implied model) Cart
Figure 7.3 Frameworks that provide server communication, but don’t provide a model to extend, will most likely provide a data source object instead.
Use the built-in $resource object to create a data source i t
Define a URL for the call and no URL parameters (null)
AngularJS’s $resource object has all CRUD operations except update(); you can configure Use the updateCart()
57
Using MV* frameworks
Even though you’re not extending anything, the overall concept is the same as our first MV* example. You can lean on the MV* framework to help you generate the request. Like Backbone.js, under the covers AngularJS sets the appropriate headers, converts the JavaScript object into JSON, and uses the HTTP method you defined. As you saw, though, the authors of this particular framework chose to not include a method to update the cart (PUT) out of the box. It’s easy enough, though, to custom- ize the data source object to add this behavior.
Another feature that MV* frameworks provide is an easy way to deal with the results of a call to the server. Some frameworks support using callback functions, whereas others rely on promises. Promises are becoming more and more prevalent with MV*
frameworks, but I’ll make sure you understand using callbacks with asynchronous requests first.