• If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

Foundation-Ajax Requests

Page history last edited by Doug Richardson 14 years, 2 months ago

When you need to communicate with your server, your application and generally use any method that makes sense for you.  For example, if you have an existing library, it is usually easy to integrate the library into your own code.

 

If you are rewriting your server code from scratch, however, SproutCore includes a new API for handling low-level server communication that can simplify your code significantly called SC.Request.

 

About SC.Request

 

SC.Request is a better way to do Ajax.  It is an abstracted library for requesting data from a server.  It designed to minimize the amount of generic setup and teardown code you need to write to handle various methods.  The configuration API is modeled on jQuery so it is very terse.  Also the Request and Response objects themselves are observable just like any other SproutCore object.

 

For example, here is a fetch() method for a DataSource that will get contacts JSON from the server and then execute a callback when the request completes:

 

in my_app/data_sources/contacts.js:

fetch: function(store, query) {

  SC.Request.getUrl('/contacts').json()

    .notify(this, this.didFetchContacts, store, query)

    .send()

},

 

didFetchContacts: function(response, store, query) {

  if (SC.ok(response)) {

    store.loadRecords(MyApp.Contact, response.get('body'));

  } else store.dataSourceDidErrorQuery(query, response);

},

 

The callback method for this request is very simple.  The response object passed to the callback either contains the results or it will appear as an Error object which you can use to display error information in the UI.

 

Configuring a Request

 

Normally you will create a new request object using one of the helper methods defined on SC.Request.  The helpers are named after the HTTP method that will be used with the request:

 

  • SC.Request.getUrl(url) - will retrieve a document using GET
  • SC.Request.postUrl(url, <body>) - will submit a document using POST.
  • SC.Request.putUrl(url, <body>) -  will submit a document using PUT.
  • SC.Request.deleteUrl(url) - will send a DELETE request

 

For both postUrl() and putUrl() the second body parameter is optional.  You can also set the request body later when you send the request.

 

Once you have created your initial request object, you will want to configure it before sending.  You configure your request using the instance methods defined on SC.Request.  All of these configuration methods are chainable, meaning you can link them together in a series of calls to quickly configure the object.  For example, the code below creates a request, sets it to use JSON and then adds a header:

 

SC.Request.getUrl('/contacts').json().header('Authorization', authKey);

 

The following sections describe the configuration methods available.

 

JSON

 

Normally when you set a request body or get a response body, SC.Request will work with the raw string.  If you expect to work with JSON documents, you can have SC.Request automatically encode and decode your documents instead using the json() method.

 

var req = SC.Request.putUrl('/contacts/123').json(); 

 

Once activated, you can set the body of your request to any JSON-compatible data structure and it will be encoded automatically.  Likewise, when you receive a response from the server, SC.Request will attempt to parse it as JSON.  SC.Request will also automatically set the Content-Type header for you.

 

Headers 

 

You can configure request headers using the header() helper method.  To set a header, just call header(), passing the header name and value to set.  To retrieve the current value of a header, call the same method but with only the header name and no value.

 

var req = SC.Request.putUrl('/contacts/123').header('X-Version', '1.0')

 

You should always use the HTTP standard for header names - capitalized words separated by dashes. 

 

Notifications

 

Finally, you will often want another part of your application to be notified when the request responds.  You configure your notifications with the notify() helper.  When you call notify() you pass a target object, a method, and zero or more additional parameters like so:

 

var req = SC.Request.getUrl('/contacts').notify(MyApp.object, 'didGetContacts', param1, param2);

 

This will call MyApp.object.didGetContacts() whenever a response is received from the request, no matter what the result.  

 

When the notification is invoked, the first parameter passed (to didGetContacts() in this case) will always be a response object describing the response, followed by any additional parameters you passed to notify.  In the example above, your didGetContacts should expect:

 

didGetContacts: function(response, param1, param2) {

  ...

}

 

In addition to defining a generic listener like the above, you can also add listeners to specific return codes by passing the expected status code as the first parameter.  For example, to add a listener ONLY for 401 (Forbidden) response codes, you would do:

 

req.notify(401, MyApp.object, 'didGetForbidden')

 

