Choosing a Development Framework

Over at Smashing Magazine they have put together a good round up of Development Frameworks. The post goes over the most popular frameworks for each language. The post also gives tips on what framework is best for you and has a brief over view of the Model View Controller (MVC) design pattern.

Below is an excerpt from the JavaScript frameworks.


  • Prototype is a JavaScript framework that serves as a foundation for other JavaScript frameworks. Don’t be fooled however, as Prototype can stand on its own.

  • is built on the Prototype framework and has found its way into many high-profile sites including Digg, Apple, and Basecamp. The documentation is clear, and has an easy learning curve. However, compared to other JavaScript frameworks it is larger in size.

  • Mootools is a compact, modular, object-oriented JavaScript framework with impressive effects and Ajax handling. The framework is for advanced users as the learning curve is rather steep.

  • jQuery continues to rise in popularity due to its extensive API and active development. jQuery is a great balance of complexity and functionality.

  • For ASP.NET developers you can’t beat the ASP.NET AJAX framework which is built into the .NET Framework as of 3.5, but you can also download it for previous versions. The amount of documentation, examples, and community continues to increase. There are controls that you can simply drag-and-drop an update panel on an ASPX page and process Ajax!


Further JavaScript Frameworks

  • The Yahoo! User Interface Library - Yahoo! released its impressed JavaScript library with incredible amounts of documentation.
  • Ext JS - Originally built as an add-on to the YUI it can now extend Prototype and jQuery. Includes an impress interface.
  • Dojo is a small library focused on interpreter independence and small core size.
  • MochiKit - A framework that has focus on scripting language standards including ECMAScript and the W3C DOM.

Click here to read the full post on the frameworks.

Forcing a File Download in PHP


I found an interesting tutorial on forcing a file to be downloaded instead of opened with the associated application.

Below is the PHP code that would be called to force the doe\wnload.


