diff --git a/src/ajax.js b/src/ajax.js index 98cf975..2771fd8 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -1,3 +1,112 @@ +/** + * == Ajax == + * + * Prototype's APIs around the `XmlHttpRequest` object. + * + * The Prototype framework enables you to deal with Ajax calls in a manner that is + * both easy and compatible with all modern browsers. + * + * Actual requests are made by creating instances of [[Ajax.Request]]. + * + *

Request headers

+ * + * The following headers are sent with all Ajax requests (and can be + * overridden with the `requestHeaders` option described below): + * + * * `X-Requested-With` is set to `XMLHttpRequest`. + * * `X-Prototype-Version` is set to Prototype's current version (e.g., + * `1.6.0.3`). + * * `Accept` is set to `text/javascript, text/html, application/xml, + * text/xml, * / *` + * * `Content-type` is automatically determined based on the `contentType` + * and `encoding` options. + * + *

Ajax options

+ * + * All Ajax classes share a common set of _options_ and _callbacks_. + * Callbacks are called at various points in the life-cycle of a request, and + * always feature the same list of arguments. + * + *
Common options
+ * + * * `asynchronous` ([[Boolean]]; default `true`): Determines whether + * `XMLHttpRequest` is used asynchronously or not. Synchronous usage is + * seriously discouraged — it halts all script execution for the duration of + * the request _and_ blocks the browser UI. + * * `contentType` ([[String]]; default `application/x-www-form-urlencoded`): + * The `Content-type` header for your request. Change this header if you + * want to send data in another format (like XML). + * * `encoding` ([[String]]; default `UTF-8`): The encoding for the contents + * of your request. It is best left as-is, but should weird encoding issues + * arise, you may have to tweak this. + * * `method` ([[String]]; default `post`): The HTTP method to use for the + * request. The other common possibility is `get`. Abiding by Rails + * conventions, Prototype also reacts to other HTTP verbs (such as `put` and + * `delete`) by submitting via `post` and adding a extra `_method` parameter + * with the originally-requested method. + * * `parameters` ([[String]]): The parameters for the request, which will be + * encoded into the URL for a `get` method, or into the request body for the + * other methods. This can be provided either as a URL-encoded string, a + * [[Hash]], or a plain [[Object]]. + * * `postBody` ([[String]]): Specific contents for the request body on a + * `post` method. If it is not provided, the contents of the `parameters` + * option will be used instead. + * * `requestHeaders` ([[Object]]): A set of key-value pairs, with properties + * representing header names. + * * `evalJS` ([[Boolean]] | [[String]]; default `true`): Automatically `eval`s + * the content of [[Ajax.Response#responseText]] and populates + * [[Ajax.Response#responseJSON]] with it if the `Content-type` returned by + * the server is set to `application/json`. If the request doesn't obey + * same-origin policy, the content is sanitized before evaluation. If you + * need to force evalutation, pass `'force'`. To prevent it altogether, pass + * `false`. + * * `sanitizeJSON` ([[Boolean]]; default is `false` for same-origin requests, + * `true` otherwise): Sanitizes the contents of + * [[Ajax.Response#responseText]] before evaluating it. + * + *

Common callbacks

+ * + * When used on individual instances, all callbacks (except `onException`) are + * invoked with two parameters: the `XMLHttpRequest` object and the result of + * evaluating the `X-JSON` response header, if any (can be `null`). + * + * For another way of describing their chronological order and which callbacks + * are mutually exclusive, see [[Ajax.Request]]. + * + * * `onCreate`: Triggered when the [[Ajax.Request]] object is initialized. + * This is _after_ the parameters and the URL have been processed, but + * _before_ opening the connection via the XHR object. + * * `onUninitialized` (*Not guaranteed*): Invoked just after the XHR object + * is created. + * * `onLoading` (*Not guaranteed*): Triggered when the underlying XHR object + * is being setup, and its connection opened. + * * `onLoaded` (*Not guaranteed*): Triggered once the underlying XHR object + * is setup, the connection is open, and it is ready to send its actual + * request. + * * `onInteractive` (*Not guaranteed*): Triggered whenever the requester + * receives a part of the response (but not the final part), should it + * be sent in several packets. + * * `onSuccess`: Invoked when a request completes and its status code is + * `undefined` or belongs in the `2xy` family. This is skipped if a + * code-specific callback is defined (e.g., `on200`), and happens _before_ + * `onComplete`. + * * `onFailure`: Invoked when a request completes and its status code exists + * but _is not_ in the `2xy` family. This is skipped if a code-specific + * callback is defined (e.g. `on403`), and happens _before_ `onComplete`. + * * `onXYZ` (_with `XYZ` representing any HTTP status code_): Invoked just + * after the response is complete _if_ the status code is the exact code + * used in the callback name. _Prevents_ execution of `onSuccess` and + * `onFailure`. Happens _before_ `onComplete`. + * * `onException`: Triggered whenever an XHR error arises. Has a custom + * signature: the first argument is the requester (i.e. an [[Ajax.Request]] + * instance), and the second is the exception object. + * * `onComplete`: Triggered at the _very end_ of a request's life-cycle, after + * the request completes, status-specific callbacks are called, and possible + * automatic behaviors are processed. Guaranteed to run regardless of what + * happened during the request. + * +**/ + //= require "ajax/ajax" //= require "ajax/responders" //= require "ajax/base" diff --git a/src/ajax/ajax.js b/src/ajax/ajax.js index 4895a10..d8174f2 100644 --- a/src/ajax/ajax.js +++ b/src/ajax/ajax.js @@ -1,3 +1,7 @@ +/** section: Ajax + * Ajax +**/ + var Ajax = { getTransport: function() { return Try.these( @@ -7,5 +11,11 @@ var Ajax = { ) || false; }, + /** + * Ajax.activeRequestCount -> Number + * + * Represents the number of active XHR requests triggered through + * [[Ajax.Request]], [[Ajax.Updater]], or [[Ajax.PeriodicalUpdater]]. + **/ activeRequestCount: 0 }; diff --git a/src/ajax/base.js b/src/ajax/base.js index b6413db..0be66bc 100644 --- a/src/ajax/base.js +++ b/src/ajax/base.js @@ -1,3 +1,4 @@ +// Abstract class; does not need documentation. Ajax.Base = Class.create({ initialize: function(options) { this.options = { diff --git a/src/ajax/request.js b/src/ajax/request.js index 6937b5d..d09d53d 100644 --- a/src/ajax/request.js +++ b/src/ajax/request.js @@ -1,6 +1,92 @@ +/** section: Ajax + * class Ajax.Request + * + * Initiates and processes an Ajax request. + * + * `Ajax.Request` is a general-purpose class for making HTTP requests. + * + *

