/*
* @project AvriComet- Mootools 1.2.1> Request.Queue Class (in the function send), Can be found on www.clientcide.com
* @author : Quentin Ambard
* @version : 0.1
* @url http://www.avricot.com/blog
* @license MIT Style License
*
it will create 4 differents types of tunnels, using the lasts specifications :
it's -i think- the best solution, but the server will have to send all the informations in 4 differents ways...
->Trident : iframeForever (using ActiveXObject)
Server :
Specific Header :
Content-Type: text/html; charset=utf-8
Push :
<script type="text/javascript">parent._cometObject.fireEvent('dataPushed', {JSON: "DATA TO PUSH"})</script>
You must flush the buffer, sending some noise when you start the connexion, for exemple :"<span></span><span></span><span></span><span></span><span></span>"
->presto : source-event
Server :
Specific Header :
Content-Type: application/x-dom-event-stream
Push :
Event: "+this.cometName+"\ndata: {JSON: "DATA TO PUSH"}\n\n (cometName is send on each Bayeux Publish request on the field ex )
->gecko : xhr object using multipart
Server :
Specific Header :
Content-Type: multipart/x-mixed-replace;boundary=XXoXoX
Push :
Content-Type: text/json\r\n\r\n{JSON: "DATA TO PUSH"}\r\n\r\n--XXoXoX\r\n
->Others : addCallback
Server :
Specific Header :
none
Push :
{JSON: "DATA TO PUSH"}
AvriComet will fire 2 events :
- error ('error text') ;
- push (Object)
When you send data to the server, you can do it throught the method send(whateverYouHaveToSend).
It will catch the answer and fire the event :
- answer (theAnswer)
*/
var AvriComet = new Class ({
Implements: [Options, Events],
options: {
cometName: "MooComet", //name of the comet connexion, essential for presto engine.
urlServer: '' //Url of the server.
},
length: 0,
initialize: function(_options){
this.setOptions(_options);
this.sender = new Request.Queue({
concurrent : 2
});
this.cometType = (Browser.Engine.trident ? 3 : (Browser.Engine.presto ? 2 : (Browser.Engine.gecko ? 1 : 0)));
//ie
if (this.cometType == 3) {
this.tunnel = new ActiveXObject("htmlfile");
}
//presto (opera)
else if (this.cometType == 2){
this.tunnel = document.createElement("event-source");
}
//gecko (firefox)
else if (this.cometType == 1){
this.tunnel = new Request({
url: this.options.urlServer,
method: 'get',
header: ''
});
this.tunnel.xhr.multipart = true ;
this.tunnel.xhr.onload = function (event) {
if (event.target.readyState == 4 ) {
this.decodeJSON(event.target.responseText) ;
//this.fireEvent('dataPushed', [event.target.responseText, event.target.responseXML]) ;
}
else {
this.fireEvent('error', "Error xhr : readyState: "+event.target.readyState) ;
}
}.bind(this.tunnel);
}
//webkit (chrome/safari)
else if (this.cometType == 0){
this.tunnel = new Request.XHR(this.options);
this.tunnel.addCallback(this.onChange.bind(this), {readyState: false, status: false});
}
return this;
},
/**
* Have to be invoked when you want to close the connection
*/
cancel: function() {
if (this.cometType == 3) {
this.tunnel.body.innerHTML="<iframe src='about:blank'></iframe>";
}
//default iframe forever for all broswer or just for browser != than presto && gecko
else if (this.cometType == 2) {
document.body.removeChild(this.tunnel);
}
else {
this.tunnel.cancel();
}
return this;
},
/**
* Create the tunnel with the server.
* 3 differents type of tunnel, depends of the browser's engine:
* trident => iframe with ActiveXObject object
* presto => event-source (html5)
* firefox => XMLHttpRequest with multipart
* post the information with : src="'+this.options.urlServer+_post'"
**/
initTunnel: function(_post) {
//Trident Engine (ie)
if (this.cometType == 3) {
this.tunnel.open();
this.tunnel.write("<html><body></body></html>");
this.tunnel.close();
this.tunnel.parentWindow._cometObject = this;
this.tunnel.parentWindow._cometObject.addEvent('dataPushed', this.firePush.bindWithEvent(this));
this.tunnel.body.innerHTML = "<iframe id='test' src='"+this.options.urlServer+'?cometType='+this.cometType+'&cometName='+this.options.cometName+'&'+_post+"'></iframe>";
}
//Presto Engine (opera)
else if (this.cometType == 2) {
this.tunnel.addEventListener(this.options.cometName, this.decodeJSON.bindWithEvent(this), false);
this.tunnel.setAttribute("src", this.options.urlServer+'?cometType='+this.cometType+'&cometName='+this.options.cometName+'&'+_post);
}
//gecko Engine (Firefox)
else //if (this.cometType == 1)
{
//this.tunnel.addEvent('dataPushed', this.decodeJSON.bindWithEvent(this));
this.tunnel.send('cometType='+this.cometType+'&cometName='+this.options.cometName+'&'+_post);
}
//safari+chrome
/*else {
this.tunnel.send({data: {'cometType': this.cometType, 'cometName': this.options.cometName}});
}*/
},
send: function (_post) {
var req = new Request.JSON({url: this.options.urlServer, link: 'cancel', method: 'get'});
req.addEvent('complete', this.answerSender.bindWithEvent(this));
this.sender.addRequest('sender', req);
req.send(_post);
},
decodeJSON: function (_textResponse) {
if(this.cometType == 2) {
_textResponse = _textResponse.data ;
}
else if (_textResponse != "") {
var newLength = text.length ;
if (newLength>this.length) {//fix firefox behaviour when push 2 information in the same time.
_textResponse = text.substring(this.length,newLength-1) ;
/* possible implementation for FF answer (be carreful with the final "," : {JSON: DATA TO PUSH},
*
* text = "["+text.substring(this.length,newLength-1)+"]" ; //So you have [{objectData1},{objectData2},{objectData3}]
var listAction = JSON.decode(text); //you get the array with all the json object from the server
var size = listAction.length ;
for(i=0;i<size;i++) {
this.firePush(listAction[i]);
}
this.length = newLength ;
*/
}
}
this.firePush(_textResponse);
},
firePush: function (_object) {
try {
if (typeof _object != "object") {
_object = JSON.decode(_object);
}
}
catch (e) {
this.fireEvent('error', 'transmission error or server error. Sometimes the last bracket has a little bug : '+_object);
}
this.fireEvent('push', JSON.decode(_object));
},
answerSender: function (_answer) {
if (typeof(_answer) != "undefined") {
this.fireEvent('answer', _answer);
}
}
}) ;
Request.XHR = new Class({
Extends: Request,
callbacks: [],
check: function() {
return true;
/*if (!this.timer) return true;
switch (this.options.link){
case 'cancel': this.cancel(); return true;
case 'chain': this.chain(this.start.bind(this, arguments)); return false;
}
return false;*/
},
addCallback: function(fn, options){
options = $extend({
fn: $empty,
readyState: 4,
status: 200
}, options);
this.callbacks.push($extend(options, {fn: fn}));
return this;
},
onStateChange: function(){
//if (!this.running) return;
this.status = 0;
$try(function(){
this.status = this.xhr.status;
}, this);
this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML};
this.callbacks.forEach(function(callback) {
if (callback.readyState != false && callback.readyState != this.xhr.readyState)
return;
if (callback.status != false && callback.status != this.status)
return;
callback.fn(this.processScripts(this.response.text), this.response.xml);
}, this);
}
});