Comparing and cloning objects in JavaScript

Here’s a handy way to determine if two JavaScript objects are identical without using a framework like jQuery or MooTools:


var compare_objects = function (obj1, obj2){

    var parameter_name;
    
    var compare = function(objA, objB, param){
        
        var param_objA = objA[param],
            param_objB = (typeof objB[param] === "undefined") ? false : objB[param];
        
        switch(typeof objA[param]){
            case "object": return (compare_objects(param_objA, param_objB));
            case "function": return (param_objA.toString() === param_objB.toString());
            default: return (param_objA === param_objB);
        }
        
    };
    
    for(parameter_name in obj1){
        if(typeof obj2[parameter_name] === "undefined" || !compare(obj1, obj2, parameter_name)){
            return false;
        }
    }

    for(parameter_name in obj2){
        if(typeof obj1[parameter_name] === "undefined" || !compare(obj1, obj2, parameter_name)){
            return false;
        }        
    }

    return true;

};

Here’s an easy way to clone a JavaScript object:


var clone_object = function (original_obj) {
    var new_obj = {};
    for(var param in original_obj) {
        if(original_obj.hasOwnProperty(param)){
            if(typeof(original_obj[param]) === "object"){
                new_obj[param] = clone_object(original_obj[param]);
            } else {
                new_obj[param] = original_obj[param];
            }
        }
    }
    return new_obj;
};

A real-world example of the two functions:


var object_1 = { fruit: "apple", tree: "dogwood", number: 3 };
var object_2 = { fruit: "apple", tree: "dogwood", number: 3, cartoons: { anime: "robotech", fantasy: "he-man" } };
var object_3 = clone_object(object_2);

console.log("objects 1 and 2 match? " +compare_objects(object_1, object_2));
//displays false;

console.log("objects 2 and 3 match? " +compare_objects(object_2, object_3));
//displays true

Bear in mind both of these functions are recursive, which means the larger the object, the slower the performance.

Rounded corners on images using CSS3

Most browsers do not allow images to be cropped using CSS3’s border-radius. Tim Van Damme recently posted a workaround for this issue. It’s a nice trick, and doesn’t require JavaScript.

It does, however, require an extra span to be added to the page’s markup, which is quite a pain to manually apply to each image. Bram Van Damme (no relation to Tim) posted a simple jQuery script that automate’s Tim’s workaround with minimal effort. It’s a perfect example of using JavaScript for progressive enhancement purposes, as the page is still usable when JavaScript and/or CSS is disabled.

Being a MooTools kind of guy, I decided to whip up a MooTools-flavored version. Enjoy!


window.addEvent("domready", function (){
    $$("img").each(function(img){
        new Element("span", {
            "class": "rounded",
            styles: {
                "background-image": "url(" + img.getProperty('src') + ")",
                height: img.getProperty('height') + "px",
                width: img.getProperty('width') + "px"
            }
        }).wraps(img);
    });
});

Bear in mind this JavaScript relies on your page having some pre-defined CSS:


.rounded {
    -webkit-border-radius: 25px;
    -moz-border-radius: 25px;
    border-radius: 25px;
    display: block;
}
.rounded img { opacity: 0; }

View demo

Notes:

  • The demo page has the code wrapped in a function, which makes the code reusable; it accepts any CSS selector, such as “img”, “#myid img”, “p img”, “img.rounded”, etc.
  • This will only work in browsers that support CSS3 border-radius, which means no IE6/7/8 or older versions of Safari, Firefox, or Opera.

A new removeClasses utility for MooTools

Note: If you want to skip to the final code (in both MooTools and framework-neutral flavors), it’s at the bottom of this post.

The problem

Readers of this blog know that I enjoy MooTools. Like other JavaScript frameworks, it has many excellent features, including addClass and removeClass functions, which I use all the time. However, when I was working on my CustomInput class the other day, I discovered a major shortcoming of MooTools’ removeClass function — it doesn’t work very well when trying to remove multiple classes (as of MooTools version 1.2.4). In particular, if you specify multiple classes to remove, removeClass will only work if the classes are listed in that element’s className property in the order specified.


