Programmatically clicking a link in Javascript

More accurately, this would be called actuating a link (i.e. the process of setting the link's behavior into motion, if you will.) In other words, you have a link:
<a id = 'bingLink' href = "http://www.bing.com">Bing</a>
And you want to programmatically make that link do its thing. This usually means having the browser navigate to the URL specified link's href attribute (though, it can also mean executing some click handler if one happens to be specified on said link.) To be honest, I don't think there are a ton of real-world uses for this, but there are a few. And depending on your needs, it may not be trivial to accomplish this in a cross-browser fashion. For example:
var myLink = document.getElementById('myLink');
myLink.click();
The above will do the trick in IE, because IE supports .click() on links. Most other browsers don't though, so that's not an all-encompassing solution. One (partial) solution in non-IE browsers might be to read the href attribute from a given link and then use Javascript to make the browser navigate to to the URL specified by href that way:
function actuateLink(link)
{
     window.location.href = link.href;
}
This is not a complete solution for a couple of reasons:
  1. Navigating by setting window.location.href blows away the document.referrer property.
  2. Any click event handler wired to the link will not execute.
For anyone who has read my post about Re-routing events in Javascript post, you'll know that it is possible to simulate an event though the use of .dispatchEvent(..). Here's a brief refresher of the part most relevant to this discussion:
var myEvt = document.createEvent('MouseEvents');
myEvt.initEvent(
   'click'      // event type
   ,true      // can bubble?
   ,true      // cancelable?
);
document.getElementById('myLink').dispatchEvent(myEvt);
This approach will ensure that any click event handlers will fire as desired (though, it's worth nothing that you'd have to handle mousedown, mouseup, keydown, keypress etc... separately,) but it does NOT actuate the link! This might surprise you. Dispatching a click event to a link doesn't actually cause the browser to navigate to the URL specified by that link's href property. This actually isn't shocking to me, personally. A little surprising, but not shocking. Anyway, one option is to dispatch the click event and then use window.location.href. This should achieve (some of) the desired effect, but again, the document.referrer will not be preserved if you do this. Also, if the event handler prevents the default action, this method will not honor the code that prevents the default action (i.e. the link will navigate despite the code that attempts to prevent it from doing so.) You can work around the window.location.href problem by creating a <form> element, settings its action attribute to that of the link's href attribute and then submitting the form through myForm.submit(), but the problem regarding preventing the default action remains.

A 95% Solution

Is there a 100% perfect solution to this problem? Probably, but I'm not 100% perfect. I'm only 95% perfect, so I give you my 95% solution:
function actuateLink(link)
{
   var allowDefaultAction = true;
      
   if (link.click)
   {
      link.click();
      return;
   }
   else if (document.createEvent)
   {
      var e = document.createEvent('MouseEvents');
      e.initEvent(
         'click'     // event type
         ,true      // can bubble?
         ,true      // cancelable?
      );
      allowDefaultAction = link.dispatchEvent(e);           
   }
         
   if (allowDefaultAction)       
   {
      var f = document.createElement('form');
      f.action = link.href;
      document.body.appendChild(f);
      f.submit();
   }
}
To use it:
actuateLink(document.getElementById('bingLink'));
This code is self explanatory, right? If .click() is available, let's use that. If not, let's try to do a W3C-style create and dispatch event. Then, in order to maintain the document.referrer property, we'll use a form submission, but we only want to submit that form if the event handler (if there is one) for the click event doesn't explicitly prevent the default action. This isn't a 100% perfect solution because, actuating a link in this manner is only going to invoke click handlers and navigate to the URL of the link's href attribute (if appropriate to do so.) It isn't going to invoke handlers for any other types of events. And I'm not about to create and dispatch every possible kind of event. Besides, I think we can all agree that click is by far the most common scenario. So there ya go. Programmatically clicking (or more accurately, actuating) a link with Javascript. Enjoy! (Tested: Firefox 3.5.7, IE8, Opera 10.01, Safari/Win 4.0.3, Chrome 3)

Fetch (a stand-alone CSS querying engine)

I've started work on a new side-project called Fetch. It's a stand-alone CSS querying engine, designed to be super lightweight and offer high performance across all modern browsers. It's still very much in development and the source is a bit sloppy, but it currently weighs in at only 2.5k compressed and gzipped and (in Firefox3) either outperforms or is on par with all other major Javascript libraries out there*. It's definitely not ready for production use, but when it is, it will become the new CSS querying engine for Gimme and will also be available as a stand-alone library, which will be extensible (so you can add your own rules) and which will also be guaranteed to play nicely with other libraries by not adding to the prototype of any native objects and only introducing one single global variable (fetch). For anyone interested, the current (i.e. sloppy) source is available on CodePlex: fetch.js. As I said, it's not ready for prime time and the code is sloppy (and probably buggy). Consider yourself warned. * Fetch was not compared with libraries that use XPath or querySelectorAll, because it uses neither, but rather is a pure Javascript implementation. Upon release, it will take advantage of querySelectorAll where appropriate, but will not use XPath.

The Ultimate addEvent(..) function

Two articles in particular that I've written on this blog have garnered a lot of positive feedback.

  1. Fixing IE's attachEvent Failures
  2. mouseenter and mouseleave Events for Firefox (and other Non-IE Browsers)

As time has gone on, I've seen mention of these articles pop up in programming forums here and there, and they still receive comments from time to time, which lets me know that people are still getting something out of them.

Unfortunately, the code in them is a tad dated. I've learned more and improved my code since I originally wrote them, and while these improvements have made their way into my Javascript Library, I know that the majority of people are just grabbing the code directly from the blog entries.

So I've decided to post an update: a sort of fusion between these two blog entries which offers functionality for addressing all of the issues discussed in the aforementioned entries (and then some).

I consider this new function the "Ultimate addEvent Function" because I really believe it addresses the large majority of cross-browser issues that people face when dealing with events in Javascript. And furthermore, this function goes beyond anything that any other Javascript library provides (at least as far as I know).

So without further ado, I offer to you:

sstchur's Ultimate addEvent(..) function!

var xb = {};
(function()
{
   var GUIDCounter = 0;
   var evtHash = {};
   var pseudoEvents =
   {
      'mouseenter': function(_fn, _useCapture, _listening)
      {
         var f = mouseEnter(_fn);
         _listening ?
            xb.addEvent(this, 'mouseover', f, _useCapture, false) :
            xb.removeEvent(this, 'mouseover', f, _useCapture, false);
         f = null;      
      },
      
      'mouseleave': function(_fn, _useCapture, _listening)
      {
         var f = mouseEnter(_fn);
         _listening ?
            xb.addEvent(this, 'mouseout', f, _useCapture, false) :
            xb.removeEvent(this, 'mouseout', f, _useCapture, false);
         f = null;      
      }
   };
   
   xb.Helper =
   {
      getObjectGUID: getObjectGUID,
      storeHandler: storeHandler,
      retrieveHandler: retrieveHandler,
      isAnAncestorOf: isAnAncestorOf,
      mouseEnter: mouseEnter
   }
   
   xb.addEvent = function()
   {
      if (typeof document.addEventListener !== 'undefined')
      {
         return w3c_addEvent;
      }
      else if (typeof document.attachEvent !== 'undefined')
      {
         return ie_addEvent;
      }
      else
      {
         // no modern event support I guess 🙁
         // (you could use DOM 0 here if you really wanted to)
         return function() {};
      }
      
      function w3c_addEvent(_elem, _evtName, _fn, _useCapture, _directCall)
      {
         var eventFn = pseudoEvents[_evtName];
                  
         if (typeof eventFn === 'function' && _directCall !== false)
         {
            eventFn.call(_elem, _fn, _useCapture, true);
         }
         else
         {
            _elem.addEventListener(_evtName, _fn, _useCapture);
         }
      }
      
      function ie_addEvent(_elem, _evtName, _fn, _useCapture, _directCall)
      {
         var eventFn = pseudoEvents[_evtName];
               
         if (typeof eventFn === 'function' && _directCall !== false)
         {
            eventFn.call(_elem, _fn, _useCapture, true);
         }
         else
         {
            // create a key to identify this element/event/function combination
            var key = generateHandlerKey(_elem, _evtName, _fn);
            
            // if this element/event/combo has already been wired up, just return
            var f = evtHash[key];
            if (typeof f !== 'undefined')
            {
               return;
            }
            
            // create a helper function to fix IE's lack of standards support
            f = function(e)
            {
               // map .target to .srcElement
               e.target = e.srcElement;
               
               // map .relatedTarget to either .toElement or .fromElement
               if (_evtName == 'mouseover') { e.relatedTarget = e.fromElement; }
               else if (_evtName == 'mouseout') { e.relatedTarget = e.toElement; }
                  
               e.preventDefault = function() { e.returnValue = false; };
               e.stopPropagation = function() { e.cancelBubble = true; };

               // call the actual function, using entity (the element) as the 'this' object
               _fn.call(_elem, e);
               
               // null out these properties to prevent memory leaks
               e.target = null;
               e.relatedTarget = null;
               e.preventDefault = null;
               e.stopPropagation = null;
               e = null;
            };
            
            // add the helper function to the event hash
            evtHash[key] = f;

            // hook up the event (IE style)
            _elem.attachEvent('on' + _evtName, f);
            
            key = null;
            f = null;
         }
      }
   }();

   xb.defineEvent = function(_evtName, _logicFn)
   {
      pseudoEvents[_evtName] = _logicFn;
   };

   
   // Helper Functions
   function storeHandler(_key, _handler)
   {
      evtHash[_key] = _handler;
   }
   
   function retrieveHandler(_key)
   {
      return evtHash[_key];
   }
   
   function generateHandlerKey(_elem, _evtName, _handler)
   {
      return '{' + getObjectGUID(_elem) + '/' + _evtName + '/' + getObjectGUID(_handler) + '}'
   }
   
   function isAnAncestorOf(_ancestor, _descendant, _generation)
   {
      if (_ancestor === _descendant) { return false; }
      
      var gen = 0;
      while (_descendant && _descendant != _ancestor)
      {
         gen++;
         _descendant = _descendant.parentNode;
      }
      
      _generation = _generation || gen;
      return _descendant === _ancestor && _generation === gen;    
   }
   
   function mouseEnter(_fn)
   { 
      var key = xb.Helper.getObjectGUID(_fn);
      var f = evtHash[key];
      if (typeof f === 'undefined')
      {
         f = evtHash[key] = function(_evt)
         {
            var relTarget = _evt.relatedTarget;
            if (this === relTarget || isAnAncestorOf(this, relTarget)) { return; }
      
            _fn.call(this, _evt);
         };
      }
      return f;   
   }
   
   function getObjectGUID(_elem)
   {
      if (_elem === window)
      {
         return 'theWindow';
      }
      else if (_elem === document)
      {
         return 'theDocument';
      }
      else if (typeof _elem.uniqueID !== 'undefined')
      {
         return _elem.uniqueID;
      }

      var ex = '__$$GUID$$__';
      if (typeof _elem[ex] === 'undefined')
      {
         _elem[ex] = ex + GUIDCounter++;
      }
      return _elem[ex];
   }

})();
 
The code above, as is, won't work "out of the box" because (for the sake of brevity) I have not included the xb.removeEvent(..) function. Fear not however. There will be a link to a download of the full source code, complete with both xb.addEvent(..) and xb.removeEvent(..) at the end of this post.

Commentary

So some of you may be wondering: "Why another addEvent function. Haven't we been through this?" We have, but I think you'll find that this version does more than any other addEvent function you've seen. For example:

  • Works in all browsers that matter
  • Ensures one event wire up for any given element/event/handler combination (this is mostly for IE)
  • Forces IE to honor the this keyword from within event handler functions
  • Normalizes the wire up mechanism in all browsers (no need to include the "on" prefix for IE)
  • Forces IE to recognize the following properties/methods on event objects: .stopPropagation(), .preventDefault(), .target, .relatedTarget
  • Enhances Non-IE browsers to support mouseenter and mouseleave events
  • Provides an extension mechanism so developers can write their own custom events that plug right in ('mousewheel' or 'DOMContentReady' for example)

Given all that the Ultimate addEvent function does, it's not surprising that it has a bit of length to it. But it isn't super long, and I think it's well worth it.

Usage

Using the function(s) is just as easy as you'd expect:

var myDiv = document.getElementById('myDiv');
xb.addEvent(myDiv, 'click', clickHandler);

function clickHandler(e)




{
   alert('The this keywords works (even in IE!): ' + this.id);
}

Extending Functionality

Since I mentioned that it was possible to extend the Ultimate addEvent function with custom events that plug right in, I figure I'd better offer an example. Actually, there already is an example built right into it. Both the mouseenter and mouseleave events utilize the extension mechanism internally, but for demonstration purposes, let's go ahead and add a mousewheel event:

// Extend the Ultimate addEvent function to recognize a "mousewheel" event
xb.defineEvent('mousewheel', function(_fn, _useCapture, _listening)
{
   // event name for IE, Opera and Safari
   var evtName = 'mousewheel';

   // hander for IE, Opera, and Safari
   var f = _fn;
   
   // if we're dealing with a Gecko browser, the event name
   // and handler need some adjustment
   var ua = navigator.userAgent.toLowerCase();
   if (ua.indexOf('khtml') === -1 && ua.indexOf('gecko') !== -1)
   {
      evtName = 'DOMMouseScroll';
      f = mouseWheel(_fn);
   }

   // _listening represents whether this is an event attachment or detachment
   // that 5th parameter?  Don't worry about it; just always use false
   _listening ? xb.addEvent(this, evtName, f, _useCapture, false) : xb.removeEvent(this, evtName, f, _useCapture, false);
});

// Helper function for dealing with mousewheel in Gecko browsers
function mouseWheel(_fn)
{
   var key = xb.Helper.getObjectGUID(_fn);
   var f = xb.Helper.retrieveHandler(key);
   if (typeof f === 'undefined')
   {
      f = function(_evt)
      {
         _evt.wheelDelta = -(_evt.detail);
         _fn.call(this, _evt);
         _evt.wheelDelta = null;
      };
      xb.Helper.storeHandler(key, f);
   }
   return f;
}

I'm glossing over some details here because I don't want this post to get bogged down in something that it isn't really about, but I trust you get the idea.

Most of this code actually comes from the The Gimme Javascript Library. If you're using Gimme in any of your web pages, you already have all of this functionality for free. The syntax is a touch different, but all the capabilities are the same.

And if you happen to be the author of a Live Maps Mashup, you already have Gimme, as the latest version shipped with the most recent Virtual Earth MapControl.

Complete Source

As promised, here is the complete source, including both xb.addEvent(..) and xb.removeEvent(..)

Enjoy! And please don't hesitate to send your feedback, both good and bad (I'm very willing to address issues or try to make requested enhancements).

