Merge pull request #7206 from open-sausages/pulls/4.0/select-them-all-again

Enhancement add support for TreeMultiselectField in react
This commit is contained in:
Damian Mooyman 2017-07-27 13:41:18 +12:00 committed by GitHub
commit 697798b464
3 changed files with 258 additions and 103 deletions

View File

@ -94,12 +94,19 @@ class TreeDropdownField extends FormField
protected $keyField = null; protected $keyField = null;
/** /**
* Name of lavel field on underlying object * Name of label field on underlying object
* *
* @var string * @var string
*/ */
protected $labelField = null; protected $labelField = null;
/**
* Similar to labelField but for non-html equivalent of field
*
* @var string
*/
protected $titleField = 'Title';
/** /**
* Callback for filtering records * Callback for filtering records
* *
@ -159,7 +166,7 @@ class TreeDropdownField extends FormField
* @var array * @var array
*/ */
protected $searchIds = []; protected $searchIds = [];
/** /**
* List of ids which matches the search result * List of ids which matches the search result
* This excludes parents of search result children * This excludes parents of search result children
@ -210,15 +217,15 @@ class TreeDropdownField extends FormField
if (!DataObject::has_extension($sourceObject, Hierarchy::class)) { if (!DataObject::has_extension($sourceObject, Hierarchy::class)) {
throw new InvalidArgumentException("SourceObject must have Hierarchy extension"); throw new InvalidArgumentException("SourceObject must have Hierarchy extension");
} }
$this->sourceObject = $sourceObject; $this->setSourceObject($sourceObject);
$this->keyField = $keyField; $this->setKeyField($keyField);
$this->labelField = $labelField; $this->setLabelField($labelField);
$this->showSearch = $showSearch; $this->setShowSearch($showSearch);
// Extra settings for Folders // Extra settings for Folders
if (strcasecmp($sourceObject, Folder::class) === 0) { if (strcasecmp($sourceObject, Folder::class) === 0) {
$this->childrenMethod = 'ChildFolders'; $this->setChildrenMethod('ChildFolders');
$this->numChildrenMethod = 'numChildFolders'; $this->setNumChildrenMethod('numChildFolders');
} }
$this->addExtraClass('single'); $this->addExtraClass('single');
@ -226,6 +233,17 @@ class TreeDropdownField extends FormField
parent::__construct($name, $title); parent::__construct($name, $title);
} }
/**
* Set the ID of the root node of the tree. This defaults to 0 - i.e.
* displays the whole tree.
*
* @return int
*/
public function getTreeBaseID()
{
return $this->baseID;
}
/** /**
* Set the ID of the root node of the tree. This defaults to 0 - i.e. * Set the ID of the root node of the tree. This defaults to 0 - i.e.
* displays the whole tree. * displays the whole tree.
@ -239,11 +257,22 @@ class TreeDropdownField extends FormField
return $this; return $this;
} }
/**
* Get a callback used to filter the values of the tree before
* displaying to the user.
*
* @return callable
*/
public function getFilterFunction()
{
return $this->filterCallback;
}
/** /**
* Set a callback used to filter the values of the tree before * Set a callback used to filter the values of the tree before
* displaying to the user. * displaying to the user.
* *
* @param callback $callback * @param callable $callback
* @return $this * @return $this
*/ */
public function setFilterFunction($callback) public function setFilterFunction($callback)
@ -256,10 +285,20 @@ class TreeDropdownField extends FormField
return $this; return $this;
} }
/**
* Get the callback used to disable checkboxes for some items in the tree
*
* @return callable
*/
public function getDisableFunction()
{
return $this->disableCallback;
}
/** /**
* Set a callback used to disable checkboxes for some items in the tree * Set a callback used to disable checkboxes for some items in the tree
* *
* @param callback $callback * @param callable $callback
* @return $this * @return $this
*/ */
public function setDisableFunction($callback) public function setDisableFunction($callback)
@ -276,7 +315,18 @@ class TreeDropdownField extends FormField
* Set a callback used to search the hierarchy globally, even before * Set a callback used to search the hierarchy globally, even before
* applying the filter. * applying the filter.
* *
* @param callback $callback * @return callable
*/
public function getSearchFunction()
{
return $this->searchCallback;
}
/**
* Set a callback used to search the hierarchy globally, even before
* applying the filter.
*
* @param callable $callback
* @return $this * @return $this
*/ */
public function setSearchFunction($callback) public function setSearchFunction($callback)
@ -309,6 +359,16 @@ class TreeDropdownField extends FormField
return $this; return $this;
} }
/**
* Get method to invoke on each node to get the child collection
*
* @return string
*/
public function getChildrenMethod()
{
return $this->childrenMethod;
}
/** /**
* @param string $method The parameter to ChildrenMethod to use when calling Hierarchy->getChildrenAsUL in * @param string $method The parameter to ChildrenMethod to use when calling Hierarchy->getChildrenAsUL in
* {@link Hierarchy}. The method specified determines the structure of the returned list. Use "ChildFolders" * {@link Hierarchy}. The method specified determines the structure of the returned list. Use "ChildFolders"
@ -324,6 +384,16 @@ class TreeDropdownField extends FormField
return $this; return $this;
} }
/**
* Get method to invoke on nodes to count children
*
* @return string
*/
public function getNumChildrenMethod()
{
return $this->numChildrenMethod;
}
/** /**
* @param string $method The parameter to numChildrenMethod to use when calling Hierarchy->getChildrenAsUL in * @param string $method The parameter to numChildrenMethod to use when calling Hierarchy->getChildrenAsUL in
* {@link Hierarchy}. Should be used in conjunction with setChildrenMethod(). * {@link Hierarchy}. Should be used in conjunction with setChildrenMethod().
@ -344,9 +414,9 @@ class TreeDropdownField extends FormField
{ {
$record = $this->Value() ? $this->objectForKey($this->Value()) : null; $record = $this->Value() ? $this->objectForKey($this->Value()) : null;
if ($record instanceof ViewableData) { if ($record instanceof ViewableData) {
$title = $record->obj($this->labelField)->forTemplate(); $title = $record->obj($this->getLabelField())->forTemplate();
} elseif ($record) { } elseif ($record) {
$title = Convert::raw2xml($record->{$this->labelField}); $title = Convert::raw2xml($record->{$this->getLabelField()});
} else { } else {
$title = $this->getEmptyString(); $title = $this->getEmptyString();
} }
@ -354,7 +424,7 @@ class TreeDropdownField extends FormField
// TODO Implement for TreeMultiSelectField // TODO Implement for TreeMultiSelectField
$metadata = array( $metadata = array(
'id' => $record ? $record->ID : null, 'id' => $record ? $record->ID : null,
'ClassName' => $record ? $record->ClassName : $this->sourceObject 'ClassName' => $record ? $record->ClassName : $this->getSourceObject()
); );
$properties = array_merge( $properties = array_merge(
@ -371,7 +441,7 @@ class TreeDropdownField extends FormField
public function extraClass() public function extraClass()
{ {
return implode(' ', array(parent::extraClass(), ($this->showSearch ? "searchable" : null))); return implode(' ', array(parent::extraClass(), ($this->getShowSearch() ? "searchable" : null)));
} }
/** /**
@ -387,11 +457,10 @@ class TreeDropdownField extends FormField
$isSubTree = false; $isSubTree = false;
$this->search = $request->requestVar('search'); $this->search = $request->requestVar('search');
$flatlist = $request->requestVar('flatList');
$id = (is_numeric($request->latestParam('ID'))) $id = (is_numeric($request->latestParam('ID')))
? (int)$request->latestParam('ID') ? (int)$request->latestParam('ID')
: (int)$request->requestVar('ID'); : (int)$request->requestVar('ID');
// pre-process the tree - search needs to operate globally, not locally as marking filter does // pre-process the tree - search needs to operate globally, not locally as marking filter does
if ($this->search) { if ($this->search) {
$this->populateIDs(); $this->populateIDs();
@ -399,29 +468,30 @@ class TreeDropdownField extends FormField
/** @var DataObject|Hierarchy $obj */ /** @var DataObject|Hierarchy $obj */
$obj = null; $obj = null;
$sourceObject = $this->getSourceObject();
if ($id && !$request->requestVar('forceFullTree')) { if ($id && !$request->requestVar('forceFullTree')) {
$obj = DataObject::get_by_id($this->sourceObject, $id); $obj = DataObject::get_by_id($sourceObject, $id);
$isSubTree = true; $isSubTree = true;
if (!$obj) { if (!$obj) {
throw new Exception( throw new Exception(
"TreeDropdownField->tree(): the object #$id of type $this->sourceObject could not be found" "TreeDropdownField->tree(): the object #$id of type $sourceObject could not be found"
); );
} }
} else { } else {
if ($this->baseID) { if ($this->getTreeBaseID()) {
$obj = DataObject::get_by_id($this->sourceObject, $this->baseID); $obj = DataObject::get_by_id($sourceObject, $this->getTreeBaseID());
} }
if (!$this->baseID || !$obj) { if (!$this->getTreeBaseID() || !$obj) {
$obj = DataObject::singleton($this->sourceObject); $obj = DataObject::singleton($sourceObject);
} }
} }
// Create marking set // Create marking set
$markingSet = MarkedSet::create($obj, $this->childrenMethod, $this->numChildrenMethod, 30); $markingSet = MarkedSet::create($obj, $this->getChildrenMethod(), $this->getNumChildrenMethod(), 30);
// Set filter on searched nodes // Set filter on searched nodes
if ($this->filterCallback || $this->search) { if ($this->getFilterFunction() || $this->search) {
// Rely on filtering to limit tree // Rely on filtering to limit tree
$markingSet->setMarkingFilterFunction(function ($node) { $markingSet->setMarkingFilterFunction(function ($node) {
return $this->filterMarking($node); return $this->filterMarking($node);
@ -452,9 +522,9 @@ class TreeDropdownField extends FormField
$customised = function (DataObject $child) use ($isSubTree) { $customised = function (DataObject $child) use ($isSubTree) {
return [ return [
'name' => $this->getName(), 'name' => $this->getName(),
'id' => $child->obj($this->keyField), 'id' => $child->obj($this->getKeyField()),
'title' => $child->getTitle(), 'title' => $child->obj($this->getTitleField()),
'treetitle' => $child->obj($this->labelField), 'treetitle' => $child->obj($this->getLabelField()),
'disabled' => $this->nodeIsDisabled($child), 'disabled' => $this->nodeIsDisabled($child),
'isSubTree' => $isSubTree 'isSubTree' => $isSubTree
]; ];
@ -465,8 +535,8 @@ class TreeDropdownField extends FormField
// Format JSON output // Format JSON output
$json = $markingSet $json = $markingSet
->getChildrenAsArray($customised); ->getChildrenAsArray($customised);
if ($flatlist) { if ($request->requestVar('flatList')) {
// format and filter $json here // format and filter $json here
$json['children'] = $this->flattenChildrenArray($json['children']); $json['children'] = $this->flattenChildrenArray($json['children']);
} }
@ -495,7 +565,8 @@ class TreeDropdownField extends FormField
*/ */
public function filterMarking($node) public function filterMarking($node)
{ {
if ($this->filterCallback && !call_user_func($this->filterCallback, $node)) { $callback = $this->getFilterFunction();
if ($callback && !call_user_func($callback, $node)) {
return false; return false;
} }
@ -513,7 +584,8 @@ class TreeDropdownField extends FormField
*/ */
public function nodeIsDisabled($node) public function nodeIsDisabled($node)
{ {
return ($this->disableCallback && call_user_func($this->disableCallback, $node)); $callback = $this->getDisableFunction();
return $callback && call_user_func($callback, $node);
} }
/** /**
@ -527,13 +599,35 @@ class TreeDropdownField extends FormField
} }
/** /**
* @return String * @return string
*/ */
public function getLabelField() public function getLabelField()
{ {
return $this->labelField; return $this->labelField;
} }
/**
* Field to use for item titles
*
* @return string
*/
public function getTitleField()
{
return $this->titleField;
}
/**
* Set field to use for item title
*
* @param string $field
* @return $this
*/
public function setTitleField($field)
{
$this->titleField = $field;
return $this;
}
/** /**
* @param string $field * @param string $field
* @return $this * @return $this
@ -563,13 +657,15 @@ class TreeDropdownField extends FormField
} }
/** /**
* @return String * Get class of source object
*
* @return string
*/ */
public function getSourceObject() public function getSourceObject()
{ {
return $this->sourceObject; return $this->sourceObject;
} }
/** /**
* Flattens a given list of children array items, so the data is no longer * Flattens a given list of children array items, so the data is no longer
* structured in a hierarchy * structured in a hierarchy
@ -583,24 +679,24 @@ class TreeDropdownField extends FormField
protected function flattenChildrenArray($children, $parentTitles = []) protected function flattenChildrenArray($children, $parentTitles = [])
{ {
$output = []; $output = [];
foreach ($children as $child) { foreach ($children as $child) {
$childTitles = array_merge($parentTitles, [$child['title']]); $childTitles = array_merge($parentTitles, [$child['title']]);
$grandChildren = $child['children']; $grandChildren = $child['children'];
$contextString = implode('/', $parentTitles); $contextString = implode('/', $parentTitles);
$child['contextString'] = ($contextString !== '') ? $contextString .'/' : ''; $child['contextString'] = ($contextString !== '') ? $contextString .'/' : '';
$child['children'] = []; unset($child['children']);
if (!$this->search || in_array($child['id'], $this->realSearchIds)) { if (!$this->search || in_array($child['id'], $this->realSearchIds)) {
$output[] = $child; $output[] = $child;
} }
$output = array_merge($output, $this->flattenChildrenArray($grandChildren, $childTitles)); $output = array_merge($output, $this->flattenChildrenArray($grandChildren, $childTitles));
} }
return $output; return $output;
} }
/** /**
* Populate $this->searchIds with the IDs of the pages matching the searched parameter and their parents. * Populate $this->searchIds with the IDs of the pages matching the searched parameter and their parents.
* Reverse-constructs the tree starting from the leaves. Initially taken from CMSSiteTreeFilter, but modified * Reverse-constructs the tree starting from the leaves. Initially taken from CMSSiteTreeFilter, but modified
@ -610,11 +706,11 @@ class TreeDropdownField extends FormField
{ {
// get all the leaves to be displayed // get all the leaves to be displayed
$res = $this->getSearchResults(); $res = $this->getSearchResults();
if (!$res) { if (!$res) {
return; return;
} }
// iteratively fetch the parents in bulk, until all the leaves can be accessed using the tree control // iteratively fetch the parents in bulk, until all the leaves can be accessed using the tree control
foreach ($res as $row) { foreach ($res as $row) {
if ($row->ParentID) { if ($row->ParentID) {
@ -624,7 +720,7 @@ class TreeDropdownField extends FormField
} }
$this->realSearchIds = $res->column(); $this->realSearchIds = $res->column();
$sourceObject = $this->sourceObject; $sourceObject = $this->getSourceObject();
while (!empty($parents)) { while (!empty($parents)) {
$items = DataObject::get($sourceObject) $items = DataObject::get($sourceObject)
@ -640,7 +736,7 @@ class TreeDropdownField extends FormField
} }
} }
} }
/** /**
* Get the DataObjects that matches the searched parameter. * Get the DataObjects that matches the searched parameter.
* *
@ -648,14 +744,15 @@ class TreeDropdownField extends FormField
*/ */
protected function getSearchResults() protected function getSearchResults()
{ {
if ($this->searchCallback) { $callback = $this->getSearchFunction();
return call_user_func($this->searchCallback, $this->sourceObject, $this->labelField, $this->search); if ($callback) {
return call_user_func($callback, $this->getSourceObject(), $this->getLabelField(), $this->search);
} }
$sourceObject = $this->sourceObject; $sourceObject = $this->getSourceObject();
$filters = array(); $filters = array();
if (singleton($sourceObject)->hasDatabaseField($this->labelField)) { if (singleton($sourceObject)->hasDatabaseField($this->getLabelField())) {
$filters["{$this->labelField}:PartialMatch"] = $this->search; $filters["{$this->getLabelField()}:PartialMatch"] = $this->search;
} else { } else {
if (singleton($sourceObject)->hasDatabaseField('Title')) { if (singleton($sourceObject)->hasDatabaseField('Title')) {
$filters["Title:PartialMatch"] = $this->search; $filters["Title:PartialMatch"] = $this->search;
@ -669,10 +766,10 @@ class TreeDropdownField extends FormField
throw new InvalidArgumentException(sprintf( throw new InvalidArgumentException(sprintf(
'Cannot query by %s.%s, not a valid database column', 'Cannot query by %s.%s, not a valid database column',
$sourceObject, $sourceObject,
$this->labelField $this->getLabelField()
)); ));
} }
return DataObject::get($this->sourceObject)->filterAny($filters); return DataObject::get($this->getSourceObject())->filterAny($filters);
} }
/** /**
@ -683,8 +780,8 @@ class TreeDropdownField extends FormField
*/ */
protected function objectForKey($key) protected function objectForKey($key)
{ {
return DataObject::get($this->sourceObject) return DataObject::get($this->getSourceObject())
->filter($this->keyField, $key) ->filter($this->getKeyField(), $key)
->first(); ->first();
} }
@ -695,9 +792,10 @@ class TreeDropdownField extends FormField
{ {
/** @var TreeDropdownField_Readonly $copy */ /** @var TreeDropdownField_Readonly $copy */
$copy = $this->castedCopy(TreeDropdownField_Readonly::class); $copy = $this->castedCopy(TreeDropdownField_Readonly::class);
$copy->setKeyField($this->keyField); $copy->setKeyField($this->getKeyField());
$copy->setLabelField($this->labelField); $copy->setLabelField($this->getLabelField());
$copy->setSourceObject($this->sourceObject); $this->setTitleField($this->getTitleField());
$copy->setSourceObject($this->getSourceObject());
return $copy; return $copy;
} }
@ -710,7 +808,7 @@ class TreeDropdownField extends FormField
$field = $classOrCopy; $field = $classOrCopy;
if (!is_object($field)) { if (!is_object($field)) {
$field = new $classOrCopy($this->name, $this->title, $this->sourceObject); $field = new $classOrCopy($this->name, $this->title, $this->getSourceObject());
} }
return parent::castedCopy($field); return parent::castedCopy($field);
@ -719,17 +817,15 @@ class TreeDropdownField extends FormField
public function getSchemaStateDefaults() public function getSchemaStateDefaults()
{ {
$data = parent::getSchemaStateDefaults(); $data = parent::getSchemaStateDefaults();
// Check label for field
$record = $this->Value() ? $this->objectForKey($this->Value()) : null; $record = $this->Value() ? $this->objectForKey($this->Value()) : null;
$selectedlabel = null;
// Ensure cache is keyed by last modified date of the underlying list // Ensure cache is keyed by last modified date of the underlying list
$data['data']['cacheKey'] = DataList::create($this->sourceObject)->max('LastEdited'); $data['data']['cacheKey'] = DataList::create($this->getSourceObject())->max('LastEdited');
if ($record) { if ($record) {
$data['data']['valueObject'] = [ $data['data']['valueObject'] = [
'id' => $record->getField($this->keyField), 'id' => $record->obj($this->getKeyField())->getValue(),
'title' => $record->getTitle(), 'title' => $record->obj($this->getTitleField())->getValue(),
'treetitle' => $record->obj($this->labelField)->getSchemaValue(), 'treetitle' => $record->obj($this->getLabelField())->getSchemaValue(),
]; ];
} }
@ -741,9 +837,10 @@ class TreeDropdownField extends FormField
$data = parent::getSchemaDataDefaults(); $data = parent::getSchemaDataDefaults();
$data['data'] = array_merge($data['data'], [ $data['data'] = array_merge($data['data'], [
'urlTree' => $this->Link('tree'), 'urlTree' => $this->Link('tree'),
'showSearch' => $this->showSearch, 'showSearch' => $this->getShowSearch(),
'emptyString' => $this->getEmptyString(), 'emptyString' => $this->getEmptyString(),
'hasEmptyDefault' => $this->getHasEmptyDefault(), 'hasEmptyDefault' => $this->getHasEmptyDefault(),
'multiple' => false,
]); ]);
return $data; return $data;
@ -791,7 +888,7 @@ class TreeDropdownField extends FormField
return $this->emptyString; return $this->emptyString;
} }
$item = DataObject::singleton($this->sourceObject); $item = DataObject::singleton($this->getSourceObject());
$emptyString = _t( $emptyString = _t(
'SilverStripe\\Forms\\DropdownField.CHOOSE_MODEL', 'SilverStripe\\Forms\\DropdownField.CHOOSE_MODEL',
'(Choose {name})', '(Choose {name})',

View File

@ -8,7 +8,7 @@ class TreeDropdownField_Readonly extends TreeDropdownField
public function Field($properties = array()) public function Field($properties = array())
{ {
$fieldName = $this->labelField; $fieldName = $this->getLabelField();
if ($this->value) { if ($this->value) {
$keyObj = $this->objectForKey($this->value); $keyObj = $this->objectForKey($this->value);
$obj = $keyObj ? $keyObj->$fieldName : ''; $obj = $keyObj ? $keyObj->$fieldName : '';
@ -23,7 +23,6 @@ class TreeDropdownField_Readonly extends TreeDropdownField
$field = new LookupField($this->name, $this->title, $source); $field = new LookupField($this->name, $this->title, $source);
$field->setValue($this->value); $field->setValue($this->value);
$field->setForm($this->form); $field->setForm($this->form);
$field->dontEscape = true;
return $field->Field(); return $field->Field();
} }
} }

View File

@ -5,9 +5,11 @@ namespace SilverStripe\Forms;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataObjectInterface; use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\ORM\FieldType\DBHTMLText; use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\Security\Group;
use SilverStripe\View\ViewableData; use SilverStripe\View\ViewableData;
use stdClass; use stdClass;
@ -54,52 +56,106 @@ use stdClass;
*/ */
class TreeMultiselectField extends TreeDropdownField class TreeMultiselectField extends TreeDropdownField
{ {
public function __construct($name, $title = null, $sourceObject = "SilverStripe\\Security\\Group", $keyField = "ID", $labelField = "Title") public function __construct(
{ $name,
$title = null,
$sourceObject = Group::class,
$keyField = "ID",
$labelField = "Title"
) {
parent::__construct($name, $title, $sourceObject, $keyField, $labelField); parent::__construct($name, $title, $sourceObject, $keyField, $labelField);
$this->removeExtraClass('single'); $this->removeExtraClass('single');
$this->addExtraClass('multiple'); $this->addExtraClass('multiple');
$this->value = 'unchanged'; $this->value = 'unchanged';
} }
public function getSchemaDataDefaults()
{
$data = parent::getSchemaDataDefaults();
$data['data'] = array_merge($data['data'], [
'hasEmptyDefault' => false,
'multiple' => true,
]);
return $data;
}
public function getSchemaStateDefaults()
{
$data = parent::getSchemaStateDefaults();
unset($data['data']['valueObject']);
$items = $this->getItems();
$values = [];
foreach ($items as $item) {
if ($item instanceof DataObject) {
$values[] = [
'id' => $item->obj($this->getKeyField())->getValue(),
'title' => $item->obj($this->getTitleField())->getValue(),
'parentid' => $item->ParentID,
'treetitle' => $item->obj($this->getLabelField())->getSchemaValue(),
];
} else {
$values[] = $item;
}
}
$data['data']['valueObjects'] = $values;
// cannot rely on $this->value as this could be a many-many relationship
$value = array_column($values, 'id');
$data['value'] = ($value) ? $value : 'unchanged';
return $data;
}
/** /**
* Return this field's linked items * Return this field's linked items
* @return ArrayList|DataList $items
*/ */
public function getItems() public function getItems()
{ {
// If the value has been set, use that $items = new ArrayList();
if ($this->value != 'unchanged' && is_array($this->sourceObject)) {
$items = array();
$values = is_array($this->value) ? $this->value : preg_split('/ *, */', trim($this->value));
foreach ($values as $value) {
$item = new stdClass;
$item->ID = $value;
$item->Title = $this->sourceObject[$value];
$items[] = $item;
}
return $items;
// Otherwise, look data up from the linked relation // If the value has been set, use that
} if ($this->value != 'unchanged' && is_string($this->value)) { if ($this->value != 'unchanged') {
$items = new ArrayList(); $sourceObject = $this->getSourceObject();
$ids = explode(',', $this->value); if (is_array($sourceObject)) {
foreach ($ids as $id) { $values = is_array($this->value) ? $this->value : preg_split('/ *, */', trim($this->value));
if (!is_numeric($id)) {
continue; foreach ($values as $value) {
} $item = new stdClass;
$item = DataObject::get_by_id($this->sourceObject, $id); $item->ID = $value;
if ($item) { $item->Title = $sourceObject[$value];
$items->push($item); $items->push($item);
} }
return $items;
} }
return $items;
} elseif ($this->form) { // Otherwise, look data up from the linked relation
if (is_string($this->value)) {
$ids = explode(',', $this->value);
foreach ($ids as $id) {
if (!is_numeric($id)) {
continue;
}
$item = DataObject::get_by_id($sourceObject, $id);
if ($item) {
$items->push($item);
}
}
return $items;
}
}
if ($this->form) {
$fieldName = $this->name; $fieldName = $this->name;
$record = $this->form->getRecord(); $record = $this->form->getRecord();
if (is_object($record) && $record->hasMethod($fieldName)) { if (is_object($record) && $record->hasMethod($fieldName)) {
return $record->$fieldName(); return $record->$fieldName();
} }
} }
return $items;
} }
/** /**
@ -121,8 +177,8 @@ class TreeMultiselectField extends TreeDropdownField
foreach ($items as $item) { foreach ($items as $item) {
$idArray[] = $item->ID; $idArray[] = $item->ID;
$titleArray[] = ($item instanceof ViewableData) $titleArray[] = ($item instanceof ViewableData)
? $item->obj($this->labelField)->forTemplate() ? $item->obj($this->getLabelField())->forTemplate()
: Convert::raw2xml($item->{$this->labelField}); : Convert::raw2xml($item->{$this->getLabelField()});
} }
$title = implode(", ", $titleArray); $title = implode(", ", $titleArray);
@ -161,7 +217,7 @@ class TreeMultiselectField extends TreeDropdownField
{ {
// Detect whether this field has actually been updated // Detect whether this field has actually been updated
if ($this->value !== 'unchanged') { if ($this->value !== 'unchanged') {
$items = array(); $items = [];
$fieldName = $this->name; $fieldName = $this->name;
$saveDest = $record->$fieldName(); $saveDest = $record->$fieldName();
@ -174,7 +230,9 @@ class TreeMultiselectField extends TreeDropdownField
); );
} }
if ($this->value) { if (is_array($this->value)) {
$items = $this->value;
} elseif ($this->value) {
$items = preg_split("/ *, */", trim($this->value)); $items = preg_split("/ *, */", trim($this->value));
} }
@ -196,11 +254,12 @@ class TreeMultiselectField extends TreeDropdownField
*/ */
public function performReadonlyTransformation() public function performReadonlyTransformation()
{ {
$copy = $this->castedCopy('SilverStripe\\Forms\\TreeMultiselectField_Readonly'); /** @var TreeMultiselectField_Readonly $copy */
$copy->setKeyField($this->keyField); $copy = $this->castedCopy(TreeMultiselectField_Readonly::class);
$copy->setLabelField($this->labelField); $copy->setKeyField($this->getKeyField());
$copy->setSourceObject($this->sourceObject); $copy->setLabelField($this->getLabelField());
$copy->setSourceObject($this->getSourceObject());
$copy->setTitleField($this->getTitleField());
return $copy; return $copy;
} }
} }