Skip to content

Commit

Permalink
Adds XHR2 event notification support.
Browse files Browse the repository at this point in the history
Here are some example uses:

  req.notify('progress', this, this.reqDidProgress);     // Handle 'progress' events
  req.notify('abort', this, this.reqDidAbort);                  // Handle 'abort' events
  req.notify('upload.progress', this, this.reqUploadDidProgress);      // Handle 'progress' events on the XMLHttpRequestUpload
  • Loading branch information
publickeating committed Aug 27, 2012
1 parent 26b9c93 commit bc0b8fc
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 78 deletions.
141 changes: 90 additions & 51 deletions frameworks/ajax/system/request.js
Expand Up @@ -10,7 +10,7 @@ sc_require('system/response');
/**
@class
Implements support for Ajax requests using XHR and other prototcols.
Implements support for Ajax requests using XHR, XHR 2 and other protocols.
SC.Request is much like an inverted version of the request/response objects
you receive when implementing HTTP servers.
Expand All @@ -28,7 +28,7 @@ SC.Request = SC.Object.extend(SC.Copyable, SC.Freezable,

// ..........................................................
// PROPERTIES
//
//

/**
Sends the request asynchronously instead of blocking the browser. You
Expand Down Expand Up @@ -65,9 +65,9 @@ SC.Request = SC.Object.extend(SC.Copyable, SC.Freezable,
you to override that behavior.
You may want to set this to NO if you are making simple CORS requests
in compatible browsers. See <a href="http://www.w3.org/TR/cors/">CORS
in compatible browsers. See <a href="http://www.w3.org/TR/cors/">CORS
Spec for more information.</a>
TODO: Add unit tests for this feature
@type Boolean
Expand Down Expand Up @@ -121,7 +121,7 @@ SC.Request = SC.Object.extend(SC.Copyable, SC.Freezable,
@default 'GET'
*/
type: 'GET',

/**
An optional timeout value of the request, in milliseconds. The timer
begins when SC.Response#fire is actually invoked by the request manager
Expand All @@ -137,7 +137,7 @@ SC.Request = SC.Object.extend(SC.Copyable, SC.Freezable,
@default null
*/
timeout: null,

/**
The body of the request. May be an object is isJSON or isXML is set,
otherwise should be a string.
Expand All @@ -146,7 +146,7 @@ SC.Request = SC.Object.extend(SC.Copyable, SC.Freezable,
@default null
*/
body: null,

/**
The body, encoded as JSON or XML if needed.
Expand Down Expand Up @@ -223,7 +223,7 @@ SC.Request = SC.Object.extend(SC.Copyable, SC.Freezable,

/** @private */
COPY_KEYS: ['attachIdentifyingHeaders', 'isAsynchronous', 'isJSON', 'isXML', 'address', 'type', 'timeout', 'body', 'responseClass', 'willSend', 'didSend', 'willReceive', 'didReceive'],

/**
Returns a copy of the current request. This will only copy certain
properties so if you want to add additional properties to the copy you
Expand Down Expand Up @@ -351,9 +351,9 @@ SC.Request = SC.Object.extend(SC.Copyable, SC.Freezable,
if (flag) { this.set('isJSON', NO); }
return this.set('isXML', flag);
},
/**
Called just before a request is enqueued. This will encode the body

/**
Called just before a request is enqueued. This will encode the body
into JSON if it is not already encoded, and set identifying headers
*/
_prep: function() {
Expand All @@ -363,23 +363,23 @@ SC.Request = SC.Object.extend(SC.Copyable, SC.Freezable,
this.header('X-Requested-With', 'XMLHttpRequest');
this.header('X-SproutCore-Version', SC.VERSION);
}

if (this.get('isJSON') && !hasContentType) {
this.header('Content-Type', 'application/json');
} else if (this.get('isXML') && !hasContentType) {
this.header('Content-Type', 'text/xml');
}
return this;
},

