Merge branch '3.1' into 3

This commit is contained in:
Robbie Averill 2018-02-12 11:12:12 +13:00
commit ed51125b91
11 changed files with 236 additions and 100 deletions

View File

@ -12,6 +12,8 @@ matrix:
env: DB=PGSQL PHPUNIT_TEST=1 env: DB=PGSQL PHPUNIT_TEST=1
- php: 7.1 - php: 7.1
env: DB=MYSQL PHPUNIT_COVERAGE_TEST=1 env: DB=MYSQL PHPUNIT_COVERAGE_TEST=1
- php: 7.2
env: DB=MYSQL PHPUNIT_TEST=1
before_script: before_script:
- phpenv rehash - phpenv rehash
@ -25,7 +27,7 @@ before_script:
script: script:
- if [[ $PHPUNIT_TEST ]]; then vendor/bin/phpunit tests/; fi - 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 [[ $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: after_success:
- if [[ $PHPUNIT_COVERAGE_TEST ]]; then bash <(curl -s https://codecov.io/bash) -f coverage.xml; fi - if [[ $PHPUNIT_COVERAGE_TEST ]]; then bash <(curl -s https://codecov.io/bash) -f coverage.xml; fi

View File

@ -14,7 +14,7 @@
} }
.add-existing-search-dialog .add-existing-search-form .field label { .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 { .add-existing-search-dialog .add-existing-search-form .Actions {
@ -22,38 +22,12 @@
padding: 0; padding: 0;
} }
.add-existing-search-dialog .add-existing-search-items li a { .add-existing-search-dialog .list-group-item {
background: #FFF; min-height: 32px;
border: solid #CCC;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
display: block;
padding: 6px;
} }
.add-existing-search-dialog .add-existing-search-items li:first-child a { .add-existing-search-dialog .btn-toolbar {
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;
margin-top: 12px; margin-top: 12px;
padding: 6px;
} }
/** /**
@ -97,7 +71,6 @@
display: inline-block; display: inline-block;
margin: 0; margin: 0;
min-width: 150px; min-width: 150px;
width: calc(100% - 20px);
} }
.ss-gridfield-add-new-multi-class .form-group:after { .ss-gridfield-add-new-multi-class .form-group:after {

View File

@ -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({ $(".add-existing-search-dialog .add-existing-search-items a").entwine({
onclick: function() { onclick: function() {
var link = this.closest(".add-existing-search-items").data("add-link"); var link = this.closest(".add-existing-search-items").data("add-link");
@ -264,6 +273,82 @@
*/ */
$(".ss-gridfield-orderable tbody").entwine({ $(".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 = '<span class="non-sortable"></span>';
self.addClass('show-filter').find('.grid-field__filter-header').show();
} else {
content = '<button type="button" title="Open search and filter" name="showFilter" class="btn btn-secondary font-icon-search btn--no-text btn--icon-large grid-field__filter-open"></button>';
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() { rebuildSort: function() {
var grid = this.getGridField(); var grid = this.getGridField();
@ -320,7 +405,7 @@
var grid = self.getGridField(); var grid = self.getGridField();
if (grid.data("immediate-update") && postback) if (grid.data("immediate-update") && postback)
{ {
grid.reload({ self.reload({
url: grid.data("url-reorder") url: grid.data("url-reorder")
}); });
} }

14
lang/en.yml Normal file
View File

@ -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

9
phpcs.xml.dist Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="SilverStripe">
<description>CodeSniffer ruleset for SilverStripe coding conventions.</description>
<rule ref="PSR2" >
<!-- Current exclusions -->
<exclude name="PSR1.Methods.CamelCapsMethodName" />
</rule>
</ruleset>

View File

@ -91,14 +91,15 @@ class GridFieldAddExistingSearchButton implements GridField_HTMLProvider, GridFi
{ {
GridFieldExtensions::include_requirements(); GridFieldExtensions::include_requirements();
$data = new ArrayData(array( $data = ArrayData::create([
'Title' => $this->getTitle(), '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( return [
$this->fragment => $data->renderWith('Symbiote\\GridFieldExtensions\\GridFieldAddExistingSearchButton'), $this->fragment => $data->renderWith(__CLASS__),
); ];
} }
public function getURLHandlers($grid) public function getURLHandlers($grid)

View File

@ -7,8 +7,10 @@ use SilverStripe\Control\RequestHandler;
use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form; use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction; use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataList;
use SilverStripe\ORM\PaginatedList; use SilverStripe\ORM\PaginatedList;
use SilverStripe\ORM\Search\SearchContext;
/** /**
* Used by {@link GridFieldAddExistingSearchButton} to provide the searching * Used by {@link GridFieldAddExistingSearchButton} to provide the searching
@ -49,7 +51,7 @@ class GridFieldAddExistingSearchHandler extends RequestHandler
public function index() public function index()
{ {
return $this->renderWith('Symbiote\\GridFieldExtensions\\GridFieldAddExistingSearchHandler'); return $this->renderWith(__CLASS__);
} }
public function add($request) public function add($request)
@ -73,19 +75,18 @@ class GridFieldAddExistingSearchHandler extends RequestHandler
*/ */
public function SearchForm() public function SearchForm()
{ {
$form = new Form( $form = Form::create(
$this, $this,
'SearchForm', 'SearchForm',
$this->context->getFields(), $this->context->getFields(),
new FieldList( FieldList::create(
FormAction::create('doSearch', _t('GridFieldExtensions.SEARCH', 'Search')) FormAction::create('doSearch', _t('GridFieldExtensions.SEARCH', 'Search'))
->setUseButtonTag(true) ->setUseButtonTag(true)
->addExtraClass('ss-ui-button') ->addExtraClass('btn btn-primary font-icon-search')
->setAttribute('data-icon', 'magnifier')
) )
); );
$form->addExtraClass('stacked add-existing-search-form'); $form->addExtraClass('stacked add-existing-search-form form--no-dividers');
$form->setFormMethod('GET'); $form->setFormMethod('GET');
return $form; return $form;

View File

@ -5,8 +5,8 @@ namespace Symbiote\GridFieldExtensions;
use Exception; use Exception;
use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Config\Configurable;
use SilverStripe\Forms\GridField\GridField; use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldPaginator;
use SilverStripe\Forms\GridField\GridField_FormAction; use SilverStripe\Forms\GridField\GridField_FormAction;
use SilverStripe\Forms\GridField\GridFieldPaginator;
use SilverStripe\Forms\GridField\GridState_Data; use SilverStripe\Forms\GridField\GridState_Data;
use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\Limitable; use SilverStripe\ORM\Limitable;
@ -269,7 +269,7 @@ class GridFieldConfigurablePaginator extends GridFieldPaginator
* {@inheritDoc} * {@inheritDoc}
* *
* @param GridField $gridField * @param GridField $gridField
* @return ArrayList|null * @return ArrayData|null
*/ */
public function getTemplateParameters(GridField $gridField) public function getTemplateParameters(GridField $gridField)
{ {
@ -281,7 +281,7 @@ class GridFieldConfigurablePaginator extends GridFieldPaginator
// Figure out which page and record range we're on // Figure out which page and record range we're on
if (!$arguments['total-rows']) { if (!$arguments['total-rows']) {
return; return null;
} }
// Define a list of the FormActions that should be generated for pager controls (see getPagerActions()) // 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( 'first' => array(
'title' => 'First', 'title' => 'First',
'args' => array('first-shown' => 1), '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) 'disable-previous' => ($this->getCurrentPage() == 1)
), ),
'prev' => array( 'prev' => array(
'title' => 'Previous', 'title' => 'Previous',
'args' => array('first-shown' => $arguments['first-shown'] - $this->getItemsPerPage()), '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) 'disable-previous' => ($this->getCurrentPage() == 1)
), ),
'next' => array( 'next' => array(
@ -307,7 +309,8 @@ class GridFieldConfigurablePaginator extends GridFieldPaginator
'last' => array( 'last' => array(
'title' => 'Last', 'title' => 'Last',
'args' => array('first-shown' => ($this->getTotalPages() - 1) * $this->getItemsPerPage() + 1), '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']) 'disable-next' => ($this->getCurrentPage() == $arguments['total-pages'])
), ),
'pagesize' => array( 'pagesize' => array(

View File

@ -11,17 +11,18 @@ use SilverStripe\Forms\GridField\GridField_DataManipulator;
use SilverStripe\Forms\GridField\GridField_HTMLProvider; use SilverStripe\Forms\GridField\GridField_HTMLProvider;
use SilverStripe\Forms\GridField\GridField_SaveHandler; use SilverStripe\Forms\GridField\GridField_SaveHandler;
use SilverStripe\Forms\GridField\GridField_URLHandler; use SilverStripe\Forms\GridField\GridField_URLHandler;
use SilverStripe\Forms\GridField\GridFieldPaginator;
use SilverStripe\Forms\HiddenField; use SilverStripe\Forms\HiddenField;
use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataObjectInterface; use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\ManyManyList; use SilverStripe\ORM\ManyManyList;
use SilverStripe\ORM\Map;
use SilverStripe\ORM\SS_List; use SilverStripe\ORM\SS_List;
use SilverStripe\ORM\SS_Map; use SilverStripe\Versioned\Versioned;
use SilverStripe\View\ViewableData; use SilverStripe\View\ViewableData;
use Exception;
/** /**
* Allows grid field rows to be re-ordered via drag and drop. Both normal data * 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; 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. * Gets the table which contains the sort field.
* *
@ -363,7 +393,7 @@ class GridFieldOrderableRows extends RequestHandler implements
*/ */
public function handleMoveToPage(GridField $grid, $request) 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'); $this->httpError(404, 'Paginator component not found');
} }
@ -499,16 +529,14 @@ class GridFieldOrderableRows extends RequestHandler implements
/** @var SS_List $map */ /** @var SS_List $map */
$map = $list->map('ID', $sortField); $map = $list->map('ID', $sortField);
//fix for versions of SS that return inconsistent types for `map` function //fix for versions of SS that return inconsistent types for `map` function
if ($map instanceof SS_Map) { if ($map instanceof Map) {
$map = $map->toArray(); $map = $map->toArray();
} }
// If not a ManyManyList and using versioning, detect it. // If not a ManyManyList and using versioning, detect it.
$isVersioned = false; $this->validateSortField($list);
$class = $list->dataClass(); $class = $list->dataClass();
if ($class == $this->getSortTable($list)) { $isVersioned = $class::has_extension(Versioned::class);
$isVersioned = $class::has_extension('SilverStripe\\ORM\\Versioning\\Versioned');
}
// Loop through each item, and update the sort values which do not // Loop through each item, and update the sort values which do not
// match to order the objects. // match to order the objects.

View File

@ -1,3 +1,3 @@
<a href="$Link" class="action ss-ui-button ui-button add-existing-search" data-icon="magnifier"> <a href="$Link" class="$Classes">
$Title <span class="btn__title">$Title</span>
</a> </a>

View File

@ -1,35 +1,55 @@
$SearchForm $SearchForm
<h3><% _t("RESULTS", "Results") %></h3> <h3><%t GridFieldExtensions.RESULTS "Results" %></h3>
<div class="add-existing-search-results"> <div class="add-existing-search-results">
<% if $Items %> <% if $Items %>
<ul class="add-existing-search-items" data-add-link="$Link('add')"> <ul class="list-group add-existing-search-items" data-add-link="$Link('add')">
<% loop $Items %> <% loop $Items %>
<li class="$EvenOdd"><a href="#" data-id="$ID">$Title</a></li> <li class="$EvenOdd list-group-item list-group-item-action">
<a href="#" data-id="$ID">$Title</a>
</li>
<% end_loop %> <% end_loop %>
</ul> </ul>
<% else %> <% else %>
<p><% _t("NOITEMS", "There are no items.") %></p> <p><%t GridFieldExtensions.NOITEMS "There are no items." %></p>
<% end_if %> <% end_if %>
<% if $Items.MoreThanOnePage %> <% if $Items.MoreThanOnePage %>
<ul class="add-existing-search-pagination"> <ul class="pagination add-existing-search-pagination">
<% if $Items.NotFirstPage %> <% if $Items.NotFirstPage %>
<li><a href="$Items.PrevLink">&laquo;</a></li> <li class="page-item">
<a class="page-link" href="$Items.PrevLink">
<span aria-hidden="true">&laquo;</span>
<span class="sr-only"><%t GridFieldExtensions.PREVIOUS "Previous" %></span>
</a>
</li>
<% end_if %> <% end_if %>
<% loop $Items.PaginationSummary(4) %> <% loop $Items.PaginationSummary(2) %>
<% if $CurrentBool %> <% if $CurrentBool %>
<li class="current">$PageNum</li> <li class="page-item active">
<a class="page-link" href="#">
$PageNum <span class="sr-only"><%t GridFieldExtensions.CURRENT "(current)" %></span>
</a>
</li>
<% else_if $Link %> <% else_if $Link %>
<li><a href="$Link">$PageNum</a></li> <li class="page-item">
<a class="page-link" href="$Link">
$PageNum
</a>
</li>
<% else %> <% else %>
<li>&hellip;</li> <li class="page-item disabled">
<a class="page-link" href="#">&hellip;</a>
</li>
<% end_if %> <% end_if %>
<% end_loop %> <% end_loop %>
<% if $Items.NotLastPage %> <% if $Items.NotLastPage %>
<li><a href="$Items.NextLink">&raquo;</a></li> <a class="page-link" href="$Items.NextLink">
<span aria-hidden="true">&raquo;</span>
<span class="sr-only"><%t GridFieldExtensions.Next "Next" %></span>
</a>
<%end_if %> <%end_if %>
</ul> </ul>
<% end_if %> <% end_if %>