Automatic JavaScript response evaluation

+ * + * If an Ajax request follows the _same-origin policy_ **and** its response + * has a JavaScript-related `Content-type`, the content of the `responseText` + * property will automatically be passed to `eval`. + * + * In other words: you don't even need to provide a callback to leverage + * pure-JavaScript AJAX responses. This is the convention that drives Rails's + * RJS. + * + * The list of JavaScript-related MIME-types handled by Prototype is: + * + * * `application/ecmascript` + * * `application/javascript` + * * `application/x-ecmascript` + * * `application/x-javascript` + * * `text/ecmascript` + * * `text/javascript` + * * `text/x-ecmascript` + * * `text/x-javascript` + * + * The MIME-type string is examined in a case-insensitive manner. + * + *

Methods you may find useful

+ * + * Instances of the `Request` object provide several methods that can come in + * handy in your callback functions, especially once the request is complete. + * + *
Is the response a successful one?
+ * + * The [[Ajax.Request#success]] method examines the XHR object's `status` + * property and follows general HTTP guidelines: unknown status is deemed + * successful, as is the whole `2xy` status code family. It's a generally + * better way of testing your response than the usual + * `200 == transport.status`. + * + *
Getting HTTP response headers
+ * + * While you can obtain response headers from the XHR object using its + * `getResponseHeader` method, this makes for verbose code, and several + * implementations raise an exception when the header is not found. To make + * this easier, you can use the [[Ajax.Response#getHeader]] method, which + * delegates to the longer version and returns `null` if an exception occurs: + * + * new Ajax.Request('/your/url', { + * onSuccess: function(response) { + * // Note how we brace against null values + * if ((response.getHeader('Server') || '').match(/Apache/)) + * ++gApacheCount; + * // Remainder of the code + * } + * }); + * + *
Evaluating JSON headers
+ * + * Some backends will return JSON not as response text, but in the `X-JSON` + * header. In this case, you don't even need to evaluate the returned JSON + * yourself, as Prototype automatically does so. It passes the result as the + * `headerJSON` property of the [[Ajax.Response]] object. Note that if there + * is no such header — or its contents are invalid — `headerJSON` will be set + * to `null`. + * + * new Ajax.Request('/your/url', { + * onSuccess: function(transport) { + * transport.headerJSON + * } + * }); +**/ Ajax.Request = Class.create(Ajax.Base, { _complete: false, + /** + * new Ajax.Request(url[, options]) + * - url (String): The URL to fetch. When the _same-origin_ policy is in + * effect (as it is in most cases), `url` **must** be a relative URL or an + * absolute URL that starts with a slash (i.e., it must not begin with + * `http`). + * - options (Object): Configuration for the request. See the + * [[Ajax section]] for more information. + * + * Creates a new `Ajax.Request`. + **/ initialize: function($super, url, options) { $super(options); this.transport = Ajax.getTransport(); @@ -95,6 +181,11 @@ Ajax.Request = Class.create(Ajax.Base, { this.transport.setRequestHeader(name, headers[name]); }, + /** + * Ajax.Request.success() -> Boolean + * + * Tests whether the request was successful. + **/ success: function() { var status = this.getStatus(); return !status || (status >= 200 && status < 300); @@ -148,10 +239,18 @@ Ajax.Request = Class.create(Ajax.Base, { })); }, + /** + * Ajax.Request.getHeader(name) -> String | null + * - name (String): The name of an HTTP header that may have been part of + * the response. + * + * Returns the value of the given response header, or `null` if that header + * was not found. + **/ getHeader: function(name) { try { return this.transport.getResponseHeader(name) || null; - } catch (e) { return null } + } catch (e) { return null; } }, evalResponse: function() { diff --git a/src/ajax/responders.js b/src/ajax/responders.js index a317a90..80a8e37 100644 --- a/src/ajax/responders.js +++ b/src/ajax/responders.js @@ -1,3 +1,71 @@ +/** section: Ajax + * Ajax.Responders + * + * A repository of global listeners notified about every step of + * Prototype-based Ajax requests. + * + * Sometimes, you need to provide generic behaviors over all Ajax operations + * happening on the page (through [[Ajax.Request]], [[Ajax.Updater]] or + * [[Ajax.PeriodicalUpdater]]). + * + * For instance, you might want to automatically show an indicator when an + * Ajax request is ongoing, and hide it when none are. You may well want to + * factor out exception handling as well, logging those somewhere on the page + * in a custom fashion. The possibilities are myriad. + * + * To achieve this, Prototype provides `Ajax.Responders`, which lets you + * register (and, if you wish, unregister later) _responders_, which are + * objects with specially-named methods. These names come from a set of + * general callbacks corresponding to different points in time (or outcomes) + * of an Ajax request's life cycle. + * + * For instance, Prototype automatically registers a responder that maintains + * a nifty variable: [[Ajax.activeRequestCount]]. This represents, at a given + * time, the number of currently active Ajax requests — by monitoring their + * `onCreate` and `onComplete` events. The code for this is fairly simple: + * + * Ajax.Responders.register({ + * onCreate: function() { + * Ajax.activeRequestCount++; + * }, + * onComplete: function() { + * Ajax.activeRequestCount--; + * } + * }); + * + *

Responder callbacks

+ * + * The callbacks for responders are similar to the callbacks described in + * the [[Ajax section]], but take a different signature. They're invoked with + * three parameters: the requester object (i.e., the corresponding "instance" + * of [[Ajax.Request]]), the `XMLHttpRequest` object, and the result of + * evaluating the `X-JSON` response header, if any (can be `null`). They also + * execute in the context of the responder, bound to the `this` reference. + * + * * `onCreate`: Triggered whenever a requester object from the `Ajax` + * namespace is created, after its parameters are adjusted and before its + * XHR connection is opened. This takes *two* arguments: the requester + * object and the underlying XHR object. + * * `onUninitialized` (*Not guaranteed*): Invoked just after the XHR object + * is created. + * * `onLoading` (*Not guaranteed*): Triggered when the underlying XHR object + * is being setup, and its connection opened. + * * `onLoaded` (*Not guaranteed*): Triggered once the underlying XHR object + * is setup, the connection is open, and it is ready to send its actual + * request. + * * `onInteractive` (*Not guaranteed*): Triggered whenever the requester + * receives a part of the response (but not the final part), should it + * be sent in several packets. + * * `onException`: Triggered whenever an XHR error arises. Has a custom + * signature: the first argument is the requester (i.e. an [[Ajax.Request]] + * instance), and the second is the exception object. + * * `onComplete`: Triggered at the _very end_ of a request's life-cycle, after + * the request completes, status-specific callbacks are called, and possible + * automatic behaviors are processed. Guaranteed to run regardless of what + * happened during the request. + * +**/ + Ajax.Responders = { responders: [], @@ -5,11 +73,30 @@ Ajax.Responders = { this.responders._each(iterator); }, + /** + * Ajax.Responders.register(responder) -> undefined + * - responder (Object): A list of functions with keys corresponding to the + * names of possible callbacks. + * + * Add a group of responders to all Ajax requests. + **/ register: function(responder) { if (!this.include(responder)) this.responders.push(responder); }, + /** + * Ajax.Responders.unregister(responder) -> undefined + * - responder (Object): A list of functions with keys corresponding to the + * names of possible callbacks. + * + * Remove a previously-added group of responders. + * + * As always, unregistering something requires you to use the very same + * object you used at registration. If you plan to use `unregister`, be sure + * to assign your responder to a _variable_ before passing it into + * [[Ajax.Responders#register]] — don't pass it an object literal. + **/ unregister: function(responder) { this.responders = this.responders.without(responder); }, diff --git a/src/ajax/response.js b/src/ajax/response.js index c8bc2f8..6224426 100644 --- a/src/ajax/response.js +++ b/src/ajax/response.js @@ -1,4 +1,64 @@ +/** section: Ajax + * class Ajax.Response + * + * A wrapper class around `XmlHttpRequest` for dealing with HTTP responses + * of Ajax requests. + * + * An instance of `Ajax.Response` is passed as the first argument of all Ajax + * requests' callbacks. You _will not_ need to create instances of + * `Ajax.Response` yourself. +**/ + +/** + * Ajax.Response#readyState -> Number + * + * The request’s current state. + * + * `0` corresponds to `"Uninitialized"`, `1` to `"Loading"`, `2` to + * `"Loaded"`, `3` to `"Interactive"`, and `4` to `"Complete"`. +**/ + +/** + * Ajax.Response#responseText -> String + * + * The text body of the response. +**/ + +/** + * Ajax.Response#responseXML -> document | null + * + * The XML body of the response if the `Content-type` of the request is set + * to `application/xml`; `null` otherwise. +**/ + +/** + * Ajax.Response#responseJSON -> Object | Array | null + * + * The JSON body of the response if the `Content-type` of the request is set + * to `application/json`; `null` otherwise. +**/ + +/** + * Ajax.Response#headerJSON -> Object | Array | null + * + * Auto-evaluated content of the `X-JSON` header if present; `null` otherwise. +**/ + +/** + * Ajax.Response#request -> Ajax.Request | Ajax.Updater + * + * The request object itself (an instance of [[Ajax.Request]] or + * [[Ajax.Updater]]). +**/ + +/** + * Ajax.Response#transport -> XmlHttpRequest + * + * The native `XmlHttpRequest` object itself. +**/ + Ajax.Response = Class.create({ + // Don't document the constructor; should never be manually instantiated. initialize: function(request){ this.request = request; var transport = this.transport = request.transport, @@ -18,7 +78,18 @@ Ajax.Response = Class.create({ } }, + /** + * Ajax.Response#status -> Number + * + * The HTTP status code sent by the server. + **/ status: 0, + + /** + * Ajax.Response#statusText -> String + * + * The HTTP status text sent by the server. + **/ statusText: '', getStatus: Ajax.Request.prototype.getStatus, @@ -29,18 +100,44 @@ Ajax.Response = Class.create({ } catch (e) { return '' } }, + /** + * Ajax.Response#getHeader(name) -> String | null + * + * See [[Ajax.Request#getHeader]]. + **/ getHeader: Ajax.Request.prototype.getHeader, + /** + * Ajax.Response#getAllHeaders() -> String | null + * + * Returns a string containing all headers separated by line breaks. _Does + * not__ throw errors if no headers are present the way its native + * counterpart does. + **/ getAllHeaders: function() { try { return this.getAllResponseHeaders(); } catch (e) { return null } }, + /** + * Ajax.Response.getResponseHeader(name) -> String + * + * Returns the value of the requested header if present; throws an error + * otherwise. This is just a wrapper around the `XmlHttpRequest` method of + * the same name. + **/ getResponseHeader: function(name) { return this.transport.getResponseHeader(name); }, + /** + * Ajax.Response.getAllResponseHeaders() -> String + * + * Returns a string containing all headers separated by line breaks; throws + * an error if no headers exist. This is just a wrapper around the + * `XmlHttpRequest` method of the same name. + **/ getAllResponseHeaders: function() { return this.transport.getAllResponseHeaders(); }, diff --git a/src/ajax/updater.js b/src/ajax/updater.js index 262c7f6..e1efa98 100644 --- a/src/ajax/updater.js +++ b/src/ajax/updater.js @@ -1,3 +1,31 @@ +/** section: Ajax + * class Ajax.Updater < Ajax.Request + * + * A class that performs an Ajax request and updates a container’s contents + * with the contents of the response. + * + * `Ajax.Updater` is a subclass of [[Ajax.Request]] built for a common + * use-case. + * + *

Example

+ * + * new Ajax.Updater('items', '/items', { + * parameters: { text: $F('text') } + * }); + * + * This example will make a request to the URL `/items` (with the given + * parameters); it will then replace the contents of the element with the ID + * of `items` with whatever response it receives. + * + * + * +**/ + + + + + + Ajax.Updater = Class.create(Ajax.Request, { initialize: function($super, container, url, options) { this.container = {