lunedì 21 marzo 2011

How to implement COMET with PHP

How to implement COMET with PHP

Comet is a programming technique that enables web servers to send data to the client without having any need for the client to request it. This technique will produce more responsive applications than classic AJAX. In classic AJAX applications, web browser (client) cannot be notified in real time that the server data model has changed. The user must create a request (for example by clicking on a link) or a periodic AJAX request must happen in order to get new data fro the server.
I will now explain how to implement Comet with PHP programming language. I will demonstrate it on two demos which uses two techniques: the first one is based on hidden "<iframe>" and the second one is based on classic AJAX non-returning request. The first demo will simply show the server date in real time on the clients and the second demo will display a mini-chat.

Comet with iframe technique : server timestamp demo

We need:
  • A PHP script that will handle the persistent http request (backend.php)
  • A HTML file that will load Javascript code and that will show the data coming from the server (index.html)
  • The prototype library that will help us to write simple JS code
To understand how it works this schema should help:
How COMET works

The backend script (PHP)

This script will do an infinite loop and will return the server time as long as the client is connected. Call it "backend.php":
<?php
 
  header("Cache-Control: no-cache, must-revalidate");
  header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
  flush();
 
  ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml">
  <head>
 
    <title>Comet php backend</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  </head>
  <body>
 
  <script type="text/javascript">
    // KHTML browser don't share javascripts between iframes
    var is_khtml = navigator.appName.match("Konqueror") || navigator.appVersion.match("KHTML");
    if (is_khtml)
    {
      var prototypejs = document.createElement('script');
      prototypejs.setAttribute('type','text/javascript');
      prototypejs.setAttribute('src','prototype.js');
      var head = document.getElementsByTagName('head');
      head[0].appendChild(prototypejs);
    }
    // load the comet object
    var comet = window.parent.comet;
 
  </script>
 
  <?php
 
  while(1) {
    echo '<script type="text/javascript">';
    echo 'comet.printServerTime('.time().');';
    echo '</script>';
    flush(); // used to send the echoed data to the client
    sleep(1); // a little break to unload the server CPU
  }
 
  ?>
 
  </body>
  </html>

The client script (HTML)

This HTML document first load the prototype library in the "<head>" tag, then it create the tag that will contains the server timer "<div id="content"></div>", and finally it create a "comet" javascript object that will connect the backend script to the time container tag.
The comet object will create some invisible "iframe" tags (depends on the web browser). These iframes are in charge to create the background persistent http connection with the backend script. Notice: this script do not handle possible connection problems between client and server.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
      <title>Comet demo</title>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
      <script type="text/javascript" src="prototype.js"></script>
 
    </head>
    <body>
      <div id="content">The server time will be shown here</div>
 
  <script type="text/javascript">
  var comet = {
    connection   : false,
    iframediv    : false,
 
    initialize: function() {
      if (navigator.appVersion.indexOf("MSIE") != -1) {
 
        // For IE browsers
        comet.connection = new ActiveXObject("htmlfile");
        comet.connection.open();
        comet.connection.write("<html>");
        comet.connection.write("<script>document.domain = '"+document.domain+"'");
        comet.connection.write("</html>");
        comet.connection.close();
        comet.iframediv = comet.connection.createElement("div");
        comet.connection.appendChild(comet.iframediv);
        comet.connection.parentWindow.comet = comet;
        comet.iframediv.innerHTML = "<iframe id='comet_iframe' src='./backend.php'></iframe>";
 
      } else if (navigator.appVersion.indexOf("KHTML") != -1) {
 
        // for KHTML browsers
        comet.connection = document.createElement('iframe');
        comet.connection.setAttribute('id',     'comet_iframe');
        comet.connection.setAttribute('src',    './backend.php');
        with (comet.connection.style) {
          position   = "absolute";
          left       = top   = "-100px";
          height     = width = "1px";
          visibility = "hidden";
        }
        document.body.appendChild(comet.connection);
 
      } else {
 
        // For other browser (Firefox...)
        comet.connection = document.createElement('iframe');
        comet.connection.setAttribute('id',     'comet_iframe');
        with (comet.connection.style) {
          left       = top   = "-100px";
          height     = width = "1px";
          visibility = "hidden";
          display    = 'none';
        }
        comet.iframediv = document.createElement('iframe');
        comet.iframediv.setAttribute('src', './backend.php');
        comet.connection.appendChild(comet.iframediv);
        document.body.appendChild(comet.connection);
 
      }
    },
 
    // this function will be called from backend.php  
    printServerTime: function (time) {
      $('content').innerHTML = time;
    },
 
    onUnload: function() {
      if (comet.connection) {
        comet.connection = false; // release the iframe to prevent problems with IE when reloading the page
      }
    }
  }
  Event.observe(window, "load",   comet.initialize);
  Event.observe(window, "unload", comet.onUnload);
 
  </script>
 
  </body>
  </html>