function _Download($f_location, $f_name){
    str_replace('/', $f_name); 
    str_replace('/', $f_location); 
    header ("Cache-Control: must-revalidate, post-check=0, pre-check=0");
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Length: ' . filesize($f_location));    header('Content-Disposition: attachment; filename=' . basename($f_name));

$file = $_GET['file'];
$loc = $_GET['location'];

downloadFiles($loc, $file);


To use this file, create a link to your download file, lets assume it is download.php. Just reference the link to download.php?file=filename.txt&location=filename.txt. Using the URL parameters the file is passed in and this then forces the download.

This is a great script to for forcing downloads of images, web pages or text files as these would otherwise be opened in the browser.

To view the full tutorial click here

jQuery easyThumb - A PHP / jQuery Thumbnail Generator with Lightbox

The first version of jQuery easyThumb was released today. easyThumb is a plugin that uses both jQuery and PHP to generate thumbnails (to use in galleries etc.) on-the-fly. All you need to do is to make a unsorted list of images. No links or thumbnails are required, the library takes care of this - thumbnails are created automatically and are linked to the full size image. This integrates well with the Lightbox library as is shown in the online demo.

From the author:

jQuery easyThumb is a plugin for jQuery. It turns a list of full-scale images to a list of thumbnails on-the-fly. It uses a php-script to return a thumbnail for each image, and jQuery to generate the list.

Online Demo with Lightbox

Check out the original post to read more about easyThumb and to download the source code.

Email2HTTP beta open for public registration


Need to accept email in your web application? Check out Email2HTTP.

You can write simple scripts in languages like PHP and immediately start accepting email in your web apps. Build features like craigslist's anonymous email addresses, TripIt's itinerary gathering, Flickr & Google Docs email uploads, etc. in no time.

Hope to see you there,


Sample PHP script to handle an email & send a reply:

Sparkline PHP Graphing Library


If you are creating an application or widget that deals with complex data such as a stock ticker you probably want to graph the data. Sparklines are a great way to graph such data in a small space. In fact it is amazing how much information you can get by just a quick look at a Sparkline.

Below is an example of a Sparkline from


Back in the dot-com heyday, you might remember that Cisco, EMC, Sun, and Oracle were nicknamed "the four horsemen of the Internet". You might now say: "companies that ride together, slide together." Large 10-year charts of these four stocks.

  5-Year Close High Low
Cisco 19.55 80.06 8.60
EMC 13.05 101.05 3.83
Sun 5.09 64.32 2.42
Oracle 13.01 43.31 7.32

The below code example is how you make a simple bar graph Sparkline.

 // Set the spacing between the bars
 // Add a color called "mygray" to the available color list
 $graph->setColorHtml("mygray", "#424542");
 // Set the background color to this new color
 // As of 0.2, if you didn't set the color beforehand,
 // your background would be black
 // Begin the loop to add the data
 foreach ($data as $key => $value) {
 // Add the data
 // This will make one white bar with the relative value
 // of $value
 $graph->setData($key, $value, 'white');
 // Draw all necessary objects for our graph
 // The height will be 16
 // Displays the graph by sending a 'Content-type: image/png'
 // header then outputting the image data

You can get more information about the Sparklines PHP Graphing library at

Now that you have the tools to create a Sparkline go make a cool new application or widget that use Sparklines.

Cross-Domain XML Access - a.k.a. Server Side Proxy


In developing Ajax applications there are quite a few times that you may need to consume some type of XML from a different domain (especially in the case of mashups). Since retrieving XML from a different domain through the browser is a security restriction you have to find another way of getting to the XML. One way to get around this is to use JSON data however many API's are only available in XML, so it is very possible that XML will be your only option.

In order to get around these security restrictions we need to use a server side proxy. How this works is that the server retrieves the XML from the other domain (which is not stopped by security restrictions) and then returns it to the Ajax application using the XMHTTPRequest object. This tricks the browser into thinking that the XML comes from the same domain.

So without further delay below is some PHP code that I have used to create a server side proxy. You just need to pass in the URL using the proxy_url variable that is passed to the PHP through the URL.

//          FILE: proxy.php
// LAST MODIFIED: 2006-03-23
//        AUTHOR: Troy Wolf 
//   DESCRIPTION: Allow scripts to request content they otherwise may not be
//                able to. For example, AJAX (XmlHttpRequest) requests from a
//                client script are only allowed to make requests to the same
//                host that the script is served from. This is to prevent
//                "cross-domain" scripting. With proxy.php, the javascript
//                client can pass the requested URL in and get back the
//                response from the external server.
//         USAGE: "proxy_url" required parameter. For example:

// proxy.php requires Troy's class_http.
// Alter the path according to your environment.

$proxy_url = isset($_GET['proxy_url'])?$_GET['proxy_url']:false;
if (!$proxy_url) {
    header("HTTP/1.0 400 Bad Request");
    echo "proxy.php failed because proxy_url parameter is missing";

// Instantiate the http object used to make the web requests.
// More info about this object at
if (!$h = new http()) {
    header("HTTP/1.0 501 Script Error");
    echo "proxy.php failed trying to initialize the http object";

$h->url = $proxy_url;
$h->postvars = $_POST;
if (!$h->fetch($h->url)) {
    header("HTTP/1.0 501 Script Error");
    echo "proxy.php had an error attempting to query the url";

// Forward the headers to the client.
$ary_headers = split("\n", $h->header);
foreach($ary_headers as $hdr) { header($hdr); }

// Send the response body to the client.
echo $h->body;

Below is the class_http that is referenced in the above.

* Filename.......: class_http.php
* Author.........: Troy Wolf []
* Last Modified..: Date: 2006/03/06 10:15:00
* Description....: Screen-scraping class with caching. Includes image_cache.php
                   companion script. Includes static methods to extract data
                   out of HTML tables into arrays or XML. Now supports sending
                   XML requests and custom verbs with support for making
                   WebDAV requests to Microsoft Exchange Server.

class http {
    var $log;
    var $dir;
    var $name;
    var $filename;
    var $url;
    var $port;
    var $verb;
    var $status;
    var $header;
    var $body;
    var $ttl;
    var $headers;
    var $postvars;
    var $xmlrequest;
    var $connect_timeout;
    var $data_ts;
    The class constructor. Configure defaults.
    function http() {
        $this->log = "New http() object instantiated.<br />\n";
        Seconds to attempt socket connection before giving up.
        $this->connect_timeout = 30;
        Set the 'dir' property to the directory where you want to store the cached
        content. I suggest a folder that is not web-accessible.
        End this value with a "/".
        $this->dir = realpath("./")."/"; //Default to current dir.


        return true;
    fetch() method to get the content. fetch() will use 'ttl' property to
    determine whether to get the content from the url or the cache.
    function fetch($url="", $ttl=0, $name="", $user="", $pwd="", $verb="GET") {
        $this->log .= "--------------------------------<br />fetch() called
\n"; $this->log .= "url: ".$url."<br />\n"; $this->status = ""; $this->header = ""; $this->body = ""; if (!$url) { $this->log .= "OOPS: You need to pass a URL!<br />"; return false; } $this->url = $url; $this->ttl = $ttl; $this->name = $name; $need_to_save = false; if ($this->ttl == "0") { if (!$fh = $this->getFromUrl($url, $user, $pwd, $verb)) { return false; } } else { if (strlen(trim($this->name)) == 0) { $this->name = MD5($url); } $this->filename = $this->dir."http_".$this->name; $this->log .= "Filename: ".$this->filename."<br />"; $this->getFile_ts(); if ($this->ttl == "daily") { if (date('Y-m-d',$this->data_ts) != date('Y-m-d',time())) { $this->log .= "cache has expired<br />"; if (!$fh = $this->getFromUrl($url, $user, $pwd, $verb)) { return false; } $need_to_save = true; if ($this->getFromUrl()) { return $this->saveToCache(); } } else { if (!$fh = $this->getFromCache()) { return false; } } } else { if ((time() - $this->data_ts) >= $this->ttl) { $this->log .= "cache has expired<br />"; if (!$fh = $this->getFromUrl($url, $user, $pwd, $verb)) { return false; } $need_to_save = true; } else { if (!$fh = $this->getFromCache()) { return false; } } } } /* Get response header. */ $this->header = fgets($fh, 1024); $this->status = substr($this->header,9,3); while ((trim($line = fgets($fh, 1024)) != "") && (!feof($fh))) { $this->header .= $line; if ($this->status=="401" and strpos($line,"WWW-Authenticate: Basic realm=\"")===0) { fclose($fh); $this->log .= "Could not authenticate<br />\n"; return FALSE; } } /* Get response body. */ while (!feof($fh)) { $this->body .= fgets($fh, 1024); } fclose($fh); if ($need_to_save) { $this->saveToCache(); } return $this->status; } /* PRIVATE getFromUrl() method to scrape content from url. */ function getFromUrl($url, $user="", $pwd="", $verb="GET") { $this->log .= "getFromUrl() called<br />"; preg_match("~([a-z]*://)?([^:^/]*)(:([0-9]{1,5}))?(/.*)?~i", $url, $parts); $protocol = $parts[1]; $server = $parts[2]; $port = $parts[4]; $path = $parts[5]; if ($port == "") { if (strtolower($protocol) == "https://") { $port = "443"; } else { $port = "80"; } } if ($path == "") { $path = "/"; } if (!$sock = @fsockopen(((strtolower($protocol) == "https://")?"ssl://":"").$server, $port, $errno, $errstr, $this->connect_timeout)) { $this->log .= "Could not open connection. Error " .$errno.": ".$errstr."<br />\n"; return false; } $this->headers["Host"] = $server.":".$port; if ($user != "" && $pwd != "") { $this->log .= "Authentication will be attempted<br />\n"; $this->headers["Authorization"] = "Basic ".base64_encode($user.":".$pwd); } if (count($this->postvars) > 0) { $this->log .= "Variables will be POSTed<br />\n"; $request = "POST ".$path." HTTP/1.0\r\n"; $post_string = ""; foreach ($this->postvars as $key=>$value) { $post_string .= "&".urlencode($key)."=".urlencode($value); } $post_string = substr($post_string,1); $this->headers["Content-Type"] = "application/x-www-form-urlencoded"; $this->headers["Content-Length"] = strlen($post_string); } elseif (strlen($this->xmlrequest) > 0) { $this->log .= "XML request will be sent<br />\n"; $request = $verb." ".$path." HTTP/1.0\r\n"; $this->headers["Content-Length"] = strlen($this->xmlrequest); } else { $request = $verb." ".$path." HTTP/1.0\r\n"; } #echo "<br />request: ".$request; if (fwrite($sock, $request) === FALSE) { fclose($sock); $this->log .= "Error writing request type to socket<br />\n"; return false; } foreach ($this->headers as $key=>$value) { if (fwrite($sock, $key.": ".$value."\r\n") === FALSE) { fclose($sock); $this->log .= "Error writing headers to socket<br />\n"; return false; } } if (fwrite($sock, "\r\n") === FALSE) { fclose($sock); $this->log .= "Error writing end-of-line to socket<br />\n"; return false; } #echo "
post_string: ".$post_string; if (count($this->postvars) > 0) { if (fwrite($sock, $post_string."\r\n") === FALSE) { fclose($sock); $this->log .= "Error writing POST string to socket<br />\n"; return false; } } elseif (strlen($this->xmlrequest) > 0) { if (fwrite($sock, $this->xmlrequest."\r\n") === FALSE) { fclose($sock); $this->log .= "Error writing xml request string to socket<br />\n"; return false; } } return $sock; } /* PRIVATE clean() method to reset the instance back to mostly new state. */ function clean() { $this->status = ""; $this->header = ""; $this->body = ""; $this->headers = array(); $this->postvars = array(); /* Try to use user agent of the user making this request. If not available, default to IE6.0 on WinXP, SP1. */ if (isset($_SERVER['HTTP_USER_AGENT'])) { $this->headers["User-Agent"] = $_SERVER['HTTP_USER_AGENT']; } else { $this->headers["User-Agent"] = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"; } /* Set referrer to the current script since in essence, it is the referring page. */ if (substr($_SERVER['SERVER_PROTOCOL'],0,5) == "HTTPS") { $this->headers["Referer"] = "https://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; } else { $this->headers["Referer"] = "http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; } } /* PRIVATE getFromCache() method to retrieve content from cache file. */ function getFromCache() { $this->log .= "getFromCache() called<br />"; //create file pointer if (!$fp=@fopen($this->filename,"r")) { $this->log .= "Could not open ".$this->filename."<br />"; return false; } return $fp; } /* PRIVATE saveToCache() method to save content to cache file. */ function saveToCache() { $this->log .= "saveToCache() called<br />"; //create file pointer if (!$fp=@fopen($this->filename,"w")) { $this->log .= "Could not open ".$this->filename."<br />"; return false; } //write to file if (!@fwrite($fp,$this->header."\r\n".$this->body)) { $this->log .= "Could not write to ".$this->filename."<br />"; fclose($fp); return false; } //close file pointer fclose($fp); return true; } /* PRIVATE getFile_ts() method to get cache file modified date. */ function getFile_ts() { $this->log .= "getFile_ts() called<br />"; if (!file_exists($this->filename)) { $this->data_ts = 0; $this->log .= $this->filename." does not exist<br />"; return false; } $this->data_ts = filemtime($this->filename); return true; } /* Static method table_into_array() Generic function to return data array from HTML table data rawHTML: the page source needle: optional string to start parsing source from needle_within: 0 = needle is BEFORE table, 1 = needle is within table allowed_tags: list of tags to NOT strip from data, e.g. "<a><b>" */ function table_into_array($rawHTML,$needle="",$needle_within=0,$allowed_tags="") { $upperHTML = strtoupper($rawHTML); $idx = 0; if (strlen($needle) > 0) { $needle = strtoupper($needle); $idx = strpos($upperHTML,$needle); if ($idx === false) { return false; } if ($needle_within == 1) { $cnt = 0; while(($cnt < 100) && (substr($upperHTML,$idx,6) != "<TABLE")) { $idx = strrpos(substr($upperHTML,0,$idx-1),"<"); $cnt++; } } } $aryData = array(); $rowIdx = 0; /* If this table has a header row, it may use TD or TH, so check special for this first row. */ $tmp = strpos($upperHTML,"<TR",$idx); if ($tmp === false) { return false; } $tmp2 = strpos($upperHTML,"</TR>",$tmp); if ($tmp2 === false) { return false; } $row = substr($rawHTML,$tmp,$tmp2-$tmp); $pattern = "/<TH>|<TH\ |<TD>|<TD\ /"; preg_match($pattern,strtoupper($row),$matches); $hdrTag = $matches[0]; while ($tmp = strpos(strtoupper($row),$hdrTag) !== false) { $tmp = strpos(strtoupper($row),">",$tmp); if ($tmp === false) { return false; } $tmp++; $tmp2 = strpos(strtoupper($row),"</T"); $aryData[$rowIdx][] = trim(strip_tags(substr($row,$tmp,$tmp2-$tmp),$allowed_tags)); $row = substr($row,$tmp2+5); preg_match($pattern,strtoupper($row),$matches); $hdrTag = $matches[0]; } $idx = strpos($upperHTML,"</TR>",$idx)+5; $rowIdx++; /* Now parse the rest of the rows. */ $tmp = strpos($upperHTML,"<<R",$idx); if ($tmp === false) { return false; } $tmp2 = strpos($upperHTML,"</TABLE>",$idx); if ($tmp2 === false) { return false; } $table = substr($rawHTML,$tmp,$tmp2-$tmp); while ($tmp = strpos(strtoupper($table),"<TR") !== false) { $tmp2 = strpos(strtoupper($table),"</TR"); if ($tmp2 === false) { return false; } $row = substr($table,$tmp,$tmp2-$tmp); while ($tmp = strpos(strtoupper($row),"<TD") !== false) { $tmp = strpos(strtoupper($row),">",$tmp); if ($tmp === false) { return false; } $tmp++; $tmp2 = strpos(strtoupper($row),"</TD"); $aryData[$rowIdx][] = trim(strip_tags(substr($row,$tmp,$tmp2-$tmp),$allowed_tags)); $row = substr($row,$tmp2+5); } $table = substr($table,strpos(strtoupper($table),"</TR>")+5); $rowIdx++; } return $aryData; } /* Static method table_into_xml() Generic function to return xml dataset from HTML table data rawHTML: the page source needle: optional string to start parsing source from allowedTags: list of tags to NOT strip from data, e.g. "<a><b>" */ function table_into_xml($rawHTML,$needle="",$needle_within=0,$allowedTags="") { if (!$aryTable = http::table_into_array($rawHTML,$needle,$needle_within,$allowedTags)) { return false; } $xml = "<?xml version=\"1.0\" standalone=\"yes\" \?\>\n"; $xml .= "<TABLE>\n"; $rowIdx = 0; foreach ($aryTable as $row) { $xml .= "\t<ROW id=\"".$rowIdx."\">\n"; $colIdx = 0; foreach ($row as $col) { $xml .= "\t\t<COL id=\"".$colIdx."\">".trim(utf8_encode(htmlspecialchars($col)))."</COL>\n"; $colIdx++; } $xml .= "\t</ROW>\n"; $rowIdx++; } $xml .= "</TABLE>"; return $xml; } } ?>

Click here to be taken to the page where you can download all of the code above.

So now you have some code to create a server side proxy and can use it to create a great new mashup! If you know of any other good server side proxy code in PHP, Ruby, Java, .Net or any other web language please leave it in the comments.

PHP Templating


If you've done any developing with PHP (especially with open source PHP projects) you will have probably at least seen some templating. A commonly used template engine for PHP is Smarty. Smarty does a good job of helping your PHP applications adhere to a Model View Controller design.

As you probably know Model View Controller is a way of abstracting portions of an applications so that you can have multiple people working on their part of the project without stepping on other peoples toes. Which in any decent sized organization (or even in a startup) is very helpful to the development process. If you develop in PHP I recommend that you use templating, even in Ajax applications with PHP on the backend I recommend using templating, as it helps abstract the design from both the backend code and the data (which is exactly what we want).

Just to get you started with templating I've included a portion of a good tutorial on templating using Smarty below.

Basic Templating

At the most basic level, templating can be achieved by removing the common HTML code into separate files and including them into any php file:

<?php include("header.php"); ?>
some content and html goes here
<?php include("footer.php"); ?>

This approach is fine for most purposes and does help reusing common HTML (and/or php) code, for menu generation and setting of page footers and headers. Unfortunately, it does not adapt particularly well to all situations. In the event of the page dealing with form handling or session usage, life becomes slightly more complex.

The relatively simple form below shows how the code can become relatively messy and difficult to follow.


if(isset($_SESSION['username'])) {
   echo "Logged in as : " . $_SESSION['username'];
if(isset($_POST['id'])) {
   $results = database_query("SELECT * FROM blah WHERE id = ?", Array($_POST['id']));
   if(sizeof($results>0)) {
      echo "<p>The following matches were found:</p>";
      echo "<ul>\n";
      foreach($results as $item) {
         echo "<li>" . $item . "</li>\n";
      echo "</ul>\n";
   else {
      echo "<p>No matches found.</p>";
<p>Please enter an ID to search for</p>
<form method='post'>
<input type='text' name='id' value=''>
<input type='submit'>

Fom looking at the above code, we have the following problems :

* We have to do some things before we can include the header page (e.g. session_start() or header() calls).
* Ideally we should only be outputting HTML at the last possible moment
* We have a mixture of PHP and HTML code, which makes the file bulky and difficult to edit
* It wouldn't be very easy to use the same underlying code to generate a page as a .pdf or other format
* Upon adding more logic, it's likely that it will become harder to follow the flow of execution
* The over complex nature of the page means it's highly likely that bugs will be introduced

We can help to solve the above problems by splitting the page into two distinct parts :

* Logic (dealing with form parameters and data retrieval)
* Display (HTML or other output).

Smarty has the answer...

There are numerous templating engines available for PHP, and some projects have the discipline to use native PHP embedded within a file for display of logic. I found Smarty's existing documentation and framework to make the migration far easier, as well as forcing me to resist the temptation to embed PHP based logic within the template.

Smarty is written in PHP, and installation is a matter of downloading a compressed file, extracting it somewhere appropriate (E.g. /usr/local/lib/php) and creating the necessary directory structure within your project. In order to use Smarty from within a PHP page, the following is required :

$smarty = new Smarty();

Jumping straight to the refactored example, the way Smarty can be used, and the benefits of it should become clear.

First, the template file which is used by Smarty (this will normally live in a directory called 'templates'). This file contains HTML markup and some simple Smarty specific syntax. This file has a suffix of .tpl. An example one follows :

{include file='header.php'}
<p>Welcome to the application</p>

{if isset $logged_in_as}

{if sizeof($results > 0)}
    {foreach from=$results item=i}
<p>Please enter an ID to search for</p>
<form method='post'>
  <input type='text' name='id' value=''>
  <input type='submit'>
{include file='footer.php'}

As you can see, Smarty provides constructs for looping and checking for the existence of variables. Smarty's template language is very similar to PHP in many respects.

In order to populate this template, the following php code could be used :

$smarty = new Smarty();
if(isset($_SESSION['username'])) {
   $smarty->assign("logged_in_as", "You are logged in as " . $_SESSION['username']);
if(isset($_POST['id'])) {
   $results = database_query("SELECT * FROM blah WHERE id = ?", Array($_POST['id']));
   $smarty->assign("results", $results);

As should be apparent, the assign function is used to pass data to the template for display, and the 'display' function is used to select which template we wish to use. Because there is no output to the browser until 'display' is called, we can use any of the functions that output HTTP headers to the browser throughout nearly all of the file without getting the 'headers already sent' error.

In addition to the above you can find more useful resources below.

This is the tutorial that the above was taken from.
This resource is from the Smarty web site and is extremely useful.

As always if you have any questions on this post you can either leave a comment or contact me through Social Ajaxonomy.

PHP Ajax Chat Tutorial


Jack Herrington has put together a starter tutorial for building a web-based chat application using PHP, MySQL, DHTML, Ajax, and the Prototype.js library. This by no means is a finished or polished application, but is intended to be a starter-kit for a chat application.

An interesting thing to note is that the tutorial does a good job of showing a potential pitfall of polling in Ajax applications. Polling can become a performance issue very quickly, especially if the amount of data returned by each request is ever increasing. The tutorial does provide a solution to help with performance along with all the required source code to build this starter application.

Suggestions the author has for extending this application are:

  • Track users: Put a list of the people actively engaged in the conversation alongside the chat. Doing so lets people know who is at the party and when they come and go.
  • Allow multiple conversations: Allow multiple conversations on different topics to go on simultaneously.
  • Allow emoticons: Translate character groupings such as :-) into the appropriate image of a smiley face.
  • Use URL parsing: Use regular expressions in the client-side JavaScript code to find URLs and turn them into hyperlinks.
  • Handle the Enter key: Instead of having an Add button, watch for users pressing the Enter or Return key by hooking onto the onkeydown event in the textarea.
  • Show when a user is typing: Alert the server when a user has started to type, so that other participants can see that a reply is pending. This lessens the perception that the conversation has died off to a minimum if you have slow typists.
  • Limit the size of a posted message: Another way to keep the conversation going is to keep messages small. Limit the maximum number of characters in the textarea —again by trapping onkeydown —to help speed up conversations.

Click here to check out the tutorial and download the source code.

Syndicate content