< ToolTips

OGC Layer Loader >

## 23. Search

## 23.1. Introduction

The search plugin aim is to offer standards search features (like recentering, result offset, guided search, ajax features...) in a generic way. Take note that in his current state the plugin only offers a server framework. There is not yet any generic JavaScript framework.

### Warning

The following examples only work with AJAX mode on !

## 23.2. Basic Usage

Introduction

### 23.2.1. General Considerations

The search plugin must be activated on both CartoClient and CartoServer side ie. the `loadPlugins` parameter must contain `search` in your project `client_conf/client.ini` and `server_conf/<mapId>/<mapId>.ini`.

Search use is mainly based on three files. `client_conf/search.ini` , `plugins/search/htdocs/js/Search.ajax.js` and `plugins/search/templates/search.tpl`.

The search plugin uses the PEAR DB abstraction class and should be able to handle all databases supported by PEAR DB.

## 23.3. Basic Sample

### 23.3.1. Basic Mechanism and Setup

The data set and the query specification are set in the `search.ini` file. The design and the fields of the search form is set in the `search.tpl` file. The search behavior mainly reside in the `Search.ajax.js.`

Let's imagine that we want to do a search with a recenter on data that are represented on a layer Addresses. The source of the data is a table in a PostGIS database.

The data are stored in a table "addresses" with the followings rows: num, street, zipcode, town, gid and the_geom. Primary key is gid.

First we have to set the configuration in the `client_conf/client.ini/search.ini` like this:

        dsn = SEARCHDSN
        encodingContext = myencoding_context
        ; Do not forget to add  EncoderClass.myencoding_context = EncoderISO (or any other context)
        ; in your client_conf/client.ini

        config.Adresses.provider.type = fulltextTable
        config.Adresses.provider.table = addresses
        config.Adresses.provider.aliases = adr_num, num, adr_street, street, adr_zip, zipcode, adr_town, town
        config.Adresses.provider.id = gid
        config.Adresses.provider.columns = adr_num, adr_street, adr_zip, adr_town,
        config.Adresses.provider.fulltextColumns = adr_num, adr_street, adr_zip, adr_town
        config.Adresses.provider.sortColumn = adr_num
        config.Adresses.provider.sortDirection = asc
        config.Adresses.provider.sortPriorities = adr_num, adr_street, adr_zip, adr_town
        config.Adresses.provider.labels =  adr_num, adr_street, adr_zip, adr_town
        config.Adresses.formatter.type = smarty
        config.Adresses.formatter.template = search_results

- The dsn correspond to your dsn dataBase connexion string. For more information please see the php PEAR corresponding page
- The encodingContext correspond to the context in your `client_conf/client.ini` see below code
- The config.XXXX is used for making a differentiation on tables.
- The config.Adresses.provider.type defines the type of query that should be done on the table. Value can be :
  - fulltextTable: That means the search is a textual search (ilike clause) that will be done on the fields listed in the config.Adresses.provider.fulltextColumns.
  - table: In this case a where clause should be set. (see Section 23.3.1.1, “Guided Search”)
  - user-defined (see Section 23.3.1.4, “Writing a Formatter”).
- The config.Adresses.provider.table specifies the name of the table to query.
- The config.Adresses.provider.aliases defines column names aliases. This could be useful if there are two tables with same column name, or if you want to migrate from one set of tables to another with different column names.
- The config.Adresses.provider.id designate the unique value used for recentering. For example, if you want to do a recenter by id (gid in our case) the id value should be set to gid. Do not forget the id-attribute-srting in the mapFile see Map file references
- The config.Adresses.provider.columns specifies the columns to be returned by the query. It contains the columns names separated by a coma.
- The config.Adresses.provider.fulltextColumns specifies the columns to be use in the ilike clause when the config.Adresses.provider.type is fulltextColumns. It contains the columns names separated by a coma.

  ### Caution

  This parameter should not be use if the type is Table.

- The config.Adresses.provider.sortColumn specifies the columns to by used for ordering the rows.
- The config.Adresses.provider.sortDirection give the ordre type. It value can be asc or desc for ascendent or descendent.
- The config.Adresses.formatter.type specifies the method for presenting the result(s). Actually only smarty is implemented. But it is possible to write your own Formatter (see chapter Writing a Formatter).
- The config.Adresses.formatter.template specifies the name of the template for formatting query results. The file must be in the template folder of the plugin search in your project directory.
- The config.Adresses.provider.labels specifies the label of the columns that should be shown (the label will be used in the translation files).

