diff --git a/.travis.yml b/.travis.yml
index a8d8436..4e18a3b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,6 +12,8 @@ matrix:
env: DB=PGSQL PHPUNIT_TEST=1
- php: 7.1
env: DB=MYSQL PHPUNIT_COVERAGE_TEST=1
+ - php: 7.2
+ env: DB=MYSQL PHPUNIT_TEST=1
before_script:
- phpenv rehash
@@ -25,7 +27,7 @@ before_script:
script:
- if [[ $PHPUNIT_TEST ]]; then vendor/bin/phpunit tests/; fi
- if [[ $PHPUNIT_COVERAGE_TEST ]]; then phpdbg -qrr vendor/bin/phpunit --coverage-clover=coverage.xml; fi
- - if [[ $PHPCS_TEST ]]; then vendor/bin/phpcs --standard=vendor/silverstripe/framework/phpcs.xml.dist src/ tests/ ; fi
+ - if [[ $PHPCS_TEST ]]; then vendor/bin/phpcs src/ tests/ *.php; fi
after_success:
- if [[ $PHPUNIT_COVERAGE_TEST ]]; then bash <(curl -s https://codecov.io/bash) -f coverage.xml; fi
diff --git a/css/GridFieldExtensions.css b/css/GridFieldExtensions.css
index f49c814..498d478 100644
--- a/css/GridFieldExtensions.css
+++ b/css/GridFieldExtensions.css
@@ -14,7 +14,7 @@
}
.add-existing-search-dialog .add-existing-search-form .field label {
- padding-bottom: 4px;
+ padding: 4px 0;
}
.add-existing-search-dialog .add-existing-search-form .Actions {
@@ -22,38 +22,12 @@
padding: 0;
}
-.add-existing-search-dialog .add-existing-search-items li a {
- background: #FFF;
- border: solid #CCC;
- border-right-width: 1px;
- border-bottom-width: 1px;
- border-left-width: 1px;
- display: block;
- padding: 6px;
+.add-existing-search-dialog .list-group-item {
+ min-height: 32px;
}
-.add-existing-search-dialog .add-existing-search-items li:first-child a {
- border-top-left-radius: 4px;
- border-top-right-radius: 4px;
- border-top-width: 1px;
-}
-
-.add-existing-search-dialog .add-existing-search-items li:last-child a {
- border-bottom-left-radius: 4px;
- border-bottom-right-radius: 4px;
-}
-
-.add-existing-search-dialog .add-existing-search-items li a:hover {
- background: #F4F4F4;
-}
-
-.add-existing-search-dialog .add-existing-search-pagination li {
- background: #FFF;
- display: block;
- float: left;
- margin-right: 2px;
+.add-existing-search-dialog .btn-toolbar {
margin-top: 12px;
- padding: 6px;
}
/**
@@ -97,7 +71,6 @@
display: inline-block;
margin: 0;
min-width: 150px;
- width: calc(100% - 20px);
}
.ss-gridfield-add-new-multi-class .form-group:after {
diff --git a/javascript/GridFieldExtensions.js b/javascript/GridFieldExtensions.js
index 2c2f478..d196464 100644
--- a/javascript/GridFieldExtensions.js
+++ b/javascript/GridFieldExtensions.js
@@ -44,6 +44,15 @@
}
});
+ // Allow the list item to be clickable as well as the anchor
+ $('.add-existing-search-dialog .add-existing-search-items .list-group-item-action').entwine({
+ onclick: function() {
+ if (this.children('a').length > 0) {
+ this.children('a').first().trigger('click');
+ }
+ }
+ });
+
$(".add-existing-search-dialog .add-existing-search-items a").entwine({
onclick: function() {
var link = this.closest(".add-existing-search-items").data("add-link");
@@ -264,6 +273,82 @@
*/
$(".ss-gridfield-orderable tbody").entwine({
+ // reload the gridfield without triggering the change event
+ // this is because the change has already been saved by reorder action
+ reload: function (ajaxOpts, successCallback) {
+ var self = this.getGridField(), form = this.closest('form'),
+ focusedElName = this.find(':input:focus').attr('name'), // Save focused element for restoring after refresh
+ data = form.find(':input').serializeArray();
+
+ if (!ajaxOpts) {
+ ajaxOpts = {};
+ }
+ if (!ajaxOpts.data) {
+ ajaxOpts.data = [];
+ }
+ ajaxOpts.data = ajaxOpts.data.concat(data);
+
+ // Include any GET parameters from the current URL, as the view state might depend on it.
+ // For example, a list prefiltered through external search criteria might be passed to GridField.
+ if (window.location.search) {
+ ajaxOpts.data = window.location.search.replace(/^\?/, '') + '&' + $.param(ajaxOpts.data);
+ }
+
+ form.addClass('loading');
+
+ $.ajax($.extend({}, {
+ headers: {"X-Pjax": 'CurrentField'},
+ type: "POST",
+ url: this.data('url'),
+ dataType: 'html',
+ success: function (data) {
+ // Replace the grid field with response, not the form.
+ // TODO Only replaces all its children, to avoid replacing the current scope
+ // of the executing method. Means that it doesn't retrigger the onmatch() on the main container.
+ self.empty().append($(data).children());
+
+ // Refocus previously focused element. Useful e.g. for finding+adding
+ // multiple relationships via keyboard.
+ if (focusedElName) self.find(':input[name="' + focusedElName + '"]').focus();
+
+ // Update filter
+ if (self.find('.grid-field__filter-header').length) {
+ var content;
+ if (ajaxOpts.data[0].filter == "show") {
+ content = '';
+ self.addClass('show-filter').find('.grid-field__filter-header').show();
+ } else {
+ content = '';
+ self.removeClass('show-filter').find('.grid-field__filter-header').hide();
+ }
+
+ self.find('.sortable-header th:last').html(content);
+ }
+
+ form.removeClass('loading');
+ if (successCallback) {
+ successCallback.apply(this, arguments);
+ }
+ self.trigger('reload', self);
+
+ // update publish button if necessary
+ const publish = $('#Form_EditForm_action_publish');
+
+ // button needs to be updated only if it's in published state
+ if (publish.length > 0 && publish.hasClass('btn-outline-primary')) {
+ publish.removeClass('btn-outline-primary');
+ publish.removeClass('font-icon-tick');
+ publish.addClass('btn-primary');
+ publish.addClass('font-icon-rocket');
+ publish.find('.btn__title').html('Save & publish');
+ }
+ },
+ error: function (e) {
+ alert(i18n._t('Admin.ERRORINTRANSACTION'));
+ form.removeClass('loading');
+ }
+ }, ajaxOpts));
+ },
rebuildSort: function() {
var grid = this.getGridField();
@@ -320,7 +405,7 @@
var grid = self.getGridField();
if (grid.data("immediate-update") && postback)
{
- grid.reload({
+ self.reload({
url: grid.data("url-reorder")
});
}
diff --git a/lang/en.yml b/lang/en.yml
new file mode 100644
index 0000000..5792708
--- /dev/null
+++ b/lang/en.yml
@@ -0,0 +1,14 @@
+en:
+ GridFieldExtensions:
+ ADD: Add
+ ADDEXISTING: 'Add Existing'
+ BACK: Back
+ CURRENT: (current)
+ NOITEMS: 'There are no items.'
+ Next: Next
+ PREVIOUS: Previous
+ RESULTS: Results
+ SEARCH: Search
+ SELECTTYPETOCREATE: '(Select type to create)'
+ Symbiote\GridFieldExtensions\GridFieldConfigurablePaginator:
+ SHOW: Show
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
new file mode 100644
index 0000000..1b984f8
--- /dev/null
+++ b/phpcs.xml.dist
@@ -0,0 +1,9 @@
+
+
+ CodeSniffer ruleset for SilverStripe coding conventions.
+
+
+
+
+
+
diff --git a/src/GridFieldAddExistingSearchButton.php b/src/GridFieldAddExistingSearchButton.php
index 7e991cb..630a2c3 100755
--- a/src/GridFieldAddExistingSearchButton.php
+++ b/src/GridFieldAddExistingSearchButton.php
@@ -91,14 +91,15 @@ class GridFieldAddExistingSearchButton implements GridField_HTMLProvider, GridFi
{
GridFieldExtensions::include_requirements();
- $data = new ArrayData(array(
+ $data = ArrayData::create([
'Title' => $this->getTitle(),
- 'Link' => $grid->Link('add-existing-search')
- ));
+ 'Classes' => 'action btn btn-primary font-icon-search add-existing-search',
+ 'Link' => $grid->Link('add-existing-search'),
+ ]);
- return array(
- $this->fragment => $data->renderWith('Symbiote\\GridFieldExtensions\\GridFieldAddExistingSearchButton'),
- );
+ return [
+ $this->fragment => $data->renderWith(__CLASS__),
+ ];
}
public function getURLHandlers($grid)
diff --git a/src/GridFieldAddExistingSearchHandler.php b/src/GridFieldAddExistingSearchHandler.php
index 8f025fd..58de381 100644
--- a/src/GridFieldAddExistingSearchHandler.php
+++ b/src/GridFieldAddExistingSearchHandler.php
@@ -7,8 +7,10 @@ use SilverStripe\Control\RequestHandler;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
+use SilverStripe\Forms\GridField\GridField;
use SilverStripe\ORM\DataList;
use SilverStripe\ORM\PaginatedList;
+use SilverStripe\ORM\Search\SearchContext;
/**
* Used by {@link GridFieldAddExistingSearchButton} to provide the searching
@@ -49,7 +51,7 @@ class GridFieldAddExistingSearchHandler extends RequestHandler
public function index()
{
- return $this->renderWith('Symbiote\\GridFieldExtensions\\GridFieldAddExistingSearchHandler');
+ return $this->renderWith(__CLASS__);
}
public function add($request)
@@ -73,19 +75,18 @@ class GridFieldAddExistingSearchHandler extends RequestHandler
*/
public function SearchForm()
{
- $form = new Form(
+ $form = Form::create(
$this,
'SearchForm',
$this->context->getFields(),
- new FieldList(
+ FieldList::create(
FormAction::create('doSearch', _t('GridFieldExtensions.SEARCH', 'Search'))
->setUseButtonTag(true)
- ->addExtraClass('ss-ui-button')
- ->setAttribute('data-icon', 'magnifier')
+ ->addExtraClass('btn btn-primary font-icon-search')
)
);
- $form->addExtraClass('stacked add-existing-search-form');
+ $form->addExtraClass('stacked add-existing-search-form form--no-dividers');
$form->setFormMethod('GET');
return $form;
diff --git a/src/GridFieldConfigurablePaginator.php b/src/GridFieldConfigurablePaginator.php
index f3af8d5..3655ae9 100644
--- a/src/GridFieldConfigurablePaginator.php
+++ b/src/GridFieldConfigurablePaginator.php
@@ -5,8 +5,8 @@ namespace Symbiote\GridFieldExtensions;
use Exception;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Forms\GridField\GridField;
-use SilverStripe\Forms\GridField\GridFieldPaginator;
use SilverStripe\Forms\GridField\GridField_FormAction;
+use SilverStripe\Forms\GridField\GridFieldPaginator;
use SilverStripe\Forms\GridField\GridState_Data;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\Limitable;
@@ -269,7 +269,7 @@ class GridFieldConfigurablePaginator extends GridFieldPaginator
* {@inheritDoc}
*
* @param GridField $gridField
- * @return ArrayList|null
+ * @return ArrayData|null
*/
public function getTemplateParameters(GridField $gridField)
{
@@ -281,7 +281,7 @@ class GridFieldConfigurablePaginator extends GridFieldPaginator
// Figure out which page and record range we're on
if (!$arguments['total-rows']) {
- return;
+ return null;
}
// Define a list of the FormActions that should be generated for pager controls (see getPagerActions())
@@ -289,13 +289,15 @@ class GridFieldConfigurablePaginator extends GridFieldPaginator
'first' => array(
'title' => 'First',
'args' => array('first-shown' => 1),
- 'extra-class' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-double-left ss-gridfield-firstpage',
+ 'extra-class' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-double-left '
+ . 'ss-gridfield-firstpage',
'disable-previous' => ($this->getCurrentPage() == 1)
),
'prev' => array(
'title' => 'Previous',
'args' => array('first-shown' => $arguments['first-shown'] - $this->getItemsPerPage()),
- 'extra-class' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-left ss-gridfield-previouspage',
+ 'extra-class' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-left '
+ . 'ss-gridfield-previouspage',
'disable-previous' => ($this->getCurrentPage() == 1)
),
'next' => array(
@@ -307,7 +309,8 @@ class GridFieldConfigurablePaginator extends GridFieldPaginator
'last' => array(
'title' => 'Last',
'args' => array('first-shown' => ($this->getTotalPages() - 1) * $this->getItemsPerPage() + 1),
- 'extra-class' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-double-right ss-gridfield-lastpage',
+ 'extra-class' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-double-right '
+ . 'ss-gridfield-lastpage',
'disable-next' => ($this->getCurrentPage() == $arguments['total-pages'])
),
'pagesize' => array(
diff --git a/src/GridFieldOrderableRows.php b/src/GridFieldOrderableRows.php
index 91cb6d3..79f1b24 100755
--- a/src/GridFieldOrderableRows.php
+++ b/src/GridFieldOrderableRows.php
@@ -11,17 +11,18 @@ use SilverStripe\Forms\GridField\GridField_DataManipulator;
use SilverStripe\Forms\GridField\GridField_HTMLProvider;
use SilverStripe\Forms\GridField\GridField_SaveHandler;
use SilverStripe\Forms\GridField\GridField_URLHandler;
+use SilverStripe\Forms\GridField\GridFieldPaginator;
use SilverStripe\Forms\HiddenField;
use SilverStripe\ORM\ArrayList;
-use SilverStripe\ORM\DataObject;
-use SilverStripe\ORM\DB;
use SilverStripe\ORM\DataList;
+use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataObjectInterface;
+use SilverStripe\ORM\DB;
use SilverStripe\ORM\ManyManyList;
+use SilverStripe\ORM\Map;
use SilverStripe\ORM\SS_List;
-use SilverStripe\ORM\SS_Map;
+use SilverStripe\Versioned\Versioned;
use SilverStripe\View\ViewableData;
-use Exception;
/**
* Allows grid field rows to be re-ordered via drag and drop. Both normal data
@@ -171,6 +172,35 @@ class GridFieldOrderableRows extends RequestHandler implements
return $this;
}
+ /**
+ * Validates sortable list
+ *
+ * @param SS_List $list
+ * @throws Exception
+ */
+ public function validateSortField(SS_List $list)
+ {
+ $field = $this->getSortField();
+
+ if ($list instanceof ManyManyList) {
+ $extra = $list->getExtraFields();
+
+ if ($extra && array_key_exists($field, $extra)) {
+ return;
+ }
+ }
+
+ $classes = ClassInfo::dataClassesFor($list->dataClass());
+
+ foreach ($classes as $class) {
+ if (singleton($class)->hasDataBaseField($field)) {
+ return;
+ }
+ }
+
+ throw new \Exception("Couldn't find the sort field '" . $field . "'");
+ }
+
/**
* Gets the table which contains the sort field.
*
@@ -363,7 +393,7 @@ class GridFieldOrderableRows extends RequestHandler implements
*/
public function handleMoveToPage(GridField $grid, $request)
{
- if (!$paginator = $grid->getConfig()->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldPaginator')) {
+ if (!$paginator = $grid->getConfig()->getComponentByType(GridFieldPaginator::class)) {
$this->httpError(404, 'Paginator component not found');
}
@@ -499,16 +529,14 @@ class GridFieldOrderableRows extends RequestHandler implements
/** @var SS_List $map */
$map = $list->map('ID', $sortField);
//fix for versions of SS that return inconsistent types for `map` function
- if ($map instanceof SS_Map) {
+ if ($map instanceof Map) {
$map = $map->toArray();
}
// If not a ManyManyList and using versioning, detect it.
- $isVersioned = false;
+ $this->validateSortField($list);
$class = $list->dataClass();
- if ($class == $this->getSortTable($list)) {
- $isVersioned = $class::has_extension('SilverStripe\\ORM\\Versioning\\Versioned');
- }
+ $isVersioned = $class::has_extension(Versioned::class);
// Loop through each item, and update the sort values which do not
// match to order the objects.
diff --git a/templates/Symbiote/GridFieldExtensions/GridFieldAddExistingSearchButton.ss b/templates/Symbiote/GridFieldExtensions/GridFieldAddExistingSearchButton.ss
index 3efa1ec..ddebae0 100644
--- a/templates/Symbiote/GridFieldExtensions/GridFieldAddExistingSearchButton.ss
+++ b/templates/Symbiote/GridFieldExtensions/GridFieldAddExistingSearchButton.ss
@@ -1,3 +1,3 @@
-
- $Title
-
+
+ $Title
+
diff --git a/templates/Symbiote/GridFieldExtensions/GridFieldAddExistingSearchHandler.ss b/templates/Symbiote/GridFieldExtensions/GridFieldAddExistingSearchHandler.ss
index eaa531a..969fc94 100644
--- a/templates/Symbiote/GridFieldExtensions/GridFieldAddExistingSearchHandler.ss
+++ b/templates/Symbiote/GridFieldExtensions/GridFieldAddExistingSearchHandler.ss
@@ -1,36 +1,56 @@
-$SearchForm
-
-
<% _t("RESULTS", "Results") %>
-
- <% if $Items %>
-
- <% loop $Items %>
- - $Title
- <% end_loop %>
-
- <% else %>
-
<% _t("NOITEMS", "There are no items.") %>
- <% end_if %>
-
- <% if $Items.MoreThanOnePage %>
-
- <% end_if %>
-
+$SearchForm
+
+<%t GridFieldExtensions.RESULTS "Results" %>
+
+ <% if $Items %>
+
+ <% loop $Items %>
+ -
+ $Title
+
+ <% end_loop %>
+
+ <% else %>
+
<%t GridFieldExtensions.NOITEMS "There are no items." %>
+ <% end_if %>
+
+ <% if $Items.MoreThanOnePage %>
+
+ <% end_if %>
+