SilverStripe 4 compatibility (#87)

* Update composer constraint and branch alias to support SS4 testing

* Add namespaces, update DataList quirk with getSourceList

* Add PSR-4 autoloader definition

* Move template to correct namespace location, update requirement paths

* FIX Visibility on allowed actions

* FIX Update chosen class names to match updates in framework

* Update Travis configuration for 4.x builds. Update docs for namespaced classes.

* Use "4" for the release instead of master.

* FIX Selected tag height. Move Readonly to own class.
This commit is contained in:
Robbie Averill 2017-01-14 08:11:59 +13:00 committed by Daniel Hensby
parent 4ce0560d6e
commit 518189e2ef
13 changed files with 226 additions and 158 deletions

View File

@ -6,4 +6,4 @@ checks:
duplication: true
filter:
paths: [code/*, tests/*]
paths: [src/*, tests/*]

View File

@ -4,26 +4,18 @@ sudo: false
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
env:
- DB=MYSQL CORE_RELEASE=3.2
matrix:
include:
- php: 5.5
env: DB=MYSQL CORE_RELEASE=4
- php: 5.6
env: DB=MYSQL CORE_RELEASE=3
env: DB=MYSQL CORE_RELEASE=4
- php: 5.6
env: DB=MYSQL CORE_RELEASE=3.1
- php: 5.6
env: DB=PGSQL CORE_RELEASE=3.2
allow_failures:
env: DB=PGSQL CORE_RELEASE=4
- php: 7.0
env: DB=MYSQL CORE_RELEASE=4
- php: 7.0
env: DB=PGSQL CORE_RELEASE=4
before_script:
- composer self-update || true

View File

@ -1,29 +1,34 @@
{
"name": "silverstripe/tagfield",
"description": "Tag field for Silverstripe",
"license": "BSD-3-Clause",
"type": "silverstripe-module",
"keywords": [
"silverstripe",
"tag",
"field"
],
"authors": [
{
"name": "Christopher Pitt",
"email": "chris@silverstripe.com",
"homepage": "http://github.com/assertchris"
}
],
"support": {
"issues": "http://github.com/silverstripe-labs/silverstripe-tagfield/issues"
},
"require": {
"silverstripe/framework": "~3.1"
},
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
}
"name": "silverstripe/tagfield",
"description": "Tag field for Silverstripe",
"license": "BSD-3-Clause",
"type": "silverstripe-module",
"keywords": [
"silverstripe",
"tag",
"field"
],
"authors": [
{
"name": "Christopher Pitt",
"email": "chris@silverstripe.com",
"homepage": "http://github.com/assertchris"
}
],
"support": {
"issues": "http://github.com/silverstripe-labs/silverstripe-tagfield/issues"
},
"require": {
"silverstripe/framework": "^4.0"
},
"autoload": {
"psr-4": {
"SilverStripe\\TagField\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
}
}

View File

@ -50,7 +50,7 @@
}
.select2-selection__choice {
height: 13px;
height: 22px;
line-height: 13px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
@ -100,4 +100,4 @@
background-image: -ms-linear-gradient(top, #3875d7 20%, #2a62bc 90%) !important;
background-image: linear-gradient(top, #3875d7 20%, #2a62bc 90%) !important;
color: #fff !important;
}
}

View File

@ -3,19 +3,21 @@
The primary use, for this module, is as a custom input field interface. For instance, imagine you had the following data objects:
```php
class BlogPost extends DataObject {
class BlogPost extends DataObject
{
private static $many_many = array(
'BlogTags' => 'BlogTag'
'BlogTags' => 'SilverStripe\\Blog\\Model\\BlogTag'
);
}
class BlogTag extends DataObject {
class BlogTag extends DataObject
{
private static $db = array(
'Title' => 'Varchar(200)',
'Title' => 'Varchar(200)'
);
private static $belongs_many_many = array(
'BlogPosts' => 'BlogPost'
'BlogPosts' => 'SilverStripe\\Blog\\Model\\BlogPost'
);
}
```
@ -33,6 +35,8 @@ $field = TagField::create(
->setCanCreate(true); // new tag DataObjects can be created
```
**Note:** This assumes you have imported the namespaces class, e.g. `use SilverStripe\TagField\TagField;`.
This will present a tag field, in which you can select existing blog tags or create new ones. They will be created/linked after the blog posts are saved.
You can also store string-based tags, for blog posts, with the following field type:
@ -51,7 +55,8 @@ $field->setShouldLazyLoad(true); // tags should be lazy loaded
This assumes you are storing tags in the following data object structure:
```php
class BlogPost extends DataObject {
class BlogPost extends DataObject
{
private static $db = array(
'Tags' => 'Text'
);

View File

@ -10,11 +10,11 @@
*/
$.fn.chosenDestroy = function () {
var $this = $(this);
if ($this.siblings('.chzn-container').length) {
if ($this.siblings('.chosen-container').length) {
$this
.show() // The field needs to be visible so Select2 evaluates the width correctly.
.removeClass('chzn-done')
.removeClass('has-chzn')
.removeClass('chosen-done')
.removeClass('has-chosen')
.next()
.remove();
}
@ -23,7 +23,7 @@
$.entwine('ss', function ($) {
$('.ss-tag-field.has-chzn + .chzn-container, .ss-tag-field:not(.has-chzn)').entwine({
$('.ss-tag-field.has-chosen + .chosen-container, .ss-tag-field:not(.has-chosen)').entwine({
applySelect2: function () {
var self = this,
$select = $(this);

4
src/.upgrade.yml Normal file
View File

@ -0,0 +1,4 @@
mappings:
StringTagField: SilverStripe\TagField\StringTagField
TagField: SilverStripe\TagField\TagField
TagField_Readonly: SilverStripe\TagField\TagField\Readonly

View File

@ -1,12 +1,26 @@
<?php
namespace SilverStripe\TagField;
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Convert;
use SilverStripe\Forms\DropdownField;
use SilverStripe\ORM\ArrayList;
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 comma-delimited tags in a DataObject string field.
*
* This is intended bridge the gap between 1.x and 2.x, and when possible TagField should be used
* instead.
*
* @package forms
* @package tagfield
* @subpackage fields
*/
class StringTagField extends DropdownField
@ -14,9 +28,9 @@ class StringTagField extends DropdownField
/**
* @var array
*/
public static $allowed_actions = array(
'suggest',
);
public static $allowed_actions = [
'suggest'
];
/**
* @var bool
@ -43,17 +57,6 @@ class StringTagField extends DropdownField
*/
protected $isMultiple = true;
/**
* @param string $name
* @param string $title
* @param array|SS_List $source
* @param array|SS_List $value
*/
public function __construct($name, $title = '', $source = array(), $value = array())
{
parent::__construct($name, $title, $source, $value);
}
/**
* @return bool
*/
@ -150,8 +153,8 @@ class StringTagField extends DropdownField
Requirements::css(TAG_FIELD_DIR . '/css/select2.min.css');
Requirements::css(TAG_FIELD_DIR . '/css/TagField.css');
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
Requirements::javascript(ADMIN_THIRDPARTY_DIR . '/jquery/jquery.js');
Requirements::javascript(ADMIN_THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
Requirements::javascript(TAG_FIELD_DIR . '/js/select2.js');
Requirements::javascript(TAG_FIELD_DIR . '/js/TagField.js');
@ -173,7 +176,7 @@ class StringTagField extends DropdownField
return $this
->customise($properties)
->renderWith(array("templates/TagField"));
->renderWith(TagField::class);
}
/**
@ -264,17 +267,16 @@ class StringTagField extends DropdownField
/**
* Returns a JSON string of tags, for lazy loading.
*
* @param SS_HTTPRequest $request
*
* @return SS_HTTPResponse
* @param HTTPRequest $request
* @return HTTPResponse
*/
public function suggest(SS_HTTPRequest $request)
public function suggest(HTTPRequest $request)
{
$responseBody = Convert::raw2json(
array('items' => array())
);
$response = new SS_HTTPResponse();
$response = new HTTPResponse;
$response->addHeader('Content-Type', 'application/json');
if ($record = $this->getRecord()) {

View File

@ -1,5 +1,21 @@
<?php
namespace SilverStripe\TagField;
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\ReadonlyField;
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.
*
@ -11,8 +27,8 @@ class TagField extends DropdownField
/**
* @var array
*/
public static $allowed_actions = array(
'suggest',
private static $allowed_actions = array(
'suggest'
);
/**
@ -35,6 +51,11 @@ class TagField extends DropdownField
*/
protected $titleField = 'Title';
/**
* @var DataList
*/
protected $sourceList;
/**
* @var bool
*/
@ -48,6 +69,7 @@ class TagField extends DropdownField
*/
public function __construct($name, $title = '', $source = null, $value = null)
{
$this->setSourceList($source);
parent::__construct($name, $title, $source, $value);
}
@ -151,6 +173,26 @@ class TagField extends DropdownField
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}
*/
@ -159,8 +201,8 @@ class TagField extends DropdownField
Requirements::css(TAG_FIELD_DIR . '/css/select2.min.css');
Requirements::css(TAG_FIELD_DIR . '/css/TagField.css');
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
Requirements::javascript(ADMIN_THIRDPARTY_DIR . '/jquery/jquery.js');
Requirements::javascript(ADMIN_THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
Requirements::javascript(TAG_FIELD_DIR . '/js/select2.js');
Requirements::javascript(TAG_FIELD_DIR . '/js/TagField.js');
@ -182,7 +224,7 @@ class TagField extends DropdownField
return $this
->customise($properties)
->renderWith(array("templates/TagField"));
->renderWith(self::class);
}
/**
@ -200,21 +242,21 @@ class TagField extends DropdownField
{
$options = ArrayList::create();
$source = $this->getSource();
$source = $this->getSourceList();
if(!$source) {
$source = new ArrayList();
$source = ArrayList::create();
}
$dataClass = $source->dataClass();
$values = $this->Value();
if(!$values) {
if (!$values) {
return $options;
}
if(is_array($values)) {
if (is_array($values)) {
$values = DataList::create($dataClass)->filter('Title', $values);
}
@ -276,26 +318,21 @@ class TagField extends DropdownField
parent::saveInto($record);
$name = $this->getName();
$titleField = $this->getTitleField();
$source = $this->getSource();
$values = $this->Value();
$relation = $record->$name();
$ids = array();
if(!$values) {
if (!$values) {
$values = array();
}
if(empty($record) || empty($source) || empty($titleField)) {
if (empty($record) || empty($source) || empty($titleField)) {
return;
}
if(!$record->hasMethod($name)) {
if (!$record->hasMethod($name)) {
throw new Exception(
sprintf("%s does not have a %s method", get_class($record), $name)
);
@ -304,31 +341,31 @@ class TagField extends DropdownField
foreach ($values as $key => $value) {
// Get or create record
$record = $this->getOrCreateTag($value);
if($record) {
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
* @param string $term
* @return DataObject
*/
protected function getOrCreateTag($term)
{
// Check if existing record can be found
$source = $this->getSource();
/** @var DataList $source */
$source = $this->getSourceList();
$titleField = $this->getTitleField();
$record = $source
->filter($titleField, $term)
->first();
if($record) {
if ($record) {
return $record;
}
@ -347,15 +384,14 @@ class TagField extends DropdownField
/**
* Returns a JSON string of tags, for lazy loading.
*
* @param SS_HTTPRequest $request
*
* @return SS_HTTPResponse
* @param HTTPRequest $request
* @return HTTPResponse
*/
public function suggest(SS_HTTPRequest $request)
public function suggest(HTTPRequest $request)
{
$tags = $this->getTags($request->getVar('term'));
$response = new SS_HTTPResponse();
$response = new HTTPResponse();
$response->addHeader('Content-Type', 'application/json');
$response->setBody(json_encode(array('items' => $tags)));
@ -365,16 +401,15 @@ class TagField extends DropdownField
/**
* Returns array of arrays representing tags.
*
* @param string $term
*
* @param string $term
* @return array
*/
protected function getTags($term)
{
/**
* @var DataList $source
* @var array $source
*/
$source = $this->getSource();
$source = $this->getSourceList();
$titleField = $this->getTitleField();
@ -415,40 +450,8 @@ class TagField extends DropdownField
*/
public function performReadonlyTransformation()
{
$copy = $this->castedCopy('TagField_Readonly');
$copy->setSource($this->getSource());
$copy = $this->castedCopy(TagFieldReadonly::class);
$copy->setSourceList($this->getSourceList());
return $copy;
}
}
/**
* A readonly extension of TagField useful for non-editable items within the CMS.
*
* @package forms
* @subpackage fields
*/
class TagField_Readonly extends TagField
{
protected $readonly = true;
/**
* Render the readonly field as HTML.
*
* @param array $properties
* @return HTMLText
*/
public function Field($properties = array())
{
$options = array();
foreach ($this->getOptions()->filter('Selected', true) as $option) {
$options[] = $option->Title;
}
$field = ReadonlyField::create($this->name.'_Readonly', $this->title);
$field->setForm($this->form);
$field->setValue(implode(', ', $options));
return $field->Field();
}
}

41
src/TagField/Readonly.php Normal file
View File

@ -0,0 +1,41 @@
<?php
namespace SilverStripe\TagField\TagField;
use SilverStripe\Forms\ReadonlyField;
use SilverStripe\TagField\TagField;
/**
* A readonly extension of TagField useful for non-editable items within the CMS.
*
* @package forms
* @subpackage fields
*/
class Readonly extends TagField
{
/**
* {@inheritDoc}
*/
protected $readonly = true;
/**
* Render the readonly field as HTML.
*
* @param array $properties
* @return HTMLText
*/
public function Field($properties = array())
{
$options = array();
foreach ($this->getOptions()->filter('Selected', true) as $option) {
$options[] = $option->Title;
}
$field = ReadonlyField::create($this->name . '_Readonly', $this->title);
$field->setForm($this->form);
$field->setValue(implode(', ', $options));
return $field->Field();
}
}

View File

@ -1,5 +1,15 @@
<?php
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\ORM\DataObject;
use SilverStripe\TagField\StringTagField;
/**
* @mixin PHPUnit_Framework_TestCase
*/
@ -103,11 +113,11 @@ class StringTagFieldTest extends SapphireTest
/**
* @param array $parameters
*
* @return SS_HTTPRequest
* @return HTTPRequest
*/
protected function getNewRequest(array $parameters)
{
return new SS_HTTPRequest(
return new HTTPRequest(
'get',
'StringTagFieldTestController/StringTagFieldTestForm/fields/Tags/suggest',
$parameters

View File

@ -1,5 +1,16 @@
<?php
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject;
use SilverStripe\TagField\TagField;
/**
* @mixin PHPUnit_Framework_TestCase
*/
@ -21,13 +32,10 @@ class TagFieldTest extends SapphireTest
public function testItSavesLinksToNewTagsOnNewRecords()
{
$record = $this->getNewTagFieldTestBlogPost('BlogPost1');
$field = new TagField('Tags', '', new DataList('TagFieldTestBlogTag'));
$field->setValue(array('Tag3', 'Tag4'));
$field->saveInto($record);
$record->write();
$this->compareExpectedAndActualTags(
array('Tag3', 'Tag4'),
$record
@ -65,7 +73,6 @@ class TagFieldTest extends SapphireTest
protected function compareTagLists(array $expected, DataList $actualSource)
{
$actual = array_values($actualSource->map('ID', 'Title')->toArray());
sort($expected);
sort($actual);
@ -247,11 +254,11 @@ class TagFieldTest extends SapphireTest
/**
* @param array $parameters
*
* @return SS_HTTPRequest
* @return HTTPRequest
*/
protected function getNewRequest(array $parameters)
{
return new SS_HTTPRequest(
return new HTTPRequest(
'get',
'TagFieldTestController/TagFieldTestForm/fields/Tags/suggest',
$parameters
@ -283,9 +290,8 @@ class TagFieldTest extends SapphireTest
public function testItIgnoresNewTagsIfCannotCreate()
{
$this->markTestSkipped(
'This test has not been updated yet.'
'This test has not been updated yet.'
);
$record = new TagFieldTestBlogPost();
@ -320,14 +326,14 @@ class TagFieldTestBlogTag extends DataObject implements TestOnly
* @var array
*/
private static $db = array(
'Title' => 'Varchar(200)',
'Title' => 'Varchar(200)'
);
/**
* @var array
*/
private static $belongs_many_many = array(
'BlogPosts' => 'TagFieldTestBlogPost',
'BlogPosts' => 'TagFieldTestBlogPost'
);
}
@ -340,15 +346,15 @@ class TagFieldTestBlogPost extends DataObject implements TestOnly
* @var array
*/
private static $db = array(
'Title' => 'Text',
'Content' => 'Text',
'Title' => 'Text',
'Content' => 'Text'
);
/**
* @var array
*/
private static $many_many = array(
'Tags' => 'TagFieldTestBlogTag',
'Tags' => 'TagFieldTestBlogTag'
);
}