Now we must set up the form that will be used for doing the search. Edit or create the `plugins/search/templates/search.tpl` file and write:

        <div id="search_div">
            <fieldset><legend>{t}My Title{/t}</legend> <br/>
                <table width="100%">
                    <tr>
                        <td>
                          {t}Street: {/t}
                        </td>
                        <td>
                            <input type="text" id="search_adr_street"
                              name="search_adr_street" size="13"/>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            {t}adr_town: {/t}
                        </td>
                        <td>
                            <input type="text" id="search_adr_town"
                              name="search_adr_town" size="13"/>
                        </td>
                    </tr>

                    ...

                </table>
                <p>
                  <input type="submit" value="{t}Search{/t}" class="your_form_button_class"
                   onclick="JavaScript: CartoWeb.trigger('Search.DoIt'); return false;"/>

                  <input type="hidden" id="search_config" name="search_config" value="Adresses" />
                  <input type="hidden" id="search_sort_column" name="search_sort_column" value="adr_num"/>
                  <input type="hidden" id="search_sort_direction" name="search_sort_direction" value="asc" />
                  <input type="hidden" id="search_number" name="search_number" />

                  <div id="search_results_div"></div>
                </p>
            </fieldset>
        </div>

Each field present in the fulltextColumns parameter of the configuration file must have a corresponding input in the template. Each input should have an id composed of the name of the column prefixed with the key-word "search_". For example, the the column adr_num should give:

        <input type="text" id="search_adr_num" name="search_adr_num" size="13"/>

The template contains a submit button that calls the CartoWeb.trigger('Search.DoIt') JavaScript method. This method defined in the `Search.ajax.js` (see below) launches the action.

You may notice the hidden inputs in the template. They provide parameters that can be dynamically changed by JavaScript:

- The search_config input determines the table to be queried. Its value should correspond to the name of a table defines in the `search.ini` (config.here_is_the_value). This value allows to set different table in the configuration file and to dynamically choose which table should be queried !!!! This input is not optional and the search plugin will generate an error !!!!
- The search_sort_column input defines the column to be used by the sort clause. If this input is not set or has no value, it's overridden by the sortColumn parameter defined in `search.ini`. If this parameter is also not set in the configuration file it will be ignored.
- The search_sort_direction input defines the sort order. It should be asc or desc. If this input is not set or has no value, it's overridden by the sortColumnDirection parameter defined in `search.ini`. If this parameter is also not set in the configuration file it will be ignored.
- The search_number input define the maximum number of rows to be returned by the query. If this input is not set or has no value, it will be ignored.

There is also two other inputs parameters available:

- offset
- page

They allow the creation of a navigation of the query result. The offset input gives the number of rows a "page" of result should contains. And page gives the current page number.

Note: The form is injected in the `cartoclient.tpl` inside the Smarty tag search:

        {if $search_active|default:''}
            {$search}
        {/if}

### Warning

Do not forget to include the `Search.ajax.js` file in your `cartoclient.tpl`.

        {if $search_active|default:''}<script type="text/javascript"
                                                src="{r type=js plugin=search}Search.ajax.js{/r}"></script>{/if}

Now we must set up the result template file. Create or edit the `plugins/search/templates/search_results.tpl` that has been defined in the configuration file and write:

         {if $table->numRows > 0}
         <table class="yourCssClass">
             <tr>
               <th>{t}Id{/t}</th>
               {foreach from=$table->columnIds item=column}
               <th> <a href="JavaScript: order('{$column}');">{t}{$column}{/t}</a></th>
               {/foreach}
             </tr>
             {foreach from=$table->rows item=row}
             <tr>
                <td>{$row->rowId}</td>
                {foreach from=$row->cells item=value}
                <td><a href="JavaScript: recenter('{$row->rowId}');">{$value}</a></td>
                {/foreach}
             </tr>
             {/foreach}
         </table>
         {else}
         {t}No results{/t}
         {/if}

This will return a table with the columns defined in the configuration file. By clicking on a row you will call the recenter method. By clicking on a row header you will order the result.

Note that the place of the result table is defined by a div that will be modified by the JavaScript.

         <div id="search_results_div"></div>

The div id must be the same as the target id in function ''handleResponse'' ! (see below)

