PhpRiot
Become Zend Certified

Prepare for the ZCE exam using our quizzes (web or iPad/iPhone). More info...


When you're ready get 7.5% off your exam voucher using voucher CJQNOV23 at the Zend Store

Cloning Google Suggest With Ajaxac

Creating The Ajaxac Widgets

The next part is to create the widgets to use in AjaxAC. There are three main widgets to create:

  1. The query input box widget
  2. The search results widget
  3. The timer widget so subrequests are only performed after a delay.

We will create each of these separately, and then piece them all together inside GoogleSuggestCloneJax.class.php.

Query input widget

This widget needs only one event attached to it, and that is the event to handle when keys are pressed. We’ll deal with handling that soon, but right now we’ll just create the widget (this will be put into context later):

Listing 9 listing-9.php
<?php
    $query = $this->createWidget('query');
    $query->addEvent(AJAXAC_EV_ONKEYDOWN, 'querykeydown');
    $this->addWidget($query);
?>

So this creates the widget (called ‘query’), and attached the onkeydown event. This means we need to write a function called event_querykeydown() that will create the JavaScript code to handle this event.

This function will look like this:

Listing 10 listing-10.php
<?php
    function event_querykeydown(&$widget, $event)
    {
        $callback = "
                        function(e)
                        {
                            key = ajaxac_getkeycode(e);
                            switch (key) {
                                case 27: // escape
                                    gsc_hide(%1\$s);
                                    return false;
                                    break;
                                case 38: // up arrow
                                    gsc_handleup(%1\$s, %2\$s);
                                    return false;
                                    break;
                                case 40: // down arrow
                                    gsc_handledown(%1\$s, %2\$s);
                                    return false;
                                    break;
                                default:
                                    %3\$s.start();
                            }
                            return true;
                        }
                    ";
 
        $callback = sprintf($callback,
                            $this->getHookName('results'),
                            $this->getHookName('query'),
                            $this->getHookName('gsctimer'));
 
        return $callback;
    }
?>

Firstly, the function determines which key was pressed by the user. We need to know this as there is certain functionality we need for certain keys:

  • When the escape key is pressed, we want the search-results element to disappear (if it already is hidden, then do nothing). We use gsc_hide() to achieve this.
  • When the up arrow is pressed, call gsc_handleup() as this deals with the up arrow.
  • When the down arrow is pressed, call gsc_handledown() as this deals with the down arrow.
  • If any other key is pressed, start the countdown time (which we haven’t yet created, but we will soon).

We pass this template into sprintf() so we can easily insert the widget names dynamically. To get the names, we use the application getHookName() method, which returns the JavaScript element name for the widget name passed.

Because this method is operating on the query widget, we could technically replace $this->getHookName(‘query’) with $widget->getHookName(), but it is slightly less confusing the way we have it.

Search results widget

This widget is what holds the suggestions loaded in the background. The only event it needs is the onload event, as there is a small amount of initialization code we need to run on the element.

Listing 11 listing-11.php
<?php
    $results = $this->createWidget('results');
    $results->addEvent(AJAXAC_EV_ONLOAD, 'resultsload');
    $this->addWidget($results);
?>

So now we need to implement a function called event_resultsload() to handle this event.

Listing 12 listing-12.php
<?php
    function event_resultsload(&$widget, $event)
    {
        return "function() { gsc_emptyresults(this); }";
    }
?>

All this function does is clear out the search-results element when the page is loaded, then hides it (if it is not already).

Timer widget

We only want the HTTP subrequest to fetch suggestions to occur after a short delay, rather than every time a key is pressed. To do this, we create a Timer widget. To make this work, we start the timer every time a key is pressed (we did this just before in event_querykeydown), or if the timer is already running then reset it and start it again. This means the subrequest will only run after the person has finished all of their typing.

