Wednesday, May 14, 2008

Adventures with the Scriptaculous Ajax.Autocompleter

I had a chance to use the Scriptaculous autocompleter control at work the other day. Since we are using Ruby on Rails, it was easy enough to use text_field_with_auto_complete() to generate the relevant HTML.

The client noted that it was difficult to enter a new word if it was too close to an existing word. For example, he would type "expensive", and the system would suggest "not expensive". When he would hit Enter, it would replace what he had typed with "not expensive". This is because the Scriptaculous control doesn't support the case where no item is selected - one item is always selected, and it defaults to being the first item.

I decided to have a peek at the source code. In maybe 15 minutes, I had managed to convince it that "nothing selected" is a valid case. Unfortunately, I didn't refactor the code in any way - now every autocompleter behaves this way.

Scriptaculous uses prototype's Class.create() to generate 2 classes: Autocompleter.Base and its sole subclass: Ajax.Autocompleter. From the source code:

Autocompleter.Base handles all the autocompletion functionality that's independent of the data source for autocompletion. This includes drawing the autocompletion menu, observing keyboard and mouse events, and similar.

I appreciate their desire to re-use code. Unfortunately, the use of inheritance has caused the whole mess to suffer some sort of object-oriented gimbal lock. It's easy to create a new Autocompleter that gets its data from a different source; it's much more work to create an Ajax autocompleter with different behavior.

If I have some time (that is, when I'm not busy complaining about something), I might see if I can refactor the scriptaculous source to further separate the data source of an autocompleter from its interaction controller, using some method other than inheritance.


dtrenz said...

This is how I "fixed" the auto-selected first entry issue:

In controls.js; Line: 286; change "this.index = 0;" to "this.index = -1;"

I would like to see this become an Autocompleter option in future releases (e.g. { autoSelectFirst: false } )

doofus said...

thanks for the info guys - ran into the same problem...