Finally we have to set up the java script file. Open or create the `plugins/search/htdocs/js/Search.ajax.js` and write:

        AjaxPlugins.Search = {

          handleResponse: function(pluginOutput) {
            //should be the same value as the input search_config
            if (pluginOutput.htmlCode.myconfig)
              $('search_results_div').innerHTML = pluginOutput.htmlCode.myconfig;
          }
        };

        /*
        * Search plugin's Actions
        */

        AjaxPlugins.Search.Actions = {};

        AjaxPlugins.Search.Actions.DoIt = {

          buildPostRequest: function(argObject) {
            return AjaxHandler.buildPostRequest();
          }
        };

        function order(column) {

          if (column != $('search_sort_column').value) {
            $('search_sort_column').value = column;
            $('search_sort_direction').value = 'asc';
          } else {
            if ($('search_sort_direction').value == 'asc') {
            $('search_sort_direction').value = 'desc';
            } else {
            $('search_sort_direction').value = 'asc';
            }
          }

          CartoWeb.trigger('Search.DoIt')
        }

        function recenter(id){
          if ($('search_config').value == 'Adresses') {
            //id to recenter, be sure there is a hidden input with that name and
            //id in your template! if not, add it
            $('id_recenter_ids').value = id;

            /* if the search database table name is the same as the related layer
            to recenter on, you can set it as below
            be sure there is a hidden input with that name and id in your template!
            if not, add it */
            $('id_recenter_layer').value = $('search_config').value;

            /* do not use the recenter_scale parameter if you recenter on ids,
            only use it with recenter on x,y.
            you can set the recenter scale in location.ini on server side */

            /* do not use the recenter_doit parameter if you recenter on ids,
            only use it with recenter on x,y. */

            CartoWeb.trigger('Location.Recenter');
          }
        }

#### 23.3.1.1. Guided Search

### Note

The Basic Usage chapter is a prerequisite to the read of this one

A often meet case is to have a incremental search. Each time a search parameter is selected the following parameter is completed.

Let's now take a concrete case: We keep the same database as in the above sample. Imagine we have a select field that is initialised with the towns names. When you choose a city, a select fields containing all the streets of the town appears. When you choose a street the final search is launched.

As mentioned above we have to set the `search.ini` file

        dsn = SEARCHDSN
        ; Do not forget to add  EncoderClass.myencoding_context = EncoderISO
        ;(or any other context)
        ; in your client_conf/client.ini

        config.Adresses_init_town.provider.type = table
        config.Adresses_init_town.provider.table = addresses
        config.Adresses_init_town.provider.aliases = adr_town, town
        config.Adresses_init_town.provider.id = adr_town
        config.Adresses_init_town.provider.columns = adr_town
        config.Adresses_init_town.provider.sortColumn = adr_town
        config.Adresses_init_town.provider.sortDirection = asc
        config.Adresses_init_town.formatter.type = smarty
        config.Adresses_init_town.formatter.template = init_adresse_town_select

        config.Adresses_init_street.provider.type = table
        config.Adresses_init_street.provider.table = addresses
        config.Adresses_init_street.provider.aliases = adr_street, street, adr_town, town
        config.Adresses_init_street.provider.id = adr_street
        config.Adresses_init_street.provider.columns = adr_street
        config.Adresses_init_street.provider.sortColumn = adr_street
        config.Adresses_init_street.provider.sortDirection = asc
        config.Adresses_init_street.provider.where = "adr_town like '@adr_town_init@'"
        config.Adresses_init_street.formatter.type = smarty
        config.Adresses_init_street.formatter.template = init_adresse_adr_street_select

        config.Adresses.provider.type = table
        config.Adresses.provider.table = addresses
        config.Adresses.provider.aliases = adr_num, num, adr_street, street, adr_zip, zipcode, adr_town, town
        config.Adresses.provider.id = gid
        config.Adresses.provider.columns = adr_num, adr_street, adr_zip, adr_town
        config.Adresses.provider.sortColumn = adr_num
        config.Adresses.provider.sortDirection = asc
        config.Adresses.provider.sortPriorities = adr_num, adr_street, adr_zip, adr_town
        config.Adresses.formatter.type = smarty
        config.Adresses.formatter.template = search_results
        config.Adresses.provider.where = "adr_street like '@adr_street_init@' and adr_town like '@adr_town_init@'"