Listing 13 listing-13.php
<?php
    require_once('Widgets/AjaxACWidgetTimer.class.php');
    $timer = new AjaxACWidgetTimer($this, 'gsctimer');
    $timer->addEvent(AJAXAC_EV_ONTIMEREXPIRE, 'timerexpire');
    $timer->setTimeoutFromInt($this->keypress_timeout_ms);
    $this->addWidget($timer);
?>

So now we need to define two things: the timeout in milliseconds, and the event_timerexpire() function, which is called when the countdown timer reaches zero.

Listing 14 listing-14.php
<?php
    var $keypress_timeout_ms = 350;
?>

350 milliseconds is a fairly nice number for the purposes of this application, and is fairly close (if not the same) as what Google uses.

Now we create the event_timerexpire() callback. This is probably the most complicated function in the application. Within this function, we need to create a XMLHttpRequest widget to deal with the HTTP subrequest.

Listing 15 listing-15.php
<?php
    function event_timerexpire(&$widget, $event)
    {
        require_once('Widgets/AjaxACWidgetXMLHttpRequest.class.php');
        $xmlhttp = new AjaxACWidgetXMLHttpRequest($this, 'gscfetch', AJAXAC_METH_GET);
        $xmlhttp->setFilenameFromString($this->getApplicationUrl('getsuggestions'));
        $xmlhttp->addParamFromHookValue('q', '_q', '');
        $xmlhttp->addEvent(AJAXAC_EV_ONXMLHTTPSUCCESS, 'handlesuggestions');
        $this->addWidget($xmlhttp);
 
        $callback = "
                        function()
                        {
                            _q = gsc_getquery(%1\$s, %2\$s.value);
                            if (_q.length == 0)
                                return false;
                            try {
                                %3\$s
                            }
                            catch (e) { }
 
 
                            return false;
                        }
                    ";
 
        $callback = sprintf($callback,
                            $this->getHookName('results'),
                            $this->getHookName('query'),
                            $xmlhttp->getJsCode());
 
        return $callback;
    }
?>

So a quick explanation. Firstly, initialize the search-results for a new query, checking if we have a valid query term to perform a subrequest on. If we do, perform the subrequest.

We have created a XMLHttpRequest widget called gscfetch. This widget fetches the suggestions using the getsuggestions action we created earlier. It includes a parameter called ‘q’, whose value is taken from a variable called _q. This means prior to running the XMLHttpRequest widget, we need to make sure the q value will exist inside the _q variable.

The gscfetch widget now needs an event handler created for it, which is called when the data has finished succesfully loading. The callback function we need to define is called event_handlesuggestions().

Listing 16 listing-16.php
<?php
    function event_handlesuggestions(&$widget, $event)
    {
        $callback = "
                        function()
                        {
                            _data = ajaxac_receivejsarray(%1\$s.responseText);
                            gsc_emptyresults(%2\$s);
                            if (_data.length > 0) {
                                for (i = 0; i < _data.length; i++) {
                                    gsc_addresult(%2\$s, %3\$s, _data[i][0], _data[i][1], i == 0);
                                }
                                gsc_show(%2\$s);
                            }
                        }
                    ";
        $callback = sprintf($callback,
                            $widget->getHookName(),
                            $this->getHookName('results'),
                            $this->getHookName('query'));
        return $callback;
    }
?>

This function uses the AjaxAC core function ajaxac_receivejsarray() to parse the received data (which if you recall, we were sending back as a ‘jsarray’, i.e. a JavaScript array).

So firstly, we empty the previous results in search-results (just in case it hasn’t been cleared yet), then loop over each of the returned results, adding them to the search-results element with the gsc_addresult() function.

Finally, once all the elements have been added, we show the search-results element again. Also note that we make the first result the one that is initially selected (i == 0 will be true only for the first result).

Also, in the sprintf(), we are using $widget->getHookName() because this XMLHttpRequest widget isn’t in the global application scope, unlike ‘results’ and ‘query’, which are.

In This Article