GUIDES Javascript Troubleshooting
- Contents
- 1. Introduction
- 2. Use Source JavaScript When Troubleshooting
- 3. Basic Troubleshooting With Alerts
- 4. Tips for Troubleshooting in Firebug
- 5. Scrunch-related Problems
- 6. Accidental Globals
1. Introduction
This document discusses various tips and techniques for troubleshooting projects and Web applications developed using the UIZE JavaScript Framework.
2. Use Source JavaScript When Troubleshooting
While developing your code, it is always best to use source JavaScript code rather than scrunched JavaScript code.
Scrunching JavaScript code obfuscates identifier names and can make JavaScript error messages rather cryptic, rendering them pretty much useless when trying to track down tricky issues. Make sure to develop using source code - not only for your own application's code, but also the various modules of the UIZE JavaScript Framework.
3. Basic Troubleshooting With Alerts
As good as tools like Firebug become, there are still times when JavaScript's tried and tested built-in alert
statement provides a good fallback for quick testing across multiple different browsers.
The UIZE JavaScript Framework provides two convenient facilities that help with this classic / old school / original flavor style of debugging.
3.1. Alerting Summary Info for Instances or Classes
The Uize.Util.Debug
module provides a Uize.Util.Debug.summary
static method that returns a summary for instances of Uize.Class
subclasses, and for Uize.Class
subclasses.
Using this method, you can conveniently display summary info for instances of Uize.Class
subclasses as well as Uize.Class
subclasses using the alert
statement.
EXAMPLE 1
alert (Uize.Util.Debug.summary (page.children.slider));
In the above example, if the page
widget has a slider
child widget that is an instance of the class Uize.Widgets.Slider.Widget
, then the output of the alert
statement could look something like...
EXAMPLE OUTPUT
[object Uize.Widgets.Slider.Widget] built : true busy : inherit busyInherited : false children : [object Object] container : undefined decimalPlacesToDisplay : undefined displayedStyleHeight : 400 displayedStyleWidth : emptyColor : undefined enabled : inherit enabledInherited : true extraClasses : fullColor : #999 html : [package Uize.Widgets.Slider.Html] idPrefix : page_slider idPrefixConstruction : concatenated inDrag : false increments : 1 insertionMode : undefined localized : undefined maxValidValue : undefined maxValue : 200 minValidValue : undefined minValue : 0 name : slider nodeMap : undefined orientation : vertical parent : [object UizeSite.Page.Example] restTime : 250 scaleFunc : [object Function] size : large trackLength : 400 v2RootNodeCssClasses : Uize_Widgets_Slider_Css Uize_Widgets_Slider_Css-vertical Uize_Widgets_Slider_Css-large value : 50 valueFunc : [object Function] wired : true
EXAMPLE 2
alert (Uize.Util.Debug.summary (page.children.slider.Class));
In the above example, if the page
widget has a slider
child widget that is an instance of the class Uize.Widgets.Slider.Widget
, then the output of the alert
statement could look something like...
EXAMPLE OUTPUT
[object Uize.Widgets.Slider.Widget] built : true busy : inherit busyInherited : false children : [object Object] container : undefined decimalPlacesToDisplay : undefined displayedStyleHeight : 400 displayedStyleWidth : emptyColor : undefined enabled : inherit enabledInherited : true extraClasses : fullColor : #999 html : [package Uize.Widgets.Slider.Html] idPrefix : page_slider idPrefixConstruction : concatenated inDrag : false increments : 1 insertionMode : undefined localized : undefined maxValidValue : undefined maxValue : 200 minValidValue : undefined minValue : 0 name : slider nodeMap : undefined orientation : vertical parent : [object UizeSite.Page.Example] restTime : 250 scaleFunc : [object Function] size : large trackLength : 400 v2RootNodeCssClasses : Uize_Widgets_Slider_Css Uize_Widgets_Slider_Css-vertical Uize_Widgets_Slider_Css-large value : 50 valueFunc : [object Function] wired : true
3.2. Alerting Values of Instances
The Uize.Class
base class implements a valueOf Intrinsic Method
that returns the value of the value
state property for instances of Uize.Class
subclasses.
Because this method is implemented in the Uize.Class
base class, all Uize.Class
subclasses get to inherit it. For any class that implements a value
state property, the value of an instance can be displayed in an alert dialog using a convenient shorthand. This can be done by prepending a "+" (plus sign) to the instance reference for classes that implement a number type value, or by appending a + ''
to the instance reference for classes that implement a string type value.
EXAMPLE 1
alert (+page.children.slider);
EXAMPLE 2
alert (page.children.imageWipe + '');
4. Tips for Troubleshooting in Firebug
Firebug is an indispensable tool when troubleshooting JavaScript code. There are certain tricks specific to the UIZE JavaScript Framework that will aid in your Firebug troubleshooting.
4.1. Traversing the Widget Tree in Firebug
Firebug provides a powerful DOM inspector that not only lets you traverse the document's node hierarchy, but also lets you inspect user-defined properties and functions.
By using the DOM inspector, you can traverse the JavaScript widget tree for a page that uses the UIZE JavaScript Framework. On a typical page you will have a page widget at the root of the widget hierarchy. Finding this in the DOM inspector, you can expand it and inspect its properties. In order to dig deeper into the widget tree, you can locate the children
property of the page widget and expand it too. For each child widget of the page widget you can do the same. You can keep sleuthing like this, digging as deep as you need to, to ultimately find the "widget of interest" that is a suspect in the code crime you are investigating.
At any stage in this process, you can inspect the values of state properties for a widget instance. Note that the state properties will be represented using their private names (i.e. prefixed with an underscore).
4.2. Display of Types in Firebug's DOM Inspector
When using the DOM inspector in Firebug to peruse the widget tree, any property that is an instance of a Uize.Class
subclass will have the module name for its class displayed to the right of the property.
EXAMPLE
page Uize.Widget.Page instanceId=uizeGuid1 _children=Object
This can be particularly useful when inspecting the children
property of a widget to peruse its child widgets. In this case, the widget class for each child widget will be displayed to the right of the child widget name, as in...
EXAMPLE
imageWipe Uize.Widget.ImageWipe ... paramsInspector UizeSite.ParamsInspector ... performWipe Uize.Widget.Button ...
4.3. Logging Summary Info for Instances or Classes to Firebug's Console
Just as with alerting summary info for instances or classes, summary info for instances of Uize.Class
subclasses as well as Uize.Class
subclasses can be conveniently logged to the Firebug console using Firebug's console.log
method.
EXAMPLE 1
console.log (Uize.Util.Debug.summary (page.children.slider));
EXAMPLE 2
console.log (Uize.Util.Debug.summary (page.children.slider.Class));
In both cases, the output that is logged to the Firebug console will look pretty much the same as what you would see in an alert dialog (see alerting summary info for instances or classes).
4.4. Logging Values of Instances or Classes to Firebug's Console
Just as with alerting values of instances or classes, values for instances of Uize.Class
subclasses as well as Uize.Class
subclasses can be conveniently logged to the Firebug console using Firebug's console.log
method.
EXAMPLE 1
console.log (+page.children.slider);
EXAMPLE 2
console.log (page.children.imageWipe + '');
5. Scrunch-related Problems
Certain kinds of problems can arise specifically with scrunched code and not be present with source code.
If you encounter a problem with production / scrunched code, switch to using source code in order to obtain a meaningful error message, only to then find that the error is not reproducible, chances are you have a scrunch-related bug on your hands.
There are a number of common mistakes one can make that will be forgiven with source code but not with scrunched code, as outlined below...
5.1. Missing Semicolons
Missing semicolons at the end of statements can lead to JavaScript syntax errors when JavaScript code is scrunched and consecutive lines that used to be separated by linebreaks are compacted together.
JavaScript interpreters are quite forgiving regarding missing semicolons at the end of statements, and use linebreaks as hints to resolve situations where there might be ambiguity. This forgiving nature can lead to missing semicolons creeping in and not being detected while developing with source code. However, when the code is scrunched and linebreaks are stripped, two lines where a linebreak was serving as a statement terminator suddenly get tacked together to produce something that is syntactically incorrect.
SOURCE CODE - IT WORKS
var _blah = 5 function _myFunction () {}
SCRUNCHED CODE - IT FAILS!!!
var _a=5function _b(){}
A particularly common place to miss a semicolon is when assigning an anonymous function declaration to a prototype property, as in...
INCORRECT
MyObject.prototype.myMethod1 = function () { // ... ... ... // ... ... ... // ... ... ... // ... ... ... // ... ... ... } MyObject.prototype.myMethod2 = function () { // ... ... ... // ... ... ... // ... ... ... // ... ... ... // ... ... ... };
The semicolon omitted after the first assignment will cause the two assignments to run together when the code is scrunched, causing a JavaScript error. This problem can be easy to miss for a few reasons...
1) | A function's implementation could be quite long, so a wide chasm is introduced between the start of the assignment and its end. By the time you get down to the close curly bracket, the assignment aspect of the statement is forgotten. |
2) | Function declarations don't usually end with a semicolon, so one can forget that you're not just declaring a function but also assigning the result of that declaration. |
3) | A function may start off its life as no more than a simple function declaration, and then later it is decided to assign it as a method (private or public) or to some local variable. When turning the function declaration into an assignment, one could easily forget to add that important semicolon at the end. |
5.2. Accessing Private Identifiers as Publics
Accessing a private identifier as though it were a public identifier will work in source code but will almost certainly not work when the code is scrunched.
When application code or some other module of code (such as a different class, or even a subclass) accesses a private identifier, such as a private property or method, then the code will most likely break when it is scrunched. This is because modules are scrunched independently, and so the same private identifier name is very unlikely to be mapped to the same scrunched name across multiple different modules of code. One can consider this a kind of serendipitous slap on the wrist delivered by the Scruncher, because code shouldn't be inappropriately looking at other code's privates - let alone touching them.
The kinds of inappropriate accessing that one may encounter when troubleshooting scrunch-related failures include...
an application inappropriately accesses private methods or properties of a class or instance it is using | |
a class inappropriately accesses private methods or properties of a helper class it is using | |
a subclass inappropriately accesses private methods or properties of its superclass |
5.2.1. Accessing a Superclass' State Properties
One less-than-obvious access of privates that can occur is with subclasses.
If a state property is declared in a superclass, with a mapping of public name to private name, then the superclass can reference that state property by its private name. However, a subclass of that superclass may not. Instead, it must use the public name. And, to query the value for a state property declared in the superclass, the subclass must use the get
method.
If you reference state properties, properties, or methods of a superclass by private name in the implementation of your subclass, your code will work when using source code but most likely produce errors or erratic behavior when using scrunched code.
5.3. Accidental Globals and Scrunching
Accidentally assigning globals can have interesting interactions with the scrunching process.
Scrunch-related side-effects of accidentally assigned globals are discussed in more detail in the section accidental globals.
5.4. Private Collisions
It can sometimes - yet rarely - happen that source code of a subclass may conflict with source code of a superclass, if the same private identifier name is used in each, but for different purposes.
To avoid collisions in private identifier names between a superclass and a subclass when code is scrunched, scrunched identifier prefixes are registered with both the superclass and the subclass that are unique to each. This has the interesting potential side effect that a superclass-subclass collision in a private identifier name could surface only in source code but be guaranteed to never occur with scrunched code, since the names will be the same in the source code but different from one another in the scrunched code.
Fortunately, a problem that occurs in source code is more likely to be detected earlier, because development will be done using source / unscrunched code. However, when writing an implementation for a subclass, you may nevertheless want to crack open the source code for the superclass that you are subclassing - just to be certain. This is pretty typical, anyway, since it is useful to understand the implementation of a superclass when implementing a subclass of it.
5.5. Private Identifiers Inside String Literals
The Scruncher does not enter string literals. This means that any code contained inside string literals will not be scrunched.
A HYPOTHETICAL SCENARIO
For performance reasons, you might want to dynamically build a function that will get executed repeatedly, rather than repeatedly performing the same conditional steps. By building a function, you can capture how the code that you need to repeatedly execute is conditionalized based upon some state that remains the same throughout the entire iteration. Now, let's say you wanted your dynamically built function to call private functions or methods. If you use the private identifiers when constructing your function's body, your function will work with source code but will fail with scrunched code.
So, if you're putting code into string literals - either to be evaluated using JavaScript's built-in eval
function, or to be built into a function using JavaScript's Function
object - you will either need to make public the identifiers that need to be accessed from the code inside string literals, or you will need to use a trick to "pipe" the scrunched name into the dynamically constructed JavaScript code.
The following example is not a good, real world example, but it does illustrate the point quite clearly...
SOURCE CODE - IT WORKS
function _myFunction () { var _myVariable = 'Hello there'; eval ('alert (_myVariable)'); }
SCRUNCHED CODE - IT FAILS!!!
function _a(){var _b='Hello there';eval('alert (_myVariable)');}
To fix the problem, you could do one of the following...
5.5.1. Avoid Privates
Make public the identifiers that need to be accessed from the code inside string literals, as in...
SOURCE CODE - IT WORKS
function _myFunction () { var myVariable = 'Hello there'; eval ('alert (myVariable)'); }
SCRUNCHED CODE - NOW IT WORKS
function _a(){var myVariable='Hello there';eval('alert (myVariable)');}
5.5.2. Pipe the Privates Through
Use the trick to "pipe" the scrunched name into dynamically constructed JavaScript code, as in...
SOURCE CODE - IT WORKS
function _myFunction () { var _myVariable = 'Hello there'; for (var _myVariablePrivateName in {_myVariable:1}); eval ('alert (' + _myVariablePrivateName + ')'); }
SCRUNCHED CODE - NOW IT WORKS
function _a(){var _b='Hello there';for(var _c in{_b:1});eval('alert ('+_c+')');}
You would never use this technique for such an absurdly trivial case, but in more sophisticated code where you're dynamically constructing JavaScript code to eval
or turn into a Function
instance, this technique may come in handy. It works because the for...in
loop iterates through the keys of an object that only has one key, where that key is the name of an identifier that will be scrunched. The loop itself does nothing, but the iterator variable is left with the name of the key after the one iteration loop has completed. When the code is scrunched, the key will be different. Using this trick, you've put an identifier name into a string variable - and it works regardless of how the private identifier is changed during scrunching.
6. Accidental Globals
Accidental globals are variables that are accidentally assigned in the global scope - usually the result of a bug / oversight.
It pays to be vigilant regarding accidental assignment of global variables. It's quite easy to accidentally assign a global variable, just by forgetting a var
keyword in a critical place. Sometimes an accidental global can lie dormant for a long time - lurking in the shadows, so to speak - before actually causing a problem.
6.1. The Problems They Create
The kinds of problem situations that can arise as a result of accidental global include...
6.1.1. Collisions Between Different Instances of a Closure
Different instances of the same closure can stomp over state that wasn't intended to be shared.
In this scenario, one piece of code is implemented so that a closure inside it is expecting a particular variable in its enclosing scope to persist a unique value for each instance of the closure. Accidentally assigned as a global, different closure instances stomp on each other by assigning to that same global variable when they thought they were using an instance of the variable unique to them. Strange behavior can ensue.
6.1.2. Collisions Between Disparate Code
Two different pieces of code - possibly in the same module or from completely different modules in use by the page - can stomp over state that wasn't intended to be shared.
6.1.2.1. Works in Source, Fails in Scrunched
In one scenario, two accidental globals have different names in source mode but get scrunched to the same identifier in scrunched mode.
Now they stomp on each other, and one of the variables gets assigned a value that wasn't intended for it. Strange things ensue. The fundamental laws of physics are compromised. The universe collapses. In this scenario, you don't notice the problem caused by the accidental globals until you switch to production code (i.e. scrunched code).
6.1.2.2. Fails in Source, Works in Scrunched
In another scenario, two accidental globals may "collide" when using source / unscrunched code, but may not collide when the scrunched version of the code is used.
This is a more likely scenario than the works in source, fails in scrunched scenario, because the probability of using the same name for a variable in two different pieces of code is higher than the probability of the same name being mapped to the same scrunched name in two different pieces of code. Fortunately, a problem that occurs in source code is more likely to be detected earlier, because development will be done using source / unscrunched code.
6.1.3. Collisions With Page Code
Code inside a module can accidentally assign a value on a global variable that is also used by the page code, when the module actually needs to be using a local scope variable.
Because module code is often intended to be scrunched and because identifiers are, therefore, often prefixed with an underscore, it is less likely for this scenario to arise. Typically, global variables that are assigned in the page that are part of the very page-specific code are not intended to be scrunched (since the code may not be in an external file) and are not likely to be prefixed with an underscore.
6.2. How They Creep In
The most obvious way to assign an accidental global is to simply forget the var
keyword in front of a plain old variable declaration / assignment.
However, more insidious traps are waiting to ensnare you.
6.2.1. Incorrect Semicolon in Compound var Statement
Consider the following somewhat innocent looking compound var
statement...
EXAMPLE
var myVariable1 = 1, myVariable2 = 2, myVariable3 = 3; myVariable4 = 4, myVariable5 = 5 ;
It's easy to miss, but there's a semicolon at the end of the assignment to myVariable3
. You don't get any kind of error, because what follows is syntactically correct in JavaScript. However, you have terminated the compound var
statement, and the assignments of myVariable4
and myVariable5
are now as globals because they have no var
keyword at the beginning of the statement.
6.2.2. Missing var statement in Loops
A slightly more obvious, yet surprisingly easy-to-make mistake is to omit a var
keyword in a loop, particularly with for...in
loops.
Whereas a normal for
loop has three statements that don't read like English all that well, a for...in
loop reads more elegantly - and therein lies the trap. It's easier to think for (key in object)
than it is to think for (var key in object)
, since the latter sounds more clumsy. As silly as it sounds, the semantics of it does seem to play a role.
EXAMPLE 1
for (_elementNo = -1; ++_elementNo < _elements.length;) { // ... // ... }
EXAMPLE 2
for (_key in _object) { // ... // ... }
6.3. How to Spot Accidental Globals
Accidental globals can be spotted by keeping an eye on the DOM in a debugging tool such as Firebug.
6.3.1. Thorough Interaction
In order to be certain that you've spotted all the lurking accidental globals, you may need to pound away at the functionality in your pages.
Some accidental globals may be lurking in the "nooks and crannies" of your code, and so you may need to interact with your pages a lot before being able to reliably assess the number of them that exist. You really want to hit all the code that could ever possibly be hit in your pages before you draw your conclusions. You want to push at all the states and conditions that could arise through a wide range of possible user interaction.
6.3.2. Using Firebug
Keep Firebug open while browsing pages of your site and have the DOM tab selected.
From the Options drop-down to the right, make sure at least the "Show User-defined Properties" and "Show User-defined Functions" options are both checked - the rest can be disabled (which may even be helpful in reducing clutter).
Now, as you browse pages in this state, keep an eye on what global identifiers show up in the DOM listing. You can expect to see some $
variables (for the declarative syntax for widget data and widget adoption), the Uize
namespace, the global page widget variable, and possibly some intentional globals that are nevertheless an indication of bad / old school design patterns. What you shouldn't see are variables that start with an underscore - those are almost certainly variables that are supposed to be locals in functions but that are being accidentally assigned globally.
IMPORTANT: As you interact with your pages to provoke the possible assignment of accidental globals, be sure to use the "Refresh" option from Firebug's Options drop-down to the right - new additions to the DOM resulting from interaction won't automatically show up.
6.4. Fixing Accidental Globals
It can be hard to fix an accidental global, because there's no easy way to know where its assignment is originating from.
Unless it's obvious from the name of the variable, one may have to do a global search (how ironic!) through the JavaScript code. The process of tracing the origin of an accidental global assignment can be complicated for variables with generic names that are used in multiple places, such as _result
, _object
, _key
, _property
, _params
, etc.
Having to hunt down the source of accidental globals can be avoided, of course, if you keep a watchful eye on the DOM as you develop code. That way, you'll notice the accidental globals sooner after they creep in and will have a better idea of what recent code changes may have introduced them.