Debugging Yahoo User Interface
From TroubleshootingWiki
| Official Page |
| Project Documentation |
| Download |
|
Contents |
[edit] Advanced Debugging with Logger
Unlike the rest of the library, the Logger is not an Interface control that is designed to be used in production applications. The Logger control is just for you, the developer, to assist you primarily in the design phase of your site or application, but also in maintenance or upgrade phases as and when required.
Like any of the controls provided by the library, Logger is both easy to implement and easy to use. It provides a mechanism for both the writing and reading of log messages generated by your application at key moments during its use.
JavaScript provides no console support natively. It is usually left to the browser or interpreting environment to manage and report runtime errors, and as I'm sure you're aware, browsers don't always provide as robust or intelligible results that we as developers would like!
[edit] The Purpose of Logger
The Logger has been built to be used directly in connection with the debug.js versions of each individual library component. The Logger is primarily used to display the different messages that are produced by each of the different library components at different points during their execution, allowing you to see exactly what is going on throughout the progress of your script.
As well as providing error messages to assist you with debugging and troubleshooting, each library component is configured to log informational messages when different custom events fire. This allows you to keep track of the different things that happen during the course of a particular interaction. When first beginning to use the library, this feature of Logger can provide valuable insights into the inner workings of each component.
You needn't rely solely on the different messages hard-coded into each library component either. It is very simple to log your own custom messages to the Logger control at any point during your script so the Logger control can help you regardless of whether you are using any of the other library components or not.
The Logger control is different from the other controls in the respect that it is something that the visitors to your site will rarely, if ever, see. You can add it to a public page of course; there is certainly nothing to stop you. The Yahoo! development team are quick to point out that it could be extended for other uses by more adventurous developers.
The main reason why you wouldn't have an instance of the Logger sitting happily on your application's landing page are based on the performance hit your application is likely to take when managing the interactions your visitor is making, while at the same time processing and displaying log messages. Some of the examples on the YUI documentation site are testament to this!
Additionally, the Logger is designed to work with the debug versions of the library files, rather than the min versions, the latter of which you should be using in a production application. The min versions are the ones served by Yahoo! and have been optimised for quick downloading and therefore have no Logger targeting code routines in them.
Using the Logger control when designing your pages or applications gives you an insight into what is going on in the library files you are using at different points during their execution. If you look through one of the debug versions of any of the library files, you'll see various statements that log messages to the Logger which can help you to understand why something isn't working the way it should if.
You can also log your own custom messages when building an application to help with troubleshooting and debugging your own code. I've always used the standard JavaScript alert to test what information is being passed around in a function and to provide guidance when things haven't been going right. But with the Logger, you have far greater control over the format of the messages that you can output, and the interface used to view them is a lot more pleasant than an intrusive and ugly alert.
The Logger provides advanced functionality including built-in methods for easily showing and hiding, pausing and resuming, or collapsing and expanding the Logger control. It also maintains an internal message stack into which logged messages are saved for reference, and even a buffer which can store log messages while the Logger is paused.
[edit] The Purpose of the debug Library Files
As well as having all of the comments, plenty of white space, and more sensible variable names, the debug.js versions of each of the different library components also have plenty of additional code that outputs messages for the Logger component to display. At their most fundamental level the -debug.js and standard files are just much more human-readable and much easier to make sense of than the min.js versions.
If you open up one of the debug.js versions of one of the library components in your text editor, you'll be able to see all of this extra code for yourself first-hand instead of just taking my word for it. Let's look at both the standard non-suffixed and debug.js versions of the Animation utility:
YAHOO.util.Anim = function(el, attributes, duration, method) {
if (!el) {
}
this.init(el, attributes, duration, method);
};
This is the very first function found in the animation.js file, note the empty if statement which checks that the el object exists. Now let's take a look at exactly the same function, but this time from the animation-debug.js file instead of the standard version:
YAHOO.util.Anim = function(el, attributes, duration, method) {
if (!el) {
YAHOO.log('element required to create Anim instance', 'error', 'Anim');
}
this.init(el, attributes, duration, method);
};
In this version of the file, the if statement contains a message to log to the Logger console. In this case the message alerts you (not the visitor) that you need to supply an element in order to create an animation. Each debug file is filled with additional code like this to alert you to potential problems with your code. Just for the sake of interest, let's view the min.js version of the same function:
YAHOO.util.Anim=function(B,A,C,D){if(!B){}this.init(B,A,C,D); };
Nice! Highly efficient for a browser to download and use, but not very helpful to the likes of you and me, and as you can clearly see, no log messages are generated.
Each component has varying amounts of Logger specific code in its debug file depending on each utility or control's capacity for error. The above code snippets from the Animation utility are one of just a couple of lines of code related to the Logger. This is because it's a simple utility where little can go wrong.
The Connection Manager utility on the other hand, is filled with Logger targeting code due to its complexity, and the fact that there is a lot more that can go wrong when working with remote applications. The Logger can be extremely helpful debugging and troubleshooting Connection problems.
[edit] How the Logger Can Help You
The Logger can be a valuable asset when initially coding your application or site. If something isn't working as you'd expect, you can instantiate Logger and then replace the min.js file for the debug.js version and see exactly where your code is falling down. As I mentioned before, it can also be extremely helpful when upgrading an application to run on a newer version of the library.
When creating a new implementation, debugging usually consists of adding alerts to your code to check that variable values are being passed from function to function correctly and as pointers to how far a script progresses before it halts. This is true whether involving components from the YUI or just coding a standard, non-library assisted JavaScript application.
So if you've added five alerts to different parts of your script and only see four of them during execution, you can pin-point (with varying degrees of success) exactly where the problem lies. Sometimes however, depending on what your script does, the mere presence of an alert can throw a spanner in the works.
[edit] Debugging the Old Way
Let's look at a simple web page, which uses a basic script that obtains an element from the DOM and then passes it to several functions, alerting different properties of the element along the way:
In your text editor, add the following page:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Old Style Debugging Example</title>
</head>
<body>
<div id="div1" class="divClass"></div>
<script type="text/javascript">
var initPage = function() {
var el = document.getElementById("div1");
getTheClass(el);
}
var getTheClass = function(el) {
alert("The div's class is " + el.className);
getTheId(el);
}
var getTheId = function(el) {
alert("The div's id is " + el.id);
getTheType(el);
}
var getTheType = function(el) {
alert("The el variable is of type " + typeof(el));
}
window.onload = initPage;
</script>
</body>
</html>
If you run the page in your browser, you should see each of the alerts and therefore know that the script has progressed as planned. The existence of the alerts however disturbs the natural flow of the script and is a less than satisfactory way of checking variables and properties.
[edit] Debugging the YUI Way
We can use the Logger to check the same objects and properties in a much friendlier way:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>YUI Style Debugging Example</title>
<script type="text/javascript" src="yui/build/yahoo-dom-event/yahoo-dom-event.js"> </script>
<script type="text/javascript" src="yui/build/logger/ logger-min.js"></script>
<link type="text/css" rel="stylesheet" href="yui/build/logger/assets/skins/sam/logger.css">
</head>
<body class="yui-skin-sam">
<div id="div1" class="divClass"></div>
<script type="text/javascript">
YAHOO.namespace("yuibook.logger");
YAHOO.yuibook.logger.initPage = function() {
var myLogger = new YAHOO.widget.LogReader();
var el = YAHOO.util.Dom.get("div1");
getTheClass(el)
}
var getTheClass = function(el) {
YAHOO.log("The div's class is " + el.className, "info");
getTheId(el);
}
var getTheId = function(el) {
YAHOO.log("The div's id is " + el.id, "info");
getTheType(el);
}
var getTheType = function(el) {
YAHOO.log("The el variable is of type " + typeof(el), "info");
}
YAHOO.util.Event.onDOMReady(YAHOO.yuibook.logger.initPage);
</script>
</body>
</html>
This time the page loads and all of the functions execute, without waiting for you to click each alert. All of the information we wanted to find out is displayed in the Logger as shown here:
Logger negates the need for littering your code with numerous alerts in the first place. This also has the added benefit of meaning that you don't need to go back through your code once the problem has been eradicated to remove all of the alerts.
Instead of using alerts to test the values of a variable or to mark different events, you can make a simple call to the YAHOO.log method, specifying the text that forms the main message and the category of message. For example, in order to check that something is loading, you could include:
YAHOO.log("Component X has loaded!", "info");
Or for clarification of the value of a particular variable, something like the following should suffice:
YAHOO.log ("The current value of myspecialvariable is: " + myspecialvariable, "info");
This is just as easy as using alerts but much more elegant and won't interfere with your application the way alerts sometimes can (and you won't have to put up with the irritating beep either).
Some components, especially those designated as beta or subject to change in new revisions, can even alert you to the fact that a method or property that you are using has been deprecated, This makes Logger the ideal companion when migrating to newer versions of the library.
[edit] Log Message Types
Logger provides access up to five different types of default message. Each message can be specified as one of the following categories:
- TIME
- ERROR
- WARNING
- INFO
- WINDOW
Each category of message is color coded within the Logger interface so it's easy to see at a glance exactly which category of message has been logged. The TIME category can be used to profile the speed at which particular features of your implementation execute.
Category ERROR messages clearly indicate that something isn't working or something that is required is not present, and WARNING messages indicate potential non-fatal errors in your code. INFO messages are logged when components are initialized and at the beginning and end of key interactions.
The extremely rare WINDOW category is only used by the Logger control itself, and is generated when the Logger causes a window error by being out of the scope of an event handler.
In addition to the Logger interface provided by the YUI for the display of log messages, the Logger control also allows you to log messages directly to some browser consoles, such as Firebug in Firefox.
[edit] Logger Layout
Like many of the different components available under the YUI, the Logger control is comprised of three sections of underlying mark up, adhering to the SMF: the header (yui-log-hd), the main body (yui-log-bd), and a footer section (yui-log-ft). These three components are created automatically by the control when a new Logger instance is generated, you don't need to add them to your page.
The header section contains a UI button that allows for the expansion and collapse of the control and a brief explanatory title. The header also serves as the drag handle by which the control can be dragged, if this feature is enabled. If the Drag-and-Drop utility is referenced in the head of the page, the Logger will be dragable automatically without further intervention.
The body of the logger is where all messages sent to the Logger are displayed. By default, new messages appear at the top of this viewing pane, although this can be configured so that new messages are added to the bottom of the pane. It is also very easy to configure the logger to be collapsed by default so that only the header section is initially visible.
The Logger footer contains most of the UI for customizing the type and source of displayed log messages and allows you to filter out the category messages that you aren't interested in, or their source, and also contains buttons for you to pause and resume log reading. If necessary, the footer section can be disabled so that it is not displayed at all. When this option is configured, the filtering options are still available programmatically.
The UI control itself is also simple and easy to use and provides buttons for collapsing or expanding the control, pausing or resuming it or clearing the message window. It also allows you to filter the log messages based on the category or whether the log message came from the global .log() method, or the LogReader class. You can even alter the styles of the different categories of log message and the title used in the head of the Logger.
The screenshot below shows the default Logger interface (highlighted by one of the Logger examples from the YUI documentation example space):
In this example just one message has been logged. Let's break down the message and take a look what each part of it refers to or represents.
The highlighted prefix shows the category of the message; in this case it is a simple INFO message. Next is the ongoing count, which is a continuous timer that begins when the Logger is initialized. The number following in brackets is a count since the previous log message and the final snippet of information on the first line is a full timestamp of when the message occurred.
The second line features the source of the message. As you can see in the footer section, global messages are enabled. If you uncheck the global box, the above message will disappear from view (although it will still be available to the control).
Finally, on the third line, the actual log message is displayed. This is the standard format of log messages by default, although the property verboseOutput can be set to false so that all messages are compacted to one-line entries.
[edit] Logger Styling
The first screenshot also shows us how the Logger appears by default. Like most of the other controls, its default appearance is controlled by the sam skin, so a reference to this must be included in the head of any page on which the Logger is to appear.
Like the other components styled by sam, it's very easy to configure the Logger to suit your own implementations. Although unless you were providing an extended version of Logger for your users to interact with, there isn't really much point in worrying about styling. The default appearance is functional and easy to make sense of, so overriding style rules simply adds to your workload.
[edit] Logger is not Omnipotent or Infallible!
The logger is an excellent addition to the library and can help you understand any process within any component in greater detail. I should point out however that the component cannot detect every problem with your code, and some fatal errors result in the Logger not even being displayed on the page, so it can't be relied upon to troubleshoot any and all errors. If you look at the source of the Debug utilities, you'll see that INFO category messages are by far the most common.
An example of where the Logger can fall down when troubleshooting is when the required utilities are not present. The Drag-and-Drop utility can present a category ERROR message when the Event utility is not present, but on a page without the Event utility, Logger won't even render. This means you won't see the message advising that event.js is not present.
[edit] The Logger Classes
Four classes provide the functionality of the Logger control: the YAHOO.widget.Logger singleton class, the tiny YAHOO.widget.LogMsg class, and the input and output classes YAHOO.widget.LogReader and YAHOO.widget.LogWriter. Let's take a look at each of them in turn.
The static YAHOO.widget.Logger class manages the core functionality of processing and displaying log messages. It's a static class because it contains no constructor and therefore the properties and methods defined by the class can be used without an instance of the Logger. It receives log messages generated by the global .log function or by LogWriter instances. It can also use the native console of the browser to log messages to, when supported by the browser and enabled.
This class acts as part of the logic behind the Logger control so you won't have to interact with it directly very often, but a property that you will probably want to manipulate from time to time is maxStackEntries, which allows you to limit the number of messages in the internal stack. The default number is 2500 which can be lowered to claim back some of that performance cost if required.
There are also a few useful methods, including:
-
disableBrowserConsoleprevents messages being logged to the browsers native console -
enableBrowserConsolelogs messages to the browsers native console using theconsole.logfunction. ThebrowserConsoleis disabled by default logsaves a log message to the internal stack and browser console if enabledresetclears the internal stack and resets the_startTimeproperty. The Logger remains enabled
A series of events are also defined by this class to mark different points in the operation of the Logger. These events are:
-
categoryCreateEventthis is fired when a new category has been created, which occurs when a log message is assigned to an unknown or custom category -
logResetEventfired when the reset method has been called -
newLogEventfires when a new log message is created -
sourceCreateEventthis is fired when a new source has been created, which occurs when a log message is from an unknown source
None of the other classes define any events for the Logger control, so short of creating your own, this is all you have to work with in a basic implementation.
[edit] LogMsg Structure
The YAHOO.widget.LogMsg class is tiny. It consists of just a constructor and five properties. It defines a single log message and while you probably won't need to call it yourself (it is used by the Logger class that we have just discussed), it is useful to see the structure of an individual message.
The constructor takes just one argument and that is the literal object containing the five message properties. These properties are:
-
categorythe category of the log can be either info, warn, error, time, window or a new category of your choice. Each category is styled and any custom categories you add can also easily be styled using the.yui-logclass.Infois the default class, used when a category is not specified -
msgholds the textual message of the log, it should be a simple string, expressing the message displayed in the Logger -
sourceandsourceDetailrelate to the source of the log. The source can consist of one or more words, where the first word appears assourceand any subsequent words (after the first blank space delimiter) are stored assourceDetail. Messages without a specifiedsourceare assigned asglobal -
timethe property which refers to when the log occurred
[edit] Generate the UI Interface with LogReader
The YAHOO.widget.LogReader class is what is used to render the log message viewer on the page, and is where the Logger class passes messages to be displayed. This is the biggest class in the control by far, defining a wide range of both private and public properties and methods, many of which you can put to good use in your own implementations. Properties you may find useful include:
-
draggablespecifies whether the Logger control can be dragged and dropped. Defaults to true, if the Drag-and-Drop utility is present on the page -
footerEnableda boolean specifying whether the footer is enabled -
newestOnTopIf true (the default) this adds new log messages to the top of the Loggers display panel -
thresholdMaxthe maximum number of messages to display on the display panel. Defaults to 500 -
thresholdMinthe minimum number of messages. Used when a Logger control reachesthresholdMax, resetting the display to the number specified -
verboseOutputwhen false, text wrapping, and whitespace are used to make the log messages more readable. Default is true
A large amount of methods are also defined by LogReader of these, a large number are private and used internally by the class, but some that you will definitely want to use are:
-
clearConsolethis method clears all messages from the Logger window but doesn't actually delete them from the stack -
collapsecollapses the Logger -
expandexpands the Logger -
hidehides the Logger -
hideCategoryandhideSourcehides individual log messages based on the category or source -
pausepauses the Logger -
resumeresumes the Logger -
showshows the Logger -
showCategoryandshowSourceshows individual messages based on the category or source
[edit] The LogWriter Class
YAHOO.widget.LogWriter allows you to write log messages from a particular source, which can be useful when debugging a class, which may generate many log messages. This is another specialized and compact class containing just the constructor, one private property and four methods.
The simple constructor takes just one argument which should be a string specifying the source of the LogWriter instance. This then appears as the _source property. The methods defined consist of:
-
getSourcea public accessor method which returns a string referring to the source of the LogWriter -
logas before, the simple log method logs a message to the stack, although this time it is attached to the LogWriter source. Arguments passed with this method are a string specifying the log message and a string specifying the category name -
setSourceallows you to programmatically set the LogWriter source -
toStringreturns the unique name of the LogWriter instance as a string
All of the members of each class can be used to build a rich and easily implemented infrastructure for receiving and displaying log messages, or generating custom messages, in a friendly and unobtrusive interface.
[edit] How to Use Logger
The Logger control has few dependencies and can be instantiated with just one line of code. For the most part, using the logger is passive. You just invoke it and sit back while it receives and displays the messages fired by different parts of each component. Let's look at this aspect of its use first.
Make sure the logger-min.js file is placed in your yui folder and begin with the following basic HTML page:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"> <title>Yui Logger Example</title> <script type="text/javascript" src="yui/build/yahoo-dom-event/yahoo-dom-event.js"> </script> <script type="text/javascript" src="yui/build/dragdrop/dragdrop-debug.js"></script> <script type="text/javascript" src="yui/build/logger/logger-min.js"></script> <link type="text/css" rel="stylesheet" href="yui/build/logger/assets/skins/sam/logger.css"> </head> <body class="yui-skin-sam"> </body> </html>
The <head> of the page contains the references to the required dependencies of the Logger, as well as the Logger source file itself, and the sam skin file that controls its appearance. Now let's display the logger on the page. Within the <body> of the document, add the following code:
<script type="text/javascript">
//create the namespace object
YAHOO.namespace("yuibook.logger");
//initialise logger
YAHOO.yuibook.logger.initLogger = function () {
//define logger instance
var myLogger = new YAHOO.widget.LogReader();
}
//execute initLogger when DOM is ready
YAHOO.util.Event.onDOMReady( YAHOO.yuibook.logger.initLogger);
</script>
Ok! So I said it could be instantiated with one line of code, yet including the above function and event handler, that's four lines. Well, usually Logger would be placed on the page with other components that would need to be initialized in the normal way anyway, so really it's just the LogReader object constructor in the above code which is unique to this example.
If you save what you have so far, as logger.html or similar, and view it in a browser, you'll see the logger control on the page, but as there's nothing else for it to interact with, it will only display two messages, the Logger initialization message and the LogReader initialization message.
Because we haven't specified an underlying container for the Logger, a new container is generated for it automatically by the control and is inserted into the DOM. The default positioning of the control pushes it to the right-hand edge of the page.
Let's liven things up a little and build on this simple example. The Logger will automatically become a drag object if the Drag-and-Drop utility is included in the head of the page. Go ahead and add a reference to the dragdrop-min.js file with the other dependencies (it should appear in between the yahoo-dom-event.js and the logger-min.js references):
<script type="text/javascript" src="yui/dragdrop-min.js"></script>
Save the file again and view it in your browser once more. Nothing has changed, except that the Logger control should now be drag-able. If you now substitute the minified dragdrop-min.js file for dragdrop-debug.js and view the page a third time, suddenly there're a whole lot more messages.
These messages reveal exactly what happens when the page loads and the order in which things occur. Every time you interact with the Logger, by say dragging it, more messages are logged, informing you that the mouseDown event was detected and the startDrag event was detected, and so on.
Next, we can take this opportunity to look at logging our very own custom messages to the Logger. We don't need to rely on the debug.js versions of each file to add our own custom messages either; for additional clarity you should switch back to the min.js version of DragDrop so that our message isn't lost amongst those generated by this control. Add the following code to the existing onDOMReady() function just below the LogReader object initialization:
<script type="text/javascript">
//create the namespace object
YAHOO.namespace("yuibook.logger");
//initialize logger
YAHOO.yuibook.logger.initLogger = function () {
//define logger instance
var myLogger = new YAHOO.widget.LogReader();
//define a callback for the click event
function clickCallback() {
//log a message
YAHOO.log("Don't click there!", "warn");
}
//add listener for the click event
YAHOO.util.Event.addListener("yui-gen0", "click", clickCallback);
}
//execute initLogger when DOM is ready
YAHOO.util.Event.onDOMReady( YAHOO.yuibook.logger.initLogger);
</script>
The container for the Logger is automatically given an id attribute of yui-gen0 so we can use this to attach an event handler that listens for the click event on the Logger control.
When this is detected, the YAHOO global method .log() allows us to add our custom message. Clicking anywhere on the logger will result in the message as shown below:
For reference, the default category of message is INFO. In our custom message example above, if we don't specify that our message is of category WARN in the .log() constructor, it will automatically be categorized as INFO.
For the final part of this simple use exercise, we can look at using a custom configuration object to alter some of the default properties of our instance of the Logger control. Add the following object literal declaration to the very first part of the initLogger() function:
<script type="text/javascript">
//create the namespace object
YAHOO.namespace("yuibook.logger");
//initialise logger
YAHOO.yuibook.logger.initLogger = function () {
//define configuration object
var myConfObj = {
width:"250px",
height:"250px",
footerEnabled:false,
thresholdMax:5,
thresholdMin:5
}
//define logger instance
var myLogger = new YAHOO.widget.LogReader(null, myConfObj);
//define a callback for the click event
function clickCallback() {
//log a message
YAHOO.log("Don't click there!", "warn");
}
//add listener for the click event
YAHOO.util.Event.addListener("yui-gen0", "click", clickCallback);
}
//execute initLogger when DOM is ready
YAHOO.util.Event.onDOMReady( YAHOO.yuibook.logger.initLogger);
</script>
We adjust the size of the Logger using the width and height properties, and can also easily disable the footer (we want to see any category of message from any source anyway, and could always change the filter via code later if we wanted).
The thresholdMax and thresholdMin properties should be used in conjunction with each other. If we set a thresholdMax but not a thresholdMin value, the Logger messages will simply be cleared when the threshold is reached. This way, we ensure that the five most recent messages are always on display.
Now we just need to add a reference to our configuration object to the existing LogReader constructor. Alter the constructor so that it appears as follows:
var mylogger = new YAHOO.widget.LogReader(null, myConfObj);
The optional object literal should be added as the second argument in the constructor. The first argument accepts a reference to the HTMLElement being used to contain the logger, but as we aren't using this particular feature, we need to pass in a null value. View the page again, and you should see something resembling like this:
[edit] Component Debugging with Logger
Let's look at a basic example of how errors in your code can be easily exposed and corrected with the assistance of the Logger control. We'll use the Animation utility for this example, and will make a new file, so in a blank page in your text editor begin with the code shown below:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"> <title>Yui Logger Example 2</title> <script type="text/javascript" src="yui/build/yahoo-dom-event/yahoo-dom-event.js"> </script> <script type="text/javascript" src="yui/build/animation/animation-debug.js"></script> <script type="text/javascript" src="yui/build/logger/logger-min.js"></script> <link type="text/css" rel="stylesheet" href="yui/build/logger/assets/skins/sam/logger.css"> <link type="text/css" rel="stylesheet" href="animError.css"> </head> <body class="yui-skin-sam"> <div id="animDiv"></div> <div> <button id="makeAnim">Animate!</button> </div> </body> </html>
Then, directly preceding the </body> tag, add the following Logger initialization function:
<script type="text/javascript">
//define namespace object
YAHOO.namespace("yuibook.logger");
//define the initLogger function
YAHOO.yuibook.logger.initLogger = function() {
//define a logger instance
var myLogger = new YAHOO.widget.LogReader();
}
//execute initLogger when DOM is ready
YAHOO.util.Event.onDOMReady( YAHOO.yuibook.logger.initLogger);
</script>
Our initial page is pretty much identical to what we started with in the previous example. Next we can begin to add the Animation specific code. We'll just make use of a simple animation for this example to highlight its error handling capabilities:
<script type="text/javascript">
//define namespace object
YAHOO.namespace("yuibook.logger");
//define the initLogger function
YAHOO.yuibook.logger.initLogger = function() {
//define a logger instance
var myLogger = new YAHOO.widget.LogReader();
//define animation attributes
var atts = {
width: { to: 200}
};
//define callback function for click event
YAHOO.util.Event.on("makeAnim", "click", function() {
//define the animation instance
var myAnim = new YAHOO.util.Anim(null, atts);
//animate!
myAnim.animate();
});
}
//execute initLogger when DOM is ready
YAHOO.util.Event.onDOMReady( YAHOO.yuibook.logger.initLogger);
</script>
The atts variable contains an object literal holding the animation attributes; in this case it specifies that the width of the target element will grow to 200 pixels wide.
Next is the animation object's constructor followed by an event handler to detect when the makeAnim button is clicked, which will in turn cause the animation to occur. Save the page so far as animError.html in your yuisite folder.
We will also need to add some page-specific styling to our <div> element. In a new file in your text editor, add the following CSS:
#animDiv {
height:100px;
width:100px;
background-color:red;
margin-bottom:10px;
}
This is all that's required for our basic example. Save it as animError.css, also in the yuisite folder. At this stage, running the page and clicking the button should give you something similar to the screenshot below:
As you probably noticed, we passed in a null reference as the first argument in the animation constructor. This is not allowed. In order to animate an element, the constructor needs to know what that element is, and when it receives a null value it logs an appropriate ERROR category message from the source: Anim.
I'm sure you'll agree that the example is tenuous at best. We have to supply a null value in the constructor in order to receive the category ERROR message in Logger. If we simply passed in an element reference for an element that didn't exist, the error would not be generated because the utility would just assume that the element referenced would eventually exist. So it's pretty unlikely that as a developer you'd generate this message in a proper debugging scenario.
Nevertheless, it does highlight the functionality provided by some of the debugging versions of the YUI components. For fun, you might want to go back and correct the constructor, passing in the correct element reference as the fist argument. Now when the page loads, the error message is not generated and a click of the button will animate our red <div>.
[edit] Logging with a Custom Class
We've already looked at writing our own simple log messages using the global YAHOO.log() method from within YUI-specific code. Now let's look at using the Logger control with a custom class or source. We'll need to define our very own class for this example but don't worry, for the sake of simplicity we'll be keeping the class relatively small.
The Yahoo! library itself is class-based, with each utility and control constructed from a range of interlinked classes which give the components their functionality and behavior. This next example should not only improve your understanding of the Logger control but also of the workings of the overall library, and even of JavaScript itself.
Let's assume that we're working on the user interface for a web-based music application; it could be a store that sells records and allows visitors to preview records they intend to purchase. Part of this interface will feature a turntable object which visitors can interact with in different ways.
To facilitate this, we'll need to add several properties and methods to the object. We can also add some code that specifically targets the Logger control, allowing us to quickly and easily log messages from within our custom object.
If this were a real implementation, the page would no doubt be full of interesting things. As this is just a demonstration, we won't clutter things up with unnecessary content. Begin with the following basic page:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"> <title>Yui Logger Example Three</title> <script type="text/javascript" src="yui/build/yahoo-dom-event/yahoo-dom-event.js"> </script> <script type="text/javascript" src="yui/build/logger/logger-min.js"></script> <link type="text/css" rel="stylesheet" href="yui/build/logger/assets/skins/sam/logger.css"> </head> <body class="yui-skin-sam"> </body> </html>
Here's the first part of the script, add it within the <body> tag:
<script type="text/javascript">
//create the namespace object
YAHOO.namespace("yuibook.logger");
//define the initLogger function
YAHOO.yuibook.logger.initLogger = function() {
//create a new logger instance
var myLogger = new YAHOO.widget.LogReader();
myLogger.hideSource("global");
myLogger.hideSource("LogReader");
myLogger.hideCategory("time");
myLogger.hideCategory("window");
}
//execute initLogger when DOM is ready
YAHOO.util.Event.onDOMReady( YAHOO.yuibook.logger.initLogger);
</script>
We instantiate a Logger control in the usual way, and as we're only interested in messages from our turntable source we can hide the other sources displayed by default. We won't be using any category TIME or WINDOW messages either, so we can hide these as well. We can now move on to defining our own custom class. Add the following code directly after the initial YAHOO.namespace() method at the start of the script (new code highlighted):
<script type="text/javascript">
//create the namespace object
YAHOO.namespace("yuibook.logger");
//define the turntable object constructor
YAHOO.yuibook.logger.turntable = function(brand) {
//set object properties
this.brand = brand;
this.playing = "false";
this.startStop = YAHOO.yuibook.logger.startOrStop;
}
//define the initLogger function
YAHOO.yuibook.logger.initLogger = function() {
//create a new logger instance
var myLogger = new YAHOO.widget.LogReader();
myLogger.hideSource("global");
myLogger.hideSource("LogReader");
myLogger.hideCategory("time");
myLogger.hideCategory("window");
}
//execute initLogger when DOM is ready
YAHOO.util.Event.onDOMReady( YAHOO.yuibook.logger.initLogger);
</script>
This function defines our turntable class and acts as a constructor for the turntable object. We give it two properties and a method (except that the method, .startstop, won't actually be a method until we add a function for it in a minute). Each property and method is added using the this keyword which is how objects refer to themselves.
Real turntables have a button that either starts or stops the record platter spinning. Our turntable object can also have a mechanism by which it is started or stopped. Add the following function to the page (new code highlighted):
<script type="text/javascript">
//create the namespace object
YAHOO.namespace("yuibook.logger");
//define the turntable object constructor
YAHOO.yuibook.logger.turntable = function(brand) {
//set object properties
this.brand = brand;
this.playing = "false";
this.startStop = YAHOO.yuibook.logger.startOrStop;
}
//define startOrStop function
YAHOO.yuibook.logger.startOrStop = function() {
//is the property already false?
if (this.playing == "false") {
//set playing property
this.playing = "true";
//write a custom message to Logger
YAHOO.log("The " + this.brand + " is playing!", "info", "turntable");
} else {
//set playing property
this.playing = "false";
//write a custom message to Logger
YAHOO.log("The " + this.brand + " has stopped!", "info", "turntable");
}
}
//define the initLogger function
YAHOO.yuibook.logger.initLogger = function() {
//create a new logger instance
var myLogger = new YAHOO.widget.LogReader();
myLogger.hideSource("global");
myLogger.hideSource("LogReader");
myLogger.hideCategory("time");
myLogger.hideCategory("window");
}
//execute initLogger when DOM is ready
YAHOO.util.Event.onDOMReady( YAHOO.yuibook.logger.initLogger);
</script>
The function (which is actually a method of our custom class) first examines the playing property to see whether or not the turntable is playing. If not, we set the property to true and output an appropriate message to the console.
We can also include the brand property of the object within the log message. If the playing property is already true we do the opposite and stop the turntable, outputting a similar message.
Next we can create an instance of our turntable object, specifying the brand name as an argument, and then calling the .startStop() method on it (this code should be added to the initLogger function, see turntableLogger.html for clarification):
<script type="text/javascript">
//create the namespace object
YAHOO.namespace("yuibook.logger");
//define the turntable object constructor
YAHOO.yuibook.logger.turntable = function(brand) {
//set object properties
this.brand = brand;
this.playing = "false";
this.startStop = YAHOO.yuibook.logger.startOrStop;
}
//define startOrStop function
YAHOO.yuibook.logger.startOrStop = function() {
//is the property already false?
if (this.playing == "false") {
//set playing property
this.playing = "true";
//write a custom message to Logger
YAHOO.log("The " + this.brand + " is playing!", "info", "turntable");
} else {
//set playing property
this.playing = "false";
//write a custom message to Logger
YAHOO.log("The " + this.brand + " has stopped!", "info", "turntable");
}
}
//define the initLogger function
YAHOO.yuibook.logger.initLogger = function() {
//create a new logger instance
var myLogger = new YAHOO.widget.LogReader();
myLogger.hideSource("global");
myLogger.hideSource("LogReader");
myLogger.hideCategory("time");
myLogger.hideCategory("window");
}
//create a new turntable object
var tt = new YAHOO.yuibook.logger.turntable("technics");
tt.startStop();
//execute initLogger when DOM is ready
YAHOO.util.Event.onDOMReady( YAHOO.yuibook.logger.initLogger);
</script>
Save the page so far as turntableLogger.html and view it in your browser, you should see something similar to the following screenshot (I've un-ticked the window and time categories so that our custom log message is shown alone in the Logger):
If you add another call to the .startStop() method directly below the first, the logger will display both the above message as well as a message advising that The technics has stopped.
We can also add our own debugging messages as well; we've specified that a brand argument should be supplied to our constructor. We can easily output an error message if this argument isn't provided. Change the turntable class function so that it appears as follows:
//define the turntable object constructor
YAHOO.yuibook.logger.turntable = function(brand) {
//log message if brand not specified
if (brand == undefined) {
YAHOO.log("Please specify a brand of turntable", "error", "turntable");
} else {
this.brand = brand;
this.playing = "false";
this.startStop = startOrStop;
}
}
If you now remove the brand from the following statement:
var tt = new turntable();
And view the page again, you should see this:
The class we've defined in this example is extremely simple and only outputs three lines of Logger code. If our class was part of a fully functional web application, there would no doubt be many more lines of Logger targeting message code. In this scenario, we would probably want to define our own named source using the LogWriter class provided by the Logger control.
We can add a few more log messages for good measure and can generate the new messages using a new property and a new method. Change the turntable class function so that it is as shown below:
//define the turntable object constructor
YAHOO.yuibook.logger.turntable = function(brand, speed) {
//log message if brand not specified
if (brand == undefined) {
YAHOO.log("Please specify a brand of turntable", "error", "turntable");
} else {
//set object properties
this.brand = brand;
this.playing = "false";
this.startStop = startOrStop;
this.speed = speed;
this.setSpeed = setRPMSpeed;
this.myLogWriter = new YAHOO.widget.LogWriter("turntable");
}
}
First we add a new argument to the object constructor, which is then set to a new property of the object, and a new method is also added. Next we define our custom LogWriter which will be used to add our messages. Now let's add the .setRPMSpeed() method:
function setRPMSpeed(rpm) {
this.speed = rpm;
this.myLogWriter.log("You've changed the speed to " + rpm + " RPM", "info");
}
Notice how we call the .log() method of our own custom LogWriter instance instead of the global YAHOO.log method. We can also omit the third argument of the .log() method, which can help shrink our code by a few bytes (don't forget that this is a cumulative saving the more message logging statements you have in your code, the more you will save).
Alter the remaining message logging statements so that they make use of the custom LogWriter:
//define startOrStop function
YAHOO.yuibook.logger.startOrStop = function() {
if (this.playing == "false") {
//set playing property
this.playing = "true";
//write a custom message from our custom class
this.myLogWriter.log("The " + this.brand + " is playing at " + this.speed + " RPM", "info");
} else {
//set playing property
this.playing = "false";
//write a custom message from our custom class
this.myLogWriter.log("The " + this.brand + " has stopped!", "info");
}
}
Now that our constructor requires an additional argument and we have a new method to play with, the code that creates a new turntable object and works with its methods will also need to be changed:
var tt = new turntable("technics", "33");
tt.startStop();
tt.setSpeed("45");
Save the changes to the file and view it one more time in your browser. Logger should now resemble that shown in the screenshot below:
[edit] Additional References
- For instructions on Installing Yahoo! User Interface library, click here
- For instructions on CSS Tools of Yahoo User Interface, click here
[edit] Source
The source of this content is Chapter 10: Advanced Debugging with Logger of Learning the Yahoo! User Interface library by Dan Wellman (Packt Publishing, 2008).

