Adding a “disable” feature to the script.aculo.us Ajax.Autocompleter

Scriptalicious’s Ajax.Autocompleter control is pretty cool. It allows a developer to add a drop-down auto-completion feature to an input box with one line of Javascript and a single div tag in the HTML. However (surprisingly), it doesn’t provide a means to turn off the autocompletion feature once it’s attached it to the target text box. There’s no method or property to disable the feature and setting the object to null has no effect (because it doesn’t affect the observers, but we’ll get to that…)

Line #8 doesn’t work as expected:

var autocomplete;

if( $('search_box') ) {
    autocomplete = new Ajax.Autocompleter("search_box", "choices", "/Autocomplete", {'frequency': 0.2, 'minChars': 3});
}

function disableAutocomplete() {
    autocomplete = null;
}

The top Google result for this issue talks about patching script.aculo.us’s “controls.js”. However, I rejected this solution because I typically load the libraries using Google’s JS API rather than serving them off my web server. Via Google the libraries load faster because they’re served off a CDN. Plus, I don’t have to pay for the bandwidth and it’s just generally more convenient.

Another solution I found is a ticket in RoR’s Trac system. Rather than hacking the source file, this used class inheritance to extend the functionality of the base Ajax.Autocompleter. This looked great because I could continue to use Google’s JS API. The bad news is that the ticket (and solution) was written over three years ago. It did not work with script.aculo.us v1.8.x or prototype.js v1.6.1.

The primary reason why this class was broken was that the methods in which the prototype library keeps track of Event observers changed considerably between versions 1.5, 1.6, and 1.6.1. Long story short: I ended up rewriting the second solution to work with prototype.js 1.6.1 and script.aculo.us v1.8.3.

My new Ajax.ToggleableAutocompleter class:

Ajax.ToggleableAutocompleter= Class.create();
Object.extend(Object.extend(Ajax.ToggleableAutocompleter.prototype, Autocompleter.Base.prototype), {
  initialize: function(element, update, url, options) {
    this.baseInitialize(element, update, options);
    this.options.asynchronous  = true;
    this.options.onComplete    = this.onComplete.bind(this);
    this.options.defaultParams = this.options.parameters || null;
    this.url                   = url;
    this.blurHandler           = new Array();
    this.keydownHandler        = new Array();
    this.keypressHandler       = new Array();    
  },
  
  disable: function() {
    var el = this.element;
    this.hide();

    if (this.blurHandler.length==0 && this.keydownHandler.length==0 && this.keypressHandler.length==0) {
        this._registerHandlers();
    }

    this.blurHandler.each( function(handler) {
      el.stopObserving('blur', handler);
      el.getStorage().get('prototype_event_registry').unset('blur');
    });    

    this.keydownHandler.each( function(handler) {
      el.stopObserving('keydown', handler);
      el.getStorage().get('prototype_event_registry').unset('keydown');
    });      
    
    this.keypressHandler.each( function(handler) {
      el.stopObserving('keypress', handler);
      el.getStorage().get('prototype_event_registry').unset('keypress');      
    });
  },
  
  enable: function() {
    var ele=this.element;
    for (var i = 0; i < this.blurHandler.length; i++) {
      Event.observe(ele, "blur", this.blurHandler[i]);
    }
    for (var i = 0; i < this.keydownHandler.length; i++) {
      Event.observe(ele, "keydown", this.keydownHandler[i]);
    }
    for (var i = 0; i < this.keypressHandler.length; i++) {
      Event.observe(ele, "keypress", this.keypressHandler[i]);
    }    
  },
  
  onComplete: function(request) {
    this.updateChoices(request.responseText);
  },
  
  getUpdatedChoices: function() {
    this.startIndicator();

    var entry = encodeURIComponent(this.options.paramName) + '=' +
      encodeURIComponent(this.getToken());

    this.options.parameters = this.options.callback ?
      this.options.callback(this.element, entry) : entry;

    if(this.options.defaultParams)
      this.options.parameters += '&' + this.options.defaultParams;

    new Ajax.Request(this.url, this.options);
  },
  
  _registerHandlers: function() {
    var o = this;
    if ( !o.element.getStorage().get('prototype_event_registry').size() > 0 ) return;
    with( o.element.getStorage().get('prototype_event_registry') ) {
       var blurEvents = get('blur');
       var keypressEvents = get('keypress');
       var keydownEvents = get('keydown');
    }
    if( blurEvents ) blurEvents.each(function(e){ 
         o.blurHandler.push(e.handler);
    });
    if( keypressEvents ) keypressEvents.each(function(e){
         o.keypressHandler.push(e.handler);
    });
    if( keydownEvents ) keydownEvents.each(function(e){
         o.keydownHandler.push(e.handler);
    });
  }
});

To use it:

var autocomplete;

if( $('search_box') ) {
    autocomplete = new Ajax.ToggleableAutocompleter("search_box", "choices", "/Autocomplete", {'frequency': 0.2, 'minChars': 3});
}

function disableAutocomplete() {
    autocomplete.disable();
}

function enableAutocomplete() {
    autocomplete.enable();
}