//Assuming element has className "hello cruel world"

//Single terms work fine
element.removeClass("world"); //becomes "hello  world"
element.removeClass("hello"); //becomes " cruel world"

//Multiple terms listed in same order as className works fine
element.removeClass("hello cruel"); //becomes " world"

//Multiple terms NOT listed in same order as className fail
element.removeClass("hello world"); //remains "hello cruel world"
element.removeClass("cruel hello"); //remains "hello cruel world"

The cause

A peek at MooTools’ removeClass code reveals the shortcoming:


removeClass: function(className){
   this.className = this.className.replace(new RegExp('(^|\s)' + className + '(?:\s|$)'), '$1');
   return this;
}

The string containing the class names you want to remove is passed as-is; it remains a whole string, and is not broken down into substrings. So "hello world" remains the single string "hello world" instead of two separate strings "hello" and "world".

I tested jQuery’s removeClass function and noticed it doesn’t have the same problem; it will remove each word, no matter what order you specify. Taking a look under the hood reveals a completely different approach to removeClass:


removeClass: function( value ) {
   if ( jQuery.isFunction(value) ) {
      return this.each(function(i) {
         var self = jQuery(this);
         self.removeClass( value.call(this, i, self.attr("class")) );
      });
   }

   if ( (value && typeof value === "string") || value === undefined ) {
      var classNames = (value || "").split(rspace);

      for ( var i = 0, l = this.length; i < l; i++ ) {
         var elem = this[i];

         if ( elem.nodeType === 1 && elem.className ) {
            if ( value ) {
               var className = (" " + elem.className + " ").replace(rclass, " ");
               for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
                  className = className.replace(" " + classNames[c] + " ", " ");
               }
               elem.className = jQuery.trim( className );

            } else {
               elem.className = "";
            }
         }
      }
   }

   return this;
}

The big difference between jQuery and MooTools in this case is that jQuery converts the arguments to an array (split using spaces as a delimiter) then loops through the className property to search for each word in the array, whereas MooTools performs a simple full-string replace using regular expressions.

The search for a solution

My first reaction was to build a MooTools-flavored variation of jQuery’s removeClass code.


Element.implement({
   removeClasses: function (classNames) {
      if(this.className){
         var classNameString = this.className;
         classNames.split(/s+/).each(function (term){
            classNameString = classNameString.replace(term, " ");
         });
         this.className = classNameString.clean();
      }
      return this;
   }
});

It follows the same basic principles of the jQuery version, but uses MooTools’ Array.each and String.clean utility functions. It works well, but I wasn’t thrilled about using a loop. I thought maybe a regular expression would be better suited for the job.

I eventually came up with this:
(purposely verbose to explain what’s happening)


Element.implement({
   removeClasses: function (classNames) {
      if(classNames && this.className){
         //Replace all spaces in classNames with vertical beams
         var terms = classNames.replace(/s+/g, "|");
         //Create a regular expression using terms variable
         var reg = new RegExp('\b(' + terms + ')\b', 'g');
         //Use the new regular expression to replace all specified terms with a space
         var newClass = this.className.replace(reg, " ");
         //Use MooTools' 'clean' method to remove extraneous spaces
         newClass = newClass.clean();
         //Set element's classname to new cleaned list of classes
         this.className = newClass;
      }
      return this;
   }
});
Notes:

  • I’m no expert on JavaScript speed tests, so for all I know a loop might be quicker. However, a regular expression feels more elegant.
  • I named my utility removesClasses so it doesn’t overwrite MooTools’ built-in utility.

Final MooTools version

Here’s a more concise version:


