From 0d21e5dc4908c234a9d50a2732ce74680248c242 Mon Sep 17 00:00:00 2001 From: Colin Tucker Date: Wed, 6 Sep 2017 21:10:34 +1000 Subject: [PATCH] Additional relation handling and options --- src/Forms/Select2AjaxField.php | 189 +++++++++++++++++++++++++-- src/Forms/Select2Field.php | 231 +++++++++++++++++++++++++++++++++ 2 files changed, 410 insertions(+), 10 deletions(-) diff --git a/src/Forms/Select2AjaxField.php b/src/Forms/Select2AjaxField.php index bb1effa..3fc006c 100644 --- a/src/Forms/Select2AjaxField.php +++ b/src/Forms/Select2AjaxField.php @@ -22,8 +22,14 @@ use SilverStripe\Control\HTTPResponse; use SilverStripe\Core\Convert; use SilverStripe\ORM\DataList; +use SilverStripe\ORM\DataObject; +use SilverStripe\ORM\DataObjectInterface; +use SilverStripe\ORM\Map; +use SilverStripe\ORM\Relation; +use SilverStripe\ORM\SS_List; use SilverStripe\View\SSViewer; use SilverStripe\View\ViewableData; +use ArrayAccess; /** * An extension of the Select2 field class for a Select2 Ajax field. @@ -74,6 +80,13 @@ class Select2AjaxField extends Select2Field */ protected $ajaxConfig; + /** + * Defines whether Ajax is enabled or disabled for the field. + * + * @var boolean + */ + protected $ajaxEnabled = true; + /** * The data class to search via Ajax. * @@ -174,6 +187,22 @@ public function Type() return sprintf('select2ajaxfield %s', parent::Type()); } + /** + * Defines the source for the receiver. + * + * @param array|ArrayAccess + * + * @return $this + */ + public function setSource($source) + { + if ($source instanceof DataList) { + $this->setDataClass($source->dataClass()); + } + + return parent::setSource($source); + } + /** * Defines either the named Ajax config value, or the Ajax config array. * @@ -209,6 +238,30 @@ public function getAjaxConfig($name = null) return $this->ajaxConfig; } + /** + * Defines the value of the ajaxEnabled attribute. + * + * @param boolean $ajaxEnabled + * + * @return $this + */ + public function setAjaxEnabled($ajaxEnabled) + { + $this->ajaxEnabled = (boolean) $ajaxEnabled; + + return $this; + } + + /** + * Answers the value of the ajaxEnabled attribute. + * + * @return boolean + */ + public function getAjaxEnabled() + { + return $this->ajaxEnabled; + } + /** * Defines the value of the dataClass attribute. * @@ -434,13 +487,27 @@ public function getDataAttributes() { $attributes = parent::getDataAttributes(); - foreach ($this->getFieldAjaxConfig() as $key => $value) { - $attributes[sprintf('data-ajax--%s', $key)] = $this->getDataValue($value); + if ($this->isAjaxEnabled()) { + + foreach ($this->getFieldAjaxConfig() as $key => $value) { + $attributes[sprintf('data-ajax--%s', $key)] = $this->getDataValue($value); + } + } return $attributes; } + /** + * Answers true if Ajax is enabled for the field. + * + * @return boolean + */ + public function isAjaxEnabled() + { + return $this->ajaxEnabled; + } + /** * Answers an HTTP response containing JSON results matching the given search parameters. * @@ -458,7 +525,7 @@ public function search(HTTPRequest $request) // Initialise: - $data = []; + $data = ['results' => []]; // Create Data List: @@ -524,15 +591,77 @@ public function getSearchFilterName($field) } /** - * Answers the record identified by the recorded field value. + * Loads the value of the field from the given relation. * - * @return ViewableData + * @param Relation $relation + * + * @return void + */ + public function loadFromRelation(Relation $relation) + { + parent::setValue($relation->column($this->getIDField())); + } + + /** + * Saves the value of the field into the given relation. + * + * @param Relation $relation + * + * @return void */ - public function getValueRecord() + public function saveIntoRelation(Relation $relation) { - if ($id = $this->Value()) { - return $this->getList()->byID($id); + $relation->setByIDList( + $this->getList()->filter( + $this->getIDField(), + $this->getValueArray() + )->getIDList() + ); + } + + /** + * Answers true if the given data value and user value match (i.e. the value is selected). + * + * @param mixed $dataValue + * @param mixed $userValue + * + * @return boolean + */ + public function isSelectedValue($dataValue, $userValue) + { + if (is_array($userValue) && in_array($dataValue, $userValue)) { + return true; + } + + return parent::isSelectedValue($dataValue, $userValue); + } + + /** + * Answers the source array for the field options, including the empty string, if present. + * + * @return array + */ + public function getSourceEmpty() + { + if (!$this->isAjaxEnabled()) { + return parent::getSourceEmpty(); + } elseif ($this->getHasEmptyDefault()) { + return ['' => $this->getEmptyString()]; } + + return []; + } + + /** + * Answers the record identified by the given value. + * + * @param mixed $id + * + * @return ViewableData + */ + protected function getValueRecord($id) + { + return $this->getList()->find($this->getIDField(), $id); } /** @@ -604,6 +733,38 @@ protected function getFormattedSelection(ViewableData $record) } } + /** + * Converts the given data source into an array. + * + * @param array|ArrayAccess $source + * + * @return array + */ + protected function getListMap($source) + { + // Extract Map from ID / Text Fields: + + if ($source instanceof SS_List) { + $source = $source->map($this->getIDField(), $this->getTextField()); + } + + // Convert Map to Array: + + if ($source instanceof Map) { + $source = $source->toArray(); + } + + // Determine Invalid Types: + + if (!is_array($source) && !($source instanceof ArrayAccess)) { + user_error('$source passed in as invalid type', E_USER_ERROR); + } + + // Answer Data Source: + + return $source; + } + /** * Answers the field config for the receiver. * @@ -613,8 +774,16 @@ protected function getFieldConfig() { $config = parent::getFieldConfig(); - if ($value = $this->Value()) { - $config['data'] = [$this->getResultData($this->getValueRecord(), true)]; + if ($values = $this->getValueArray()) { + + $data = []; + + foreach ($values as $value) { + $data[] = $this->getResultData($this->getValueRecord($value), true); + } + + $config['data'] = $data; + } return $config; diff --git a/src/Forms/Select2Field.php b/src/Forms/Select2Field.php index c956535..1b0d420 100644 --- a/src/Forms/Select2Field.php +++ b/src/Forms/Select2Field.php @@ -19,6 +19,9 @@ use SilverStripe\Core\Convert; use SilverStripe\Forms\DropdownField; +use SilverStripe\ORM\DataObject; +use SilverStripe\ORM\DataObjectInterface; +use SilverStripe\ORM\Relation; /** * An extension of the dropdown field class for a Select2 field. @@ -48,6 +51,13 @@ class Select2Field extends DropdownField */ protected $config; + /** + * Defines whether the field can handle multiple options. + * + * @var boolean + */ + protected $multiple = false; + /** * Constructs the object upon instantiation. * @@ -116,6 +126,50 @@ public function getConfig($name = null) return $this->config; } + /** + * Defines the value of the multiple attribute. + * + * @param boolean $multiple + * + * @return $this + */ + public function setMultiple($multiple) + { + $this->multiple = (boolean) $multiple; + + return $this; + } + + /** + * Answers the value of the multiple attribute. + * + * @return boolean + */ + public function getMultiple() + { + return $this->multiple; + } + + /** + * Answers the multiple name of the field. + * + * @return string + */ + public function getMultipleName() + { + return sprintf('%s[]', $this->getName()); + } + + /** + * Answers true if the field handles multiple tags. + * + * @return boolean + */ + public function isMultiple() + { + return $this->getMultiple(); + } + /** * Answers an array of HTML attributes for the field. * @@ -128,6 +182,11 @@ public function getAttributes() $this->getDataAttributes() ); + if ($this->isMultiple()) { + $attributes['multiple'] = true; + $attributes['name'] = $this->getMultipleName(); + } + if (!isset($attributes['data-placeholder'])) { $attributes['data-placeholder'] = $this->getEmptyString(); } @@ -151,6 +210,166 @@ public function getDataAttributes() return $attributes; } + /** + * Defines the value of the field. + * + * @param mixed $value + * @param array|DataObject $data + * + * @return $this + */ + public function setValue($value, $data = null) + { + if ($data instanceof DataObject) { + $this->loadFrom($data); + return $this; + } + + return parent::setValue($value); + } + + /** + * Answers the value(s) of this field as an array. + * + * @return array + */ + public function getValueArray() + { + return $this->getListValues($this->Value()); + } + + /** + * Loads the value of the field from the given data object. + * + * @param DataObjectInterface $record + * + * @return void + */ + public function loadFrom(DataObjectInterface $record) + { + // Obtain Field Name: + + $fieldName = $this->getName(); + + // Bail Early (if needed): + + if (empty($fieldName) || empty($record)) { + return; + } + + // Determine Value Mode: + + if (!$this->isMultiple()) { + + // Load Singular Value: + + parent::setValue($record->$fieldName); + + } else { + + // Load Multiple Value: + + $relation = $this->getNamedRelation($record); + + if ($relation instanceof Relation) { + $this->loadFromRelation($relation); + } elseif ($record->hasField($fieldName)) { + parent::setValue($this->stringDecode($record->$fieldName)); + } + + } + } + + /** + * Saves the value of the field into the given data object. + * + * @param DataObjectInterface $record + * + * @return void + */ + public function saveInto(DataObjectInterface $record) + { + // Obtain Field Name: + + $fieldName = $this->getName(); + + // Bail Early (if needed): + + if (empty($fieldName) || empty($record)) { + return; + } + + // Determine Value Mode: + + if (!$this->isMultiple()) { + + // Save Singular Value: + + parent::saveInto($record); + + } else { + + // Save Multiple Value: + + $relation = $this->getNamedRelation($record); + + if ($relation instanceof Relation) { + $this->saveIntoRelation($relation); + } elseif ($record->hasField($fieldName)) { + $record->$fieldName = $this->stringEncode($this->getValueArray()); + } + + } + } + + /** + * Loads the value of the field from the given relation. + * + * @param Relation $relation + * + * @return void + */ + public function loadFromRelation(Relation $relation) + { + parent::setValue(array_values($relation->getIDList())); + } + + /** + * Saves the value of the field into the given relation. + * + * @param Relation $relation + * + * @return void + */ + public function saveIntoRelation(Relation $relation) + { + $relation->setByIDList($this->getValueArray()); + } + + /** + * Converts the given array of values into a string. + * + * @param array $values + * + * @return string + */ + public function stringEncode($values) + { + return $values ? Convert::array2json(array_values($values)) : null; + } + + /** + * Decodes the given string of values into an array. + * + * @param string $values + * + * @return array + */ + public function stringDecode($values) + { + return $values ? Convert::json2array($values) : []; + } + /** * Converts the given data value to a string suitable for a data attribute. * @@ -184,4 +403,16 @@ protected function getFieldConfig() return $config; } + + /** + * Answers the relation with the field name from the given data object. + * + * @param DataObjectInterface $record + * + * @return Relation + */ + protected function getNamedRelation(DataObjectInterface $record) + { + return $record->hasMethod($this->Name) ? $record->{$this->Name}() : null; + } }