Welcome!

AJAX & REA Authors: Lee Novak, Brad Abrams, Alin Irimie, Jonny Defh, RealWire News Distribution

Related Topics: AJAX & REA

AJAX & REA: Article

AJAX Logging Functions

Because we want our JavaScript to be able to write to the log window in our HTML, we'll first add the logging code

This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs for the special pre-order price, click here for more information. Aimed at everyone from enterprise developers to self-taught scripters, Real-World AJAX: Secrets of the Masters is the perfect book for anyone who wants to start developing AJAX applications.

Logging Functions
Because we want our JavaScript to be able to write to the log window in our HTML, we'll first add the logging code to our JavaScript.

As we described earlier in Listing 12.6:

  • <div id="logmsgs"> has nothing in it and will need to be updated via JavaScript with the log messages.
  • An event listener will need to be added to each radio button (log0, log1, and log2) to catch the click event and set the corresponding filter on our log outputter.
In our JavaScript file (named im.js and stored in the same directory as the other files), we'll create a couple global variables relevant to logging, and create a function that can write log messages to our logwindow.

Listing 12.11 Logging

var logDisplayLevel = 1;
var logWindow = 'logmsgs';
var logLevels = new Array('debug', 'info', 'error');
var logmsgcnt = 0;

function log(msg, level) {
    if (level != null & logLevels.indexOf(level) >= logDisplayLevel) {
      logmsgcnt++
      msg = '<p class="'+level+'text">• ' + level.toUpperCase() +' ('+logmsgcnt+'):
'+ msg+'</p>';
      new Insertion.Bottom(logWindow, msg);
      $(logWindow).scrollTop = $(logWindow).scrollHeight;
    }
}

The code in Listing 12.11 allows us to write messages to the log window in our HTML. The key code are the last two lines of the log() function that use the Prototype library's Insertion.Bottom() to append new text at the bottom of the log window, and $() to set the log window's scroll position to the bottom.

The log() function also checks against the global setting of log levels and the display level. This allows us to display more or less messages in the window. Throughout our JavaScript, we'll include a lot of debug and fewer info-level messages.

The globals at top of Listing 12.11 define three log levels (debug, info, and error) in the logLevels array. And, the logDisplayLevel variable then represents a default/initial log level (i.e., it corresponds to the value at logLevels[1], which is "info").

We'll also set the HTML ID of the logWindow here--this allows us to easily switch to a different HTML element if we want to use a different design.

Note here that there are a few interdependencies between the JavaScript and the HTML. These are:

  • The log levels in the logLevels array also appear as radio buttons in the HTML (see Listing 12.6).
  • The logDisplayLevel corresponds with the default checked radio button in the HTML.
  • The log() function outputs formatted HTML that is designed to fit within the known HTML structure, and take advantage of the CSS rules (which, among other things, color the different log level messages different colors).
In the next section, we'll see how event listeners are added to the radio buttons to detect when the log levels are changed.

The Initialization, Login and Logout Related Functions, Round 1
The window.onload function kicks-off all of our JavaScript and AJAX functionality. Once the HTML page loads, this function is called.

The first thing we do is log that the window has loaded; this is the first line in Listing 12.12..

Listing 12.12 on load

