Over a million developers have joined DZone.

GMail Chat - Gmail Client Side Architecture Part 3

· Web Dev Zone

Start coding today to experience the powerful engine that drives data application’s development, brought to you in partnership with Qlik.

In this the final of the three part series on GMail's client side architecture we will be taking a look at implementing a GMial like chat window such as you will find inside the GMail web interface. This is a basic overview and implementation but I hope some will find it useful.

This implementation is not an Ajax client with a chat server but instead focuses on the client side of things. Let's start be taking a look at the working example here. Open this link in a new window or tab and once loaded move focus back to this window/tab. After a few seconds you will hear he Windows notification sound, could not find Google's, switch back and youwill find that the header of the chat window's color has changed as well as the title. Let's look at the code that made this happen. First our bare HTML page:

<!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" lang="en" xml:lang="en">
<head>
</head>
<body onBlur="lostFocus();" onFocus="gotFocus()">
<strong><font color="#FF0000">Gmail</font> Chat Implementation</strong><br />
(take a new tab , or a new window, or minimize this window , and wait 3 sec, and
come back.)
<div id="Layer1" style="position:absolute; left:14px; top:46px; width:246px; height:258px; z-index:1;border: 1px solid #999999;"></div>
<div id="Layer2" style="position:absolute; left:13px; top:46px; width:247px; height:40px; z-index:2; background-color: #73A6FF ; layer-background-color: #FF8A00; border: 1px none #000000;"></div>
<div id="Layer3" style="position:absolute; left:17px; top:200px; width:237px; height:98px; z-index:3"></div>
<div id="Layer4" style="position:absolute; left:21px; top:268px; width:67px; height:26px; z-index:4">Options</div>
<div id="Layer5" style="position:absolute; left:154px; top:265px; width:71px; height:28px; z-index:5">Popout
</div>
<div id="Layer6" style="position:absolute; left:15px; top:89px; width:240px; height:105px; z-index:6">
<p><strong>me</strong>: Hai Anand</p>
<p><strong>Anand</strong>: Hello</p>
</div>
<div id="Layer7" style="position:absolute; left:216px; top:55px; width:41px; height:26px; z-index:7">
<div align="right"><strong><font color="#FFFFFF" size="6">-</font><font color="#FFFFFF" size="4">
X </font></strong></div>
</div>
<div id="Layer8" style="position:absolute; left:19px; top:205px; width:234px; height:53px; z-index:8;border: 1px solid #999999;">
<div align="left">
<textarea name="textarea" style="width:230px;height:50px;" onclick="changeColorBlue();">Hows life </textarea>
</div>
</div>
<div id="Layer9" style="position:absolute; left:20px; top:58px; width:18px; height:17px; z-index:9"><img src="green.png" width="20" /></div>
<div id="Layer10" style="position:absolute; left:53px; top:56px; width:159px; height:18px; z-index:10"><font color="#FFFFFF"><strong>Anand@gmail.com...</strong></font></div>
</body>
</html>

You will see that there are two function calls on the body tag, for two different event types, the onBlur and onFocus events. These two function calls will check whether the current window has focus or has lost focus and based on that execute a specific block of JavaScript code. First let's look at the lostFocus() function that will be called when the browser window looses focus i.e. onBlur:

function lostFocus()
{
document.title = 'Sajith M.R Says...';
state = 'nonfocus';

played = 0 ;

changeColorRed();

alterTitle();
}

function changeColorRed()
{
document.getElementById('Layer2').style.background = '#FF8A00';

}

function alterTitle()
{
if(state =='nonfocus')
{
if ( document.title == 'Gmail Inbox(1)')
{
if(played == 0)
{
soundManager.play('notify');
played = 1;
}
document.title = 'Sajith M.R Says...';
}
else
document.title = 'Gmail Inbox(1)';

setTimeout("alterTitle()",3000);
}
}

 

The first thing the lostFocus() function does is set the document title to 'Sajith M.R Says...'. It then sets the var state to 'nofocus', to indicate that the window has lost focus, sets played to 0 and then calls then changeColorRed() function. This simply selects the DIV with the id of 'Layer2' from the DOM and changes it's background color to #FF8A00 via the following line:

document.getElementById('Layer2').style.background = '#FF8A00';

