diff --git a/admin/client/dist/js/bundle.js b/admin/client/dist/js/bundle.js index ae0205463..b37f8f6fb 100644 --- a/admin/client/dist/js/bundle.js +++ b/admin/client/dist/js/bundle.js @@ -2039,41 +2039,41 @@ this.closest(".grid-field").addClass("show-filter"),this.parent().html(''),e("body").append(n)):n.addClass("fade in").fadeIn() -var i=e(this.data("target")) -i.addClass("in"),i.find("[data-dismiss]").on("click",function(){n.fadeOut(function(){n.removeClass("in")}),i.removeClass("in")})}}),e(".grid-field .action:button").entwine({onclick:function p(e){var t="show" - +n.length<1?(n=e(''),e("body").append(n)):n.addClass("fade in").fadeIn(),t.addClass("in"),t.find("[data-dismiss]").on("click",function(){n.fadeOut(function(){n.removeClass("in") +}),t.removeClass("in")})}}),e(".grid-field .action:button").entwine({onclick:function g(e){var t="show" return this.is(":disabled")?void e.preventDefault():(!this.hasClass("ss-gridfield-button-close")&&this.closest(".grid-field").hasClass("show-filter")||(t="hidden"),this.getGridField().reload({data:[{name:this.attr("name"), -value:this.val(),filter:t}]}),void e.preventDefault())},actionurl:function h(){var t=this.closest(":button"),n=this.getGridField(),i=this.closest("form"),r=i.find(":input.gridstate").serialize(),a=i.find('input[name="SecurityID"]').val() +value:this.val(),filter:t}]}),void e.preventDefault())},actionurl:function v(){var t=this.closest(":button"),n=this.getGridField(),i=this.closest("form"),r=i.find(":input.gridstate").serialize(),a=i.find('input[name="SecurityID"]').val() r+="&"+encodeURIComponent(t.attr("name"))+"="+encodeURIComponent(t.val()),a&&(r+="&SecurityID="+encodeURIComponent(a)),window.location.search&&(r=window.location.search.replace(/^\?/,"")+"&"+r) var o=n.data("url").indexOf("?")==-1?"?":"&" -return e.path.makeUrlAbsolute(n.data("url")+o+r,e("base").attr("href"))}}),e(".grid-field .add-existing-autocompleter").entwine({onbuttoncreate:function m(){var e=this -this.toggleDisabled(),this.find('input[type="text"]').on("keyup",function(){e.toggleDisabled()})},onunmatch:function g(){this.find('input[type="text"]').off("keyup")},toggleDisabled:function v(){var e=this.find(".ss-ui-button"),t=this.find('input[type="text"]'),n=""!==t.val(),i=e.is(":disabled") +return e.path.makeUrlAbsolute(n.data("url")+o+r,e("base").attr("href"))}}),e(".grid-field .add-existing-autocompleter").entwine({onbuttoncreate:function y(){var e=this +this.toggleDisabled(),this.find('input[type="text"]').on("keyup",function(){e.toggleDisabled()})},onunmatch:function b(){this.find('input[type="text"]').off("keyup")},toggleDisabled:function _(){var e=this.find(".ss-ui-button"),t=this.find('input[type="text"]'),n=""!==t.val(),i=e.is(":disabled") -;(n&&i||!n&&!i)&&e.attr("disabled",!i)}}),e(".grid-field .grid-field__col-compact .action.gridfield-button-delete, .cms-edit-form .btn-toolbar button.action.action-delete").entwine({onclick:function y(e){ -return confirm(s["default"]._t("TABLEFIELD.DELETECONFIRMMESSAGE"))?void this._super(e):(e.preventDefault(),!1)}}),e(".grid-field .action.gridfield-button-print").entwine({UUID:null,onmatch:function b(){ -this._super(),this.setUUID((new Date).getTime())},onunmatch:function _(){this._super()},onclick:function w(e){var t=this.actionurl() -return window.open(t),e.preventDefault(),!1}}),e(".ss-gridfield-print-iframe").entwine({onmatch:function C(){this._super(),this.hide().bind("load",function(){this.focus() +;(n&&i||!n&&!i)&&e.attr("disabled",!i)}}),e(".grid-field .grid-field__col-compact .action.gridfield-button-delete, .cms-edit-form .btn-toolbar button.action.action-delete").entwine({onclick:function w(e){ +return confirm(s["default"]._t("TABLEFIELD.DELETECONFIRMMESSAGE"))?void this._super(e):(e.preventDefault(),!1)}}),e(".grid-field .action.gridfield-button-print").entwine({UUID:null,onmatch:function C(){ +this._super(),this.setUUID((new Date).getTime())},onunmatch:function T(){this._super()},onclick:function E(e){var t=this.actionurl() +return window.open(t),e.preventDefault(),!1}}),e(".ss-gridfield-print-iframe").entwine({onmatch:function P(){this._super(),this.hide().bind("load",function(){this.focus() var e=this.contentWindow||this -e.print()})},onunmatch:function T(){this._super()}}),e(".grid-field .action.no-ajax").entwine({onclick:function E(e){return window.location.href=this.actionurl(),e.preventDefault(),!1}}),e(".grid-field .action-detail").entwine({ -onclick:function P(){return this.getGridField().showDetailView(e(this).prop("href")),!1}}),e(".grid-field[data-selectable]").entwine({getSelectedItems:function O(){return this.find(".ss-gridfield-item.ui-selected") +e.print()})},onunmatch:function O(){this._super()}}),e(".grid-field .action.no-ajax").entwine({onclick:function S(e){return window.location.href=this.actionurl(),e.preventDefault(),!1}}),e(".grid-field .action-detail").entwine({ +onclick:function k(){return this.getGridField().showDetailView(e(this).prop("href")),!1}}),e(".grid-field[data-selectable]").entwine({getSelectedItems:function j(){return this.find(".ss-gridfield-item.ui-selected") -},getSelectedIDs:function S(){return e.map(this.getSelectedItems(),function(t){return e(t).data("id")})}}),e(".grid-field[data-selectable] .ss-gridfield-items").entwine({onadd:function k(){this._super(), -this.selectable()},onremove:function j(){this._super(),this.data("selectable")&&this.selectable("destroy")}}),e(".grid-field .filter-header :input").entwine({onmatch:function x(){var e=this.closest(".extra").find(".ss-gridfield-button-filter"),t=this.closest(".extra").find(".ss-gridfield-button-reset") +},getSelectedIDs:function x(){return e.map(this.getSelectedItems(),function(t){return e(t).data("id")})}}),e(".grid-field[data-selectable] .ss-gridfield-items").entwine({onadd:function R(){this._super(), +this.selectable()},onremove:function I(){this._super(),this.data("selectable")&&this.selectable("destroy")}}),e(".grid-field .filter-header :input").entwine({onmatch:function F(){var e=this.closest(".extra").find(".ss-gridfield-button-filter"),t=this.closest(".extra").find(".ss-gridfield-button-reset") -this.val()&&(e.addClass("filtered"),t.addClass("filtered")),this._super()},onunmatch:function R(){this._super()},onkeydown:function I(e){if(!this.closest(".ss-gridfield-button-reset").length){var t=this.closest(".extra").find(".ss-gridfield-button-filter"),n=this.closest(".extra").find(".ss-gridfield-button-reset") +this.val()&&(e.addClass("filtered"),t.addClass("filtered")),this._super()},onunmatch:function A(){this._super()},onkeydown:function D(e){if(!this.closest(".ss-gridfield-button-reset").length){var t=this.closest(".extra").find(".ss-gridfield-button-filter"),n=this.closest(".extra").find(".ss-gridfield-button-reset") if("13"==e.keyCode){var i=this.closest(".filter-header").find(".ss-gridfield-button-filter"),r="show" return!this.hasClass("ss-gridfield-button-close")&&this.closest(".grid-field").hasClass("show-filter")||(r="hidden"),this.getGridField().reload({data:[{name:i.attr("name"),value:i.val(),filter:r}]}),!1 -}t.addClass("hover-alike"),n.addClass("hover-alike")}}}),e(".grid-field .relation-search").entwine({onfocusin:function F(t){this.autocomplete({source:function n(t,i){var r=e(this.element),a=e(this.element).closest("form") +}t.addClass("hover-alike"),n.addClass("hover-alike")}}}),e(".grid-field .relation-search").entwine({onfocusin:function M(t){this.autocomplete({source:function n(t,i){var r=e(this.element),a=e(this.element).closest("form") e.ajax({headers:{"X-Pjax":"Partial"},dataType:"json",type:"GET",url:e(r).data("searchUrl"),data:encodeURIComponent(r.attr("name"))+"="+encodeURIComponent(r.val()),success:i,error:function o(e){alert(s["default"]._t("GRIDFIELD.ERRORINTRANSACTION","An error occured while fetching data from the server\n Please try again later.")) @@ -2081,7 +2081,7 @@ e.ajax({headers:{"X-Pjax":"Partial"},dataType:"json",type:"GET",url:e(r).data("s }})},select:function i(t,n){var i=e('') i.val(n.item.id),e(this).closest(".grid-field").find(".action_gridfield_relationfind").replaceWith(i) var r=e(this).closest(".grid-field").find(".action_gridfield_relationadd") -r.removeAttr("disabled")}})}}),e(".grid-field .pagination-page-number input").entwine({onkeydown:function A(t){if(13==t.keyCode){var n=parseInt(e(this).val(),10),i=e(this).getGridField() +r.removeAttr("disabled")}})}}),e(".grid-field .pagination-page-number input").entwine({onkeydown:function N(t){if(13==t.keyCode){var n=parseInt(e(this).val(),10),i=e(this).getGridField() return i.setState("GridFieldPaginator",{currentPage:n}),i.reload(),!1}}})})},function(e,t,n){"use strict" function i(e){if(e&&e.__esModule)return e var t={} diff --git a/admin/client/dist/styles/bundle.css b/admin/client/dist/styles/bundle.css index 7af3774f3..5e22ce4a3 100644 --- a/admin/client/dist/styles/bundle.css +++ b/admin/client/dist/styles/bundle.css @@ -15594,7 +15594,7 @@ div.grid-field__sort-field+.form__fieldgroup-item{ } .grid-field-import.in .modal-content{ - overflow-y:scroll; + overflow-y:auto; } .grid-field-import.in .modal-dialog{ diff --git a/admin/client/src/components/GridField/GridField.scss b/admin/client/src/components/GridField/GridField.scss index de61b6431..cb6e46725 100644 --- a/admin/client/src/components/GridField/GridField.scss +++ b/admin/client/src/components/GridField/GridField.scss @@ -264,7 +264,7 @@ div.grid-field__sort-field + .form__fieldgroup-item { display: block; .modal-content { - overflow-y: scroll; + overflow-y: auto; } .modal-dialog { diff --git a/admin/client/src/legacy/GridField.js b/admin/client/src/legacy/GridField.js index 3a1ce55d3..0f655ecac 100644 --- a/admin/client/src/legacy/GridField.js +++ b/admin/client/src/legacy/GridField.js @@ -137,9 +137,30 @@ $.entwine('ss', function($) { $('.grid-field .action.action_import:button').entwine({ onclick: function(e) { e.preventDefault(); + this.openmodal(); + }, + onmatch: function() { + this._super(); + // Trigger auto-open + if (this.data('state') === 'open') { + this.openmodal(); + } + }, + onunmatch: function() { + this._super(); + }, - var backdrop = $('.modal-backdrop'); + openmodal: function() { + // Remove existing modal + let modal = $(this.data('target')); + modal.remove(); + // Add modal to end of body tag + modal = $(this.data('modal')); + modal.appendTo(document.body); + + // Apply backdrop + let backdrop = $('.modal-backdrop'); if(backdrop.length < 1) { backdrop = $(''); $('body').append(backdrop); @@ -147,8 +168,6 @@ $.entwine('ss', function($) { backdrop.addClass('fade in').fadeIn(); } - var modal = $(this.data('target')); - modal.addClass('in'); modal.find('[data-dismiss]').on('click', function() { backdrop.fadeOut(function() { diff --git a/admin/code/ModelAdmin.php b/admin/code/ModelAdmin.php index 258ee9481..0da71204a 100644 --- a/admin/code/ModelAdmin.php +++ b/admin/code/ModelAdmin.php @@ -11,6 +11,7 @@ use SilverStripe\Forms\FieldList; use SilverStripe\Forms\Form; use SilverStripe\Forms\FormAction; use SilverStripe\Forms\GridField\GridFieldDetailForm; +use SilverStripe\Forms\GridField\GridFieldFilterHeader; use SilverStripe\Forms\ResetFormAction; use SilverStripe\Forms\RequiredFields; use SilverStripe\Forms\HiddenField; @@ -30,8 +31,8 @@ use SilverStripe\ORM\DataObject; use SilverStripe\ORM\Search\SearchContext; use SilverStripe\ORM\SS_List; use SilverStripe\Security\Member; -use SilverStripe\View\Requirements; use SilverStripe\View\ArrayData; +use SilverStripe\View\SSViewer; /** * Generates a three-pane UI for editing model classes, with an @@ -170,7 +171,7 @@ abstract class ModelAdmin extends LeftAndMain $list, $fieldConfig = GridFieldConfig_RecordEditor::create($this->stat('page_length')) ->addComponent($exportButton) - ->removeComponentsByType('SilverStripe\\Forms\\GridField\\GridFieldFilterHeader') + ->removeComponentsByType(GridFieldFilterHeader::class) ->addComponents(new GridFieldPrintButton('buttons-before-left')) ); @@ -178,21 +179,16 @@ abstract class ModelAdmin extends LeftAndMain if (singleton($this->modelClass)->hasMethod('getCMSValidator')) { $detailValidator = singleton($this->modelClass)->getCMSValidator(); /** @var GridFieldDetailForm $detailform */ - $detailform = $listField->getConfig()->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDetailForm'); + $detailform = $listField->getConfig()->getComponentByType(GridFieldDetailForm::class); $detailform->setValidator($detailValidator); } if ($this->showImportForm) { - $import = CompositeField::create(array( - new LiteralField( - 'ImportForm', - $this->customise(new ArrayData(array( - - )))->renderWith('SilverStripe\\Forms\\GridField\\GridFieldImportButton_Modal') - ) - )); - - $fieldConfig->addComponent(new GridFieldImportButton('buttons-before-left', $import)); + $fieldConfig->addComponent( + GridFieldImportButton::create('buttons-before-left') + ->setImportForm($this->ImportForm()) + ->setModalTitle(_t('ModelAdmin.IMPORT', 'Import from CSV')) + ); } $form = Form::create( diff --git a/admin/code/SecurityAdmin.php b/admin/code/SecurityAdmin.php index 78c5a16d4..8794dc7c1 100755 --- a/admin/code/SecurityAdmin.php +++ b/admin/code/SecurityAdmin.php @@ -11,9 +11,7 @@ use SilverStripe\Forms\LiteralField; use SilverStripe\Forms\Tab; use SilverStripe\Forms\TabSet; use SilverStripe\Forms\HiddenField; -use SilverStripe\Forms\CompositeField; use SilverStripe\Forms\FieldList; -use SilverStripe\Forms\HeaderField; use SilverStripe\Forms\Form; use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor; use SilverStripe\Forms\GridField\GridFieldButtonRow; @@ -178,29 +176,16 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider // Add import capabilities. Limit to admin since the import logic can affect assigned permissions if (Permission::check('ADMIN')) { // @todo when grid field is converted to react use the react component - $memberImport = CompositeField::create(array( - new LiteralField( - 'MemberImportFormIframe', - $this->customise(new ArrayData(array( - 'URL' => $this->Link('memberimport') - )))->renderWith('SilverStripe\\Forms\\GridField\\GridFieldImportButton_Modal') - ) - )); - - $memberListConfig - ->addComponent(new GridFieldImportButton('buttons-before-left', $memberImport)); - - $groupImport = CompositeField::create(array( - new LiteralField( - 'GroupImportFormIframe', - $this->customise(new ArrayData(array( - 'URL' => $this->Link('groupimport') - )))->renderWith('SilverStripe\\Forms\\GridField\\GridFieldImportButton_Modal') - ) - )); - - $groupListConfig - ->addComponent(new GridFieldImportButton('buttons-before-left', $groupImport)); + $memberListConfig->addComponent( + GridFieldImportButton::create('buttons-before-left') + ->setImportIframe($this->Link('memberimport')) + ->setModalTitle(_t('SecurityAdmin.IMPORTUSERS', 'Import users')) + ); + $groupListConfig->addComponent( + GridFieldImportButton::create('buttons-before-left') + ->setImportIframe($this->Link('groupimport')) + ->setModalTitle(_t('SecurityAdmin.IMPORTGROUPS', 'Import groups')) + ); } // Tab nav in CMS is rendered through separate template @@ -277,11 +262,7 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider /** @var Group $group */ $group = $this->currentPage(); - /** @skipUpgrade */ - $form = new MemberImportForm( - $this, - 'MemberImportForm' - ); + $form = new MemberImportForm($this, __FUNCTION__); $form->setGroup($group); return $form; @@ -312,8 +293,7 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider return null; } - $form = new GroupImportForm($this, 'GroupImportForm'); - return $form; + return new GroupImportForm($this, __FUNCTION__); } /** diff --git a/admin/templates/SilverStripe/Admin/Includes/ModelAdmin_Content.ss b/admin/templates/SilverStripe/Admin/Includes/ModelAdmin_Content.ss index f2df0e70d..d466ecec0 100644 --- a/admin/templates/SilverStripe/Admin/Includes/ModelAdmin_Content.ss +++ b/admin/templates/SilverStripe/Admin/Includes/ModelAdmin_Content.ss @@ -35,20 +35,6 @@
$EditForm
- - <% if ImportForm %> - diff --git a/src/Forms/GridField/GridFieldImportButton.php b/src/Forms/GridField/GridFieldImportButton.php index b023cf372..2f089e37a 100644 --- a/src/Forms/GridField/GridFieldImportButton.php +++ b/src/Forms/GridField/GridFieldImportButton.php @@ -2,27 +2,46 @@ namespace SilverStripe\Forms\GridField; -use SilverStripe\Forms\CompositeField; +use SilverStripe\Control\Session; +use SilverStripe\Core\Injector\Injectable; +use SilverStripe\Forms\Form; +use SilverStripe\View\ArrayData; +use SilverStripe\View\SSViewer; class GridFieldImportButton implements GridField_HTMLProvider { + use Injectable; + /** * Fragment to write the button to */ protected $targetFragment; /** - * @var CompositeField + * Import form + * + * @var Form */ - protected $importFormField; + protected $importForm; + + /** + * @var string + */ + protected $modalTitle = null; + + /** + * URL for iframe + * + * @var string + */ + protected $importIframe = null; /** * @param string $targetFragment The HTML fragment to write the button into */ - public function __construct($targetFragment = "after", $importFormField = null) + public function __construct($targetFragment = "after") { $this->targetFragment = $targetFragment; - $this->importFormField = $importFormField; } /** @@ -33,6 +52,23 @@ class GridFieldImportButton implements GridField_HTMLProvider */ public function getHTMLFragments($gridField) { + $modalID = $gridField->ID() . '_ImportModal'; + + // Check for form message prior to rendering form (which clears session messages) + $form = $this->getImportForm(); + $hasMessage = $form && $form->getMessage(); + + // Render modal + $template = SSViewer::get_templates_by_class(static::class, '_Modal'); + $viewer = new ArrayData([ + 'ImportModalTitle' => $this->getModalTitle(), + 'ImportModalID' => $modalID, + 'ImportIframe' => $this->getImportIframe(), + 'ImportForm' => $this->getImportForm(), + ]); + $modal = $viewer->renderWith($template)->forTemplate(); + + // Build action button $button = new GridField_FormAction( $gridField, 'import', @@ -40,15 +76,18 @@ class GridFieldImportButton implements GridField_HTMLProvider 'import', null ); - $button->addExtraClass('btn btn-secondary no-ajax font-icon-upload btn--icon-large action_import'); - - // means that you can only have 1 import per page $button - ->setAttribute('data-toggle', "modal") - ->setAttribute('data-target', "#". $gridField->getForm()->getHTMLID() . '_ImportModal'); + ->addExtraClass('btn btn-secondary no-ajax font-icon-upload btn--icon-large action_import') + ->setForm($gridField->getForm()) + ->setAttribute('data-toggle', 'modal') + ->setAttribute('aria-controls', $modalID) + ->setAttribute('data-target', "#{$modalID}") + ->setAttribute('data-modal', $modal); - $button->setForm($gridField->getForm()); - $extra = null; + // If form has a message, trigger it to automatically open + if ($hasMessage) { + $button->setAttribute('data-state', 'open'); + } return array( $this->targetFragment => '

'. $button->Field() . '

' @@ -65,4 +104,58 @@ class GridFieldImportButton implements GridField_HTMLProvider { return []; } + + /** + * @return string + */ + public function getModalTitle() + { + return $this->modalTitle; + } + + /** + * @param string $modalTitle + * @return $this + */ + public function setModalTitle($modalTitle) + { + $this->modalTitle = $modalTitle; + return $this; + } + + /** + * @return Form + */ + public function getImportForm() + { + return $this->importForm; + } + + /** + * @param Form $importForm + * @return $this + */ + public function setImportForm($importForm) + { + $this->importForm = $importForm; + return $this; + } + + /** + * @return string + */ + public function getImportIframe() + { + return $this->importIframe; + } + + /** + * @param string $importIframe + * @return $this + */ + public function setImportIframe($importIframe) + { + $this->importIframe = $importIframe; + return $this; + } } diff --git a/templates/SilverStripe/Forms/GridField/GridFieldImportButton_Modal.ss b/templates/SilverStripe/Forms/GridField/GridFieldImportButton_Modal.ss index e8de94d62..853913e03 100644 --- a/templates/SilverStripe/Forms/GridField/GridFieldImportButton_Modal.ss +++ b/templates/SilverStripe/Forms/GridField/GridFieldImportButton_Modal.ss @@ -1,15 +1,21 @@ -