Likewise, if you register for a generic response code (any of the even 100's like 200, 300, 400), your notification will be called for ANY response in that code range.  The example below will be invoked anytime any 50x (Server error codes) occurs:

 

ret.notify(500, MyApp.object, 'didGetServerError');

 

Adding Multiple Notifications

 

SC.Request can notify one listener per status type as well as one generic listener.  You can use this fact to layer listeners - implementing handlers for specific error cases and then implementing a generic listener to do regular processing.

 

Whenever a response comes in, first any listener on the specific status code received will be invoked.  If that listener returns YES, then no further listeners will be notified.  If the listener returns NO, then SC.Request will notify any listener on that generic status code group.  If that listener returns NO, then the generic listener will be notified.

 

The example below shows an example of this technique:

 

var req = SC.Request.getUrl('/contacts').json()

  .notify(404, MyApp.object, 'handleNotFound')

  .notify(400, MyApp.object, 'handleOtherClientError'),

  .notify(MyApp.object, 'handleResponse');

 

In this example, a 404 response will be handled by handleNotFound(), any other 400-style errors will be handled by handleOtherClientError(), and finally handleResponse() will mostly need to deal with processing actual data.

 

Processing a Response

 

TODO: finish

 

  • Callbacks and send() return a SC.Response object. 
  • SC.Response is a class cluster - concrete subclasses support specific transports such as XHR.
  • Currently XHR is the only built-in one.
  • Responses have a status, header() and body property.  Body is auto-decoded if you set json().
  • Responses become "Errors" if the result status is anything outside of 200 range.  This means you can check a response with SC.ok().  You can also set the response as an error object on a record and use it to display error messages in the UI.

 

 

Prototype Requests 

 

Whenever you create a request and call send(), SC.Request first creates a copy of itself in its current state and then uses that copy to actually send the request.  This means you can create a single request object that describes an endpoint in your server, and then simply call send() anytime you want to send the request again. 

 

We call request objects that you use over and over in this way prototype requests.

 

In the example below, we use a prototype request to periodically poll the server for new data.  If there is new data, we call another method "handleChanges()" to process it.  If your server implements long polling (the most reliable form of push over HTTP), this is basically all you would need to implement in your client to support it:

 

/**

  Poll manager - initiates poll requests every N periods or when the

  request returns, whichever comes first.  Call start() to begin the

  process, stop() to cancel it.  The method handleChanges() must know

  how to process info from the poll request.

*/

MyApp.pollManager = SC.Object.create({

 

  start: function() {

    if (!this._running) {

      this._running = YES;

      this.poll(); // initiate a poll

    }

  },

 

  // starts a new poll immediately - canceling any outstanding

  poll: function() {

 

      // cancel outstanding

      if (this._request) this._request.cancel();

      if (this._timer) this._timer.invalidate();

 

      // start new request to server;

      this._request = MyApp.pollRequest.send();

 

      // set timeout to restart poll in 5 sec no matter what

      this._timer = this.invokeLater(this.poll, 5000);

    }

  },

 

  stop: function() {

    if (this._running) {

      this._running = NO;

 

      // cancel any outstanding request.  aborts the XHR

      if (this._request) this._request.cancel();

  

      // clear any timer too

      if (this._timer) this._timer.invalidate();

 

      this._timer = this._request = null;

    }

  },

 

  // called by the request when it returns

  pollDidRespond: function(response) {

 

    // process changes if there are any - otherwise just ignore

    if (SC.ok(response)) this.handleChanges(response);

    

    // start polling again

    this.poll();

  }

};

 

/**

  Request prototype for the poll request.  Configure with JSON support and special headers

  to tell server we want to long poll.

*/

MyApp.pollRequest = SC.Request.getUrl('/poll').json()

  .header('X-Push-Mode', 'long-poll') // custom header

  .notify(MyApp.pollManager, 'pollDidRespond');

 

 

Reusing the same poll request allows you to avoid having to configure the same request over and over.  This is particularly useful if you need to setup custom headers, we as do in this example.

 

 

 

Comments (1)

Levi McCallum said

at 1:19 am on Oct 10, 2009

Note that many of these features are not yet implemented in 1.0 beta, as of October 10th. Reference request.js in foundation/system for current feature set.

You don't have permission to comment on this page.