After the function exits the next function call is made to alertTitle(), this one is a little bit more involved.It first checks the variable state to detect whether out chat window has focus or not. As the originating function setthe value of state to 'nofocus' we will step into the code block inside the if statement. Next we check whether the title of our document is equal to 'Gmail Inbox(1)' and since our originating script set the title to 'Sajith M.R Says...' we will jump to outside the  if block and execute the else block.

This now sets the document title to  'Gmail Inbox(1)' and following this calls the alertTitle inside a timout function set to 3000.

setTimeout("alterTitle()",3000);

After having waited 300 miliseconds the alertTitle() function is executed again. We again step into the first if statement as the state has not changed. But now, instead of skipping the second if block and jumping to else, we step into the if statement as our document title boolean check now evaluates to true. Inside the if statement we immediately encounter another boolean if statement. This statement checks whether our notification sound has been played or not.

Our played variable is still at 0 so we will execute the code inside the if statement. This calls the play() function of our soundManager script and passes in the notify parameter. Here is the soundManager.js file:

var isIE = navigator.appName.toLowerCase().indexOf('internet explorer')+1;
var isMac = navigator.appVersion.toLowerCase().indexOf('mac')+1;

function SoundManager(container) {
// DHTML-controlled sound via Flash
var self = this;
this.movies = []; // movie references
this.container = container;
this.unsupported = 0; // assumed to be supported
this.defaultName = 'default'; // default movie

this.FlashObject = function(url) {
var me = this;
this.o = null;
this.loaded = false;
this.isLoaded = function() {
if (me.loaded) return true;
if (!me.o) return false;
me.loaded = ((typeof(me.o.readyState)!='undefined' && me.o.readyState == 4) || (typeof(me.o.PercentLoaded)!='undefined' && me.o.PercentLoaded() == 100));
return me.loaded;
}
this.mC = document.createElement('div');
this.mC.className = 'movieContainer';
with (this.mC.style) {
// "hide" flash movie
position = 'absolute';
left = '-256px';
width = '64px';
height = '64px';
}
var html = ['<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0"><param name="movie" value="'+url+'"><param name="quality" value="high"></object>','<embed src="'+url+'" width="1" height="1" quality="high" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash"></embed>'];
if (navigator.appName.toLowerCase().indexOf('microsoft')+1) {
this.mC.innerHTML = html[0];
this.o = this.mC.getElementsByTagName('object')[0];
} else {
this.mC.innerHTML = html[1];
this.o = this.mC.getElementsByTagName('embed')[0];
}
document.getElementsByTagName('div')[0].appendChild(this.mC);
}

this.addMovie = function(movieName,url) {
self.movies[movieName] = new self.FlashObject(url);
}

this.checkMovie = function(movieName) {
movieName = movieName||self.defaultName;
if (!self.movies[movieName]) {
self.errorHandler('checkMovie','Exception: Could not find movie',arguments);
return false;
} else {
return (self.movies[movieName].isLoaded())?self.movies[movieName]:false;
}
}

this.errorHandler = function(methodName,message,oArguments,e) {
writeDebug('<div class="error">soundManager.'+methodName+'('+self.getArgs(oArguments)+'): '+message+(e?' ('+e.name+' - '+(e.message||e.description||'no description'):'')+'.'+(e?')':'')+'</div>');
}

this.play = function(soundID,loopCount,noDebug,movieName) {
if (self.unsupported) return false;
movie = self.checkMovie(movieName);
if (!movie) return false;
if (typeof(movie.o.TCallLabel)!='undefined') {
try {
self.setVariable(soundID,'loopCount',loopCount||1,movie);
movie.o.TCallLabel('/'+soundID,'start');
if (!noDebug) writeDebug('soundManager.play('+self.getArgs(arguments)+')');
} catch(e) {
self.errorHandler('play','Failed: Flash unsupported / undefined sound ID (check XML)',arguments,e);
}
}
}

this.stop = function(soundID,movieName) {
if (self.unsupported) return false;
movie = self.checkMovie(movieName);
if (!movie) return false;
try {
movie.o.TCallLabel('/'+soundID,'stop');
writeDebug('soundManager.stop('+self.getArgs(arguments)+')');
} catch(e) {
// Something blew up. Not supported?
self.errorHandler('stop','Failed: Flash unsupported / undefined sound ID (check XML)',arguments,e);
}
}

this.getArgs = function(params) {
var x = params?params.length:0;
if (!x) return '';
var result = '';
for (var i=0; i<x; i++) {
result += (i&&i<x?', ':'')+(params[i].toString().toLowerCase().indexOf('object')+1?typeof(params[i]):params[i]);
}
return result
}

this.setVariable = function(soundID,property,value,oMovie) {
// set Flash variables within a specific movie clip
if (!oMovie) return false;
try {
oMovie.o.SetVariable('/'+soundID+':'+property,value);
// writeDebug('soundManager.setVariable('+self.getArgs(arguments)+')');
} catch(e) {
// d'oh
self.errorHandler('setVariable','Failed',arguments,e);
}
}

this.setVariableExec = function(soundID,fromMethodName,oMovie) {
try {
oMovie.o.TCallLabel('/'+soundID,'setVariable');
} catch(e) {
self.errorHandler(fromMethodName||'undefined','Failed',arguments,e);
}
}

this.callMethodExec = function(soundID,fromMethodName,oMovie) {
try {
oMovie.o.TCallLabel('/'+soundID,'callMethod');
} catch(e) {
// Something blew up. Not supported?
self.errorHandler(fromMethodName||'undefined','Failed',arguments,e);
}
}

this.callMethod = function(soundID,methodName,methodParam,movieName) {
movie = self.checkMovie(movieName||self.defaultName);
if (!movie) return false;
self.setVariable(soundID,'jsProperty',methodName,movie);
self.setVariable(soundID,'jsPropertyValue',methodParam,movie);
self.callMethodExec(soundID,methodName,movie);
}

this.setPan = function(soundID,pan,movieName) {
self.callMethod(soundID,'setPan',pan,movieName);
}

this.setVolume = function(soundID,volume,movieName) {
self.callMethod(soundID,'setVolume',volume,movieName);
}

// constructor - create flash objects

if (isIE && isMac) {
this.unsupported = 1;
}

if (!this.unsupported) {
this.addMovie(this.defaultName,'soundcontroller.swf');
// this.addMovie('rc','rubber-chicken-audio.swf');
}

}