/**
Will fire the actual request. If you have set the request to use JSON
mode then you can pass any object that can be converted to JSON as the
body. Otherwise you should pass a string body.
@param {String|Object} [body]
@returns {SC.Response} New response object
*/
*/
send: function(body) {
// Sanity-check: Be sure a timeout value was not specified if the request
// is synchronous (because it wouldn't work).
Expand Down Expand Up @@ -407,56 +407,95 @@ SC.Request = SC.Object.extend(SC.Copyable, SC.Freezable,
},

/**
Configures a callback to execute when a request completes. You must pass
at least a target and action/method to this and optionally a status code.
You may also pass additional parameters which will be passed along to your
callback. If your callback handled the notification, it should return YES.
Configures a callback to execute as a request progresses or completes. You
must pass at least a target and action/method to this and optionally an
event name or status code.
You may also pass additional arguments which will then be passed along to
your callback.
## Scoping With Status Codes
If you pass a status code as the first option to this method, then your
notification callback will only be called if the response status matches
the code. For example, if you pass 201 (or SC.Request.CREATED) then
your method will only be called if the response status from the server
is 201.
If you pass a status code as the first argument to this method, the
accompanying notification callback will only be called if the response
status matches the status code. For example, if you pass 201 (or
SC.Request.CREATED), the accompanying method will only be called if the
response status from the server is also 201.
You can also pass "generic" status codes such as 200, 300, or 400, which
will be invoked anytime the status code is the range if a more specific
notifier was not registered first and returned YES.
will be invoked anytime the status code is in the same range and if a more
specific notifier was not registered first and returned YES.
Finally, passing a status code of 0 or no status at all will cause your
method to be executed no matter what the resulting status is unless a
more specific notifier was registered and returned YES.
more specific notifier was registered first and returned YES.
For example,
var req = SC.Request.create({type: 'POST'});
req.notify(201, this, this.reqWasCreated); // Handle a specific status code
req.notify(401, this, this.reqWasUnauthorized); // Handle a specific status code
req.notify(400, this, this.reqDidRedirect); // Handle any 4xx status
req.notify(this, function(response, arg1, arg2) {
// do something
}, arg1, arg2); // Handle any status. Also, passing additional arguments to the callback handler
## Notifying on Progress Events
If you pass a progress event name your callback will be called each time
the event fires on the response. For example, the XMLHttpRequest Level 2
specification defines several progress events: loadstart, progress, abort,
error, load, timeout and loadend. Therefore, when using the default
SC.Request responseClass, SC.XHRResponse, you can be notified of each of
these events by simply providing the matching event name.
Note that many older browsers do not support XMLHttpRequest Level 2. See
http://caniuse.com/xhr2 for a list of supported browsers.
For example,
var req = SC.Request.create({type: 'GET'});
req.notify('progress', this, this.reqDidProgress); // Handle 'progress' events
req.notify('abort', this, this.reqDidAbort); // Handle 'abort' events
req.notify('upload.progress', this, this.reqUploadDidProgress); // Handle 'progress' events on the XMLHttpRequestUpload
req.send();
## Callback Format
Your notification callback should expect to receive the Response object
as the first parameter plus any additional parameters that you pass.
Your notification callback should expect to receive the Response object as
the first parameter for status code notifications and the Event object for
progress notifications; plus any additional parameters that you pass. If
your callback handles the notification and to prevent further handling, it
should return YES.
@param {Number} status
@param {Object} target
@param {String|Function} action
@param {Hash} params
@returns {SC.Request} receiver
@param [statusOrEvent] {Number|String} A Number status code or String Event name.
@param target {Object} The target object for the callback action.
@param action {String|Function} The method name or function to call on the target.
@returns {SC.Request} The SC.Request object.
*/
notify: function(status, target, action, params) {
// normalize status
var hasStatus = YES;
if (SC.typeOf(status) !== SC.T_NUMBER) {
params = SC.A(arguments).slice(2);
notify: function(statusOrEvent, target, action) {
var args;

// Normalize arguments
if (SC.typeOf(statusOrEvent) !== SC.T_NUMBER && SC.typeOf(statusOrEvent) !== SC.T_STRING) {
action = target;
target = status;
status = 0;
hasStatus = NO;
target = statusOrEvent;
statusOrEvent = 0;

// Accept multiple additional arguments.
args = SC.A(arguments).slice(2);
} else {
params = SC.A(arguments).slice(3);
// Accept multiple additional arguments.
args = SC.A(arguments).slice(3);
}

// Prepare listeners for this object and notification target.
var listeners = this.get('listeners');
if (!listeners) { this.set('listeners', listeners = {}); }
if(!listeners[status]) { listeners[status] = []; }
if(!listeners[statusOrEvent]) { listeners[statusOrEvent] = []; }

listeners[status].push({target: target, action: action, params: params});
// Add another listener for the given status code or event name.
listeners[statusOrEvent].push({target: target, action: action, args: args});

return this;
}
Expand Down Expand Up @@ -542,7 +581,7 @@ SC.Request.manager = SC.Object.create(

/**
Maximum number of concurrent requests allowed. 6 for all browsers.
@type Number
@default 6
*/
Expand All @@ -567,12 +606,12 @@ SC.Request.manager = SC.Object.create(

// ..........................................................
// METHODS
//
//

/**
Invoked by the send() method on a request. This will create a new low-
level transport object and queue it if needed.
@param {SC.Request} request the request to send
@returns {SC.Object} response object
*/
Expand All @@ -591,7 +630,7 @@ SC.Request.manager = SC.Object.create(
return response;
},

/**
/**
Cancels a specific request. If the request is pending it will simply
be removed. Otherwise it will actually be cancelled.
Expand Down Expand Up @@ -634,7 +673,7 @@ SC.Request.manager = SC.Object.create(

return NO;
},

/**
Checks the inflight queue. If there is an open slot, this will move a
request from pending to inflight.
Expand Down

0 comments on commit bc0b8fc

Please sign in to comment.