NEW Relation search for GridFieldAddExistingAutocompleter

Now also searches 1:n relations if they where defined in searchableFields() with dot notation.
This commit is contained in:
Ingo Schommer 2012-10-04 15:59:36 +02:00
parent 26c449d69b
commit 8108f7f936
5 changed files with 116 additions and 30 deletions

View File

@ -154,6 +154,8 @@ more specifically the `[api:GridFieldAddExistingAutocompleter]` and `[api:GridFi
They provide a list/detail interface within a single record edited in your ModelAdmin.
The `[GridField](/reference/grid-field)` docs also explain how to manage
extra relation fields on join tables through its detail forms.
The autocompleter can also search attributes on relations,
based on the search fields defined through `[api:DataObject::searchableFields()]`.
## Permissions

View File

@ -28,10 +28,25 @@ class GridFieldAddExistingAutocompleter
protected $searchList;
/**
* Which columns that should be used for doing a "StartsWith" search.
* Define column names which should be included in the search.
* By default, they're searched with a {@link StartsWithFilter}.
* To define custom filters, use the same notation as {@link DataList->filter()},
* e.g. "Name:EndsWith".
*
* If multiple fields are provided, the filtering is performed non-exclusive.
* If no fields are provided, tries to auto-detect a "Title" or "Name" field,
* and falls back to the first textual field defined on the object.
* If no fields are provided, tries to auto-detect fields from
* {@link DataObject->searchableFields()}.
*
* The fields support "dot-notation" for relationships, e.g.
* a entry called "Team.Name" will search through the names of
* a "Team" relationship.
*
* @example
* array(
* 'Name',
* 'Email:StartsWith',
* 'Team.Name'
* )
*
* @var Array
*/
@ -192,15 +207,16 @@ class GridFieldAddExistingAutocompleter
$dataClass));
}
// TODO Replace with DataList->filterAny() once it correctly supports OR connectives
$stmts = array();
$params = array();
foreach($searchFields as $searchField) {
$stmts[] .= sprintf('"%s" LIKE \'%s%%\'', $searchField,
Convert::raw2sql($request->getVar('gridfield_relationsearch')));
$name = (strpos($searchField, ':') !== FALSE) ? $searchField : "$searchField:StartsWith";
$params[$name] = $request->getVar('gridfield_relationsearch');
}
$results = $allList->where(implode(' OR ', $stmts))->subtract($gridField->getList());
$results = $results->sort($searchFields[0], 'ASC');
$results = $results->limit($this->getResultsLimit());
$results = $allList
->subtract($gridField->getList())
->filterAny($params)
->sort(strtok($searchFields[0], ':'), 'ASC')
->limit($this->getResultsLimit());
$json = array();
foreach($results as $result) {
@ -250,20 +266,44 @@ class GridFieldAddExistingAutocompleter
}
/**
* Detect searchable
* Detect searchable fields and searchable relations.
* Falls back to {@link DataObject->summaryFields()} if
* no custom search fields are defined.
*
* @param String
* @return Array
* @param String the class name
* @return Array|null names of the searchable fields
*/
protected function scaffoldSearchFields($dataClass) {
public function scaffoldSearchFields($dataClass) {
$obj = singleton($dataClass);
if($obj->hasDatabaseField('Title')) {
return array('Title');
} else if($obj->hasDatabaseField('Name')) {
return array('Name');
} else {
return null;
$fields = null;
if($fieldSpecs = $obj->searchableFields()) {
$customSearchableFields = $obj->stat('searchable_fields');
foreach($fieldSpecs as $name => $spec) {
if(is_array($spec) && array_key_exists('filter', $spec)) {
// The searchableFields() spec defaults to PartialMatch,
// so we need to check the original setting.
// If the field is defined $searchable_fields = array('MyField'),
// then default to StartsWith filter, which makes more sense in this context.
if(!$customSearchableFields || array_search($name, $customSearchableFields)) {
$filter = 'StartsWith';
} else {
$filter = preg_replace('/Filter$/', '', $spec['filter']);
}
$fields[] = "{$name}:{$filter}";
} else {
$fields[] = $name;
}
}
}
if (is_null($fields)) {
if ($obj->hasDatabaseField('Title')) {
$fields = array('Title');
} elseif ($obj->hasDatabaseField('Name')) {
$fields = array('Name');
}
}
return $fields;
}
/**

View File

@ -472,6 +472,14 @@ class GridFieldTest_Team extends DataObject implements TestOnly {
);
static $many_many = array('Players' => 'GridFieldTest_Player');
static $has_many = array('Cheerleaders' => 'GridFieldTest_Cheerleader');
static $searchable_fields = array(
'Name',
'City',
'Cheerleaders.Name'
);
}
class GridFieldTest_Player extends DataObject implements TestOnly {
@ -483,6 +491,14 @@ class GridFieldTest_Player extends DataObject implements TestOnly {
static $belongs_many_many = array('Teams' => 'GridFieldTest_Team');
}
class GridFieldTest_Cheerleader extends DataObject implements TestOnly {
static $db = array(
'Name' => 'Varchar'
);
static $has_one = array('Team' => 'GridFieldTest_Team');
}
class GridFieldTest_HTMLFragments implements GridField_HTMLProvider, TestOnly{
public function __construct($fragments) {
$this->fragments = $fragments;

View File

@ -3,9 +3,28 @@ class GridFieldAddExistingAutocompleterTest extends FunctionalTest {
static $fixture_file = 'GridFieldTest.yml';
protected $extraDataObjects = array('GridFieldTest_Team', 'GridFieldTest_Player');
protected $extraDataObjects = array('GridFieldTest_Team', 'GridFieldTest_Player', 'GridFieldTest_Cheerleader');
public function testSearch() {
function testScaffoldSearchFields() {
$autoCompleter = new GridFieldAddExistingAutocompleter($targetFragment = 'before', array('Test'));
$gridFieldTest_Team = singleton('GridFieldTest_Team');
$this->assertEquals(
$autoCompleter->scaffoldSearchFields('GridFieldTest_Team'),
array(
'Name:PartialMatch',
'City:StartsWith',
'Cheerleaders.Name:StartsWith'
)
);
$this->assertEquals(
$autoCompleter->scaffoldSearchFields('GridFieldTest_Cheerleader'),
array(
'Name:StartsWith'
)
);
}
function testSearch() {
$team1 = $this->objFromFixture('GridFieldTest_Team', 'team1');
$team2 = $this->objFromFixture('GridFieldTest_Team', 'team2');
@ -17,21 +36,26 @@ class GridFieldAddExistingAutocompleterTest extends FunctionalTest {
$response = $this->post(
'GridFieldAddExistingAutocompleterTest_Controller/Form/field/testfield/search'
. '/?gridfield_relationsearch=Team 2',
array(
(string)$btns[0]['name'] => 1
)
array((string)$btns[0]['name'] => 1)
);
$this->assertFalse($response->isError());
$result = Convert::json2array($response->getBody());
$this->assertEquals(1, count($result));
$this->assertEquals(array($team2->ID => 'Team 2'), $result);
$response = $this->post(
'GridFieldAddExistingAutocompleterTest_Controller/Form/field/testfield/'
. 'search/?gridfield_relationsearch=Heather',
array((string)$btns[0]['name'] => 1)
);
$this->assertFalse($response->isError());
$result = Convert::json2array($response->getBody());
$this->assertEquals(1, count($result), "The relational filter did not work");
$response = $this->post(
'GridFieldAddExistingAutocompleterTest_Controller/Form/field/testfield/search'
. '/?gridfield_relationsearch=Unknown',
array(
(string)$btns[0]['name'] => 1
)
array((string)$btns[0]['name'] => 1)
);
$this->assertFalse($response->isError());
$result = Convert::json2array($response->getBody());
@ -78,7 +102,7 @@ class GridFieldAddExistingAutocompleterTest_Controller extends Controller implem
public function Form() {
$player = DataObject::get('GridFieldTest_Player')->find('Email', 'player1@test.com');
$config = GridFieldConfig::create()->addComponents(
$relationComponent = new GridFieldAddExistingAutocompleter('before', 'Name'),
$relationComponent = new GridFieldAddExistingAutocompleter('before'),
new GridFieldDataColumns()
);
$field = new GridField('testfield', 'testfield', $player->Teams(), $config);

View File

@ -15,4 +15,8 @@ GridFieldTest_Player:
player1_team1:
Name: Player 1
Email: player1@test.com
Teams: =>GridFieldTest_Team.team1
Teams: =>GridFieldTest_Team.team1
GridFieldTest_Cheerleader:
cheerleader1_team1:
Name: Heather
Team: =>GridFieldTest_Team.team4