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 !
Introduction
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.
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.inisee 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 24.3.1.1, “Guided Search”)
- user-defined (see Section 24.3.1.5, “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');
}
}
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="">  </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="">  </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);
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.
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.
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 ordre to recenter on and hilight search results.
This option is only available for client-side search configurations.
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.