function SoundManagerNull() {
// Null object for unsupported case
this.movies = []; // movie references
this.container = null;
this.unsupported = 1;
this.FlashObject = function(url) {}
this.addMovie = function(name,url) {}
this.play = function(movieName,soundID) {
return false;
}
this.defaultName = 'default';
}

function writeDebug(msg) {
var o = document.getElementById('debugContainer');
if (!o) return false;
var d = document.createElement('div');
d.innerHTML = msg;
o.appendChild(d);
}

var soundManager = null;

function soundManagerInit() {
soundManager = new SoundManager();
}

Include this in the <head> section of your HTML document as follows:

<script type="text/javascript" src="script/soundmanager.js"></script>

After the sound manager completed it's job and played the mp3 notify sound it completes it's function by setting played to 1 and setting the document's title back to Sajith M.R Says...'. When the window/tab regains focus the gotFocus() script is executed:

function gotFocus()
{
document.title = 'Gmail Inbox(1)';

state = 'focus';

played = 0 ;
}

This script is very simple and has only three steps, set the document title to 'Gmail Inbox(1)', sets state to focus and played back to 0. That is almost it, there is only one small script to look at. If you look at the HTML for the textarea you will notice a function call that is triggered by the onclick event:

<textarea name="textarea" style="width:230px;height:50px;" onclick="changeColorBlue();">Hows life </textarea>

This is a very simple script with one goal, to turn the Layer2's color back to '#73A6FF':

function changeColorBlue()
{
document.getElementById('Layer2').style.background = '#73A6FF';
}

And that's it! A very simple but working example of the client side aspects of the GMail chat interface. You can download all the source code from here and do not hesitate to visit the original post with your comments and suggestion.This then concludes the 3 part series on the GMail client side architecture. I hope you enjoyed it and learnt something from it.

Original Author

Original article written by Sajith.M.R

Create data driven applications in Qlik’s free and easy to use coding environment, brought to you in partnership with Qlik.

Topics:

Published at DZone with permission of Schalk Neethling. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}