How Not To Write an API

avoiding Unnecessary rabbit-holes

Introduction

typeahead.js renders a list of suggested options below an input box as a user enters text:

You just need data and a DOM node to bind it to. This sounds easy, but the talented maintainers have unwittingly created a very frustrating experience.

Remote Data

If your data is on a server, you can specify url: /remote/%QUERY.json where %QUERY is replaced by the value of the input. That is, if you are running 0.10.x. In 0.11.x you need to manually specify a wildcard: "%QUERY" because although this option has the same syntax as the previous version, the default wildcard is now null.

Once you pull the data down as an array of objects you can specify the key with the handy displayKey: objectKey option. In versions prior to 0.11.0 this content is inserted as HTML, whereas now it's in plaintext.

If you want to continue to do HTML insertion you may be thinking you can use display: function(){ … } instead of the displayKey. This would make sense…

Instead, you ignore the display and displayKey options and write something like

{
  templates: {
    suggestion: function(row) { 
      // Return a JQuery node, because
      // that's all it will accept.
      return $("<p>")
        .html(row.html); 
    }
  }
}

This gives you similar functionality to displayKey in 0.10.5.

You'll probably want to do this since the inner DOM nodes in 0.11.x have changed from <p> tags to <div> tags. Oh, that brings us to style.

Stylizing

You'd think that being explicitly referenced in the Bootstrap documentation as a supported library (in 2.3.2 but not 3.x1), typeahead.js would structure their HTML as a Bootstrap-friendly style drop-down…

Note from 2015-06-16: This history isn't correct. Chris Rebert explains:

To dispel a key misconception here: The similarity of the names is indeed unfortunate & confusing, but Twitter's Typeahead.js is completely separate from and has never shared any code with Bootstrap v2's typeahead widget. I imagine that the fact that old versions of Twitter's Typeahead.js offered optional Bootstrap integration also contributed to this confusion.

In Bootstrap v3, Bootstrap's own typeahead widget from v2 was removed due to its complexity and its suffering from some significant bugs. The Bootstrap Core Team recommended that users migrate to Twitter's Typeahead.js since it was a superior alternative (see http://getbootstrap.com/migration/#notes), and since there was hope that Twitter's Typeahead.js would continue to offer an optional Bootstrap integration (which sadly didn't pan out).

With the release of version 3, Bootstrap ceased to be affiliated with Twitter (see twbs/bootstrap#9899), which makes Typeahead.js's lack of integration/compatibility with Bootstrap more understandable.

In Bootstrap 3, the selectors for the dropdown are .dropdown-menu > li > a. However, in 0.10.x the dropdowns are span > div > p and in 0.11.x, it's div > div > div.

If you think typeahead.js will still work with Bootstrap through the classNames interface — it doesn't and the object keys and class names have changed between the versions.

To make things display nicely, you need a separate stylesheet that inherits from Bootstrap's less source for 0.10.x and 0.11.x.

Local data

If you have local data ... since typeahead has a prefetch and a local option, you'd think you can just do a local search without specifying the remote.

What actually happens is that an internal this.query parameter doesn't get set, which determines the value of the input box on blur. Once you navigate away from it, the value is cleared.3

There's an undocumented setQuery you can use if you provide it with a correctly wrapped object.2

But wait, you say, looking at the website, there's a simple example provided here with local data. Look at the gist and see there's small differences between the two. These seem to be irrelevant and stylistic. If you try the new code on the old version, you'll see that these changes matter.

If you use the Bloodhound engine to provide an instance as a source, you can in 0.9.x and 0.11.x. In 0.10.x you use instance.ttAdapter(). You can see that in the example where they shadow a local variable they didn't define.

Events

In 0.10.x you can bind the node to a typehead:selected event (which has been renamed to typeahead:select in 0.11.0). Now if you choose to do a setQuery inside the listener, you'll get a JQuery error that is thrown by typeahead. You can avoid this by wrapping that in a setTimeout in the renamed listener.

Compatibility

I'm glad this all makes sense. Especially since the main readme assures us: New additions without breaking backwards compatibility bumps the minor They've taken the time to provide a 0.9.x0.10.x migration guide (there's compatibility issues within the z versions of 0.10.x itself).

They should really just drop the first zero because that's what they actually mean.


  1. Where it changed from Bootstrap-typeahead to typeahead.js.
  2. This was noted in #577 and #247 but never made its way to the documentation.
  3. At least for me.

This isn't how you make an API

A release means you are ready to go

Everyone has bugs. If you are going to tag a version and make it a release, you are telling the world that it's ready to go. The only person with the hand on the release-trigger is the maintainer. If it's not ready, don't do it.

The developer is the end user. You are providing the user interface

When writing an API, you should refer to the programmers using it as "your users" and the thing you provide as "your interface". All of the rules of designing an intuitive consistent end product still apply when providing a documented interface for fellow programmers.

Don't do things pre-emptively or frivolously

When designing something that people will use, you can either put things in liberally and then have no qualms about breaking things a few weeks later, put things in liberally and then feel the terrible burden of support, or create a wall between supported (conservative) things and unsupported (liberal) things.

The last option is the only sensible one if you anticipate on supporting a large number of users.

You are providing a tool

Developers are utilizing your tool to solve a problem, not deal with a bunch of new ones presented by the presumptions or constraints of the technology. There is no merit to providing a solution that takes just as long to implement due to obtuse design then it would to do directly. You are effectively burning your user base every time you do this.

The worst thing you can do is make your users think they've made a mistake after sinking significant time and effort and then either holding their nose as they try to just "make it work", or worse, abandon their efforts entirely and think terrible thoughts about you.

Conclusion

Nobody builds things to make enemies. We're in this to help people. We write APIs for people to make better software — not waste their time unraveling our implementation to finish theirs. The whole point is to remove a problem, not create new ones. Exhausting time, effort, and mind-share due to poor release management is a terrible shame.

Their are real people behind every project and we must never forget that. It's easy to remove the human from the experience and think evil malevolent thoughts as you sip that third cup of coffee trying to get that thing done that should have been 15 seconds.

We must do better.