Element.implement({
   removeClasses: function (classNames) {
      this.className = this.className.replace(new RegExp("\b(" + classNames.replace(/s+/g, "|") + ")\b", "g"), " ").clean();
      return this;
   }
});

You can see it in action via the MooShell (apparently the MooTools Shell is no longer online)

Standalone (framework-neutral) version

For those of you who don’t use MooTools, a few small edits will allow you to use this code without relying on any outside JavaScript libraries. First, a verbose version explaining what’s happening:


function removeClass(el, classNames) {
   //Only run if the element is available and supports the className property
   if(el && el.className && classNames){
         //Replace all spaces in classNames with vertical beams
         var terms = classNames.replace(/s+/g, "|");
         //Create a regular expression using terms variable
         var reg = new RegExp('\b(' + terms + ')\b', 'g');
         //Use the new regular expression to replace all specified terms with a space
         var newClass = el.className.replace(reg, " ");
         //Use regular expression to remove extraneous whitespace between class names
         newClass = newClass.replace(/s+/g, " ");
         //Use regular expression to remove all whitespace at front and end of string
         newClass = newClass.replace(/^s+|s+$/g, "");
         //Set element's classname to new cleaned list of classes
         el.className = newClass;
   }
}

The concise version:


function removeClass(el, classNames) {
   if(el && el.className && classNames){
      el.className = el.className.replace(new RegExp("\b(" + classNames.replace(/s+/g, "|") + ")\b", "g"), " ").replace(/s+/g, " ").replace(/^s+|s+$/g, "");
   }
}

Used as follows:


var myelement = document.getElementById("myelement");
removeClass(myelement, "one three");
//<div id="myelement" class="one two three"></div>
//becomes
//<div id="myelement" class="two"></div>

Enjoy!

Successfully tested in Internet Explorer 6 (WinXP), Firefox 3.5 (WinXP), Firefox 3.6 (OS X 10.6.2), Safari 4 (OS X 10.6.2), Opera 10.1 (OS X 10.6.2), Chrome 5 (OS X 10.6.2)

CustomInput Class: Accessible, Custom-Styled Checkboxes and Radio Buttons

I’m a big fan of the Filament Group’s UI work.  They put a lot of thought into their work, and ensure everything they make is not only beautiful, but as accessible and as semantic as possible.

One of my favorite pieces of work by the Filament Group is their approach to stylized checkbox and radio <input> elements, as described in their post Accessible, Custom Designed Checkbox and Radio Button Inputs Styled with CSS (and a dash of jQuery)

The system is remarkably well-constructed, degrades gracefully, and is completely accessible if JavaScript, image loading, or CSS is disabled can’t be loaded. No small feat. Plus it remains semantically sound, with no bloated markup.

Having said all that, I’ve never actually used any of Filment Group’s UI code in my projects because of one little obstacle: they rely on jQuery.  Mind you, I’m not a jQuery hater, but I prefer MooTools and build all my major projects using MooTools. Switching to jQuery for such a small UI customization is not going to happen. I’ve often thought it would be great to build a MooTools version of Filament’s UI examples, but never had the time… until today.

I’m currently working on a new quiz system at work, and decided I’d incorporate Filament’s wonderful stylized checkboxes and radio buttons into my project, which meant it was time to roll up my sleeves and code me some Moo. 

View Demo

I made a couple of changes to the underlying JavaScript to suit a MooTools approach (and add some flexibility with the CSS selectors) but the basic premise is the same as Filament’s demo.

The following JavaScript will style ALL checkbox and radio inputs on your page. Note that CustomInput returns the collection of elements that have been styled.


window.addEvent("domready", function(){ 
    var all_styled_inputs = new CustomInput();
});

If you want to target specific parts of the page or only certain input types, pass a CSS selector as an argument:


window.addEvent("domready", function(){
    //Only style checkboxes
    var styled_checkboxes = new CustomInput("input[type='checkbox']");

    //Only style radios in a div named "walkman"
    var styled_radios = new CustomInput("#walkman input[type='radio']");
});

