Tutorial # 5: jQuery-style Autocomplete

Table of contents

Tutorial # 5: jQuery-style Autocomplete
Javascript for attaching an autocomplete action to a CGI form field
Perl code for the Controller responding to such an autocomplete request
Traps
But the 'jQuery Cookbook' (at the bottom of p. 249) uses *q*, not *term*!
How do I send more than 2 fields to the server?
What happens if the CGI form is not permanently in the web page?
Links

Tutorial # 5: jQuery-style Autocomplete

Javascript for attaching an autocomplete action to a CGI form field

Step 1: Declare the field (CGI form field in HTML):

        <input id="wine_maker_name" type="text" size="50" maxlength="100">

Step 2: Declare the field to be subject to autocomplete (Javascript):

        <script type="text/javascript">

        $(function()
        {
                $("#wine_maker_name").autocomplete
                ({
                        source: "/AutoComplete?type=wine_maker",
                        minLength: 2,
                        select: function(event, ui)
                        {
                                $("#wine_maker_name").empty().append(ui.item ? ui.item.value : "-");
                        }
                });
        });

        ...

        </script>

Notes:

# 1: You'll see that I did not need to put this JS code into the doc ready logic:

        $(document).ready(function()
        {
                $(function()
                {
                        $("#wine_maker_name").autocomplete
                        ({
                                source: "/AutoComplete?type=wine_maker",
                                minLength: 2,
                                select: function(event, ui)
                                {
                                        $("#wine_maker_name").empty().append(ui.item ? ui.item.value : "-");
                                }
                        });
                });

                ...
        }

Why is that? Because the CGI form - which contains the field *wine_maker_name* - is permanently present.

By permanently present, I mean a form which is permanently on your web page, even if in a hidden div.

This is in contrast to a form which is loaded into the web page as the result of an Ajax request. That case is handled below, under Traps.

# 2: You'll also see how easy this is, compared to what's required by YUI. (Yes, I know I'm labouring a point here).

Perl code for the Controller responding to such an autocomplete request

        sub display
        {
                my($self) = @_;

                $self -> log(debug => 'display()');

                my($type) = $self -> query -> param('type') || '';
                my($name) = $self -> query -> param('term') || ''; # jQuery forces use of 'term'.

                my($result);

                given ($type)
                {
                        when ('comment')
                        {
                                $result = $self -> param('db') -> autocomplete -> comment($name);
                        }
                        when ('grape')
                        {
                                $result = $self -> param('db') -> autocomplete -> grape($name);
                        }
                        when ('style')
                        {
                                $result = $self -> param('db') -> autocomplete -> style($name);
                        }
                        when ('vineyard')
                        {
                                $result = $self -> param('db') -> autocomplete -> vineyard($name);
                        }
                        when ('wine_maker')
                        {
                                $result = $self -> param('db') -> autocomplete -> wine_maker($name);
                        }
                }

                return JSON::XS -> new -> utf8 -> encode($result);

        } # End of display.

Notes:

# 1: display() is the default method for this Controller.

# 2: You can see that the request passes 2 parameters from the client to the server, *type* and *term*.

I use *type* so I can have just 1 Controller handling all such requests.

jQuery forces the data being sent to be called *term*.

# 3: The value $result returned by those calls to methods within the autocomplete object, wine_maker() etc, is always an arrayref. Perl is sooo simple (at times).

# 4: The data returned to the client must be encoded as JSON. You have been warned!

Traps

As you know, jQuery is full of astonishing and infuriating traps.

Here are some connected with autocomplete.

But the 'jQuery Cookbook' (at the bottom of p. 249) uses *q*, not *term*!

Yes, that's true.

I assume this is because the book used an earlier version of jQuery. I'm using V 1.9 of the UI, and, within DataTables, V 1.8 of jQuery itself.

Be warned, *term* it is.

How do I send more than 2 fields to the server?

Step 1: Add another field to the CGI form:

Let's add a session id, which is a typical usage:

        <input type="hidden" id="sid" value="<: $sid :>" />

Step 2: Add the value of the sid to the parameter list:

        $(function()
        {
                $("#wine_maker_name").autocomplete
                ({
                        source: "/AutoComplete?type=wine_maker&sid=" + $("#sid").val(),
                        minLength: 2,
                        select: function(event, ui)
                        {
                                $("#wine_maker_name").empty().append(ui.item ? ui.item.value : "-");
                        }
                        });
        });

Notes:

# 1: You might be thinking: Why not use this structure:

        $(function()
        {
                $("#wine_maker_name").autocomplete
                ({
                        data:
                        {
                                type: "wine_maker",
                                sid:  $("#sid").val()
                        },
                        source: "/AutoComplete",
                        minLength: 2,
                        select: function(event, ui)
                        {
                                $("#wine_maker_name").empty().append(ui.item ? ui.item.value : "-");
                        }
                        });
        });

After all, it's discussed on p. 394 of 'jQuery Cookbook' (near the bottom), and on p. 127 of 'jQuery UI' (near the top).

The answer is that I simply could not get it to work.

What happens if the CGI form is not permanently in the web page?

More pain. You can't attach the autocomplete logic as I did above. You have to use jQuery to insert the CGI form into the DOM *before* attaching the autocomplete logic. Try this:

Step 1: Declare a JS function to be called when a certain button is clicked.

        $("#occ_person_button").click(function(e)
        {
                ...

                display_occs();
        });

Notes:

# 1: display_occs() lists occupations for people who work at 2 or more full-time or part-time jobs.

# 2: In the module (App::Office::Contacts V 2.00) that this is copied from, this bit of code happens to be in the success function of another Ajax call, but that does not matter.

Step 2: Declare the function display_occs():

        function display_occs()
        {
                $.ajax
                ({
                        data:
                        {
                                id:  $("#update_person_id").val(),
                                sid: $("#update_person_sid").val(),
                        },
                        dataType: "html",
                        type: "POST",
                        url:  "<: $self_url :>/Occupation/display",
                        success: function(html, status, jqObject)
                        {
                                $("#status_div").empty().append("Status: OK");
                                $("#occ_detail_div").append(html);

                                // These 2 functions don't work if included globally.
                                // They must follow after the 'html' is inserted into the DOM.

                                $(function()
                                {
                                        $("#add_occ_occ_title").autocomplete
                                        ({
                                                source: "/AutoComplete?type=occ_title&sid=" + $("#add_occ_sid").val(),
                                                minLength: 2,
                                                select: function(event, ui)
                                                {
                                                        $("#add_occ_occ_title").empty().append(ui.item ? ui.item.value : "-");
                                                }
                                        });
                                });

                                $(function()
                                {
                                        $("#add_occ_org_name").autocomplete
                                        ({
                                                source: "/AutoComplete?type=org_name&sid=" + $("#add_occ_sid").val(),
                                                minLength: 2,
                                                select: function(event, ui)
                                                {
                                                        $("#add_occ_org_name").empty().append(ui.item ? ui.item.value : "-");
                                                }
                                        });
                                });
                        },
                        error: function(jqObject, status, error)
                        {
                                ajax_error("Cannot display occupations for " + generate_update_persons_name(), jqObject, status, error);
                        }
                });
        }

Notes:

# 1: Scrutinize that code, noting the order of these statements:

        $("#occ_detail_div").append(html);

        // These 2 functions don't work if included globally.
        // They must follow after the 'html' is inserted into the DOM.

        $(function()
        {
                $("#add_occ_occ_title").autocomplete
                ({
                        source: "/AutoComplete?type=occ_title&sid=" + $("#add_occ_sid").val(),
                        minLength: 2,
                        select: function(event, ui)
                        {
                                $("#add_occ_occ_title").empty().append(ui.item ? ui.item.value : "-");
                        }
                });
        });

That's saying, get jQuery to add the CGI form (*html*) to the DOM before trying to attach autocomplete logic to a form field within that CGI form.

I tried various alternatives to this set up, but all others - which involved trying to attach the autocomplete logic inside and outside the doc ready function - just did not work. You have been warned!

# 2: Scrutinize that code again, and note these 2 lines:

        // These 2 functions don't work if included globally.
        // They must follow after the 'html' is inserted into the DOM.

Links

My home page

All tutorials (Includes references)

POD source for this tutorial