/*
Script: JSONRequest.js
JSONRequest implementation:
This object is based on JSONRequest "official" draft and allows developers to perform get or post Ajax requestes in a simple way.
With 3 public methods it's possible to manage more than a single request, using a queue to order requestes and to preserve server interactions.
To know more about JSONRequest, please visit this page: <http://www.json.org/JSONRequest.html>
Version:
0.9 - probably requires more debug
Compatibility:
FireFox - Version 1, 1.5, 2 and 3 (FireFox uses secure code evaluation)
Internet Explorer - Version 5, 5.5, 6 and 7
Opera - 8 and 9 (probably 7 too)
Safari - Version 2 (probably 1 too)
Konqueror - Version 3 or greater
Dependencies:
<JSONRequestError.js>
Credits:
- JSON site for draft, <http://www.json.org/JSONRequest.html>
- Douglas Crockford to wrote above draft, <http://www.crockford.com/>
Author:
Andrea Giammarchi, <http://www.3site.eu>
License:
>Copyright (C) 2007 Andrea Giammarchi - www.3site.eu
>
>Permission is hereby granted, free of charge,
>to any person obtaining a copy of this software and associated
>documentation files (the "Software"),
>to deal in the Software without restriction,
>including without limitation the rights to use, copy, modify, merge,
>publish, distribute, sublicense, and/or sell copies of the Software,
>and to permit persons to whom the Software is furnished to do so,
>subject to the following conditions:
>
>The above copyright notice and this permission notice shall be included
>in all copies or substantial portions of the Software.
>
>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
>INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
>FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
>IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
>DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
>ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
>OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
Object: JSONRequest
Personal JSONRequest implementation with queue and multiple exception filters.
Example:
>try {
> JSONRequest.get("mypage.php", function(sn, response, exception){
> alert(exception || response);
> });
>}
>catch(e) {
> alert(e);
>}
*/
JSONRequest = new function(){
/* Section: Methods - Public */
/*
Method: cancel
blocks a request and call user callback if request has been successful blocked adding 10 milliseconds as delay time for next interaction.
Arguments:
Number - A valid JSONRequest serial number.
Example:
>mybtn.serialNumber = JSONRequest.get("page?something", function(sn, result, error){alert(result)});
>mybtn.onclick = function(){
> JSONRequest.cancel(this.serialNumber);
>};
*/
this.cancel = function(i){
if(i-- > 0 && i < queue.length && queue[i].timeout)
cancel(i, new JSONRequestError("cancelled"));
changeDelay(10);
};
/*
Method: get
prepares a request and push them inside queue. Throws a JSONRequest Exception if some parameter is wrong.
Arguments:
String - A valid url to call using Ajax respecting Same Origin Policy.
Function - A callback with 3 arguments: serialNumber, responceObject, exceptionObject
Returns:
Number - A new valid serial number.
Example:
>JSONRequest.get("page.psp?var=value", function(sn, result, error){alert([sn, result, error])});
Note:
If callback has not 3 arguments this method will throw an exception.
If request has not problems callback is called using only 2 arguments, serialNumber and responseObject.
This method uses default JSONRequest timeout, 10 seconds.
*/
this.get = function(url, done){
var i = queue.length;
method = "get";
try{
this.post(url, {}, done);
queue[i].data = null;
return i+1;
} catch(e) {
throw e
}
};
/*
Method: post
prepares a request and push them inside queue. Throws a JSONRequest Error if some parameter is wrong.
Arguments:
String - A valid url to call using Ajax respecting Same Origin Policy.
Array / Object - Data to send
Function - A callback with 3 arguments: serialNumber, responceObject, exceptionObject
[Number] - optional milliseconds timeout. Default: 10000
Returns:
Number - A new valid serial number.
Example:
>JSONRequest.post("page.psp?var=value", {name:"Andrea"}, function(sn, result, error){alert([sn, result, error])});
Note:
Server side will recieve a JSONRequest key with escaped JSON data (using encodeURIComponent).
If data is not an Array, an Object or a valid JSON compatible variable, this method throws a JSONRequest Exception.
*/
this.post = function(url, send, done, timeout){
var i = queue.length;
try {
prepare(url, send, done, timeout || 10000);
return i+1;
} catch(e) {
method = "post";
queue[i] = {"timeout":0};
changeDelay(500, 512);
throw e;
}
};
/* Section: Methods - Private */
/*
Method: cancel
removes timeout and block XHR interaction. Changes queue index properties to save memory.
Arguments:
Number - A valid JSONRequest serial number.
[JSONRequestError] - optional dedicated error
*/
function cancel(l, JSONRequestError){
clearTimeout(queue[l].timeout);
queue[l].xhr.onreadystatechange = function(){};
queue[l].xhr.abort();
if(JSONRequestError)
queue[l].done(l + 1, null, JSONRequestError);
queue[l] = {"timeout":0};
};
/*
Method: changeDelay
adds arbitrary delay time to internal variable.
Arguments:
Number - milliseconds to add
[Number] - optional random milliseconds to add
*/
function changeDelay(a, b){
delay += (a + Math.floor(Math.random() * b || 0));
if(delay < 0)
delay = 0;
};
/*
Method: prepare
checks every user get or post method arguments. Throws different JSONRequest Exceptions if these are not valid.
Arguments:
String - A valid url to call using Ajax respecting Same Origin Policy.
Array / Object - Data to send
Function - A callback with 3 arguments: serialNumber, responceObject, exceptionObject
[Number] - optional milliseconds timeout. Default: 10000
*/
function prepare(url, send, done, timeout){
var i = queue.length,
uri = url.indexOf(document.domain);
if(uri > 8 || (uri === -1 && re.test(uri)))
throw new JSONRequestError("bad URL");
else if(parseInt(timeout) !== timeout || timeout < 0)
throw new JSONRequestError("bad timeout");
else if(done.constructor !== Function || done.length !== 3)
throw new JSONRequestError("bad function");
else {
try {
queue[i] = {"data":JSON.encode(send), "done":done, "method":method, "send":true, "timeout":timeout, "url":url, "xhr":xhr()};
if(queue[i].data === undefined || (send.constructor !== Array && send.constructor !== Object))
throw new JSONRequestError;
}
catch(e) {
throw new JSONRequestError("bad data")
}
};
};
/*
Method: request
interval callback, performs one request using queue and next available index.
*/
function request(){
var request = queue[l],
data = null,
xhr;
if(delay < 0)
delay = 0;
if(!delay && request && request.timeout && request.send) {
request.send = false;
request.timeout = setTimeout(function(){
changeDelay(500, 512);
cancel(l++, new JSONRequestError("no response"));
}, request.timeout);
xhr = request.xhr;
xhr.open(request.method, request.url, true);
xhr.setRequestHeader("Content-Type", "application/jsonrequest");
if(request.method === "post") {
data = "JSONRequest=".concat(encodeURIComponent(request.data));
xhr.setRequestHeader("Content-Length", data.length);
xhr.setRequestHeader("Content-Encoding", "identity");
};
xhr.onreadystatechange = function(){
if(xhr.readyState === 4) {
if(xhr.status === 200) {
clearTimeout(request.timeout);
try {
if(xhr.getResponseHeader("Content-Type") === "application/jsonrequest") {
request.done(l+1, JSON.decode(xhr.responseText));
cancel(l);
changeDelay(-10);
}
else
throw new JSONRequestError;
}
catch(e) {
changeDelay(500, 512);
cancel(l, new JSONRequestError("bad response"));
}
}
else {
changeDelay(500, 512);
cancel(l, new JSONRequestError("not ok"));
};
l++;
}
};
xhr.send(data);
}
else if(request && !request.timeout && l < queue.length - 1)
l++;
if(delay)
delay -= 10;
};
/*
Method: xhr
creates a new XMLHttpRequest or ActiveX object.
Returns:
new XMLHttpRequest or new Microsoft.XMLHTTP ActiveX object
*/
function xhr(){
return window.XMLHttpRequest ? new XMLHttpRequest : new ActiveXObject("Microsoft.XMLHTTP");
};
/* Section: Properties - Private */
/*
Property: Private
List:
Number - 'delay' - delay time for next interaction. Each error, except for cancel one, add a delay of 500 + rand(0, 511) milliseconds.
Number - 'l' - queue index, used to perform one request each time.
String - 'method' - temporary method used with request
Array - 'queue' - a list of objects used for each interaction.
RegExp - 're' - basic Regular Expression to verify url
*/
var delay = 0,
l = 0,
method = "post",
queue = [],
re = /^(\s*)http/;
setInterval(request, 10);
};