The CustomInput class has been fully JSLinted, and remains very close to the original jQuery version’s size. When compressed (YUI), it squeezes down to about 1.6kb. It uses ‘dollar-safe’ mode for compatibility with other JS libraries.

It has been successfully tested in the following systems:

Windows XP

  • Internet Explorer 6
  • Firefox 3.5
  • Safari 4
  • Chrome 4
  • Opera 10

Mac OS X (10.6.2)

  • Firefox 3.6
  • Safari 4
  • Opera 10.1
  • Chrome 5 (beta)

View Demo | Download Project Files

Major kudos to Filament Group for sharing their ideas with the world.

Update 3/12/2010: Fixed a typo in the JS file preventing the checkedHover class from being assigned.

Dealing with Internet Explorer in your JavaScript Code

It’s almost the end of 2008, and thanks to the hard work of web standardistas, browser vendors, and JavaScript framework developers, cross-browser JavaScript code is much less of an issue than it used to be. Even Microsoft is feeling the love — the upcoming Internet Explorer 8 will be a (mostly) clean break from legacy Internet Explorer releases and will behave much more like Firefox, Safari (WebKit) and Opera. …And they rejoiced.

So why is it that when I look under the hood of some recently produced web pages (learning management systems, courses produced by e-learning rapid development tools, general web pages, etc.), the pages’ JavaScript often includes incredibly out-of-date and bad-practice Internet Explorer detection? Check out these samples I randomly copied:


_ObjBrowser.prototype.Init = function() {
    var $nBrowserChar    = "";
    var $nBrowserStart   = 0 ;
    if ( this.$strUA.indexOf("MSIE") >= 0 ){
        this.$nBrowser = BROWSER_IE ;
        this.$nBrowserVersion = "";
        $nBrowserStart   = this.$strUA.indexOf("MSIE")+5
        $nBrowserChar    = this.$strUA.charAt($nBrowserStart);
        while ( $nBrowserChar != ";" ){
            if ( ( $nBrowserChar >= '0' && $nBrowserChar <= '9' ) || $nBrowserChar == '.' )
                this.$nBrowserVersion += $nBrowserChar ;
            $nBrowserStart++;
            $nBrowserChar     = this.$strUA.charAt($nBrowserStart);
        };
        this.$nBrowserVersion = parseInt( parseFloat( this.$nBrowserVersion ) * 100 ) ;
    } else if ( this.$strUA.indexOf("Mozilla") >= 0 ){
        this.$nBrowser        = BROWSER_MOZILLA ;
        this.$nBrowserVersion = parseInt ( (this.$strUA.substring( this.$strUA.indexOf("/") + 1,  this.$strUA.indexOf("/") + 5  )) * 100 );
    }
};

or even these simpler yet equally problematic sniffers:

UserAgent detection

if (navigator.appName &&
    navigator.appName.indexOf("Microsoft") != -1 &&
    navigator.userAgent.indexOf("Windows") != -1 &&
    navigator.userAgent.indexOf("Windows 3.1") == -1) {
        //Do something
}

Bad object detection

if (document.all) {
    //Do something for Internet Explorer 4
} else if (document.layers) {
    //Do something for Netscape Navigator 4
} else {
    //Do something for other browsers
}

These examples are BAD BAD BAD. Why? Well, there are a million web articles explaining the topic, but I’ll give you a quick rundown.

You shouldn’t test for specific browsers, but for specific functionality instead

In most cases, browser detection is being used because the developer is making an assumption that a particular browser does or doesn’t have a specific feature. The problem is that browsers change over time, and detecting for a browser based on assumptions can come back to bite you. Even the prickly Internet Explorer gets updates from time to time. Case in point: The versions of IE6 in Windows 2000 and Windows XP have a different JavaScript engine (JScript version) than IE6 running in Windows XP Service Pack 3. This means a general IE6 detection script might not lead you down the path you expected.