window.onload = function() {
    log('Window load complete', 'info');
    Event.observe('user', 'change', login, true);
    Event.observe('logoutlink', 'click', logout, true);

    Event.observe('log0', 'click', function() {logDisplayLevel=0; log('Log Level Changed
to Debug','debug');}, true);
    Event.observe('log1', 'click', function() {logDisplayLevel=1; log('Log Level Changed
to Info','info');}, true);
    Event.observe('log2', 'click', function() {log('Log Level Changing to Error','info');l
ogDisplayLevel=2}, true);
    loadUsers();
}

Besides our first log message, we set event listeners for the general interface features of login and logout, and the radio buttons in the log window.

As we noted earlier, our HTML includes no inline JavaScript. And, specifically, for each of the Java Script triggers we need to be bound to the HTML, we instead define this binding in the JavaScript itself by using the Prototype library's Event.observe() function.

Event.observe() is a convenient and altogether handy way to grab an element in the HTML (the first parameter), set a listener for a specific event type (the second parameter), and attach a function (the third parameter) that is called for that event.

For example, our second line, Event.observe("user", "change", login, true), refers to the user dropdown (HTML select element) that we saw earlier in Listing 12.7. When that drop-down changes values (e.g., a username is selected), we'll call a function called login (explained in detail later).

Note how we do not use a named function for window.onload, but instead use the window.onload = function() {} form. Since we know that the window will load only once, we don't need a name for our function as we won't be calling it again.

Similarly, in the Event.observe() calls for the radio buttons, we use unnamed functions to set the logDisplay level and log the change.

The last line of our window.onload function is to call a loadUsers() function - let's look at this next.

Load Users
The loadUsers() function is part of our super simple user management scheme. What we need to do is very simple: load all of the usernames from the server and display them in the user interface.

We'll use the Prototype library's Ajax.Update function to call our server's /userlist method. As we noted above (see Listing 12.10a), the server returns exactly the format we need - each user listed as an HTML <option>.

Ajax.Updater is a good choice here because, as soon as it receives the results from the server, it immediately writes the results into the HTML element of our choice (our <select id="user"> in this case).

Listing 12.13a Load Users

var baseURL = 'im.php';

function loadUsers() {
   userlistURL = baseURL + '/userlist';
   log('Loading Users into drop down list', 'debug');
   new Ajax.Updater(
     'user',
     userlistURL,
     {
       method: 'get',
       parameters: ''
     });
}

Here's what our IM client looks like with the users loaded.

Login and Initialize
We'll be creating two classes in our JavaScript to encapsulate the properties and methods of our buddy window and chat windows. We'll use Prototype's Class.create() method to initialize these.

Along with these, we'll also define some global variables whose values will be reset with each login. Our initialize() and login() functions will then begin assigning values and objects to these variables, as well as update the user interface to reflect the logged-in state.

Note that user name is stored in activeUser, and that this value comes from the HTML <select id="user"> that we just populated in Listing 12.13a.

Listing 12.13b Login and Initialize

var ChatWindow = Class.create();
var Buddies = Class.create();

/* global varaibles that get re-initialized with each login */
var activeUser, windowsUsed, windowMgr, buddyMgr, refreshtimer;

function initialize() {
   activeUser = '';
   windowsUsed = 0;
   windowMgr = new Array();
}

function login() {
   idx = this.options.selectedIndex;
   user = this.options[idx].value
   initialize();
   log('Logging in ' + user, 'debug');
   activeUser = user;

   Element.update('loginuser', activeUser);
   $('login').style.display = 'none';
   $('logout').style.display = 'block';
   this.options.selectedIndex = 0;
   log('Successfully logged in as ' + activeUser, 'info');
   buddyMgr = new Buddies();
   refresh();
}

The login() function is bound to the change event of <select id="user"> (see Listing 12.12), and so the JavaScript keyword, in this case, refers to <select id="user">. We are able to get the value of the user name by first getting the index of the selected option (this.options.selectedIndex) and then using it to get the value of that option (this.options[idx].value).

The login() function also updates the user interface to show the current logged-in user, hides the login control, and displays the logout control, and also resets the login control to its first option.

Finally, login() creates a new Buddies() object (see Listing 12.16-22 for more details), stores it in the buddyMgr variable, and then calls a refresh() function (see Listing 12.33).

Logout
We'll also create our basic logout() function here (we'll add more to it below, see Listing 12.34). For now, we'll just have the logout() function change the user interface to show the login control and hide the logout control.

Listing 12.14 Logout

function logout() {
   log('Logging out ' + activeUser, 'debug');
   $('logout').style.display = 'none';
   $('login').style.display = 'block';
   log(activeUser + ' Successfully logged out', 'info');
}

Global Variables We've introduced a number of global variables in the preceeding examples. We'll consolidate them all and place them at the top of our im.js file.

Note that we've added a refreshInterval variable that we'll soon use in our refresh() function (see Listing 12.33). This variable will be used to set how often our IM client will poll the server for new data. By placing the variable here at the top, we'll keep it handy in case we want to adjust the value to make updates more or less frequent.

We've also added a responseFormat variable that we'll use in all of our URLs to indicate the response format the server should return (in this case, HTML, e.g., to create a server request in the form of im.php/xml/John/buddies (see Listing 12.10d).

While our IM client code will not directly work with others if you change the reponseFormat to XML or JSON, we included this as a variable to suggest that, as an experiment (left as an excercise for the reader, as the old textbooks used to say) one could extend the code to handle the different response types, and then could switch between them by changing this value.

Listing 12.15 Global Variables

[EXAMPLE 15: global variables]

/* global varaibles that persist across logins */
var refreshInterval = 2 * 1000; //first number = seconds
var logDisplayLevel = 1;
var logWindow = 'logmsgs';
var logLevels = new Array('debug', 'info', 'error');
var logmsgcnt = 0;
var baseURL = 'im.php';
var responseFormat = 'html';
var ChatWindow = Class.create();
var Buddies = Class.create();

/* global varaibles that get re-initialized with each login */
var activeUser, windowsUsed, windowMgr, buddyMgr, refreshtimer;

Basic Process Flow of the IM Client
What we've covered up until this point has a relatively linear flow. Let's look at a simple diagram of this flow, as it will help us better understand what happens next (after the user is logged in), which is less linear.

First, we load the users. This gives us a list of user names we can select from. We select a user name to log us in.

Logging in does three things:

  1. It hides the login control, and shows a logout link.
  2. It initializes and displays the Buddy List (which cover next).
  3. It starts a refresh() loop that triggers the IM client to poll our IM server for new data.
Clicking on buddies in our Buddy List will open Chat Windows. These will also be updated via our refresh loop.

Finally, logging out will return the application to the ready for login state.

Buddies and Chat Window Objects
As we mention in Listing 12.13b, we used Prototype's Class.create() method to set us up for creating classes that encapsulate the Buddy List and Chat Window properties and methods. Before we get into these classes, let's briefly examine how classes are created in JavaScript.

Classes are defined in JavaScript as prototypes, which have a syntax that looks something like creating a variable and assigning it a value. But, instead of assigning it a value, we assign it what can generically be called methods (functions) and properties (variables).

Listing 12.16 provides a skeleton example using this syntax.

Listing 12.16 Creating Classes

ClassName.prototype = {
   methodName: function () {},
   propertyName: 'value',
   method2Name: function () {}
}

Buddies Object
The Buddies object will encapsulate the methods and properties of our Buddy List. First we'll examine the initialize() function.

The Prototype library extends the basic JavaScript class and defines an intitialize() function that is automatically called at the time of object creation (not unlike the init() method common in Java classes). We'll take advantage of this and use initialize() to set up our Buddies object.

Initialize

Listing 12.17 Initialize

Buddies.prototype = {
   initialize: function () {
     this.listWindow = $('buddy');
     this.listWindow.style.display = 'block';
     this.statusControl = $('status');
     this.statusControl.options.selectedIndex = 0;
     this.postStatusUpdate('Available', '');
     this.changeStatusObserver = this.changeStatus.bindAsEventListener(this);

     Event.observe('status', 'change', this.changeStatusObserver, true);
   }
}

In our intitialize() function, we define a number of properties (instance variables) by using this keyword in front of variable names. Each object created from this class will include these properties, and these properties will have their values set at the time of creation by our initialize() function.

First, we define the listWindow to be the HTML element that will contain the buddy list. We then set its CSS display style to "block" to make it visible.

We also define the statusControl to be the HTML element that allows the user to change their status. This is the <select id="status"> element, which we also set to display the first option.

Next, we set the user's status by calling the Buddies object postStatusUpdate() function that we'll describe in Listing 12.21.

What we're doing here is encapsulating all of the status-changing features in the Buddies object. This particular status change is a consequence of the user logging in, e.g., when the user logs in, his status becomes "Available". But, because all of the other status changes are triggered within the Buddy List context (i.e., because we designed the status change control to be part of the Buddy List display), we trigger this status change at this point rather than earlier in the login process.

Finally, we add an Event observer on the status control to catch when the user uses it to change his status. Although this is functionally almost the same as using Prototype's Event.observe() function, we have to do a little trick to reconcile the "this" keyword between the Event's this and our Buddies object's this.

By default (and, as we described earlier in Listing 12.13b), Events are bound to the HTML element. This means that, in the function called by the Event, "this" refers to the HTML element.

This information is all one needs when not working in an object-oriented manner because, generally, all of the data and functions that are needed are available globally in the JavaScript.

When working in an object-oriented manner, data and functions become encapsulated inside objects, and so are not available with reference to the specific object variable that contains the object.

In our case, we want the Event to call a method inside of an instance of an object. We need the "this" keyword to refer to that object instance.

Fortunately, Prototype allows us to add bindAsEventListener(this) to our Event objects to bind the Event to our Buddies object rather than to the HTML element. And, so we create a new Event object like this:

this.changeStatusObserver = this.changeStatus.bindAsEventListener(this);

And, bind the Event object to the HTML object like this:

Event.observe('status', 'change', this.changeStatusObserver, true);

We'll be using this technique throughout our classes to cause Events to trigger methods within specific object instances of the class.

This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs, click here to order.

More Stories By James Benson

Jim Benson, AICP, is the COO of Gray Hill Solutions in Seattle. Gray Hill creates tools for government and industry to harness and utilize real-time data. Jim has always driven applications for his clients to store and provide information in easily extensible ways. Web 2.0 has therefore been a natural environment for him. He is also involved with the Cooperation Commons and the Institute for the Future's Future Commons to study human cooperation and envision the future of cooperation. Jim's tags: Gray Hill Solutions (www.grayhillsolutions.com), Jim's Blog (http://ourfounder.typepad.com), Cooperation Commons (www.cooperationcommons.org), Institute for the Future (www.iftf.org).

More Stories By Jay Fienberg

Jay Fienberg is co-founder of Juxtaprose where he designs information architecture and user experience for websites and information systems.

He specializes in design for content and information-rich websites and web-based social and collaboration systems. His current preferred CMS is ExpressionEngine, though he also works with Wordpress and Joomla, and still gets called upon to make SharePoint do interesting CMS-like things for enterprise intranets.

Since the early 1990s, Jay also has designed and developed hypertext, database, and content management systems and worked in a wide range of programming languages including XML, SQL, SGML, Python, PHP, Javascript, Java, HTML, CSS and APL.

For anything more official, please visit jayfienberg.com.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.