As you can see, we have have declared 3 search configurations that are pointing on the same database entity. The first two tables are used for initializing the select. They are not of type FullTextTable as we do not want to use the default search comportement. So the provider.label and provider.fulltextColumns are not used. There is also a new configuration parameter that is :

        config.Adresses.provider.where

This parameter allows to specifiy a where clause. The where clause is written in traditional sql but the data corresponding to the value of the form have to be replaced by @the_field_name@.

### Warning

Do not forget to remove the "search_" prefix that is in the select field when defining the @the_field_name@ parameter in the ini file.

For the first two tables, the template file have been modified in order not to give back a result table but a select containing all the data required

        config.Adresses_init_street.formatter.type = smarty
        config.Adresses_init_street.formatter.template = init_adresse_adr_street_select

Here is the content of the `init_adresse_town_select.tpl` file :

        {t}Town:{/t}<br />
        <select name="search_adr_town_init" id="search_adr_town_init"
          onchange="javascript: initializeStreet()">
            <option value="">&nbsp </option>
                {foreach from=$table->rows item=row}
                <option value="{$row->rowId}">
                    {foreach from=$row->cells item=value}
                    {$value}
                    {/foreach}
                </option>
                {/foreach}
        </select>

Here is the content of the `init_adresse_adr_street_select.tpl` file :

        {t}Street:{/t} <br />
        <select name="search_adr_street_init"
          id="search_adr_street_init" onchange="search()" >
            <option value="">&nbsp </option>
                {foreach from=$table->rows item=row}
                <option value="{$row->rowId}">
                    {foreach from=$row->cells item=value}
                    {$value}
                    {/foreach}
                </option>
                {/foreach}
        </select>

Now we have to setup the `search.tpl` file.

        <div id="search_div">
        <fieldset><legend>{t}My Title{/t}</legend> <br/>
          here we inser the placeholder for the select(s)
          <br />
          <div id="town_select_div"> Here comes the list containing the towns </div>
          <br />
          <div id ="street_select_div"> Here comes the list containing the streets  </div>
          <br />
          <input type="hidden" id="search_config" name="search_config" value="" />
          <input type="hidden" id="search_sort_column" name="search_sort_column" value=""/>
          <input type="hidden"
            id="search_sort_direction" name="search_sort_direction" value="asc" />
          <input type="hidden" id="search_number" name="search_number" />
        <div id="search_results_div"></div>
        </fieldset>
        </div>

Finally we have to set up the java script file. Open or create the `plugins/search/htdocs/js/Search.ajax.js` file and write:

        AjaxPlugins.Search = {

          handleResponse: function(pluginOutput) {
            //we inject the select input in the HTML
            if (pluginOutput.htmlCode.Adresses_init_town) {
              //should be the same value as the input search_config
              $('town_select_div').innerHTML = pluginOutput.htmlCode.Adresses_init_town;
            }

            if (pluginOutput.htmlCode.Adresses_init_street) {
              //should be the same value as the input search_config
              $('street_select_div').innerHTML = pluginOutput.htmlCode.Adresses_init_street;
            }

            if (pluginOutput.htmlCode.Adresses) {
              //should be the same value as the input search_config
              $('search_results_div').innerHTML = pluginOutput.htmlCode.Adresses;
            }
          }
        };

        /*
        * Search plugin's Actions
        */

        AjaxPlugins.Search.Actions = {};

        AjaxPlugins.Search.Actions.DoIt = {

          buildPostRequest: function(argObject) {
            return AjaxHandler.buildPostRequest();
          }
        };

        function order(column) {

          if (column != $('search_sort_column').value) {
            $('search_sort_column').value = column;
            $('search_sort_direction').value = 'asc';
          } else {
            if ($('search_sort_direction').value == 'asc') {
              $('search_sort_direction').value = 'desc';
            } else {
              $('search_sort_direction').value = 'asc';
            }
          }

          CartoWeb.trigger('Search.DoIt')
        }

        //we empty the old selects and reinsert the new ones instead
        //Please do not forget to modify the search_config input value
        function initializeTown() {
        $('town_select_div').innerHTML = '';
             var myinput = $('search_config');
             myinput.value = 'Adresses_init_town';
             CartoWeb.trigger('Search.DoIt');
        }

        function initializeStreet() {
            $('street_select_div').innerHTML = '';
            var myinput = $('search_config');
            myinput.value = 'Adresses_init_street';
            CartoWeb.trigger('Search.DoIt');
        }

        function search() {
            var myinput = $('search_config');
            myinput.value = 'Adresses';
            CartoWeb.trigger('Search.DoIt');
        }

        function recenter(id){
            if ($('search_config').value == 'Adresses') {
                //id to recenter, be sure there is a hidden input
                //with that name and id in your template! if not, add it
                $('id_recenter_ids').value = id;

                /* if the search database table name is the same
                as the related layer to recenter on, you can set it as below
                   be sure there is a hidden input with that name and id in your template! if not, add it */
                $('id_recenter_layer').value = $('search_config').value;

                /* do not use the recenter_scale parameter
                if you recenter on ids, only use it with recenter on x,y.
                   you can set the recenter scale in location.ini on server side */

                /* do not use the recenter_doit parameter
                if you recenter on ids, only use it with recenter on x,y. */

                CartoWeb.trigger('Location.Recenter');
            }
        }
        Event.observe(window, 'load', initializeTown, true);