If you test for features instead, your code will be more future-compatible and less likely to break.

if(document.getElementById){
   //It is safe to use document.getElementById()
} else {
   //document.getElementbyId() is not supported in this browser
}

When hacks for Internet Explorer are required

Nowadays, most browsers offer roughly the same features and adhere to W3C standards. But, as we know, our friend Internet Explorer is… different. If you must use hacks custom code for Internet Explorer, know your options (and please, for the love of… something… don’t use ActiveX controls. Just don’t.).

Use cleaner IE detection

The old User-Agent sniffing approach has been abused for years. Internet Explorer 7 (Windows XP SP2) uses the following User-Agent to identify itself:

Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)

Yes, you read that correctly: IE declares itself a Mozilla browser. Opera’s User-Agent can be changed on the fly, allowing a user to set it to Opera, IE, or Mozilla! This all renders User-Agent detection useless. So what are your other IE detection options?

Conditional comments. Conditional comments are a proprietary Microsoft mechanism that is often used to include IE-specific CSS files in a page. For our discussion, you could conceivably use conditional comments to include a JavaScript file containing IE hacks in your page. You could also do a quick and dirty hack like this:


<!--[if IE]>
   <script type="text/javascript">
      var isIE = true;
   </script>
<![endif]-->

But, as you can see, it’s a clunky solution and isn’t inline with the other JavaScript, which makes it a less-than-ideal option.

Conditional compilation. This method is THE method of choice right now. Dean Edwards described this gem of an approach on his blog post Sniff. As far as I can tell, it is the cleanest and most reliable way to detect IE.

var isMSIE = /*@cc_on!@*/false;

Simple, isn’t it? No User-Agents, no 20 lines of conditional statements and string searches.

How does this code work? Internet Explorer uses JScript, a proprietary form of JavaScript (ECMAScript) developed by Microsoft. JScript has a feature called conditional compilation, which allows us to add JScript-specific JavaScript inside a specially formatted comment. Other JavaScript compilers will ignore this code, thinking it’s a comment, while JScript will recognize it and process it.

/*@cc_on begins the comment, and @*/ ends the comment. When any non-IE browser sees var isMSIE = /*@cc_on!@*/false;, it actually sees it as var isMSIE = <strong>false</strong>;. Internet Explorer’s JScript will add the exclamation point contained in the comment, meaning IE will see var isMSIE = <strong>!false</strong>;. Remember, an exclamation point reverses a boolean’s value, so !false is equivalent to true.

As Dean pointed out, there is an even simpler way to write this (though it’s slightly less legible):

var isMSIE/*@cc_on=1@*/;

If a variable is not assigned a value when it is declared, by default it will return ‘falsy‘ (null). Thus a non-IE browser will see this line as var isMSIE; while Internet Explorer’s JScript will see it as var isMSIE=1; (assigning a 1 makes the variable ‘truthy‘).

I prefer to shorten it a bit more by removing the MS from isMSIE, making it simply isIE. Note: it’s a best practice to give booleans a name that implies what the boolean stands for, usually in a quasi-question format. Using plain old var IE doesn’t tell us the variable is a boolean, and doesn’t tell us what question is being answered by the boolean’s value. var isIE does.


var isIE/*@cc_on=1@*/;
if(isIE){
   //Do something. I suggest downloading Firefox.  ^_^
}

Avoid forking your code for hacks whenever possible.

If your code can be written in a way that satisfies all browsers, do it that way, even if it veers to the left of official web standards — just be sure to document your decision in the comments. For example, using the standards-friendly setAttribute for specifying class names will fail in IE, while direct assignment will work in all major browsers.

//standards-friendly, but fails in IE
element.setAttribute("class", "myClass");

//direct assignment, works in all major browsers
element.className = "myClass";