Download it

Here is the tar.gz archive of this demo.

Comet with classic AJAX : litte chat demo

As on the above technique, we need:
  • A file to exchange data (data.txt)
  • A PHP script that will handle the persistent http request (backend.php)
  • A HTML file that will load Javascript code and that will show the data coming from the server (index.html)
  • The prototype library that will help us to write simple JS code

The backend script (PHP)

This script will do two things:
  • Write into "data.txt" when new messages are sent
  • Do an infinite loop as long as "data.txt" file is unchanged
<?php
 
  $filename  = dirname(__FILE__).'/data.txt';
 
  // store new message in the file
  $msg = isset($_GET['msg']) ? $_GET['msg'] : '';
  if ($msg != '')
  {
    file_put_contents($filename,$msg);
    die();
  }
 
  // infinite loop until the data file is not modified
  $lastmodif    = isset($_GET['timestamp']) ? $_GET['timestamp'] : 0;
  $currentmodif = filemtime($filename);
  while ($currentmodif <= $lastmodif) // check if the data file has been modified
  {
    usleep(10000); // sleep 10ms to unload the CPU
    clearstatcache();
    $currentmodif = filemtime($filename);
  }
 
  // return a json array
  $response = array();
  $response['msg']       = file_get_contents($filename);
  $response['timestamp'] = $currentmodif;
  echo json_encode($response);
  flush();
 
  ?>

The client script (HTML)

