mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
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:
parent
26c449d69b
commit
8108f7f936
@ -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.
|
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
|
The `[GridField](/reference/grid-field)` docs also explain how to manage
|
||||||
extra relation fields on join tables through its detail forms.
|
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
|
## Permissions
|
||||||
|
|
||||||
|
@ -28,10 +28,25 @@ class GridFieldAddExistingAutocompleter
|
|||||||
protected $searchList;
|
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 multiple fields are provided, the filtering is performed non-exclusive.
|
||||||
* If no fields are provided, tries to auto-detect a "Title" or "Name" field,
|
* If no fields are provided, tries to auto-detect fields from
|
||||||
* and falls back to the first textual field defined on the object.
|
* {@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
|
* @var Array
|
||||||
*/
|
*/
|
||||||
@ -192,15 +207,16 @@ class GridFieldAddExistingAutocompleter
|
|||||||
$dataClass));
|
$dataClass));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Replace with DataList->filterAny() once it correctly supports OR connectives
|
$params = array();
|
||||||
$stmts = array();
|
|
||||||
foreach($searchFields as $searchField) {
|
foreach($searchFields as $searchField) {
|
||||||
$stmts[] .= sprintf('"%s" LIKE \'%s%%\'', $searchField,
|
$name = (strpos($searchField, ':') !== FALSE) ? $searchField : "$searchField:StartsWith";
|
||||||
Convert::raw2sql($request->getVar('gridfield_relationsearch')));
|
$params[$name] = $request->getVar('gridfield_relationsearch');
|
||||||
}
|
}
|
||||||
$results = $allList->where(implode(' OR ', $stmts))->subtract($gridField->getList());
|
$results = $allList
|
||||||
$results = $results->sort($searchFields[0], 'ASC');
|
->subtract($gridField->getList())
|
||||||
$results = $results->limit($this->getResultsLimit());
|
->filterAny($params)
|
||||||
|
->sort(strtok($searchFields[0], ':'), 'ASC')
|
||||||
|
->limit($this->getResultsLimit());
|
||||||
|
|
||||||
$json = array();
|
$json = array();
|
||||||
foreach($results as $result) {
|
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
|
* @param String the class name
|
||||||
* @return Array
|
* @return Array|null names of the searchable fields
|
||||||
*/
|
*/
|
||||||
protected function scaffoldSearchFields($dataClass) {
|
public function scaffoldSearchFields($dataClass) {
|
||||||
$obj = singleton($dataClass);
|
$obj = singleton($dataClass);
|
||||||
if($obj->hasDatabaseField('Title')) {
|
$fields = null;
|
||||||
return array('Title');
|
if($fieldSpecs = $obj->searchableFields()) {
|
||||||
} else if($obj->hasDatabaseField('Name')) {
|
$customSearchableFields = $obj->stat('searchable_fields');
|
||||||
return array('Name');
|
foreach($fieldSpecs as $name => $spec) {
|
||||||
} else {
|
if(is_array($spec) && array_key_exists('filter', $spec)) {
|
||||||
return null;
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -472,6 +472,14 @@ class GridFieldTest_Team extends DataObject implements TestOnly {
|
|||||||
);
|
);
|
||||||
|
|
||||||
static $many_many = array('Players' => 'GridFieldTest_Player');
|
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 {
|
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');
|
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{
|
class GridFieldTest_HTMLFragments implements GridField_HTMLProvider, TestOnly{
|
||||||
public function __construct($fragments) {
|
public function __construct($fragments) {
|
||||||
$this->fragments = $fragments;
|
$this->fragments = $fragments;
|
||||||
|
@ -3,9 +3,28 @@ class GridFieldAddExistingAutocompleterTest extends FunctionalTest {
|
|||||||
|
|
||||||
static $fixture_file = 'GridFieldTest.yml';
|
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');
|
$team1 = $this->objFromFixture('GridFieldTest_Team', 'team1');
|
||||||
$team2 = $this->objFromFixture('GridFieldTest_Team', 'team2');
|
$team2 = $this->objFromFixture('GridFieldTest_Team', 'team2');
|
||||||
|
|
||||||
@ -17,21 +36,26 @@ class GridFieldAddExistingAutocompleterTest extends FunctionalTest {
|
|||||||
$response = $this->post(
|
$response = $this->post(
|
||||||
'GridFieldAddExistingAutocompleterTest_Controller/Form/field/testfield/search'
|
'GridFieldAddExistingAutocompleterTest_Controller/Form/field/testfield/search'
|
||||||
. '/?gridfield_relationsearch=Team 2',
|
. '/?gridfield_relationsearch=Team 2',
|
||||||
array(
|
array((string)$btns[0]['name'] => 1)
|
||||||
(string)$btns[0]['name'] => 1
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
$this->assertFalse($response->isError());
|
$this->assertFalse($response->isError());
|
||||||
$result = Convert::json2array($response->getBody());
|
$result = Convert::json2array($response->getBody());
|
||||||
$this->assertEquals(1, count($result));
|
$this->assertEquals(1, count($result));
|
||||||
$this->assertEquals(array($team2->ID => 'Team 2'), $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(
|
$response = $this->post(
|
||||||
'GridFieldAddExistingAutocompleterTest_Controller/Form/field/testfield/search'
|
'GridFieldAddExistingAutocompleterTest_Controller/Form/field/testfield/search'
|
||||||
. '/?gridfield_relationsearch=Unknown',
|
. '/?gridfield_relationsearch=Unknown',
|
||||||
array(
|
array((string)$btns[0]['name'] => 1)
|
||||||
(string)$btns[0]['name'] => 1
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
$this->assertFalse($response->isError());
|
$this->assertFalse($response->isError());
|
||||||
$result = Convert::json2array($response->getBody());
|
$result = Convert::json2array($response->getBody());
|
||||||
@ -78,7 +102,7 @@ class GridFieldAddExistingAutocompleterTest_Controller extends Controller implem
|
|||||||
public function Form() {
|
public function Form() {
|
||||||
$player = DataObject::get('GridFieldTest_Player')->find('Email', 'player1@test.com');
|
$player = DataObject::get('GridFieldTest_Player')->find('Email', 'player1@test.com');
|
||||||
$config = GridFieldConfig::create()->addComponents(
|
$config = GridFieldConfig::create()->addComponents(
|
||||||
$relationComponent = new GridFieldAddExistingAutocompleter('before', 'Name'),
|
$relationComponent = new GridFieldAddExistingAutocompleter('before'),
|
||||||
new GridFieldDataColumns()
|
new GridFieldDataColumns()
|
||||||
);
|
);
|
||||||
$field = new GridField('testfield', 'testfield', $player->Teams(), $config);
|
$field = new GridField('testfield', 'testfield', $player->Teams(), $config);
|
||||||
|
@ -15,4 +15,8 @@ GridFieldTest_Player:
|
|||||||
player1_team1:
|
player1_team1:
|
||||||
Name: Player 1
|
Name: Player 1
|
||||||
Email: player1@test.com
|
Email: player1@test.com
|
||||||
Teams: =>GridFieldTest_Team.team1
|
Teams: =>GridFieldTest_Team.team1
|
||||||
|
GridFieldTest_Cheerleader:
|
||||||
|
cheerleader1_team1:
|
||||||
|
Name: Heather
|
||||||
|
Team: =>GridFieldTest_Team.team4
|
Loading…
x
Reference in New Issue
Block a user