This commit is contained in:
Roël Couwenberg 2024-04-19 10:47:54 +00:00 committed by GitHub
commit d901ebf74e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 241 additions and 209 deletions

3
.gitignore vendored
View File

@ -7,3 +7,6 @@
**/css/bourbon
**/.sass-cache
node_modules/
vendor/
public/
composer.lock

3
_config/config.yml Normal file
View File

@ -0,0 +1,3 @@
SilverStripe\Admin\LeftAndMain:
extra_requirements_javascript:
- "colymba/gridfield-bulk-editing-tools: client/dist/js/main.js"

View File

@ -1,18 +1,18 @@
/* global window, alert, confirm */
import jQuery from 'jquery';
import i18n from 'i18n';
import jQuery from "jquery";
import i18n from "i18n";
jQuery.entwine('colymba', ($) => {
jQuery.entwine("colymba", ($) => {
/**
* Makes sure the component is above the headers
*/
$('.bulkManagerOptions').entwine({
$(".bulkManagerOptions").entwine({
onmatch() {
const $parent = this.parents('thead');
const $tr = $parent.find('tr');
const $parent = this.parents("thead");
const $tr = $parent.find("tr");
const targets = ['.filter-header', '.sortable-header'];
const $target = $parent.find(targets.join(','));
const targets = [".filter-header", ".sortable-header"];
const $target = $parent.find(targets.join(","));
const index = $tr.index(this);
let newIndex = $tr.length - 1;
@ -30,77 +30,84 @@ jQuery.entwine('colymba', ($) => {
},
});
/**
* Bulkselect table cell behaviours
*/
$('td.col-bulkSelect').entwine({
$("td.col-bulkSelect").entwine({
onmouseover() {
// disable default row click behaviour -> avoid navigation to edit form when
// clicking the checkbox
$(this).parents('.ss-gridfield-item').find('.edit-link').removeClass('edit-link')
.addClass('tempDisabledEditLink');
$(this)
.parents(".ss-gridfield-item")
.find(".edit-link")
.removeClass("edit-link")
.addClass("tempDisabledEditLink");
},
onmouseout() {
// re-enable default row click behaviour
$(this).parents('.ss-gridfield-item').find('.tempDisabledEditLink').addClass('edit-link')
.removeClass('tempDisabledEditLink');
$(this)
.parents(".ss-gridfield-item")
.find(".tempDisabledEditLink")
.addClass("edit-link")
.removeClass("tempDisabledEditLink");
},
onclick(e) {
// check/uncheck checkbox when clicking cell
const cb = $(e.target).find('input');
if (!$(cb).prop('checked')) {
$(cb).prop('checked', true);
const cb = $(e.target).find("input");
if (!$(cb).prop("checked")) {
$(cb).prop("checked", true);
} else {
$(cb).prop('checked', false);
}
$(cb).prop("checked", false);
}
},
});
/**
* Individual select checkbox behaviour
*/
$('td.col-bulkSelect input').entwine({
onmatch() {
},
onunmatch() {
},
$("td.col-bulkSelect input").entwine({
onmatch() {},
onunmatch() {},
onclick() {
$(this).parents('.grid-field__table').find('input.bulkSelectAll').prop('checked', '');
}
$(this)
.parents(".grid-field__table")
.find("input.bulkSelectAll")
.prop("checked", "");
},
});
/**
* Bulkselect checkbox behaviours
*/
$('input.bulkSelectAll').entwine({
$("input.bulkSelectAll").entwine({
onclick() {
const state = $(this).prop('checked');
$(this).parents('.grid-field__table')
.find('td.col-bulkSelect input')
.prop('checked', state)
.trigger('change');
const state = $(this).prop("checked");
$(this)
.parents(".grid-field__table")
.find("td.col-bulkSelect input")
.prop("checked", state)
.trigger("change");
},
getSelectRecordsID() {
return $(this).parents('.grid-field__table')
.find('td.col-bulkSelect input:checked')
return $(this)
.parents(".grid-field__table")
.find("td.col-bulkSelect input:checked")
.map(function () {
return parseInt($(this).data('record'), 10);
return parseInt($(this).data("record"), 10);
})
.get();
}
},
});
/**
* Bulk action dropdown behaviours
*/
$('select.bulkActionName').entwine({
$("select.bulkActionName").entwine({
onchange() {
const value = $(this).val();
const $parent = $(this).parents('.bulkManagerOptions');
const $btn = $parent.find('.doBulkActionButton');
const config = $btn.data('config');
const $parent = $(this).parents(".bulkManagerOptions");
const $btn = $parent.find(".doBulkActionButton");
const config = $btn.data("config");
$.each(config, (configKey, configData) => {
if (configKey !== value) {
@ -109,45 +116,45 @@ jQuery.entwine('colymba', ($) => {
});
if (!value) {
$btn.addClass('disabled');
$btn.addClass("disabled");
return;
}
$btn.removeClass('disabled');
$btn.removeClass("disabled");
$btn.addClass(config[value].buttonClasses).addClass('btn-outline-secondary');
$btn
.addClass(config[value].buttonClasses)
.addClass("btn-outline-secondary");
if (config[value].icon) {
const $img = $btn.find('img');
const $img = $btn.find("img");
if ($img.length) {
$img.attr('src', config[value].icon);
$img.attr("src", config[value].icon);
} else {
$btn.prepend(`<img src="${config[value].icon}" alt="" />`);
}
} else {
$btn.find('img').remove();
$btn.find("img").remove();
}
if (config[value].destructive) {
$btn.addClass('btn-outline-danger');
$btn.addClass("btn-outline-danger");
} else {
$btn.removeClass('btn-outline-danger');
}
$btn.removeClass("btn-outline-danger");
}
},
});
/**
* bulk action button behaviours
*/
$('.doBulkActionButton').entwine({
$(".doBulkActionButton").entwine({
getActionURL(action, url) {
const cacheBuster = new Date().getTime();
let newUrl = url.split('?');
let newUrl = url.split("?");
let newAction = '';
let newAction = "";
if (action) {
newAction = `/${action}`;
}
@ -160,9 +167,12 @@ jQuery.entwine('colymba', ($) => {
return newUrl;
},
onclick() {
const $parent = $(this).parents('.bulkManagerOptions');
const action = $parent.find('select.bulkActionName').val();
const ids = $(this).parents('.bulkManagerOptions').find('input.bulkSelectAll:first').getSelectRecordsID();
const $parent = $(this).parents(".bulkManagerOptions");
const action = $parent.find("select.bulkActionName").val();
const ids = $(this)
.parents(".bulkManagerOptions")
.find("input.bulkSelectAll:first")
.getSelectRecordsID();
this.doBulkAction(action, ids);
},
@ -170,38 +180,40 @@ jQuery.entwine('colymba', ($) => {
doBulkAction(action, ids) {
const { bulkTools } = window;
const $parent = $(this).parents('.bulkManagerOptions');
const $btn = $parent.find('a.doBulkActionButton');
const $msg = $parent.find('.message');
const $parent = $(this).parents(".bulkManagerOptions");
const $btn = $parent.find("a.doBulkActionButton");
const $msg = $parent.find(".message");
const config = $btn.data('config');
let url = this.getActionURL(action, $(this).data('url'));
const config = $btn.data("config");
let url = this.getActionURL(action, $(this).data("url"));
const inputData = { records: ids };
if (ids.length <= 0) {
alert(i18n._t('GRIDFIELD_BULK_MANAGER.BULKACTION_EMPTY_SELECT'));
alert(i18n._t("GRIDFIELD_BULK_MANAGER.BULKACTION_EMPTY_SELECT"));
return false;
}
// if ( $btn.hasClass('ss-ui-action-destructive') )
if (config[action].destructive) {
if (!confirm(i18n._t('GRIDFIELD_BULK_MANAGER.CONFIRM_DESTRUCTIVE_ACTION'))) {
if (
!confirm(i18n._t("GRIDFIELD_BULK_MANAGER.CONFIRM_DESTRUCTIVE_ACTION"))
) {
return false;
}
}
$btn.addClass('loading');
$msg.removeClass('static show error warning');
$btn.addClass("loading");
$msg.removeClass("static show error warning");
if (config[action].xhr) {
$.ajax({
url,
data: inputData,
type: 'POST',
context: $(this)
type: "POST",
context: $(this),
}).always(function (data) {
let returnData = data;
$btn.removeClass('loading');
$btn.removeClass("loading");
// if request fail, return a +4xx status code, extract json response
if (data.responseText) {
@ -211,23 +223,26 @@ jQuery.entwine('colymba', ($) => {
$msg.html(returnData.message);
if (returnData.isError) {
$msg.addClass('static error');
$msg.addClass("static error");
} else if (returnData.isWarning) {
$msg.addClass('show warning');
$msg.addClass("show warning");
} else {
$msg.addClass('show');
$msg.addClass("show");
}
bulkTools.gridfieldRefresh($(this).parents('.ss-gridfield'), returnData);
bulkTools.gridfieldRefresh(
$(this).parents(".ss-gridfield"),
returnData,
);
});
} else {
const records = `records[]=${ids.join('&records[]=')}`;
const records = `records[]=${ids.join("&records[]=")}`;
url = `${url}&${records}`;
window.location.href = url;
}
return true;
}
},
});
});

View File

@ -1,49 +1,50 @@
/* global window */
import jQuery from 'jquery';
import jQuery from "jquery";
jQuery.entwine('colymba', ($) => {
jQuery.entwine("colymba", ($) => {
/**
* Toggle all accordion forms
* open or closed
*/
$('#bulkEditToggle').entwine({
$("#bulkEditToggle").entwine({
onclick() {
const toggleFields = this.parents('form').find('.ss-toggle .ui-accordion-header');
let state = this.data('state');
const toggleFields = this.parents("form").find(
".ss-toggle .ui-accordion-header",
);
let state = this.data("state");
if (!state || state === 'close') {
state = 'open';
if (!state || state === "close") {
state = "open";
} else {
state = 'close';
state = "close";
}
toggleFields.each(function () {
const $this = $(this);
if (state === 'open' && !$this.hasClass('ui-state-active')) {
if (state === "open" && !$this.hasClass("ui-state-active")) {
$this.click();
}
if (state === 'close' && $this.hasClass('ui-state-active')) {
if (state === "close" && $this.hasClass("ui-state-active")) {
$this.click();
}
});
this.data('state', state);
}
this.data("state", state);
},
});
/**
* Contains each rocrds editing fields,
* tracks changes and updates...
*/
$('.bulkEditingFieldHolder').entwine({
$(".bulkEditingFieldHolder").entwine({
onchange() {
this.removeClass('updated');
if (!this.hasClass('hasUpdate')) {
this.addClass('hasUpdate');
}
this.removeClass("updated");
if (!this.hasClass("hasUpdate")) {
this.addClass("hasUpdate");
}
},
});
});

View File

@ -3,7 +3,12 @@
"type": "silverstripe-vendormodule",
"description": "SilverStripe GridField component to upload images/files and edit records in bulk",
"homepage": "https://github.com/colymba/GridFieldBulkEditingTools",
"keywords": ["silverstripe", "bulk upload", "image upload", "gridfield bulk upload"],
"keywords": [
"silverstripe",
"bulk upload",
"image upload",
"gridfield bulk upload"
],
"license": "BSD-3-Clause",
"authors": [
{
@ -16,8 +21,7 @@
}
],
"require": {
"silverstripe/framework": "^5",
"silverstripe/asset-admin": "^2"
"silverstripe/framework": "^5 || ^4"
},
"extra": {
"branch-alias": {
@ -34,5 +38,11 @@
"Colymba\\BulkManager\\": "src/BulkManager/",
"Colymba\\BulkUpload\\": "src/BulkUploader/"
}
},
"config": {
"allow-plugins": {
"composer/installers": true,
"silverstripe/vendor-plugin": true
}
}
}

View File

@ -1,7 +1,9 @@
# Bulk Manager
Perform actions on multiple records straight from the GridField. Comes with *unlink*, *delete* and bulk *editing*. You can also easily create/add your own.
Perform actions on multiple records straight from the GridField. Comes with _unlink_, _delete_ and bulk _editing_. You can also easily create/add your own.
## Usage
Simply add component to your `GridFieldConfig`
```php
@ -9,6 +11,7 @@ $config->addComponent(new \Colymba\BulkManager\BulkManager());
```
## Configuration
The component's options can be configurated individually or in bulk through the 'config' functions like this:
```php
@ -16,13 +19,17 @@ $config->getComponentByType('Colymba\\BulkManager\\BulkManager')->setConfig($ref
```
### $config overview
The available configuration options are:
* 'editableFields' : array of string referencing specific CMS fields available for editing
- 'editableFields' : array of string referencing specific CMS fields available for editing
## Custom actions
You can remove or add individual action or replace them all via `addBulkAction()` and `removeBulkAction()`
### Adding a custom action
To add a custom bulk action to the list use:
```php
@ -32,9 +39,11 @@ $config
```
#### Custom action handler
When creating your own bulk action `RequestHandler`, you should extend `Colymba\BulkManager\BulkAction\Handler` which will expose 2 useful functions `getRecordIDList()` and `getRecords()` returning either an array with the selected records IDs or a `DataList` of the selected records.
Make sure to define the handler's `$url_segment`, from which the handler will be called and its relating `$allowed_actions` and `$url_handlers`. See `Handler`, `DeleteHandler` and `UnlinkHandler` for examples.
#### Front-end config
Bulk action handler's front-end configuration is set via class properties `label`, `icon`, `buttonClasses`, `xhr` and `destructive`. See `Handler`, `DeleteHandler` and `UnlinkHandler` for reference and examples.

View File

@ -100,13 +100,11 @@ class ArchiveHandler extends Handler
$response = new HTTPBulkToolsResponse(true, $this->gridField);
try {
foreach ($records as $record)
{
foreach ($records as $record) {
$done = $record->doArchive();
if ($done)
{
if ($done) {
$response->addSuccessRecord($record);
}else{
} else {
$response->addFailedRecord($record, $done);
}
}

View File

@ -108,7 +108,7 @@ class EditHandler extends Handler
*/
public function Link($action = null)
{
return Controller::join_links(parent::Link(), $this->stat('url_segment'), $action);
return Controller::join_links(parent::Link(), self::$url_segment, $action);
}
/**
@ -130,7 +130,7 @@ class EditHandler extends Handler
FormAction::create('doSave', _t('GRIDFIELD_BULKMANAGER_EDIT_HANDLER.SAVE_BTN_LABEL', 'Save all'))
->setAttribute('id', 'bulkEditingSaveBtn')
->addExtraClass('btn btn-success')
->setAttribute('data-icon', 'accept')
->setAttribute('data-icon', '! ')
->setUseButtonTag(true)
);
@ -138,10 +138,10 @@ class EditHandler extends Handler
FormAction::create('Cancel', _t('GRIDFIELD_BULKMANAGER_EDIT_HANDLER.CANCEL_BTN_LABEL', 'Cancel'))
->setAttribute('id', 'bulkEditingUpdateCancelBtn')
->addExtraClass('btn btn-danger cms-panel-link')
->setAttribute('data-icon', 'decline')
->setAttribute('data-icon', 'P ')
->setAttribute('href', $one_level_up->Link)
->setUseButtonTag(true)
->setAttribute('src', '')//changes type to image so isn't hooked by default actions handlers
->setAttribute('src', '') //changes type to image so isn't hooked by default actions handlers
);
$recordList = $this->getRecordIDList();
@ -164,7 +164,7 @@ class EditHandler extends Handler
);
$header = LiteralField::create(
'bulkEditHeader',
'<h1 id="bulkEditHeader">'.$headerText.'</h1>'
'<h1 id="bulkEditHeader">' . $headerText . '</h1>'
);
$recordsFieldList->push($header);
@ -206,7 +206,7 @@ class EditHandler extends Handler
//and add record ids GET var
$bulkEditForm->setAttribute(
'action',
$this->Link('bulkEditForm?records[]='.implode('&', $recordList))
$this->Link('bulkEditForm?records[]=' . implode('&', $recordList))
);
return $bulkEditForm;

View File

@ -100,13 +100,11 @@ class PublishHandler extends Handler
$response = new HTTPBulkToolsResponse(false, $this->gridField);
try {
foreach ($records as $record)
{
foreach ($records as $record) {
$done = $record->publishRecursive();
if ($done)
{
if ($done) {
$response->addSuccessRecord($record);
}else{
} else {
$response->addFailedRecord($record, $done);
}
}

View File

@ -100,13 +100,11 @@ class UnPublishHandler extends Handler
$response = new HTTPBulkToolsResponse(false, $this->gridField);
try {
foreach ($records as $record)
{
foreach ($records as $record) {
$done = $record->doUnpublish();
if ($done)
{
if ($done) {
$response->addSuccessRecord($record);
}else{
} else {
$response->addFailedRecord($record, $done);
}
}

View File

@ -129,8 +129,7 @@ class BulkManager implements GridField_HTMLProvider, GridField_ColumnProvider, G
$handler = Injector::inst()->get($handlerClassName);
$urlSegment = $handler->config()->get('url_segment');
if (!$urlSegment)
{
if (!$urlSegment) {
$rc = new ReflectionClass($handlerClassName);
$urlSegment = $rc->getShortName();
}
@ -154,10 +153,8 @@ class BulkManager implements GridField_HTMLProvider, GridField_ColumnProvider, G
user_error("Provide either a class name or URL segment", E_USER_ERROR);
}
foreach ($this->config['actions'] as $url => $class)
{
if ($handlerClassName === $class || $urlSegment === $url)
{
foreach ($this->config['actions'] as $url => $class) {
if ($handlerClassName === $class || $urlSegment === $url) {
unset($this->config['actions'][$url]);
return $this;
}

View File

@ -123,7 +123,7 @@ class HTTPBulkToolsResponse extends HTTPResponse
*/
public function addHeader($header, $value)
{
if($header === "content-type") {
if ($header === "content-type") {
return $this;
}
return parent::addHeader($header, $value);
@ -139,7 +139,7 @@ class HTTPBulkToolsResponse extends HTTPResponse
*/
public function removeHeader($header)
{
if($header === "content-type") {
if ($header === "content-type") {
return $this;
}
return parent::removeHeader($header);
@ -333,7 +333,7 @@ class HTTPBulkToolsResponse extends HTTPResponse
);
foreach ($this->successRecords as $record) {
$data = array('id' => $record->ID, 'class' => str_replace('\\', '\\\\', $record->ClassName));
$data = array('id' => $record->ID, 'class' => str_replace('\\', '\\\\', $record->ClassName ?? ''));
if (!$this->removesRows) {
$data['row'] = $this->getRecordGridfieldRow($record);
}

View File

@ -112,8 +112,7 @@ class BulkUploadHandler extends RequestHandler
$assetAdmin = AssetAdmin::singleton();
$uploadResponse = $assetAdmin->apiCreateFile($request);
if ($uploadResponse->getStatusCode() == 200)
{
if ($uploadResponse->getStatusCode() == 200) {
$responseData = Convert::json2array($uploadResponse->getBody());
$responseData = array_shift($responseData);

View File

@ -183,7 +183,8 @@ class BulkUploader implements GridField_HTMLProvider, GridField_URLHandler
$imageField = null;
foreach ($hasOneFields as $field => $type) {
if ($type === 'SilverStripe\\Assets\\Image'
if (
$type === 'SilverStripe\\Assets\\Image'
|| $type === 'SilverStripe\\Assets\\File'
|| is_subclass_of($type, 'SilverStripe\\Assets\\File')
) {
@ -293,8 +294,8 @@ class BulkUploader implements GridField_HTMLProvider, GridField_URLHandler
'Colspan' => (count($gridField->getColumns() ?? [])),
'UploadField' => $uploadField->Field() // call ->Field() to get requirements in right order
));
Requirements::javascript('colymba/gridfield-bulk-editing-tools:client/dist/js/main.js');
//This one is no longer needed since the javascript is now loaded at the top of the cms
//Requirements::javascript('colymba/gridfield-bulk-editing-tools:client/dist/js/main.js');
Requirements::css('colymba/gridfield-bulk-editing-tools:client/dist/styles/main.css');
Requirements::add_i18n_javascript('colymba/gridfield-bulk-editing-tools:client/lang');