prototype/src/ajax.js

379 lines
11 KiB
JavaScript
Raw Normal View History

2007-01-18 22:24:27 +00:00
var Ajax = {
getTransport: function() {
return Try.these(
function() {return new XMLHttpRequest()},
function() {return new ActiveXObject('Msxml2.XMLHTTP')},
function() {return new ActiveXObject('Microsoft.XMLHTTP')}
) || false;
},
activeRequestCount: 0
};
2007-01-18 22:24:27 +00:00
Ajax.Responders = {
responders: [],
_each: function(iterator) {
this.responders._each(iterator);
},
register: function(responder) {
if (!this.include(responder))
this.responders.push(responder);
},
unregister: function(responder) {
this.responders = this.responders.without(responder);
},
dispatch: function(callback, request, transport, json) {
this.each(function(responder) {
if (Object.isFunction(responder[callback])) {
2007-01-18 22:24:27 +00:00
try {
responder[callback].apply(responder, [request, transport, json]);
} catch (e) { }
2007-01-18 22:24:27 +00:00
}
});
}
};
Object.extend(Ajax.Responders, Enumerable);
Ajax.Responders.register({
onCreate: function() { Ajax.activeRequestCount++ },
onComplete: function() { Ajax.activeRequestCount-- }
2007-01-18 22:24:27 +00:00
});
Ajax.Base = Class.create({
initialize: function(options) {
2007-01-18 22:24:27 +00:00
this.options = {
method: 'post',
asynchronous: true,
contentType: 'application/x-www-form-urlencoded',
encoding: 'UTF-8',
parameters: '',
evalJSON: true,
evalJS: true
};
Object.extend(this.options, options || { });
2007-01-18 22:24:27 +00:00
this.options.method = this.options.method.toLowerCase();
if (Object.isString(this.options.parameters))
2007-01-18 22:24:27 +00:00
this.options.parameters = this.options.parameters.toQueryParams();
}
});
2007-01-18 22:24:27 +00:00
Ajax.Request = Class.create(Ajax.Base, {
2007-01-18 22:24:27 +00:00
_complete: false,
initialize: function($super, url, options) {
$super(options);
2007-01-18 22:24:27 +00:00
this.transport = Ajax.getTransport();
this.request(url);
},
request: function(url) {
this.url = url;
this.method = this.options.method;
var params = Object.clone(this.options.parameters);
2007-01-18 22:24:27 +00:00
if (!['get', 'post'].include(this.method)) {
// simulate other verbs over post
params['_method'] = this.method;
this.method = 'post';
}
this.parameters = params;
2007-10-13 10:55:52 +00:00
if (params = Object.toQueryString(params)) {
// when GET, append parameters to URL
if (this.method == 'get')
this.url += (this.url.include('?') ? '&' : '?') + params;
else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
params += '&_=';
}
2007-01-18 22:24:27 +00:00
try {
var response = new Ajax.Response(this);
if (this.options.onCreate) this.options.onCreate(response);
Ajax.Responders.dispatch('onCreate', this, response);
2007-01-18 22:24:27 +00:00
this.transport.open(this.method.toUpperCase(), this.url,
this.options.asynchronous);
if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
2007-01-18 22:24:27 +00:00
this.transport.onreadystatechange = this.onStateChange.bind(this);
this.setRequestHeaders();
this.body = this.method == 'post' ? (this.options.postBody || params) : null;
this.transport.send(this.body);
2007-01-18 22:24:27 +00:00
/* Force Firefox to handle ready state 4 for synchronous requests */
if (!this.options.asynchronous && this.transport.overrideMimeType)
this.onStateChange();
}
catch (e) {
this.dispatchException(e);
}
},
onStateChange: function() {
var readyState = this.transport.readyState;
if (readyState > 1 && !((readyState == 4) && this._complete))
this.respondToReadyState(this.transport.readyState);
},
setRequestHeaders: function() {
var headers = {
'X-Requested-With': 'XMLHttpRequest',
'X-Prototype-Version': Prototype.Version,
'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
};
if (this.method == 'post') {
headers['Content-type'] = this.options.contentType +
(this.options.encoding ? '; charset=' + this.options.encoding : '');
/* Force "Connection: close" for older Mozilla browsers to work
* around a bug where XMLHttpRequest sends an incorrect
* Content-length header. See Mozilla Bugzilla #246651.
*/
if (this.transport.overrideMimeType &&
(navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
headers['Connection'] = 'close';
}
// user-defined headers
if (typeof this.options.requestHeaders == 'object') {
var extras = this.options.requestHeaders;
if (Object.isFunction(extras.push))
2007-01-18 22:24:27 +00:00
for (var i = 0, length = extras.length; i < length; i += 2)
headers[extras[i]] = extras[i+1];
else
$H(extras).each(function(pair) { headers[pair.key] = pair.value });
}
for (var name in headers)
this.transport.setRequestHeader(name, headers[name]);
},
success: function() {
var status = this.getStatus();
return !status || (status >= 200 && status < 300);
2007-01-18 22:24:27 +00:00
},
getStatus: function() {
try {
return this.transport.status || 0;
} catch (e) { return 0 }
},
2007-01-18 22:24:27 +00:00
respondToReadyState: function(readyState) {
var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
2007-01-18 22:24:27 +00:00
if (state == 'Complete') {
try {
this._complete = true;
(this.options['on' + response.status]
2007-01-18 22:24:27 +00:00
|| this.options['on' + (this.success() ? 'Success' : 'Failure')]
|| Prototype.emptyFunction)(response, response.headerJSON);
2007-01-18 22:24:27 +00:00
} catch (e) {
this.dispatchException(e);
}
var contentType = response.getHeader('Content-type');
if (this.options.evalJS == 'force'
|| (this.options.evalJS && contentType
&& contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
this.evalResponse();
2007-01-18 22:24:27 +00:00
}
try {
(this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
2007-01-18 22:24:27 +00:00
} catch (e) {
this.dispatchException(e);
}
if (state == 'Complete') {
// avoid memory leak in MSIE: clean up
this.transport.onreadystatechange = Prototype.emptyFunction;
}
},
getHeader: function(name) {
try {
return this.transport.getResponseHeader(name);
} catch (e) { return null }
},
evalResponse: function() {
try {
return eval((this.transport.responseText || '').unfilterJSON());
2007-01-18 22:24:27 +00:00
} catch (e) {
this.dispatchException(e);
}
},
dispatchException: function(exception) {
(this.options.onException || Prototype.emptyFunction)(this, exception);
Ajax.Responders.dispatch('onException', this, exception);
}
});
Ajax.Request.Events =
['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
Ajax.Response = Class.create({
initialize: function(request){
this.request = request;
var transport = this.transport = request.transport,
readyState = this.readyState = transport.readyState;
if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
this.status = this.getStatus();
this.statusText = this.getStatusText();
this.responseText = String.interpret(transport.responseText);
this.headerJSON = this._getHeaderJSON();
}
if(readyState == 4) {
var xml = transport.responseXML;
this.responseXML = xml === undefined ? null : xml;
this.responseJSON = this._getResponseJSON();
}
},
status: 0,
statusText: '',
getStatus: Ajax.Request.prototype.getStatus,
getStatusText: function() {
try {
return this.transport.statusText || '';
} catch (e) { return '' }
},
getHeader: Ajax.Request.prototype.getHeader,
getAllHeaders: function() {
try {
return this.getAllResponseHeaders();
} catch (e) { return null }
},
getResponseHeader: function(name) {
return this.transport.getResponseHeader(name);
},
getAllResponseHeaders: function() {
return this.transport.getAllResponseHeaders();
},
_getHeaderJSON: function() {
var json = this.getHeader('X-JSON');
try {
return json ? json.evalJSON(this.request.options.sanitizeJSON) : null;
} catch (e) {
this.request.dispatchException(e);
}
},
_getResponseJSON: function() {
var options = this.request.options;
try {
if (options.evalJSON == 'force' || (options.evalJSON &&
(this.getHeader('Content-type') || '').include('application/json')))
return this.transport.responseText.evalJSON(options.sanitizeJSON);
return null;
} catch (e) {
this.request.dispatchException(e);
}
}
});
2007-01-18 22:24:27 +00:00
Ajax.Updater = Class.create(Ajax.Request, {
initialize: function($super, container, url, options) {
2007-01-18 22:24:27 +00:00
this.container = {
success: (container.success || container),
failure: (container.failure || (container.success ? null : container))
};
2007-01-18 22:24:27 +00:00
options = options || { };
var onComplete = options.onComplete;
options.onComplete = (function(response, param) {
this.updateContent(response.responseText);
if (Object.isFunction(onComplete)) onComplete(response, param);
2007-01-18 22:24:27 +00:00
}).bind(this);
$super(url, options);
2007-01-18 22:24:27 +00:00
},
updateContent: function(responseText) {
var receiver = this.container[this.success() ? 'success' : 'failure'],
options = this.options;
2007-01-18 22:24:27 +00:00
if (!options.evalScripts) responseText = responseText.stripScripts();
2007-01-18 22:24:27 +00:00
if (receiver = $(receiver)) {
if (options.insertion) {
if (Object.isString(options.insertion)) {
var insertion = { }; insertion[options.insertion] = responseText;
receiver.insert(insertion);
}
else options.insertion(receiver, responseText);
}
else receiver.update(responseText);
2007-01-18 22:24:27 +00:00
}
if (this.success()) {
if (this.onComplete) this.onComplete.bind(this).defer();
2007-01-18 22:24:27 +00:00
}
}
});
Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
initialize: function($super, container, url, options) {
$super(options);
2007-01-18 22:24:27 +00:00
this.onComplete = this.options.onComplete;
this.frequency = (this.options.frequency || 2);
this.decay = (this.options.decay || 1);
this.updater = { };
2007-01-18 22:24:27 +00:00
this.container = container;
this.url = url;
this.start();
},
start: function() {
this.options.onComplete = this.updateComplete.bind(this);
this.onTimerEvent();
},
stop: function() {
this.updater.options.onComplete = undefined;
clearTimeout(this.timer);
(this.onComplete || Prototype.emptyFunction).apply(this, arguments);
},
updateComplete: function(response) {
2007-01-18 22:24:27 +00:00
if (this.options.decay) {
this.decay = (response.responseText == this.lastText ?
2007-01-18 22:24:27 +00:00
this.decay * this.options.decay : 1);
this.lastText = response.responseText;
2007-01-18 22:24:27 +00:00
}
this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
2007-01-18 22:24:27 +00:00
},
onTimerEvent: function() {
this.updater = new Ajax.Updater(this.container, this.url, this.options);
}
});