In this case, I’m advocating using defacto standards that aren’t codified in the W3C standards, but are supported in every major browser. Some standardistas will balk, but this is a much easier way to maintain code; it’s a bad idea to use unnecessary conditional statements simply to prove that IE doesn’t always follow standards. We know, we don’t like it either, but get over it. If a non-standard but universally supported alternative is available, your extra “see I told you so” code will be pure bloat, increasing file size, bandwidth requirements, and browser processing requirements.

Don’t get me wrong: standards are important. Very important. But they only go so far; if we stuck to codified standards on principle, no one would be using AJAX today! That’s right, xmlhttprequest — the heart of AJAX — was created by Microsoft, then copied by other browsers. It is not an official standard, but it is universally supported, and is used by tens of millions of web site visitors every day.

If you’re not comfortable with non-standard code littered throughout your project, abstract it into a function so you can easily modify it later.

Old way:

element.className = "myClass";

Abstracted function:

function setClass(targetElement, name){
   /*
      not using setAttribute here because setAttribute
      won't work with class names in IE
   */
   targetElement.className = name;
}

setClass("element", "myClass");

Now whenever you need to set a class, you won’t worry about IE and can simply use your class function. If you decide to change how you assign the class name, you can just change it in the function and not have to dig around your code looking for every instance of direct assignment (className =).

Note: Many JavaScript frameworks, such as jQuery and MooTools, provide this type of support function for you. These frameworks protect you from needing to know about browser inconsistencies, and free you to focus on your application. If the above example were rewritten to use MooTools, it would simply be

element.addClass("myClass");

There are many ways to approach the problem, and all without using nasty browser detection scripts. It pays to know your options.

Use abstractions for forking code

Getting back to IE detection, if you aren’t using a framework and need to handle browser inconsistencies on your own, it’s a good idea to use support functions. These can encapsulate the browser inconsistencies and isolate them from the rest of your code. This makes your code more maintainable and readable.

Case in point: Internet Explorer will not allow you to dynamically assign a name value to an <input> element. This requires an IE-specific hack.

You would normally use the following W3C-compliant code:

var input = document.createElement("input");
input.setAttribute("name", "myInput");

In Internet Explorer, we’re forced to do this:

var input = document.createElement("<input name='myInput'>");

In your project, you’d have to use conditional statements each time you need to create an input element.

var isIE/*@cc_on=1@*/;

if(isIE){
   var input1 = document.createElement("<input name='myInput1'>");
} else {
   var input1 = document.createElement("input");
   input.setAttribute("name", "myInput1");
}
input1.setAttribute("id", "myInput1");

//Later on in your code...
if(isIE){
   var input2 = document.createElement("<input name='myInput2'>");
} else {
   var input2 = document.createElement("input");
   input.setAttribute("name", "myInput2");
}
input2.setAttribute("id", "myInput2");

What a pain. The best way to deal with this is to wrap the code in a function like so

function createInput(parentElement, id){

   //Make sure everything is supported. Error-checking is divine.
   if(!parentElement || !id || !document.createElement || !parentElement.createElement){ return false; }

   var isIE/*@cc_on=1@*/;
   var input;

   if(isIE) {
      input = parentElement.createElement("<input name='" +id +"'>");
   } else {
      input = parentElement.createElement("input");
      input.setAttribute("name", id);
   }

   input.setAttribute("id", id);
   return input;

}

As you can see, the code is now easily reusable throughout our project, contains better support for IE detection, and also contains feature detection ensuring our function won’t throw any errors. To use the code in the project, you’d simply write:

var input1 = createInput(document, "myInput1");
var input2 = createInput(document, "myInput2");

In closing

I hope you’ve found this post helpful, and I especially hopes it helps spread the word about using best practices and cleaner code techniques. I’ll be the first to say I’m no programming expert, so feel free to add a comment if you have suggestions for improvements. 🙂

PS: If you have to write a conditional statement, follow Crockford’s advice and wrap it in curly braces.