Autocompletion with Zend Framework and Dojo

I've fielded several questions about setting up an autocompleter with
Zend Framework and
Dojo, and decided it was time to
create a HOWTO on the subject, particularly as there are some nuances you
need to pay attention to.

Which dijits perform autocompletion?

Your first task is selecting an appropriate form element capable of
autocompletion. Dijit provides two, ComboBox and
FilteringSelect. However, they have different capabilities:

ComboBox allows you to enter arbitrary text; if it doesn't
match the associated list, it is still considered valid. The text
entered is submitted -- not the option value. (This
differs from normal dropdown selects.)

FilteringSelect also allows you to enter arbitrary text,
but it will only be considered valid if it matches an option provided to it.
The option value is submitted, just like a normal dropdown
select.

Once you've chose the appropriate form element type, you then need to
specify a dojo.data store. dojo.data provides a
consistent API for data structures consumed by dijits and other dojo
components. At it's heart, it's simply an array of arbitrary JSON structures
that each contain a common identifier field containing a unique value per
item. Internally, both ComboBox and
FilteringSelect can utilize dojo.data stores to
populate their options and/or provide matches. Dojo provides a variety of
dojo.data stores for such purposes.

Defining the form element

Defining the form element is very straightforward. From your
Zend_Dojo_Form instance (or your form extending that class),
simply call addElement() as usual. Later in this tutorial,
depending on the approach you use, you may need to add some information to
the element definition, but for now, all that's needed is the most basic of
element definitions:

Providing data to a dojo.data store

We're going to work backwards now, as providing data to the data store is
relatively trivial when using Zend_Dojo_Data.

First, we'll create an action in our controller, and assign the model and
the query parameter to the view. We'll be setting up our
dojo.data store to send the query string via the GET parameter
"q", so that's what we'll assign to the view.

<?php
// Disable layouts
$this->layout()->disableLayout();
// Fetch results from the model; again, merely illustrative
$results = $this->model->query($this->params);
// Now, create a Zend_Dojo_Data object.
// The first parameter is the name of the field that has a
// unique identifier. The second is the dataset. The third
// should be specified for autocompletion, and should be the
// name of the field representing the data to display in the
// dropdown. Note how it corresponds to \"name\" in the
// AutocompleteReadStore.
$data = new Zend_Dojo_Data('id', $results, 'name');
// Send our output
echo $data;

That's really all there is to it. You can actually automate some of this
using the AjaxContext action helper, making it even simpler.

Using dojox.data.QueryReadStore

We now have an endpoint for our dojo.data data store, so now we
need to determine which store type to use.

dojox.data.QueryReadStore is a fantastic dojo.data
store allowing you to create arbitrary queries on data. It creates the query
as a JSON object:

This is problematic in two ways. First, if you were to use it directly,
you'd be limited to POST requests, submitting it as a raw post. Second, and
related, this means that requests could not be cached client-side.

Fortunately, there's an easy way to correct the situation: extend
dojox.data.QueryReadStore and override the fetch
method to rewrite the query as a simple GET query with a single parameter.

You have two options: you can inline the custom definition (less intuitive)
and connect the data store manually to the form element, or you can create
an actual javascript class file (slightly more work) and have your form
element setup the data store for you.

Inlining a custom QueryReadStore class extension

Inlining is a bit tricky to accomplish, as you need to declare things in the
appropriate order. When using this technique, you need to do the following:

This works well, and is an expedient way to get autocompletion working for
your element. However, it breaks the DRY principle as you cannot re-use the
custom class in other areas. So, let's look at a better solution

Creating a reusable custom QueryReadStore class extension

The recommendation by the Dojo developers is that you should create this
class as a javascript class, with your other javascript
code. The reasons for this are numerous: you can re-use the class elsewhere,
and you can also include it in custom builds -- which will ensure that it is
stripped of whitespace and packed, leading to smaller downloads for your end
users.

The process isn't as scary as it may initially sound. Assuming that your
"public/" directory has the following structure:

public/
js/
dojo/
dojo.js
dijit/
dojox/

what we'll do here is to create a sibling to the "dojo" subdirectory, called
"custom", and create our class file there:

We'll use the definition as originally shown above, and simply save it as
"public/js/custom/AutocompleteReadStore.js", with one addition: after the
dojo.provide call, add this:

dojo.require(\"dojox.data.QueryReadStore\");

This is analagous to a require_once call in PHP, and ensures
that the class has all dependencies prior to declaring itself. We'll
leverage this fact later, when we hint in our ComboBox element
what type of data store to use.

On the framework side of things, we're going to alter our element definition
slightly to include information about the dojo.data store it
will be using:

If you've been following along closely, you'll notice that the "storeParams"
are exactly the same as what we used to initialize the data store when
inlining. The difference is that now the ComboBox view helper
will create all the necessary Javascript for you.

The view script now becomes greatly simplified; we no longer need to setup
any javascript, and can literally simply echo the form:

<?= $this->form ?>

Hopefully it should now be clear which method is easiest in the long run.

Next Steps

dojox.data.QueryReadStore offers much more than simply
specifying the query string. As noted when introducing the component, it
creates a JSON structure that also includes keys for sorting, selecting how
many results to display, and offsets when pulling results. These, too, can
be added to your query strings to allow finer grained selection of results
-- for instance, you could ensure that no more than 3 or 5 results are
returned, to allow for a more manageable list of matches, or specify a sort
order that makes more sense to users.

Summary

Learning new tools can be difficult, and Dojo and Zend Framework are no
exceptions. One compelling reason to learn Dojo if you're using Zend
Framework, however, is that its structure and design should be familiar: it
uses the same 1:1 class name:filename mapping paradigm. Additionally,
because it is written to utilize strong OOP principles, familiar concepts
such as extending classes can be used to customize Dojo for your site's
needs.

Hopefully this tutorial will shed a little light on both the subject of
autocompletion in Dojo, as well as class extensions in Dojo, and help get
you started creating your own custom Dojo libraries for use with your
applications.