Palindromes

The other evening while I was watching TV, I happened to have my laptop out, so I started dabbling around in Firebug just for the heck of it. I'll often just start writing functions for no particular reason, other than to refresh my skills or perhaps discover something about Javascript that I hadn't previously known.

So I was monkeying around, and I decided to write a function to determine if a string were a palindrome. This is not a very difficult problem, but it might make for a descent introductory interview question (one of those questions you'd ask to rule out the truly inept).

With problems like this, I'm mostly interested in two things:

  1. Writing the function in a clever way, so as to achieve the least amount of code possible
  2. Writing the function in the most efficient way possible

Now, if it were Ruby, writing the clever solution (which might also happen to be the most efficient solution in that language -- not sure) would be ridiculously easy:

def pal(s)
   s == s.reverse
end

That's so trivial, it's probably not even worth writing.

But Javascript doesn't have a .reverse() method on strings, so you have to take an extra step (still pretty easy though):

function pal(s)
{
   return s === s.split('').reverse().join('');
}

Split the string into an array, reverse the array, and then join it back together. Since this is all native Javascript stuff, it turns out to be reasonably fast, though one might expect you could do better.

For grins and giggles, I decided to see how a manual solution, where I compare characters starting at each end of the string and work towards the middle, would compare:

function pal(s)
{
   var i, j,
   len = s.length,
   mid = Math.floor(len / 2);

   for (i = 0, j = len - 1; i < mid; i++, j--)
   {
      if (s.charAt(i) !== s.charAt(j)) return false;
   }
   return true;
}

This function will probably fail quickly, so that's good, but it also turns out to be generally faster than the split/reverse/join version. It makes sense, since you're going through the string only one time and twice as fast as normal since you're traversing from both ends at the same time.

Can you do even better though? Maybe. At least in Firefox you can, and quick tests seemed to confirm across other browsers too (but I didn't test extensively).

What if we split the string in half, and only reverse half of it. Then we can compare the first half with the original half:

function pal(s)
{
   var len = s.length,
   x = len / 2,
   y = x === Math.floor(x) ? x : (x = Math.floor(x)) + 1;

   return s.substr(0, x).split('').reverse().join('') === s.substr(y,len);
}

The idea here is just split the string in half, reverse one of the two pieces and compare them. For example:

TOOT: splits into "TO" and "OT." Reverse one of them, say the second part ("OT") and compare.

If you've got a odd number of characters, you can ignore the middle most character:

RACECAR: splits into "RAC" and "CAR" (ignore the "E" in the middle). Reverse the second part ("CAR") and compare.

As it turned out, this seemed to be faster than the other two methods. Not by orders of magnitude, but not insignificantly either. Here are the number from Firefox, running each function 10,000 time on the string "gohangasalamiimalasagnahog" (go hang a salami, i'm a lasagna hog).

  • (Traverse from both ends): 797ms
  • (Split/Reverse/Join): 969ms
  • (Compare halves): 640ms

Of course, numbers varied slightly during each run, but overall the they were consistent relative to each other.

So there ya go. Nothing in particular I wanted to point out here; just found it interesting and thought you might too.

Think my code sucks? Got a better solution? Say so in the comments (you won't hurt my feelings).