diff --git a/_config/admin.yml b/_config/admin.yml new file mode 100644 index 0000000..0b33853 --- /dev/null +++ b/_config/admin.yml @@ -0,0 +1,5 @@ +SilverStripe\Admin\LeftAndMain: + extra_requirements_css: + - 'silverstripe/tagfield:client/dist/styles/bundle.css' + extra_requirements_javascript: + - 'silverstripe/tagfield:client/dist/js/bundle.js' diff --git a/client/src/components/TagField.js b/client/src/components/TagField.js index edec46f..af2e900 100644 --- a/client/src/components/TagField.js +++ b/client/src/components/TagField.js @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import Select from 'react-select'; import fetch from 'isomorphic-fetch'; +import fieldHolder from 'components/FieldHolder/FieldHolder'; import url from 'url'; import debounce from 'debounce-promise'; import PropTypes from 'prop-types'; @@ -15,6 +16,7 @@ class TagField extends Component { }; this.onChange = this.onChange.bind(this); + this.handleOnBlur = this.handleOnBlur.bind(this); this.getOptions = this.getOptions.bind(this); this.fetchOptions = debounce(this.fetchOptions, 500); } @@ -43,6 +45,15 @@ class TagField extends Component { return this.fetchOptions(input); } + /** + * Required to prevent TagField being cleared on blur + * + * @link https://github.com/JedWatson/react-select/issues/805 + */ + handleOnBlur() { + + } + fetchOptions(input) { const { optionUrl, labelKey, valueKey } = this.props; const fetchURL = url.parse(optionUrl, true); @@ -52,8 +63,8 @@ class TagField extends Component { .then((response) => response.json()) .then((json) => ({ options: json.items.map(item => ({ - [labelKey]: item.id, - [valueKey]: item.text, + [labelKey]: item.Title, + [valueKey]: item.Value, })) })); } @@ -85,6 +96,7 @@ class TagField extends Component { @@ -113,4 +125,6 @@ TagField.defaultProps = { disabled: false }; -export default TagField; +export { TagField as Component }; + +export default fieldHolder(TagField); diff --git a/src/TagField.php b/src/TagField.php index 2a91410..5b376ae 100644 --- a/src/TagField.php +++ b/src/TagField.php @@ -6,15 +6,13 @@ use SilverStripe\Control\Controller; use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPResponse; use SilverStripe\Core\Injector\Injector; -use SilverStripe\Forms\DropdownField; +use SilverStripe\Forms\MultiSelectField; use SilverStripe\Forms\Validator; use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObjectInterface; -use SilverStripe\ORM\SS_List; use SilverStripe\View\ArrayData; -use SilverStripe\View\Requirements; /** * Provides a tagging interface, storing links between tag DataObjects and a parent DataObject. @@ -22,7 +20,7 @@ use SilverStripe\View\Requirements; * @package forms * @subpackage fields */ -class TagField extends DropdownField +class TagField extends MultiSelectField { /** * @var array @@ -61,6 +59,9 @@ class TagField extends DropdownField */ protected $isMultiple = true; + /** @skipUpgrade */ + protected $schemaComponent = 'TagField'; + /** * @param string $name * @param string $title @@ -236,19 +237,6 @@ class TagField extends DropdownField return $schema; } - /** - * When not used in a React form factory context, this adds the schema data to SilverStripe template - * rendered attributes lists - * - * @return array - */ - public function getAttributes() - { - $attributes = parent::getAttributes(); - $attributes['data-schema'] = json_encode($this->getSchemaData()); - return $attributes; - } - /** * @return string */ @@ -258,11 +246,12 @@ class TagField extends DropdownField } /** - * @param bool $onlySelected Only return options that are selected * @return ArrayList */ - protected function getOptions($onlySelected = false) + protected function getOptions() { + $options = ArrayList::create(); + $source = $this->getSourceList(); if (!$source) { @@ -270,35 +259,28 @@ class TagField extends DropdownField } $dataClass = $source->dataClass(); - $titleField = $this->getTitleField(); + $values = $this->Value(); - if ($values) { - if (is_array($values)) { - $values = $source->filter($titleField, $values); - } - } - if ($onlySelected) { - $source = $values; + if (!$values) { + return $options; } - return $source instanceof DataList ? $this->formatOptions($source) : ArrayList::create(); - } + if (is_array($values)) { + $values = DataList::create($dataClass) + ->filter($this->getTitleField(), $values); + } + + $ids = $values->column($this->getTitleField()); - /** - * @param DataList $source - * @return ArrayList - */ - protected function formatOptions(DataList $source) - { - $options = ArrayList::create(); $titleField = $this->getTitleField(); foreach ($source as $object) { $options->push( ArrayData::create([ 'Title' => $object->$titleField, - 'Value' => $object->Title, + 'Value' => $object->ID, + 'Selected' => in_array($object->$titleField, $ids), ]) ); } @@ -315,17 +297,41 @@ class TagField extends DropdownField $name = $this->getName(); if ($source->hasMethod($name)) { - $value = $source->$name()->column($this->getTitleField()); + $values = []; + $titleField = $this->getTitleField(); + + foreach ($source->$name() as $tag) { + $values[] = [ + 'Title' => $tag->$titleField, + 'Value' => $tag->ID, + 'Selected' => true + ]; + } + + return parent::setValue($values); } - } elseif ($value instanceof SS_List) { - $value = $value->column($this->getTitleField()); } if (!is_array($value)) { return parent::setValue($value); } - return parent::setValue(array_filter($value)); + return parent::setValue($value); + } + + /** + * {@inheritdoc} + */ + public function getAttributes() + { + return array_merge( + parent::getAttributes(), + [ + 'name' => $this->getName() . '[]', + 'style' => 'width: 100%', + 'data-schema' => json_encode($this->getSchemaData()), + ] + ); } /** @@ -333,8 +339,6 @@ class TagField extends DropdownField */ public function saveInto(DataObjectInterface $record) { - parent::saveInto($record); - $name = $this->getName(); $titleField = $this->getTitleField(); $values = $this->Value(); @@ -344,6 +348,7 @@ class TagField extends DropdownField if (!$values) { $values = []; } + if (empty($record) || empty($titleField)) { return; } @@ -388,6 +393,11 @@ class TagField extends DropdownField if ($this->getCanCreate()) { $dataClass = $source->dataClass(); $record = Injector::inst()->create($dataClass); + + if (is_array($term)) { + $term = $term['Value']; + } + $record->{$titleField} = $term; $record->write(); if ($source instanceof SS_List) { @@ -438,8 +448,8 @@ class TagField extends DropdownField $titleField = $this->getTitleField(); foreach ($query->map('ID', $titleField) as $id => $title) { $items[$title] = [ - 'id' => $title, - 'text' => $title, + 'Title' => $title, + 'Value' => $id, ]; } @@ -480,4 +490,20 @@ class TagField extends DropdownField { return ''; } + + public function getSchemaStateDefaults() + { + $data = parent::getSchemaStateDefaults(); + + // Add options to 'data' + $data['lazyLoad'] = $this->getShouldLazyLoad(); + $data['multi'] = $this->getIsMultiple(); + $data['optionUrl'] = $this->getSuggestURL(); + $data['creatable'] = $this->getCanCreate(); + $data['value'] = $this->Value(); + + + return $data; + } } +