This HTML document first load the prototype library in the "<head>" tag, then it create the tag "<div id="content"></div>" that will contains the chat messages comming from "data.txt" file, and finally it create a "comet" javascript object that will call the backend script in order to watch for new chat messages.
The comet object will send AJAX requests each time a new message has been received and each time a new message is posted. The persistent connection is only used to watch for new messages. A timestamp url parameter is used to identify the last requested message, so that the server will return only when the "data.txt" timestamp is newer that the client timestamp.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
      <title>Comet demo</title>
 
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
      <script type="text/javascript" src="prototype.js"></script>
    </head>
    <body>
 
  <div id="content">
  </div>
 
  <p>
    <form action="" method="get" onsubmit="comet.doRequest($('word').value);$('word').value='';return false;">
      <input type="text" name="word" id="word" value="" />
      <input type="submit" name="submit" value="Send" />
    </form>
  </p>
 
  <script type="text/javascript">
  var Comet = Class.create();
  Comet.prototype = {
 
    timestamp: 0,
    url: './backend.php',
    noerror: true,
 
    initialize: function() { },
 
    connect: function()
    {
      this.ajax = new Ajax.Request(this.url, {
        method: 'get',
        parameters: { 'timestamp' : this.timestamp },
        onSuccess: function(transport) {
          // handle the server response
          var response = transport.responseText.evalJSON();
          this.comet.timestamp = response['timestamp'];
          this.comet.handleResponse(response);
          this.comet.noerror = true;
        },
        onComplete: function(transport) {
          // send a new ajax request when this request is finished
          if (!this.comet.noerror)
            // if a connection problem occurs, try to reconnect each 5 seconds
            setTimeout(function(){ comet.connect() }, 5000); 
          else
            this.comet.connect();
          this.comet.noerror = false;
        }
      });
      this.ajax.comet = this;
    },
 
    disconnect: function()
    {
    },
 
    handleResponse: function(response)
    {
      $('content').innerHTML += '<div>' + response['msg'] + '</div>';
    },
 
    doRequest: function(request)
    {
      new Ajax.Request(this.url, {
        method: 'get',
        parameters: { 'msg' : request 
      });
    }
  }
  var comet = new Comet();
  comet.connect();
  </script>
 
  </body>
  </html>

Download it

Here is the tar.gz archive of this demo.

jQuery UI Notification Widget by Eric Hynds

jQuery UI Notification Widget by Eric Hynds

Gaya Design - sNotify

Gaya Design - sNotify

esempio con js/sNotify.js

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
  <title>Gaya Design - sNotify</title>
  <meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1' />
  <link href='css/style.css' rel='stylesheet' type='text/css' />
  
  <script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js'></script>
  
  <link href='css/sNotify.css' rel='stylesheet' type='text/css' />
  <script src="js/sNotify.js" type="text/javascript"></script>
</head>
 
 <body>
  <div style='padding: 15px;'>
  
   <div class='content'>
    
    <h1>sNotify</h1>
    
    <p>
     This is an example of how sNotify works.
    </p> 
    
    <h2>Example:</h2>
    <input type='text' name='add_message' class='add_message' value='type your message' />
    <input type='button' onclick='sNotify.addToQueue($(".add_message").attr("value"))' value='add message'>
    
    <script type="text/javascript">
     sNotify.addToQueue("Thanks for checking out sNotify created by <a href='http://www.gayadesign.com'>Gaya Design</a>");
     sNotify.addToQueue("This easy to use jQuery script allows you to spawn notifications with ease. It's dead simple!");
     sNotify.addToQueue("You can check out how it works by filling in a message on the left and click: <strong>add message</strong>");
    </script>
    
    <br />
    <br />
    
    <a href='sNotify.zip'>Download</a><br />
    <a href='http://www.gayadesign.com/diy/snotify-easy-notifications-in-jquery/'>Read the post</a>
   </div>
   
  </div>
 </body>
</html>

Pines Notify is a JavaScript notification plugin

Pines Notify is a JavaScript notification plugin, developed by Hunter Perrin as part of Pines. It is designed to provide an unparalleled level of flexibility, while still being very easy to implement and use. It uses the jQuery UI CSS library for styling, which means it is fully and easily themeable. Try out some of the readymade themes using the selector in the top right corner of this page. It is licensed under the GNU Affero GPL, which means you are free to use, study, modify, and freely redistribute it under the same license.

Features

  • Timed hiding with visual effects.
  • Sticky (no automatic hiding) notices.
  • Optional hide button.
  • Supports dynamically updating text, title, icon, type...
  • Stacks allow notice sets to stack independently.
  • Control stack direction and push to top or bottom.
  • Supports HTML (including forms) in title and text.
  • Variable opacity.
  • Supports Pines icons/jQuery UI icons/any CSS based icons.
  • Supports custom classes for individual notice styling.
  • Standard and custom effects.
  • Optional drop shadows.

Simple Demos



Advanced Demos



Custom Stacks:
Top Left. Moves down, then right. Pushes to stack top.
Bottom Left. Moves right, then up. Pushes to stack bottom.
Bottom Right. Moves up, then left. Pushes to stack bottom.
Custom. Moves right, then down. Pushes to stack bottom.
Custom. Moves left, then up. Pushes to stack top.
Tooltip

Benchmarking:

Effects built in to jQuery:

Effects from jQuery UI:

Custom Functions:
This isn't really a feature of Pines Notify, but it's cool:

Theming

The Pines Notify plugin uses the jQuery UI CSS Framework to style its look and feel, including colors and background textures. We recommend using the ThemeRoller tool to create and download custom themes that are easy to build and maintain.

If a deeper level of customization is needed, there are widget-specific classes referenced within the jquery.pnotify.default.css stylesheet that can be modified. These classes are highlighted in bold below.

Sample markup with jQuery UI CSS Framework classes

<div class="ui-pnotify ui-widget ui-helper-clearfix" style="width: 300px; opacity: 1; display: block; right: 15px; top: 15px;">
<div class="ui-pnotify-shadow ui-widget-shadow"></div> (This div only appears when shadow is turned on.)
<div class="ui-pnotify-container ui-corner-all ui-state-highlight" style="min-height: 16px;">
<div class="ui-pnotify-closer" style="cursor: pointer; display: none;">
<span class="ui-icon ui-icon-circle-close"></span>
</div>
<div class="ui-pnotify-icon">
<span class="ui-icon ui-icon-info"></span>
</div>
<div class="ui-pnotify-title">Pines Notify</div>
<div class="ui-pnotify-text">Welcome to Pines Notify.</div>
</div>
</div>
Note: This is a sample of markup generated by Pines Notify, not markup you should use to create a notice. You don't need markup for that.

Installation

Installation is simple. Include jQuery 1.4 and jQuery UI (only the CSS is required). Then include the following files:
  • jquery.pnotify.js or jquery.pnotify.min.js (Minified)
  • jquery.pnotify.default.css
If you want to use jQuery UI effect animations, you also need to include the jQuery UI JavaScript.

notifiche javascript

//varia roba dell'head  <script type="text/javascript"
<!-- 
function 
getData() { 
if (
window.XMLHttpRequest) {  request = new XMLHttpRequest(); 
} else {  
   
request = new ActiveXObject("Msxml2.XMLHTTP"); 

var 
message""

var 
url"tesi2.php"
if (
request) {  request.open("POST"url);  

 request.setRequestHeader("Content-Type" 
"application/x-www-form-urlencoded; charset=UTF-8"); 
 //o cmq il charset che usi  request.onreadystatechange 
function() { 
if (
request.readyState == 4) { 
document.getElementById("tabella").innerHTML request.responseText


request.send(message); 

else 
alert("Il tuo browser obsoleto non supporta ajax."); 

var 
tempo =  setTimeout("getData();",30000); 

 //sono millisecondi, ed indicano la frequenza del refresh   
//-->  </script> 
<body onLoad="getData();"> 
//presentazione etc etc 

<div id="tabella"> 
Caricamento in corso... 
</div> 
qui si pu' mettere il resto del testo
</body> 

</html>  



Crea la nuvoletta (html, css, immagini) e tramite css imposta:


Codice:
display:none;
nella funzione javascript cambia aggiungi
Codice:
document.getElementById("idnuvoletta").innerHTML = request.responseText;
document.getElementById("idnuvoletta").style.display="block";
dove idnuvoletta è l'id dato all'elemento.

Notifiche con jQuery e PHP

Notifiche con jQuery e PHP



Per rima cosa ho aggiunto un paio di nuove classi (“conferma” e “errore”) al foglio di stile:
1
2
3
.conferma, .errore { padding: 3px 5px; margin-bottom: 10px; font-weight: bold; color: #FFFFFF; }
.conferma { background-color: #007000; }
.errore { background-color: #CC1100; }
Dopodichè ho aggiunto un semplice DIV nel codice HTML della pagina nel punto in cui volevo far apparire le notifiche:
1
<div id="notifiche"></div>
Fatto questo ho creato una semplicissima funzione PHP il cui scopo è creare un piccolo javascript che produce la notifica sfruttando la potenza della libreria jQuery (opportunamente inclusa nella pagina web) per creare un effetto fadeIn ed un successivo fadeOut temporizzato:
1
2
3
4
5
6
7
function notifica_jquery($class,$text)
{
  echo "<script type=\"text/javascript\">
  $('#notifiche').attr('class','" . $class . "').hide().html('" . $text . "').fadeIn('slow');
  setTimeout(function(){ $('#notifiche').fadeOut('slow'); }, 3000);
  </script>";
}
Questa funzione viene poi utilizzata all’interno del codice di controllo dell’applicazione in questo modo:
1
2
3
4
5
6
7
8
// se c'è un errore mostro la notifica di fallimento dell'operazione
if ($errore) {
  notifica_jquery('errore','Si è verificato un errore...');
}
// in caso contrario mostro una conferma di operazione riuscita
else {
  notifica_jquery('conferma','Tutto ha funzionato bene!!!');
}
come potete notare non faccio altro che passare, come primo parametro, la classe definita nei CSS e, come secondo parametro, un semplice testo da stampare a video.
Affinchè la notifica funzioni correttamente è necessario che lo script venga inserito nell’output della pagina dopo il DIV con ID “notifiche”. In caso contrario si otterrà un avviso di errore javascript da parte del browser.