Thursday, March 27, 2008

JavaScript Method Overloading

posted by Jonah Dempcy


A feature of languages such as Java, C# and C++ is the ability to overload methods with multiple parameters. Overloaded methods execute different code depending on the type and amount of parameters supplied-- in other words, the type signature of the method. Thus, overloading methods allows for a more flexible API which can handle different possible signatures.

Here's an example of how it works in Java. Let's say that I have a record collection and want to be able to add records by name, ID number, or a Record object:
public void addRecord(String name) {
 // implementation details
}

public void addRecord (Number id) {
 // implementation details
}

public void addRecord(Record record) {
 // implementation details
}
This is convenient because I can call addRecord() elsewhere in the code and use whatever data is available at that time.

Unfortunately, JavaScript does not currently allow you to do this, at least not in this manner. One reason for this is that JavaScript does not have type signatures for its methods. The signatures of JavaScript methods are simply the names of the parameters, without any type information.

Compare:

Java
public void addRecord(String name) {}
JavaScript
function addRecord(name) {}
As you can see, JavaScript method declarations do not have the types of return values or parameters. This makes sense given that JavaScript is not a typesafe language, or a strongly typed language at all.

Regardless, JavaScript 2.0 will support method overloading inherently (so-called 'multifunctions'), but for now, we're left to devise our own solutions to this.

JS Method Overloading: A First Attempt



If your method only has a single argument, it's fairly easy to overload a method.

For instance, here's an overloaded method that hides an element, given either its id (a string) or the HTML object itself.
function hideElement(element) { // takes id of element (string) or HTML obj
  if (typeof element == 'string') {
      element = document.getElementById(element);
  }
  element.style.display = 'none';
}
In this example, a check of the argument's type (using the typeof keyword) determines if it is a string. If so, it redefines element as the HTML object itself, instead of the string id.

Here's how you might use it:
// Create an element and append to the DOM
var myElement = document.createElement('div');
myElement.style.backgroundColor = '#00FF40'; // green
myElement.setAttribute('id', 'greenbox');
document.body.appendChild(myElement);

// Hide the element using a direct object reference
hideElement(myElement);

// Hide the element using the string id
hideElement('greenbox');
This is pretty rudimentary, and certainly doesn't allow for code reuse. Each time you want to overload a method, you have to do all these type checks using the typeof keyword. Not the ideal solution, but workable if you are just overloading a method with one or two type signatures.

JS Method Overloading: Making it reusable

Working from the end back, an ideal solution would be to have a simple utility function to add methods along with their type signature. Something like this:
function addMethod(obj, name, method, signature) {
   // implementation details
}

obj .......... we add the method to this object
name ......... the name of the method
method ....... the method to be added
signature .... the signature of the method, e.g. ["string", "boolean", "number"]
It turns out that someone already came up with this! In November 2007, John Resig wrote a post on JavaScript method overloading that contains a version of this very function. However, although his version is useful and very clever, it does not take into account overloading by type. It only allows overloading based on a different number of arguments.