For each initialisation, we have to set the correct value inside the search_config input and add a "onload" event in order to initialize the town select when the page is loaded.

    Event.observe(window, 'load', initializeTown, true);

#### 23.3.1.2. Multi table Search

Sometimes, you may want to have a search that is done on multiple tables. It can be done easily. You have just to use the mechanisms presented in the basic sample and create in the `search.ini` a table that looks like this:

        config.parcels.provider.type = table
        config.parcels.provider.table = parcelles, addresses
        config.parcels.provider.aliases = adr_num, num, adr_street, street, adr_zip, zipcode, adr_town, town
        ;//be sure not to have twice the same id column name in different tables (or use aliases)
        config.parcels.provider.id = parcel_id
        config.parcels.provider.columns = parcel_ref, adr_street
        config.parcels.provider.fulltextColumns = parcel_ref, adr_num, adr_street, adr_zip, adr_town,
        config.parcels.provider.sortColumn = parcel_ref, adr_street
        config.parcels.provider.sortDirection = asc
        config.parcels.formatter.type = smarty
        config.parcels.formatter.template = search_results
        config.parcels.provider.labels = parcel_ref, street
        config.parcels.provider.where = "adr_street like '%@adr_street@%' AND addresses.parcel_ref = parcels.parcel_ref"

In this sample we have a search that is done on a table adresses and a table parcels where a parcel contains many adresses. The search return all the streets where the name matches the name given in the form and their corresponding parcel reference.

#### 23.3.1.3. Writing a Provider

### Warning

Documentation below is still in draft state

A provider is an interface implementation that allows you to acess a type of data that is not supported by PEAR DB.

You have to modify the `Search.php`. In this file there is an abstract class that is named :

        abstract class ResultProvider

You have to extend this class :

        class MyResultProvider extends ResultProvider {
            public function getResult(SearchRequest $request) {
                $table = New Table();
                $table->tableId = 'search';
                $table->columnIds = array('col1');
                $table->noRowId = false;
                $table->rows = array('hello world');
                $table->numRows = $dbResult->numRows();
                // Generates pages information
                $table->rowsPage = 1;
                $table->totalPages = 1;
                $table->page = 1; //curent page number
                $result = new SearchResult();
                $result->table = $table;
                return $result;
            }
        }

### Warning

Do not forget to modify the provider in the ini file.

You have to implements the getResult fonction. It should return a Table() instance.

#### 23.3.1.4. Writing a Formatter

### Warning

Documentation below is still in draft state

A formatter allows you to format the result of the query. Actually it returns code generated by Smarty. But if you want to use an other template engine or a more specifique formatter (JSON for example or in our case a smarty with differents delimiters), you have to modify the `ClientSearch.php`

In this file there is an abstract calss :

    abstract class ResponseFormatter

you have to extend this class

        /**
        * Formats a response using a Smarty template with different separators
        * @see ResponseFormatter
        */
        class MYSmartyResponseFormatter extends ResponseFormatter {

            /**
            * @see ResponseFormatter::getResponse()
            */
            public function getResponse(SearchResult $result) {

            $smarty = new Smarty_Plugin($this->plugin->getCartoclient(),
            $this->plugin);
            $smarty->right_delimiter = '[EOF';
            $smarty->left_delimiter  = 'EOF]';
            $smarty->assign('table', $result->table);
            return $smarty->fetch($this->template . '.tpl');
            }
        }

### Warning

Do not forget to modify the formatter in the ini file.

You have to implements the getResponse function. It should return a string.The $result variable correspond to the value return by the provider.