< ToolTips

OGC Layer Loader  >

24. Search [plugin]

24.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 !

24.2. Basic Usage

Introduction

24.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.

24.3. Basic Sample

24.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 :
  • 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');
      } 
    }

                    

24.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);

24.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.

24.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.

24.3.1.4. Recenter and Hilight

In previous samples, recentering is done afterwards, by clicking on on result. If you want to recenter and hilight on search results directly during the search, you can add those two lines in search.ini:

    config.my_config.provider.recenter = recenter_layer
    config.my_config.provider.hilight = hilight_layer                               
            

The two layers must exist in the mapfile. They will be used in order to recenter on and hilight search results.

It is interesting to note that this feature uses plugin Query (see Chapter 9, Queries [coreplugin] (query)). So hilight option can be used as a tool to help the user selecting objects on the map. He can select objects either by clicking on the map or using Search plugin.

HTTP Post parameter search_selection can be used to choose whether Search plugin will add objects to or remove them from selection. Setting search_selection to "plus" will tell the plugin to add objects to selection, while setting it to "minus" will tell the plugin to remove them from selection. For instance:

            
  <input type="hidden" id="search_selection" name="search_selection" />
  <input type="submit" value="{t}Add to Selection{/t}" class="form_button"
           onclick="javascript: $('search_selection').value = 'plus';
                                search('layer');
                                return false;" />
  <input type="submit" value="{t}Remove From Selection{/t}" class="form_button"
           onclick="javascript: $('search_selection').value = 'minus';
                                search('layer');
                                return false;" />
                              
            

This option is only available for client-side search configurations.

24.3.1.5. 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.

valid xhtml 1.0 valid css