Let's try implementing the addMethod() function now. Warning, this attempt fails (so don't copy from the code below, because it won't work) -- but it's educational to see the process of developing the code, so I've left this example in:
function addMethod(obj, name, method, signature) {

    // we will use this variable to check if a method already exists 
    // with the same name

   var existingMethod; 
   
   if (typeof obj[name] == 'function') { // if method already exists
        existingMethod = obj[name];      // store a reference to it
    }

    obj[name] = function(arguments) {        
        var signatureMatches = true;
        for (var i = 0; i < signature.length; i++) {
            if (signature[i] != typeof arguments[i]) signatureMatches = false;
        }

        if (signatureMatches) method((arguments);

        if (existingMethod) existingMethod(arguments);
    }
}
At first glance, this looks like it might work, but alas, does not. It somehow overwrites the existing methods so it is not working correctly. But fret not, for there is a working solution. As they say, when you fail, try, try again.

Finally, a Working Solution

This solution may not be the most elegant, but it gets the job done. This is licensed under the MIT license so feel free to use it for your projects (per the terms of the license).
function addMethod(obj, name, method, signature) {
    // The first time addMethod() is called, define the method
    if (typeof obj[name] == 'undefined') {
        obj[name] = function() {
            for (var signatureString in obj.overloadedMethods[name].signatures) {
                var signatureArray = signatureString.split(",");
                
                var signatureMatches = true;                
                
                for (var i = 0; i < arguments.length; i++) {
                    if (typeof arguments[i] != typeof signatureArray[i]) signatureMatches = false;
                }            
                
                if (signatureMatches) {
                    obj.overloadedMethods[name].signatures[signatureString](arguments);
                }

            }
        }
    }

    // The first time addMethod() is called, create overloadedMethods property
    if (typeof obj.overloadedMethods == 'undefined') {
        obj.overloadedMethods = {};
    }
    
    // The first time a method is added for a given name, create it
    if (typeof obj.overloadedMethods[name] == 'undefined') {
        obj.overloadedMethods[name] = {'signatures': {}};
    }
    
    // Store the method in an associative array (hash map)
    // keyed by the signature string, e.g. "number,boolean,array"
    obj.overloadedMethods[name].signatures[signature.toString()] = method;
}
This approach differs from the previous one in that I am saving an associative array of methods' signatures mapped to the method itself. Here are some tests which demonstrate how to use it:
// tests
var jonah = {};
addMethod(jonah, 'test', function(str) {alert("it's a string");}, ['string']);
addMethod(jonah, 'test', function(num) {alert("it's a number");}, ['number']);

jonah.test('string'); // alerts "its a string"
jonah.test(123); // alerts "it's a number"
The addMethod() function is actually only creating one method for "test", a method which iterates through the stored methods in the overloadedMethods object, checking the signature of each one. When it finds a match, it calls that method. Simple enough, I think.

Here's another example:
addMethod(jonah, 'test', function(bool, str) {alert("it's a boolean, and string");}, ['boolean', 'string']);

jonah.test(true); // this should do nothing
jonah.test(true, "this should work");
There is definitely room for improvement to this code. For one, you can break out of the outer for() loop once finding a match to save some cycles.

Another optimization is perhaps rewriting the way it finds the match. But I'll leave these for the topic of another article. Comments are welcome. Feel free to post your own improvements to the code, or alternate solutions.

Labels:

Sunday, March 23, 2008

Managing Cross Browser issues with JS browser detects & Conditional Comments

posted by Alex Grande
Until all browsers completely comply with w3c specifications we have to find workarounds in order for our websites to be pixel perfect in various browsers. The browsers we need to be most concerned with include Internet Explorer, Safari, and Firefox.

Firefox is a web developers wet dream. It features many plugins that allow a web developer to write CSS, JS, and HTML in real time. Thus you do not need a hack or workaround for Firefox since you are developing your websites in Firefox.

With the release of Safari 3 for both Mac and Windows, Safari is becoming a strong player. When Safari-centric adjustments need to be made, you can use a browser detect, or when you write javascript that detects the user's browser and can set styles only to that browser. You may have heard that browser detects bad and should be avoided, but when you do not have any other options it may be your only hope. Using Core DOM API, or just plain old javascript, browser detect code can be confusing and long. A library, like dojo, prototype, or mootools, can make life get much easier and tasks can be checked off much faster. In jquery, a popular library, it is easy to detect the browser.

In this example, not only do I detect Safari but set a CSS class called "safari" to the tag seen only by Safari's eyes.
// detects the browser in an if statement
if ($.browser.safari) {
   // Writes a class safar on the <body> tag. Or in other words, <body class="safari">
   $("body").addClass("safari");
}
And in this example we write it in Mootools,

if (window.safari) {
   $$("body").addClass("safari");
}


On to Internet Explorer. For styling (CSS) we can use conditional comments placed in your html. Right after the tag insert the following code:
<!--[if lte IE 5]>
<div class="ie">
<![endif]-->
<!--[if IE 6]>
<div class="ie ie6">
<![endif]-->
<!--[if IE 7]>
<div class="ie ie7">
<![endif]-->
Results:

IE5+ gets <div class="ie">

IE6 gets <div class="ie6">

IE7 gets <div class="ie7">

At the bottom of your document, right before the closing tag add these lines of code to close the IE <div>s.
<!--[if IE]>
</div>
<![endif]-->
On to the stylizing in CSS:

To write styles for Safari just start any style rule with .safari. For instance,
.safari div#title {
   margin: 0 0 3px 0;
}
And similar for IE:
.ie div#title {
   position: relative;
}

.ie7 div#title {
   margin: 0 0 3px 0;
}

.ie6 div#title {
   left: 305px;
}
If you have to make browser specific Javascript adjustments only browser detects will suffice. We already covered Safari. For Internet explorer you can write:

Jquery:
if ($.browser.msie) {
   // js goes here for internet explorer
}
Mootools:
if (window.ie) {
   // js goes here for internet explorer
}
Luckily, Safari and internet explorer styling adjustments are often similar. Having already adjusted IE to match Firefox, I usually need to apply similar Safari rules to the same trouble elements.

For example,
.ie div#rightNav,
.safari div#rightNav {
   top: -3px;
}

Labels: , , ,

Friday, March 21, 2008

Firebug Tutorial: Getting Started

