Skip to content

Commit

Permalink
new feature: progressive actions
Browse files Browse the repository at this point in the history
  • Loading branch information
lekoala committed Jun 8, 2021
1 parent 081522a commit a52e1bd
Show file tree
Hide file tree
Showing 10 changed files with 291 additions and 4 deletions.
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
[![scrutinizer](https://scrutinizer-ci.com/g/lekoala/silverstripe-cms-actions/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/lekoala/silverstripe-cms-actions/)
[![Code coverage](https://codecov.io/gh/lekoala/silverstripe-cms-actions/branch/master/graph/badge.svg)](https://codecov.io/gh/lekoala/silverstripe-cms-actions)

## NEW in 1.2

- Progressive actions!

![progressive action](docs/progressive-action.gif "progressive action")

## Intro

For those of you missing betterbuttons :-) Because let's face it, adding custom actions in SilverStripe is a real pain.
Expand Down Expand Up @@ -327,6 +333,62 @@ The issue here is that the `updateItemEditForm` is never called (this is only ca

For instance, this means that you actions are displayed before the 'save' button provided by the Profile controller. Currently, this module fixes this with a bit of css.

## Progressive actions

Since version 1.2, this module supports progressive actions. Progressive actions are buttons that use a progress bar to display what is happening. Under the hood,
it translates to multiple ajax calls to the same handler function and passing the following post parameters:

- progress_step: the current step
- progress_total: this can be either set in advance or provided by the handler function

![progressive action](docs/progressive-action.gif "progressive action")

Progressive actions are supported for `GridFieldTableButton` and `CustomLink`.

Here is a sample implementation. The action needs to return an array with the following keys:
- progress_step: the updated step. Usually +1.
- progress_total: the total number of records. It should only be computed once (on the initial run) when none is provided.
- reload: should we reload at the end ?
- message: each run can display a short lived notification with specific text
- label: the end label (by default : Completed).

```php
class MyProgressiveTableButton extends GridFieldTableButton
{
public function __construct($targetFragment = "buttons-before-right", $buttonLabel = null)
{
$this->progressive = true;
parent::__construct($targetFragment, $buttonLabel);
}

public function handle(GridField $gridField, Controller $controller)
{
$step = (int) $controller->getRequest()->postVar("progress_step");
$total = (int) $controller->getRequest()->postVar("progress_total");
if (!$total) {
$total = $list->count();
}
$i = 0;
$res = null;
foreach ($list as $rec) {
if ($i < $step) {
$i++;
continue;
}
$res = "Processed record $i";
break;
}
$step++;
return [
'progress_step' => $step,
'progress_total' => $total,
'reload' => true,
'message' => $res,
];
}
}
```

## Todo

- Explore pages or siteconfig support
Expand Down
21 changes: 21 additions & 0 deletions css/cms-actions.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,24 @@ table .btn-link {
order: 1;
margin-right: 5px;
}

/* Progressive actions */
.progressive-action {
position: relative;
overflow: hidden;
white-space: nowrap;
}
.progressive-action span {
position: relative;
z-index: 2;
}
.progressive-action .btn__progress {
position: absolute;
width: 0;
height: 100%;
top: 0;
left: 0;
background: #368b39;
opacity: 0.5;
z-index: 1;
}
Binary file added docs/progressive-action.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
105 changes: 105 additions & 0 deletions javascript/cms-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,110 @@
}
},
});

// Handle progressive actions
function progressiveCall(inst, url, formData) {
$.ajax({
headers: { "X-Progressive": 1 },
type: "POST",
data: formData,
url: url,
dataType: "json",
success: function (data) {
if (data.message) {
jQuery.noticeAdd({
text: data.message,
stayTime: 1000,
inEffect: { left: "0", opacity: "show" },
});
}
// It's finished!
if (data.progress_step >= data.progress_total) {
if (!data.label) {
data.label = "Completed";
}
inst.find("span").text(data.label);
inst.find(".btn__progress").remove();

if (data.reload) {
window.reload();
}
return;
}
if (data.progress_step) {
formData["progress_step"] = data.progress_step;
}
if (data.progress_total) {
formData["progress_total"] = data.progress_total;
}
if (data.progress_step && data.progress_total) {
var perc = Math.round(
(data.progress_step / data.progress_total) * 100
);
inst.find("span").text(perc + "%");
inst.find(".btn__progress").css("width", perc);
}
progressiveCall(inst, url, formData);
},
error: function (e) {
inst.find("span").text("Failed");
console.error("Invalid response");
},
});
}
$(".progressive-action").entwine({
onclick: function (e) {
e.preventDefault();

if (this.hasClass("disabled")) {
return;
}

if (this.hasClass("confirm")) {
var confirmed = confirm($(this).data("message"));
if (!confirmed) {
return;
}
}

var url = this.data("url");
if (!url) {
url = this.attr("href");
}
var form = this.closest("form");
var formData = {};
var csrf = form.find('input[name="SecurityID"]').val();
// Add csrf
if (csrf) {
formData["SecurityID"] = csrf;
}

// Add current button
formData[this.attr("name")] = this.val();

// And step
formData["progress_step"] = 0;

// Total can be preset
if (
typeof this.data("progress-total") !== "undefined" &&
this.data("progress-total") !== null
) {
formData["progress_total"] = this.data("progress-total");
}

// Cosmetic things
this.addClass("disabled");
if (!this.find("span").length) {
this.html("<span>" + this.text() + "</span>");
}
this.css("width", this.outerWidth());
this.find("span").text("Please wait");
this.append('<div class="btn__progress"></div>');

progressiveCall(this, url, formData);
return false;
},
});
});
})(jQuery);
11 changes: 11 additions & 0 deletions src/ActionsGridFieldItemRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,17 @@ protected function forwardActionToRecord($action, $data = [], $form = null)
if ($error) {
$status = 'bad';
}

