setSourceList($source); $this->setTitleField($titleField); parent::__construct($name, $title, $source, $value); } /** * @return bool */ public function getShouldLazyLoad() { return $this->shouldLazyLoad; } /** * @param bool $shouldLazyLoad * * @return static */ public function setShouldLazyLoad($shouldLazyLoad) { $this->shouldLazyLoad = $shouldLazyLoad; return $this; } /** * @return int */ public function getLazyLoadItemLimit() { return $this->lazyLoadItemLimit; } /** * @param int $lazyLoadItemLimit * * @return static */ public function setLazyLoadItemLimit($lazyLoadItemLimit) { $this->lazyLoadItemLimit = $lazyLoadItemLimit; return $this; } /** * @return bool */ public function getIsMultiple() { return $this->isMultiple; } /** * @param bool $isMultiple * * @return static */ public function setIsMultiple($isMultiple) { $this->isMultiple = $isMultiple; return $this; } /** * @return bool */ public function getCanCreate() { return $this->canCreate; } /** * @param bool $canCreate * * @return static */ public function setCanCreate($canCreate) { $this->canCreate = $canCreate; return $this; } /** * @return string */ public function getTitleField() { return $this->titleField; } /** * @param string $titleField * * @return $this */ public function setTitleField($titleField) { $this->titleField = $titleField; return $this; } /** * Get the DataList source. The 4.x upgrade for SelectField::setSource starts to convert this to an array * @return DataList */ public function getSourceList() { return $this->sourceList; } /** * Set the model class name for tags * @param DataList $className * @return self */ public function setSourceList($sourceList) { $this->sourceList = $sourceList; return $this; } /** * {@inheritdoc} */ public function Field($properties = array()) { Requirements::css('silverstripe/tagfield:client/dist/styles/bundle.css'); Requirements::javascript('silverstripe/tagfield:client/dist/js/bundle.js'); $schema = [ 'name' => $this->getName() . '[]', 'lazyLoad' => $this->getShouldLazyLoad(), 'creatable' => $this->getCanCreate(), 'multiple' => $this->getIsMultiple(), 'value' => $this->Value(), 'disabled' => $this->isDisabled() || $this->isReadonly(), ]; if (!$this->getShouldLazyLoad()) { $schema['options'] = array_values($this->getOptions()->toNestedArray()); } else { if ($this->Value()) { $schema['value'] = $this->getOptions(true)->toNestedArray(); } $schema['optionUrl'] = $this->getSuggestURL(); } $this->setAttribute('data-schema', Convert::array2json($schema)); $this->addExtraClass('ss-tag-field'); return $this ->customise($properties) ->renderWith(self::class); } /** * @return string */ protected function getSuggestURL() { return Controller::join_links($this->Link(), 'suggest'); } /** * @param bool $onlySelected Only return options that are selected * @return ArrayList */ protected function getOptions($onlySelected = false) { $source = $this->getSourceList(); if (!$source) { $source = ArrayList::create(); } $dataClass = $source->dataClass(); $titleField = $this->getTitleField(); $values = $this->Value(); if ($values) { if (is_array($values)) { $values = DataList::create($dataClass)->filter($titleField, $values); } } if ($onlySelected) { $source = $values; } return $source instanceof DataList ? $this->formatOptions($source) : ArrayList::create(); } /** * @param DataList $source * @return ArrayList */ protected function formatOptions(DataList $source) { $options = ArrayList::create(); $titleField = $this->getTitleField(); foreach ($source as $object) { $options->push( ArrayData::create(array( 'Title' => $object->$titleField, 'Value' => $object->Title, )) ); } return $options; } /** * {@inheritdoc} */ public function setValue($value, $source = null) { if ($source instanceof DataObject) { $name = $this->getName(); if ($source->hasMethod($name)) { $value = $source->$name()->column($this->getTitleField()); } } elseif ($value instanceof SS_List) { $value = $value->column($this->getTitleField()); } if (!is_array($value)) { return parent::setValue($value); } return parent::setValue(array_filter($value)); } /** * {@inheritdoc} */ public function saveInto(DataObjectInterface $record) { parent::saveInto($record); $name = $this->getName(); $titleField = $this->getTitleField(); $source = $this->getSource(); $values = $this->Value(); $relation = $record->$name(); $ids = array(); if (!$values) { $values = array(); } if (empty($record) || empty($titleField)) { return; } if (!$record->hasMethod($name)) { throw new Exception( sprintf("%s does not have a %s method", get_class($record), $name) ); } foreach ($values as $key => $value) { // Get or create record $record = $this->getOrCreateTag($value); if ($record) { $ids[] = $record->ID; $values[$key] = $record->Title; } } $relation->setByIDList(array_filter($ids)); } /** * Get or create tag with the given value * * @param string $term * @return DataObject */ protected function getOrCreateTag($term) { // Check if existing record can be found /** @var DataList $source */ $source = $this->getSourceList(); $titleField = $this->getTitleField(); $record = $source ->filter($titleField, $term) ->first(); if ($record) { return $record; } // Create new instance if not yet saved if ($this->getCanCreate()) { $dataClass = $source->dataClass(); $record = Injector::inst()->create($dataClass); $record->{$titleField} = $term; $record->write(); return $record; } else { return false; } } /** * Returns a JSON string of tags, for lazy loading. * * @param HTTPRequest $request * @return HTTPResponse */ public function suggest(HTTPRequest $request) { $tags = $this->getTags($request->getVar('term')); $response = new HTTPResponse(); $response->addHeader('Content-Type', 'application/json'); $response->setBody(json_encode(array('items' => $tags))); return $response; } /** * Returns array of arrays representing tags. * * @param string $term * @return array */ protected function getTags($term) { /** * @var array $source */ $source = $this->getSourceList(); $titleField = $this->getTitleField(); $query = $source ->filter($titleField . ':PartialMatch:nocase', $term) ->sort($titleField) ->limit($this->getLazyLoadItemLimit()); return $this->formatOptions($query)->toNestedArray(); } /** * DropdownField assumes value will be a scalar so we must * override validate. This only applies to Silverstripe 3.2+ * * @param Validator $validator * @return bool */ public function validate($validator) { return true; } /** * Converts the field to a readonly variant. * * @return TagField_Readonly */ public function performReadonlyTransformation() { $copy = $this->castedCopy(ReadonlyTagField::class); $copy->setSourceList($this->getSourceList()); return $copy; } }