posted by Jonah Dempcy
Firebug is a plugin for Firefox that greatly aids CSS and JavaScript debugging. This guide will walk you through installing Firebug and using some of its functionality, including debugging JavaScript, inspecting the DOM and editing CSS on the fly.

Getting Started

First, make sure you're running Firefox. If you aren't, get Firefox now. You won't be disappointed. Next, install Firebug by following the link and clicking "Install Now." After installing, restart Firefox and continue.

Now you can click the small icon in the bottom right corner of the Firefox window to open Firebug. Go ahead and open it-- the first time running, you may have to choose to enable it for sites on the world wide web.



Using the Console

The first tab that's open in Firebug is the console. The console tab shows JavaScript error messages, as well as having a command line interface (CLI) where you can enter JavaScript and execute it in real time.

Using the CLI, you can call functions, define variables, or even write new functions.

The JavaScript error messages are are also helpful for debugging since they show you which line the error occurred on, as well as a stack trace.

Another interesting feature is the ability to log messages to the console by using the method console.log(). Note that this will throw an error in browsers which don't have Firebug installed, so it's just for development purposes.

Logging messages can be very useful when debugging complex JavaScript applications that have a lot of moving parts. It can also be used for page profiling. Here's an example of how you might use logging to the console to add some rudimentary speed profiling of a JavaScript function:
function doStuff() {
var startTime = new Date().getTime(); // The current time in epoch milliseconds

// Add code here to be profiled

var endTime = new Date().getTime() - startTime;
console.log("The function doStuff() took " + endTime + " milliseconds to complete.");
}
This would result in a message being logged to the console with the amount of milliseconds it took for that code to execute. Nothing fancy, but it gets the job done!

DOM Inspection

Clicking the next tab over in Firebug, to HTML, you'll find a two-paned window that allows you to browse the DOM on the left and edit CSS on the right.

Start opening HTML tags and mousing over them. You'll notice that the HTML element on the screen lights up when you hover over the tag. It's a nice feature that lets you quickly orient yourself in the code.

The highlighted area also shows how much space an element is taking up on the screen. The color blue denotes the element's height and width, while yellow is margin and purple is padding.

Clicking the Inspect button (next to the bug icon in the top left corner of Firebug) will toggle a mode where you can click on HTML elements on the screen and it will go to that tag in the code.

There's another tab for DOM inspection, the aptly-named DOM tab. Here you will find all of the DOM objects created by JavaScript, such as variables and functions, as well as the core DOM API (for example, the window and document objects can be inspected here).

I check this page when I want to view the current state of the page and watch it change as JavaScript executes. For example, say I have a variable that keeps track of how many items are in a shopping cart. I could watch this variable in the DOM tab to make sure that it's incremented when I add an item to the cart.

Editing CSS on the Fly

Going back to the HTML tab, notice that the pane on the right shows CSS styles for whichever element is currently selected. You can choose an element in the left pane (or by clicking Inspect and then clicking the element), and the CSS styles for that element will be displayed. Furthermore, you can edit the properties and even add new ones.

Firebug has a different take on CSS editing than some other debugging programs, namely the Microsoft Web Developer Toolbar, so I'll clarify how it works: In Firebug, when you change a CSS style, it updates it for every element on the page. Conversely, in Microsoft Web Developer Toolbar, changing CSS styles only affects the currently selected element.

By way of example, say you have the CSS rule p {padding: 10px;}. You then select a P tag and change it to padding: 20px; using Firebug. The result is that every P tag will now have 20px padding, unlike other programs where it would only affect the styles of the selected tag.

To add a new style, simply click twice in the whitespace inside of a style block, next to the existing declarations. Firebug even has a convenient auto-complete feature for writing CSS.

In the right page, there are three tabs: Style, Layout and DOM. So far, we've been editing CSS in the Style tab, but make sure to check out the other two as well. The Layout tab offers an alternate view of the box model for the selected element, along with pixel-precise measurements of its dimensions. The DOM tab shows all properties and methods for that element.

Page Profiling and Network Metrics

Nothing's worse than making a great site only to have it suffer from poor front end performance. (OK, well, a lot of things are worse -- but it's still frustrating!). The Firebug Net profiler can help get to the source of that slowness.

Although it is far from robust, the Net tab offers semi-reliable metrics on the download speed of all assets including the HTML document, CSS, JavaScript, images and any other linked files.

You can see which files are being downloaded concurrently and which are blocking (hint: it's the JavaScript), and how many connections are being opened with a given domain.

For more extensive page profiling, be sure to check out YSlow!, a plugin for Firebug (I guess that makes it an extension-of-an-extension) developed by Yahoo.

Labels: ,