Example: Maintaining Multiple Event Logs

In the various examples in preceding reference chapters, we have had the need to display log events when various events occur. JavaScript's alert function is often used for this type of demonstration, but does not allow for the frequent, timely messages we needed on occasion. A better alternative is the console.log function available to Firefox and Safari, which allows printing messages to a separate log that does not interrupt the flow of interaction on the page. As this function is not available to Internet Explorer, however, we used a custom function to achieve this style of message logging.

Note

The Firebug Lite script (described in Appendix B) provides a very robust cross‑platform logging facility. The method we develop here is tailored specifically for the examples in the preceding chapters; for general utility, Firebug Lite is typically preferable.

A simple way to log messages would be to create a global function that appends messages to a specific element on the page:

jQuery.log = function(message) {
  $('<div class="log-message"></div>')
    .text(message).appendTo('.log');
};

We can get a bit fancier, and have the new message appear with an animation:

jQuery.log = function(message) {
  $('<div class="log-message"></div>')
    .text(message)
    .hide()
    .appendTo('.log')
    .fadeIn();
};

Now we can call $.log('foo') to display foo in the log box on the page.

We sometimes had multiple examples on a single page, however, and it was convenient to be able to keep separate logs for each example. We accomplished this by using a method rather than global function:

jQuery.fn.log = function(message) {
  return this.each(function() {
    $('<div class="log-message"></div>')
      .text(message)
      .hide()
      .appendTo(this)
      .fadeIn();
  });
};

Now calling $('.log').log('foo') has the effect our global function call did previously, but we can change the selector expression to target different log boxes.

Ideally, though, the .log method would be intelligent enough to locate the most relevant box to use for the log message without an explicit selector. By exploiting the context passed to the method, we can traverse the DOM to find the log box nearest the selected element:

jQuery.fn.log = function(message) {
  return this.each(function() {
    $context = $(this);
    while ($context.length) {
      $log = $context.find('.log');
      if ($log.length) {
        $('<div class="log-message"></div>')
          .text(message).hide().appendTo($log).fadeIn();
        break;
      }
      $context = $context.parent();
    }
  });
};

This code looks for a log message box within the matched elements, and if none is found, walks up the DOM in search of one.

Finally, at times we require the ability to display the contents of an object. Printing out the object itself yields something barely informative like [object Object], so we can detect the argument type and do some of our own pretty-printing in the case that an object is passed in:

jQuery.fn.log = function(message) {
  if (typeof(message) == 'object') {
    string = '{';
    $.each(message, function(key, value) {
      string += key + ': ' + value + ', ';
    });
    string += '}';
    message = string;
  }
  return this.each(function() {
    $context = $(this);
    while ($context.length) {
      $log = $context.find('.log');
      if ($log.length) {
        $('<div class="log-message"></div>')
          .text(message).hide().appendTo($log).fadeIn();
        break;
      }
      $context = $context.parent();
    }
  });
};

Now we have a method that can be used to write out both objects and strings in a place that is relevant to the work being done on the page.