Implementing Squirrel Mail Autocomplete Plugin under Mozilla Firefox

Posted by & filed under , .

For the heavy SquirrelMail users, you are without a doubt using your address book a lot! (Unless you have a really good memory and can remember by heart any email address in your address book! Personally I can’t! 🙂

And if you do, you might have contemplated using the Autocomplete Plugin for SquirrelMail, which does auto-completion of email addresses based on nicknames or first name + surname (or both!). I must admit, I love this little plugin as it saves me a lot of time and it’s quite easy to use.

Unfortunately it only works with Internet Explorer! 🙁 Well, this is at the time I’m writing this (2/Dec/2006) — future versions of this plugin will probably include the fix that I’ve submitted for Firefox, however, if you don’t want to wait until the next release of the plugin, here’s how you can change it to work with Firefox or any other Mozilla browser.

For obvious reasons, I will use throughout this article the paths that I have used to configure and install SquirrelMail on my machine, but the same notes should apply for any other configurations as long as you replace the paths with the ones you have used in your configuration.

I am using a Debian Linux distribution, so my SquirrelMail plugins are installed under /usr/share/squirrelmail/plugins/ and the Auto-complete plugin gets installed therefore under /usr/share/squirrelmail/plugins/autcomplete — your configuration might be different but the only path we are really interested in is the plugin directory. In order to avoid using the complete paths throughout this article, I will be referring to /usr/share/squirrelmail/plugins as the plugins directory. Also, the /usr/share/squirrelmail/plugins/autcomplete becomes the Autocomplete directory.

So, you’ve installed the plugin, and enabled it in your SquirrelMail configuration (use squirrelmail-configure on Debian systems). Next, log into the system using MSIE and make sure the autcomplete plugin works fine. (Try the same with a Firefox browser and you will notice a whole lot of errors in your JavaScript Console!)

This is because the JavaScript provided with the plugin makes usage of some special features present only in Internet Explorer (so it’s not cross-browser). We’re going to change this JavaScript in order to support both IE (no point in breaking the compatibility with IE just for the sake of supporting Firefox only!) and Firefox as well.

To do so, go to your Autocomplete directory and look inside the folder. The file we are really interested in is functions.php. If you edit this function using a text editor, you will notice a line containing the following comment:

// Spit out javascript

Following this line there is a biiig echo call which (as the comment describes it) spits the needed JavaScript to the browser. So our task is to change this echo call such that the JavaScript produced is cross-browser. For the impatient users, scroll down to the download section and download this file, replacing the existing version (make sure you make a backup of it first!); those of you who want to rather modify their local copy, here’s how:

The first JavaScript function being output is called autocomplete_find (function autocomplete_find (str) {) — we are not going to change this file, so scroll in the code right after the closing brace for this function. Next we’re going to introduce 2 new functions: selectRange and typeAhead as follows:

 * TheLiv at : This is our selection function
function selectRange( src, start, len ) {
 if( src.createTextRange ) {
  var oRange = src.createTextRange();
  oRange.moveStart( "character", start );
  oRange.moveEnd( "character", len - src.value.length );;
 } else if( src.setSelectionRange ) {
  src.setSelectionRange( start, len );
 * TheLiv at : And this is our magincal type ahead/suggestion function that does it!
function typeAhead( src, suggestion ) {
 if( src.createTextRange || src.setSelectionRange ) {
  var iLen = src.value.length;
  src.value = suggestion;
  selectRange( src, iLen, suggestion.length );

Next, change the autocomplete_core (function autocomplete_core (src) {) function, the autocomplete_work function (function autocomplete_work (event) {) and the autocomplete_scroll function (function autocomplete_scroll(event) {) as follows:

function autocomplete_core (src) {
 // kinda support multiple addresses
 var Str = src.value;
 var StartPos = 0;
 var newSP = Str.lastIndexOf(", ");
 if (newSP <= StartPos) {
  StartPos = newSP + 2;
 // do not search for nothing
 if (Str.length - StartPos <= 0)
 // If we can find something in our lookup list
 newValue = autocomplete_find(Str.substr(StartPos, Str.length));
 if (newValue == "" || newValue == Str.substr(StartPos, Str.length))
 newValue = Str.substr(0, StartPos) + newValue;
 var pos = Str.length;
 typeAhead( src, newValue );
 document.acMatch = Str;
function autocomplete_work (event) {
 var src = event.srcElement ? event.srcElement :;
 if (! src.createTextRange && !src.setSelectionRange)
 // Ignore cursor keys, shift, alt, etc
 // look only for A-Z, 0-9, etc.
 if (event.keyCode < 48 || (event.keyCode > 57 && event.keyCode < 65) ||
 (event.keyCode > 90 && event.keyCode < 96) || event.keyCode == 108 ||
 (event.keyCode > 111 && event.keyCode < 186) ||
 (event.keyCode > 192 && event.keyCode < 219) || event.keyCode > 222)
 // If there is no change in the field, ignore
 if (src.value == src._value)
function autocomplete_scroll(event) {
 var src = event.srcElement ? event.srcElement :;
 if (isNaN(document.acOffset))
  document.acOffset = 0;
 if (event.keyCode == 38 || event.keyCode == 40) {
  if (document.acMatch != "") {
   if (event.keyCode == 40) {
   } else if (document.acOffset < 0) {
   src.value = document.acMatch;
  } else {
   document.acOffset = 0;
  event.returnValue = false;
 } else {
  document.acOffset = 0;
  document.acMatch = "";

Next we introduce 2 new functions (getNamedEl and assignHandlers):

 * TheLiv at Function used to find an element by name.
function getNamedEl(name) {
 var el = document.getElementsByName(name);
 if( !el ) return;
 if( el.length <= 0 ) return;
 return el[0];
 * TheLiv at Function used to assign dynamically our handlers.
 * Note that we only do this if we have found our send_to text control!
function assignHandlers(send_to) {
 if( !send_to ) return;
 var isIE = (navigator.appVersion.indexOf("MSIE") <= 0) || (navigator.appVersion.indexOf("Internet Explorer") <= 0);
 if( isIE ) { // Internet Explorer :-(
  send_to.onkeydown = new Function( "autocomplete_scroll(event);" );
  send_to.onkeyup = new Function( "autocomplete_work(event);" );
 } else { // The almighty Firefox :-)
  send_to.onkeydown = new Function( "event", "autocomplete_scroll(event);" );
  send_to.onkeyup = new Function( "event", "autocomplete_work(event);" );

And finally we set up our newly introduced JavaScript handlers for the To:, Cc: and Bcc: text boxes on the Compose page:

 * TheLiv at Cross-browser compatibility.
 * OK, so we are now doing stuff the DOM way which should work on all browsers.
assignHandlers( getNamedEl("send_to") );
assignHandlers( getNamedEl("send_to_cc") );
assignHandlers( getNamedEl("send_to_bcc") );

Note that this last line of code is now followed by the closing </script> tag. Also, the original file has a few other <script> tags after this code — you will need to remove those lines as they are no longer needed.

Having made the above changes, empty your browser cache and log back into SquirrelMail using Firefox, then go to the Compose screen, and you will notice that the auto-complete facility now works under Firefox too — hooray!

Download the complete functions.php file to replace your local version (make sure you make a backup copy first!)

NOTE: This article was published initially on my old website (outside WordPress); having looked recently at some of the searches done in Google and then in my WordPress installation, I figured out that the article must have been pretty popular so I decided to resurrect it. I since migrated my hosting a bunch of times so I stopped using SquirrelMail — which might mean that this article is out of date. I have re-directed the original link though so it redirects to this article, so search engine users should get at least this page rather than a 404.