// Progressive actions return array with json data
if (method_exists($clickedAction, 'getProgressive') && $clickedAction->getProgressive()) {
$response = $controller->getResponse();
$response->addHeader('Content-Type', 'application/json');
if ($result) {
$response->setBody(json_encode($result));
}
return $response;
}

// We don't have a form, simply return the result
if (!$form) {
if ($error) {
Expand Down
5 changes: 5 additions & 0 deletions src/CustomAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ public function actionName()
return rtrim(str_replace('action_doCustomAction[', '', $this->name), ']');
}

public function Type()
{
return 'action';
}

public function Field($properties = array())
{
if ($this->buttonIcon) {
Expand Down
18 changes: 18 additions & 0 deletions src/CustomGridField_FormAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace LeKoala\CmsActions;

use SilverStripe\Forms\GridField\GridField_FormAction;

class CustomGridField_FormAction extends GridField_FormAction
{
use ProgressiveAction;

public function Type()
{
if ($this->progressive) {
return 'progressive-action';
}
return 'action';
}
}
10 changes: 9 additions & 1 deletion src/CustomLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class CustomLink extends LiteralField
{
use CustomButton;
use DefaultLink;
use ProgressiveAction;

/**
* @var boolean
Expand Down Expand Up @@ -46,6 +47,9 @@ public function __construct($name, $title = null, $link = null)

public function Type()
{
if ($this->progressive) {
return 'progressive-action';
}
return 'custom-link';
}

Expand All @@ -67,7 +71,11 @@ public function FieldHolder($properties = array())
}
if ($this->confirmation) {
$attrs .= ' data-message="' . Convert::raw2htmlatt($this->confirmation) . '"';
$attrs .= ' onclick="return confirm(this.dataset.message);"';
if ($this->progressive) {
$classes .= " confirm";
} else {
$attrs .= ' onclick="return confirm(this.dataset.message);"';
}
}

$content = '<a href="' . $link . '" class="' . $classes . '"' . $attrs . '>' . $title . '</a>';
Expand Down
34 changes: 31 additions & 3 deletions src/GridFieldTableButton.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
use ReflectionClass;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridField_FormAction;
use SilverStripe\Forms\GridField\GridField_URLHandler;
use SilverStripe\Forms\GridField\GridField_HTMLProvider;
use SilverStripe\Forms\GridField\GridField_ActionProvider;
Expand All @@ -19,6 +17,8 @@
*/
abstract class GridFieldTableButton implements GridField_HTMLProvider, GridField_ActionProvider, GridField_URLHandler
{
use ProgressiveAction;

/**
* Fragment to write the button to
* @string
Expand Down Expand Up @@ -101,7 +101,7 @@ public function getHTMLFragments($gridField)
{
$action = $this->getActionName();

$button = new GridField_FormAction(
$button = new CustomGridField_FormAction(
$gridField,
$action,
$this->getButtonLabel(),
Expand All @@ -123,6 +123,9 @@ public function getHTMLFragments($gridField)
$button->setAttribute('data-prompt-default', $promptDefault);
}
}
if ($this->progressive) {
$button->setProgressive(true);
}
if ($this->confirm) {
$button->setAttribute('data-confirm', $this->confirm);
}
Expand Down Expand Up @@ -167,8 +170,33 @@ public function handleAction(GridField $gridField, $actionName, $arguments, $dat
{
if (in_array($actionName, $this->getActions($gridField))) {
$controller = Controller::curr();

if ($this->progressive) {
// Otherwise we would need some kind of UI
if (!Director::is_ajax()) {
return $controller->redirectBack();
}
}

$result = $this->handle($gridField, $controller);
if ((!$result || is_string($result)) && $this->progressive) {
// simply increment counter and let's hope last action will return something
$step = (int) $controller->getRequest()->postVar("progress_step");
$total = (int) $controller->getRequest()->postVar("progress_total");
$result = [
'progress_step' => $step + 1,
'progress_total' => $total,
'message' => $result,
];
}
if ($result) {
// Send a json response this will be handled by cms-actions.js
if ($this->progressive) {
$response = $controller->getResponse();
$response->addHeader('Content-Type', 'application/json');
$response->setBody(json_encode($result));
return $response;
}
return $result;
}

Expand Down
29 changes: 29 additions & 0 deletions src/ProgressiveAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace LeKoala\CmsActions;

trait ProgressiveAction
{
protected $progressive = false;

/**
* Get the value of progressive
* @return mixed
*/
public function getProgressive()
{
return $this->progressive;
}

/**
* Set the value of progressive
*
* @param mixed $progressive
* @return $this
*/
public function setProgressive($progressive)
{
$this->progressive = $progressive;
return $this;
}
}

0 comments on commit a52e1bd

Please sign in to comment.