Duplicate IDs are baaaaaaaaaaaaaaad

If you are not already very aware, duplicate IDs in your markup are a very bad thing. Now, technically they will work fine in CSS, and can possibly even make it faster, BUT they WILL break your javascript. Basically when you have multiple elements with the same ID what the browser will do is unpredictable.

Sometimes in some browsers you might get what you want, other times in other browsers the wrong thing will happen, sometimes nothing will happen. You never know. On many different occasions I have been helping someone with some javascript, or fixing a website for a job and things just won’t work, and eventually I find the problem to be duplicate IDs. Dealing with this problem again today, I wrote a little jQuery plugin that checks your page for duplicate IDs. It is pretty simple, and you should never actually have this on a production site, it is just something you run while you are developing so you can yell at the jackass who wrote crappy markup to make sure you aren’t doing things you shouldn’t. The full code for the plugin is this:


(function($) {
$.dupCheck = function() {
var ids = [], dups = [], dupCount = 0;

//this just displays the result if there is no console
if (!window.console) {
var console = {
log: function(msg) {
$('body').append(msg);
}
};
} else {
console = window.console;
}

$('*').each(function() {
var id = this.id, i = 0, len = ids.length, isDup = false;

if(id) {
for(i=0 ; i < len ; i++) { //loop over the IDs we have already processed to see if the current one is a dup if(!isDup && id === ids[i]) { //if the ID is a dup, add it to the dups list and set our bool if(checkIfAlreadyDup(id) === false) { dups.push(id); } isDup = true; dupCount++; break; } } if(!isDup) { //if the ID was not a dup, add it to the list of processed IDs ids.push(id); } else { //if the ID was not a dup, clear out our bool isDup = false; } } }); if(dups.length === 0) { console.log('No duplicate IDs found =)'); } else { console.log('' + dups.length + ' duplicate IDs found repeated ' + dupCount + ' times =('); } function checkIfAlreadyDup(id) { var len = dups.length, i = 0; for(i = 0; i < len; i++) { if(dups[i] === id) { return true; } } return false; } return dups; } })(jQuery); 

The way to use this is to load the plugin (or even just paste the code into your javascript console) then run the function:

 var dups = $.dupCheck(); 

You obviously need to do this after the document is ready, and after any offending javascript/ajax calls have completed. It will output a message telling you how may duplicate IDs you have, and it returns an array of the IDs that are duplicates. It is pretty simple, but if you are banging you head against the wall trying to figure out why your javascript is not working, just run it, and it might give you the answer. Download: Dupcheck.js

And I have now run this on this page, apparently I have a duplicate ID! Maybe I need to just get rid of that facebook ‘like’ button.

Updates to my pub/sub

I have updated my pub/sub plugin.  The two changes are:

It now uses the full words, publish, subscribe, and unsubscribe – this change was because with 1.5, jQuery now has a sub method which I do not want to overwrite.

Also, you can now publish and subscribe multiple tags at once using a space separated list like so:



$.publish('first/tag second/tag');

$.subscribe('first/tag second/tag', function(){/*do something*/});

This just make it more like the rest of jQuery events and such.

The new version can be downloaded here:

Live DOM sub/pub

Got to hang out at Google for dayofjs

I got to go to the dayofjs conference (thanks MJG!) Not only was the conference really interesting, but I also got to hang out at Google which was cool. So, I snapped a few pics, as one does.

This is where I found out that I parked about as far as I could have from where I wanted to be.

Google lunch.

Can’t really complain about the day

Ahh, the free beverage fridge

This doesn’t have anything to do with Google or Dayofjs, but I got In-and-Out on my way home after being stuck in traffic for 2 hours, because it has been a long time since I have had it.


The mobile talks of the day were about:

Sencha Touch: http://www.sencha.com/products/touch/

Appcelerator’s Titanium: http://www.appcelerator.com/

jQuery Mobile: http://jquerymobile.com/

There was also a fair amount of talk about PhoneGap: http://www.phonegap.com/

Yehuda Katz also gave a talk about Sproutcore that was really good.  It was not really a mobile talk, but it was still very informative and interesting.

Unit testing jQuery w/ QUnit

So, like most developers I love having unit test, but I hate writing them. Really figuring out the best way to write them and have them work well, is a pain in the ass, and that is what I hate. I have spent some time recently playing with QUnit and despite the number fact that I have found a number of posts online about it, none of them have provided (in my mind) a good way to test a site. The ideas used work well for testing a library, or specific functions, but I don’t write libraries or one off functions, I develop websites. So, I have come up with a method that I like that allows me to test my sites in a way that I like.

The Problems:

The unit testing information I have found mainly deals with testing libraries – this is no good for me.
Testing involves putting QUnit on your page, along with its specific markup – although you can have a “debug” mode for this, it just seems like a bad idea, for a number of reasons, including it not really being an accurate representation of your page, and it does not work across page requests.
Using headless testing – it is cool, but not cross browser or even really O/S ready.
Using something like Selenium – also cool, but involves building tests using a new tool that uses java.

My solution:

I have come up with my own solution, which, I admit is not perfect, but it does a darn good job of testing what I need tested. Basically it involves having a very simply html page next to your site, that loads your site in an iframe and runs the tests on it. It is cross-browser and cross-O/S safe, it works for pretty much any site, and uses the same javascript/jQuery I already write every day.

There are a couple of problems still with my solution, but they are pretty easy to overcome, and writing tests becomes very simple. The basic page we need to get started with the testing is this:



 



 

QUnit Tests

    test markup, will be hidden
     

    Note: We can (and probably should) actually move our tests into different .js files making it much easier to deal with as our number of tests grow.

    We are doing a couple of interesting things here, first we are testing multiple pages at one time. Every time a new page loads up, we grab the jQuery refrence from inside of it, then run the tests that are specified for that page.

    Another cool thing is how we get the jQuery reference for each page.

    We start by loading jQuery using noConflict(true)

    This is because we need to grab the jQuery instance from inside the actual website. When jQuery attaches events to DOM elements, it actually saves those events inside of itself, so although we can trigger events inside of the page itself using our external jQuery, triggering events inside of the iframe will not actually fire any events that were created inside the page. So, instead we grab the jQuery that is actually on the page and trigger the events using it. This has the added bonus that because it is the reference from inside the page we are loading, we can reference DOM nodes as though we were working that that page directly.

    Here we grab the jQuery reference from inside the iframe with:

    
    $ = window.frames[0].jQuery;
    

    Note: For ANY of this to work we must adhere to SOP, this is why we load this up on a server, side by side with our site. This is why I am loading up the iframe using location.protocol + ‘//’ + location.hostname rather than typing out the URL. This will allow it to work as we move between servers and such.

    One of the cool things we are doing here is tying the starting of the tests to a button, so we can get the page to any state necessary before starting the tests – such as logging in, waiting for ajax requests to finish, or navigating to somewhere specific (not that we couldn’t do these in the tests, it just gives us options.)
    Also, before we run our tests we are running the 2 following commands:

    
    $.ajax({async: false});
    $.fx.off = true;
    

    This will make our ajax requests and animations synchronous. Although you would never do this (especially the ajax one) on a site, for our tests it is necessary to ensure that the page is ready before we perform each test.

    Now that we have done this we can run our test just like we were on the page directly.

    We can select elements and trigger events on them, like so:

    
    $(elem).click(); or $(elem).trigger(‘click’);
    $(elem).mousedown(); or $(elem).trigger(‘mousedown);
    Test things, such as visibility:
    $(elem).is(‘:visible’);
    

    Basically anything you would normally do. Simple and easy.

    This is not a perfect way to test your events, it is however pretty good, and will let us know when we break things.

    Here are some examples of tests in action. I hope this helps you get started with unit testing.

    Tests in action!

    Playing with CSS hooks

    So, I wanted to give the new CSS hooks in jQuery a try. Unfortunately, the usually well documented jQuery has this as the total documentation on the hooks:

    As of version 1.4.3, jQuery’s CSS module was rewritten to provide a set of “hooks” for the .css() method. This advanced feature, $.cssHooks, allows for fine-grained control over the way jQuery handles certain style properties and enables the creation of custom, browser-normalized properties. The $.cssHooks object also extends the set of properties available to the .animate() method.

    Which isn’t much to go on. So, I broke out the old source code to see whats up. Basically, you can hook into a CSS property and create setters and getters for it. I started by making a simple hook that allows you to use show and hide on the display property, and have it call show() and hide(). The code is pretty simple:

    
    jQuery.cssHooks.display = {
    set: function(elem, value) {
    if (typeof (value) === 'string') {
    if (value === 'hide') {
    $(elem).hide();
    } else if (value === 'show') {
    $(elem).show();
    } else {
    elem.style['display'] = value;
    }
    } else {
    if (value.hide) {
    $(elem).hide(value.hide);
    } else if (value.show) {
    $(elem).show(value.show);
    } else {
    elem.style['display'] = value;
    }
    }
    }
    };
    

    This simply checks if you are passing in a string or an object, and then calls show/hide with the supplied value (in the case of an object.) If you are not using show/hide it runs node.style[‘display’] = value; which is what normally happens when you call .css.

    I made an example for it here:

    I’m a thing!




    The code here is pretty simple too:

    
    $('#hookButton').click(function(e) {
    var $this = $(this);
    if($this.text() === 'Hide') {
    $('#cssHookTest').css('display', 'hide');
    $this.text('Show');
    } else {
    $('#cssHookTest').css('display', 'show');
    $this.text('Hide');
    }
    });
    
    $('#hookButton2').click(function(e) {
    var $this = $(this);
    if($this.text() === 'Hide Slow') {
    $('#cssHookTest').css('display', {'hide': 'slow'});
    $this.text('Show Slow');
    } else {
    $('#cssHookTest').css('display', {'show': 'slow'});
    $this.text('Hide Slow');
    }
    });
    
    $('#hookButton3').click(function(e) {
    var $this = $(this);
    if($this.text() === 'Hide REALLY Slow') {
    $('#cssHookTest').css('display', {'hide': 1400});
    $this.text('Show REALLY Slow');
    } else {
    $('#cssHookTest').css('display', {'show': 1400});
    $this.text('Hide REALLY Slow');
    }
    });
    
    $('#hookButton4').click(function(e) {
    var $this = $(this);
    if($this.text() === 'Hide Normal') {
    $('#cssHookTest').css('display', 'none');
    $this.text('Show Slow');
    } else {
    $('#cssHookTest').css('display', 'block');
    $this.text('Hide Normal');
    }
    });
    
    

    So, that’s about it, the CSS hooks, although called an advanced feature are really not that hard to use. So, give ’em a try.

    jQuery Live DOM sub/pub plugin

    Updated 2/10/10 – for the updates to the plugin (See: here)

    I made a new jQuery plugin the other day, it is pretty simple, it expands on the idea subscribe/publish idea. Basically it allows you to bind subscriptions to DOM nodes.

    You use it like this:

    
    
    //subscribe a node
    $(selector).subscribe(
    'subscription/name',
    function(subName, [arg1], [arg2],...)
    {/*do something*/}
    );
    
    //publish
    $.publish(
    'subscription/name', [arg1], [arg2],...
    );
    

    The idea is that you are probably using sub/pub to keep your DOM interaction separate from your non DOM related javascript. So, when you publish, there is a good chance that the subscriber will take the published information and do something to the DOM based on it. Inside of you sub callback, this is the DOM node following jQuerys normal pattern for this.

    You can still setup normal subscriptions like so:

    
    $.subscribe(
    'subscription/name',
    function(subName, [arg1], [arg2],...)
    {/*do something*/}
    );
    

    One thing to note is that nothing is actually bound to the nodes. In reality the selector that you give is simply saved, and used again when you publish, so this works like .live in that it will even work on nodes added to the DOM after you subscribe. Also like .live though, you must provide the full selector as a string, and you can’t use any of the traversal methods to select your nodes.

    Demo:

    You must enter something here
    Watch me.

    The code for this is :

    
    $('#formId').submit(function(e){
    validateForm();
    return false;
    });
    
    $('#response').subscribe('formValidateDone', function(subName, result) {
    if(result) {
    $(this).html('The form is valid!');
    } else {
    $(this).html('Please try again!');
    }
    });
    
    function validateForm() {
    $.publish('formValidateDone', $('#textInput').val() !== '');
    }

    Plugin Link: Plugin page

    Download: Download