Integrating Tabulator into SilverStripe. Because GridField doesn't always cut it. This works in the front end and in the cms.
This module is used in my alternative admin module and in a production project using fairly complex front end editable tables. This means that I will have probably faced most of the issues you might have in your own projects :-)
It supports many features out of the boxes:
When editing or viewing a record, the default getCMSFields
is used. This might not always be ideal (fields may have gridfields or depend on some implementation).
To return a custom list, you can use tabulatorCMSFields
. In order to help you get a decent set of fields, the TabulatorScaffolder
class is here to help.
public function tabulatorCMSFields(array $_params = [])
{
$fields = TabulatorScaffolder::scaffoldFormFields($this, [
// Don't allow has_many/many_many relationship editing before the record is first saved
'includeRelations' => ($this->ID > 0),
'tabbed' => true,
'ajaxSafe' => true
]);
$this->extend('updateCMSFields', $fields);
return $fields;
}
You may or may not wish to trigger updateCMSFields
. On the good side, it will help with extensions not aware of tabulator.
On the bad side, it can break because it most probably expect gridfields everywhere.
This module scaffold some default columns based on summary and searchable fields.
For more advanced usage, please define a tabulatorColumns
method that returns all the columns according to Tabulator definitions.
By default, Tabulator will init through a custom elements provided by formidable elements
You can configure any of the available options using the setOption
call.
For dynamic callbacks, you can specify a function available in the global namespace using a namespaced function to avoid scope pollution. These callbacks are the same as the one of formidable elements meaning they have a form of {"__fn" : "app.callback"}
Some defaults callback are available under SSTabulator.
For example:
$grid->registerJsNamespace('MyApp');
$grid->addColumn('MyCell', 'My Cell', [
'headerSort' => false,
'editable' => '_editable',
'editor' => ["__fn" => 'SSTabulator.externalEditor'],
'mutatorEdit' => ["__fn" => 'MyApp.mutateValue'],
]);
The class contain a couple of "wizard" functions that will set a group of options in a consistent manner. Please check the source code for more informations.
The class use by default the Bootstrap 5 theme with a few custom improvements to make it more "silverstripy".
You can choose your theme with the theme
config option or set it to null to include your own theme.
Disabling the bootstrap5 theme also disables the custom css.
You can make any column editable. Simply call makeColumnEditable
and pass along relevant editor details.
Upon blur, it will trigger a ajaxEdit request on the editUrl endpoint if set.
By default, the grid expects records to be editable. You can use setViewOnly
to disable the add and edit buttons
and make the detail form readonly.
By default, the grid does not edit relations (no link/unlink).
You can create button columns with the makeButton
function. Under the hood, it will use the buttonFormatter and
buttonHandler.
You can also enable ajax mode by setting the ajax
parameter either as true/1 or as a string.
Using true/1 will use built-in ajax handler, or you can choose to pick any global function name
$btn = $grid->makeButton("myaction/{ID}", "", "Confirm");
$btn['width'] = '100';
$btn['tooltip'] = '';
$btn['formatterParams']['classes'] = "btn btn-primary d-block";
$btn['formatterParams']['ajax'] = "*MyApp.handleAjax";
$grid->addButtonFromArray("MyBtn", $btn);
You custom handler just look like this and return a promise
var handleAjax = function (e, cell, btn, formData) {
// do something here
// return promise
};
Buttons are not responsive by default. Simply unset the responsive key if needed
unset($btn['responsive']);
In order to forward actions to a record (the preferred way), add a tabulatorRowActions
on your record.
public function tabulatorRowActions()
{
return [
'doTabulatorAction' => [
'title' => 'Do This',
'icon' => 'favorite_border',
'ajax' => true, // submitted through xhr
'reload' => true, // reload table data after action
'refresh' => true, // refresh the whole page after action
],
];
}
This will call the method doTabulatorAction
on your record.
Tabulator supports "tools" that can be added above the grid. This is how, for example, the add new button is used.
All tools inherit from the AbstractTabulatorTool
class.
Tools can handle action by calling /tools/$ID on the TabulatorGrid field.
Because creating a class for every tool is tedious, you can do the following
$grid->addToolEnd(new GenericTabulatorTool('my_unique_action', 'Do something unique', function () {
return $this->doSomethingUnique();
}));
You can make items selectable and pass an array of actions. Actions must extend the AbstractBulkAction
class.
$grid->wizardSelectable([
new BulkDeleteAction()
]);
See /src/BulkActions for a full list of actions. These are roughly the equivalent of those you can find in the BulkManager module.
Because creating a class for every simple action is tedious, you can do the following
$grid->wizardSelectable([
new GenericBulkAction('bulk_cancel', 'Cancel', function (MyRecord $record, $grid) {
return $record->doCancel();
}),
new GenericBulkAction('bulk_approve', 'Approve', function (MyRecord $record, $grid) {
return $record->doApprove();
}),
]);
You can add custom listeners that should exist in the global namespace.
$grid->addListener('tableBuilt', 'MyApp.onTableBuilt');
Some of the settings of Tabulator are set using data attributes. This has been made in order to avoid mixing custom behaviour with built-in options from Tabulator. Function names should be regular strings, since they are json encoded and resolved by our custom code.
There is a built-in notify
helper function that supports quite a few notification types by default.
If needed, you can register a global SSTabulator.notify function that will be called instead of the default function.
If you are using silverstripe-hasonefield, I have a good news for you
because this module includes a basic (for now) support for a simple has one editing button.
Simply replace your instances of HasOneButtonField
with HasOneTabulatorField
and you should be good to go!
Searching by columns is not always possible or convenient. Maybe you want to have a global search feature.
This is really easy, just do this. It will create a top search bar where you can type anything!
$grid->setGlobalSearch(true);
By default, it will make a PartialMatch against the string. For large tables, you might not want to do that to use your indexes properly. You can also use shortcut syntax for filters:
You can set the search fields using setWildcardFields
. Otherwise it will default to searchableFields
.
When global search is enabled, you can also provide a custom list of "quick filters".
$grid->setQuickFilters([
'blacklist' => [
'label' => 'Blacklisted Companies',
'callback' => function (&$list) {
$blacklistedIDS = DB::query('SELECT ID FROM Company WHERE `Status` = \'Blacklisted\'')->column();
$list = $list->filter('ID', $blacklistedIDS);
}
]
]);
If you add a tabulatorOptions
method, you can configure how the model will be autoconfigured by Tabulator.
It can provide the followings keys:
For custom columns, please use tabulatorColumns
.
For custom actions, please use tabulatorRowActions
.
If you are in a project currently using GridFields, there are a couple of ways you can slowly migrate to Tabulator. One way is to inject Tabulator instead of a GridField so that all GridField::create call return a TabulatorGrid. As a convenience, Tabulator define a getConfig method that returns a blank GridFieldConfig so that code expecting that doesn't crash.
Another way is to use the TabulatorGrid::replaceGridfield method that tries its best to replace your GridField instance with an appropriate and configured TabulatorGrid.
TabulatorGrid doesn't use the concept of a config object. These tend to make the architecture more complex and it's not always easy to deal with various components playing nicely together. Instead, TabulatorGrid has an opiniated config system which is still very flexible, but only works for intended use cases.
4.10+
LeKoala - thomas@lekoala.be