SilverStripe: How to add a checkbox column to a gridfield
From FVue
Problem
I have a GridField showing a list of reactions and I want to select multiple records and do either a delete or publish on this selection.
How to add a column with checkboxes to a GridField and process them?
Environment
- SilverStripe-3.0
Solution
See also: (SOLVED) Checkboxes In GridField
Ascii-art
+--------------------------+ +--------------------------+ | «interface» | | «interface» | | GridField_ColumnProvider | | GridField_ActionProvider | +------------+-------------+ +-------------+------------+ /_\ /_\ : : : ................;................ : : : +------------+----------+ +------------+------------+ +------------+-------------+ | GridFieldSelectColumn | | GridFieldSelectedDelete | | GridFieldSelectedPublish | +-----------------------+ +------------+------------+ +------------+-------------+ /_\ /_\ | | | | +-------------+------------+ +-------------+-------------+ | GridFieldReactionsDelete | | GridFieldReactionsPublish | +--------------------------+ +---------------------------+
Code
NOTE: The CSS-class no-change-track
prevents the browser from asking the question below if you made a selection and want to navigate away from the page:
- Are you sure? // This page is asking you to confirm that you want to leave - data you have entered may not be saved. // Stay on Page Leave Page
Index: articles/templates/GridFieldSelectColumn.ss =================================================================== --- articles/templates/GridFieldSelectColumn.ss (revision 0) +++ articles/templates/GridFieldSelectColumn.ss (revision 271) @@ -0,0 +1 @@ +<input id="item_$ID" class="checkbox no-change-track" name="selected[]" type="checkbox" value="$ID" /> Index: articles/javascript/lang/en_US.js =================================================================== --- articles/javascript/lang/en_US.js (revision 0) +++ articles/javascript/lang/en_US.js (revision 272) @@ -0, 0 +1,8 @@ +if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') { + if(typeof(console) != 'undefined') console.error('Class ss.i18n not defined'); +} else { + ss.i18n.addDictionary('en_US', { + 'GRIDFIELD.DELETEMULTIPLECONFIRMMESSAGE': 'Delete selected records(s)?', + 'GRIDFIELD.PUBLISHMULTIPLECONFIRMMESSAGE': 'Publish selected records(s)?', + }); +} Index: articles/javascript/lang/nl_NL.js =================================================================== --- articles/javascript/lang/nl_NL.js (revision 0) +++ articles/javascript/lang/nl_NL.js (revision 271) @@ -0,0 +1,4 @@ +ss.i18n.addDictionary('nl_NL', { + 'GRIDFIELD.DELETEMULTIPLECONFIRMMESSAGE': 'Verwijderen geselecteerde records(s)?', + 'GRIDFIELD.PUBLISHMULTIPLECONFIRMMESSAGE': 'Publiceren geselecteerde records(s)?', +}); Index: articles/javascript/ReactionsAdmin_GridField.js =================================================================== --- articles/javascript/ReactionsAdmin_GridField.js (revision 0) +++ articles/javascript/ReactionsAdmin_GridField.js (revision 271) @@ -0,0 +1,40 @@ +(function($){ + $(document).ready(function() { + $.entwine('ss', function($) { + $('.ss-gridfield-item[data-class="Reaction"]').entwine({ + onclick: function(e) { + // This prevents clicking a checkbox from jumping to item view + return true; + }, + onmouseover: function() { + return false; + }, + onmouseout: function() { + return false; + } + }); + + $('#action_deleteSelected').entwine({ + onclick: function(e){ + if(!confirm(ss.i18n._t('GRIDFIELD.DELETEMULTIPLECONFIRMMESSAGE'))) { + e.preventDefault(); + return false; + } else { + this._super(e); + } + } + }); + + $('#action_publishSelected').entwine({ + onclick: function(e){ + if(!confirm(ss.i18n._t('GRIDFIELD.PUBLISHMULTIPLECONFIRMMESSAGE'))) { + e.preventDefault(); + return false; + } else { + this._super(e); + } + } + }); + }); + }); +}(jQuery)); Index: articles/code/GridFieldReactionsDelete.php =================================================================== --- articles/code/GridFieldReactionsDelete.php (revision 0) +++ articles/code/GridFieldReactionsDelete.php (revision 271) @@ -0,0 +1,18 @@ +<?php +/** + * @package framework + * @subpackage gridfield + */ + +/** + * Does the actual deletion of GridField selected records + */ +class GridFieldReactionsDelete extends GridFieldSelectedDelete { + /** + * Implement ancestor method. + * @see GridFieldDeleteSelected::deleteRecord() + */ + protected function deleteRecord($id) { + Reaction::get()->byID($id)->delete(); + } +} Index: articles/code/GridFieldSelectedDelete.php =================================================================== --- articles/code/GridFieldSelectedDelete.php (revision 0) +++ articles/code/GridFieldSelectedDelete.php (revision 271) @@ -0,0 +1,77 @@ +<?php +/** + * @package framework + * @subpackage gridfield + */ + +/** + * Adds a "Delete" button to the bottom of a GridField. + */ +class GridFieldSelectedDelete implements GridField_HTMLProvider, GridField_ActionProvider { + /** + * Fragment to write the button to + */ + protected $targetFragment; + + + /** + * @param string $targetFragment The HTML fragment to write the button into + */ + public function __construct($targetFragment = "after") { + $this->targetFragment = $targetFragment; + } + + + /** + * Place the export button in a <p> tag below the field + */ + public function getHTMLFragments($gridField) { + $button = new GridField_FormAction( + $gridField, + 'deleteSelected', + 'Verwijderen', + 'deleteSelected', + null + ); + $button->setAttribute('data-icon', 'delete'); + $button->addExtraClass('no-ajax'); + return array( + $this->targetFragment => '<span class="grid-delete-button">' . $button->Field() . '</span>', + ); + } + + + /** + * "Delete" is an action button + */ + public function getActions($gridField) { + return array('deleteSelected'); + } + + public function handleAction(GridField $gridField, $actionName, $arguments, $data) { + if ($actionName == 'deleteselected') { + $this->deleteRecords($gridField, $data); + $gridField->form->getController()->redirectBack(); + } + } + + + /** + * Handle the delete + */ + public function deleteRecords($gridField, $request = null) { + if (is_array($request) && isset($request['selected'])) { + $ids = $request['selected']; + foreach ($ids as $id) { + $this->deleteRecord($id); + } + } + } + + + /** + * To be implemented by descendant. + */ + protected function deleteRecord($id) { + } +} Index: articles/code/GridFieldReactionsPublish.php =================================================================== --- articles/code/GridFieldReactionsPublish.php (revision 0) +++ articles/code/GridFieldReactionsPublish.php (revision 271) @@ -0,0 +1,20 @@ +<?php +/** + * @package framework + * @subpackage gridfield + */ + +/** + * Does the actual publish of GridField selected records + */ +class GridFieldReactionsPublish extends GridFieldSelectedPublish { + /** + * Implement ancestor method. + * @see GridFieldDeleteSelected::deleteRecord() + */ + protected function publishRecord($id) { + $reaction = Reaction::get()->byID($id); + $reaction->IsPublished = 1; + $reaction->write(); + } +} Index: articles/code/GridFieldSelectedPublish.php =================================================================== --- articles/code/GridFieldSelectedPublish.php (revision 0) +++ articles/code/GridFieldSelectedPublish.php (revision 271) @@ -0,0 +1,77 @@ +<?php +/** + * @package framework + * @subpackage gridfield + */ + +/** + * Adds a "Publish" button to the bottom of a GridField. + */ +class GridFieldSelectedPublish implements GridField_HTMLProvider, GridField_ActionProvider { + /** + * Fragment to write the button to + */ + protected $targetFragment; + + + /** + * @param string $targetFragment The HTML fragment to write the button into + */ + public function __construct($targetFragment = "after") { + $this->targetFragment = $targetFragment; + } + + + /** + * Place the export button in a <p> tag below the field + */ + public function getHTMLFragments($gridField) { + $button = new GridField_FormAction( + $gridField, + 'publishSelected', + 'Publiceren', + 'publishSelected', + null + ); + $button->setAttribute('data-icon', 'network-cloud'); + $button->addExtraClass('no-ajax'); + return array( + $this->targetFragment => '<span class="grid-publish-button">' . $button->Field() . '</span>', + ); + } + + + /** + * "Publish" is an action button + */ + public function getActions($gridField) { + return array('publishSelected'); + } + + public function handleAction(GridField $gridField, $actionName, $arguments, $data) { + if ($actionName == 'publishselected') { + $this->publishRecords($gridField, $data); + $gridField->form->getController()->redirectBack(); + } + } + + + /** + * Handle the publish + */ + public function publishRecords($gridField, $request = null) { + if (is_array($request) && isset($request['selected'])) { + $ids = $request['selected']; + foreach ($ids as $id) { + $this->publishRecord($id); + } + } + } + + + /** + * To be implemented by descendant. + */ + protected function publishRecord($id) { + } +} Index: articles/code/ReactionsAdminExtension.php =================================================================== --- articles/code/ReactionsAdminExtension.php (revision 0) +++ articles/code/ReactionsAdminExtension.php (revision 271) @@ -0,0 +1,19 @@ +<?php +class ReactionsAdminExtension extends Extension { + function updateEditForm($form) { + $fieldList = $form->Fields(); + + foreach($fieldList as $field) { + if ($field instanceof GridField) { + $config = $field->getConfig(); + $config->getComponentByType('GridFieldDataColumns')->setFieldFormatting( + array( + 'Body' => '<a href=\"' . $this->owner->Link() . '/EditForm/field/Reaction/item/$ID/edit\">$value</a>', + ) + ); + $field->setConfig($config); + } + + } + } +} Index: articles/code/ReactionsAdmin.php =================================================================== --- articles/code/ReactionsAdmin.php (revision 268) +++ articles/code/ReactionsAdmin.php (revision 271) @@ -18,148 +18,23 @@ public $showImportForm = false; + public function getEditForm($id = null, $fields = null) { + $form = parent::getEditForm($id, $fields); + $gridfield = $form->fields->items[0]; + $gridfield->config->addComponent(new GridFieldSelectColumn, $insert_before = 'GridFieldDataColumns'); + $gridfield->config->addComponent(new GridFieldReactionsDelete); + $gridfield->config->addComponent(new GridFieldReactionsPublish); + return $form; + } + + + public function init() { + parent::init(); + Requirements::javascript('articles/javascript/ReactionsAdmin_GridField.js'); + Requirements::add_i18n_javascript('articles/javascript/lang'); + } } Index: articles/code/GridFieldSelectColumn.php =================================================================== --- articles/code/GridFieldSelectColumn.php (revision 0) +++ articles/code/GridFieldSelectColumn.php (revision 271) @@ -0,0 +1,33 @@ +<?php + +class GridFieldSelectColumn implements GridField_ColumnProvider { + public function __construct($useToggle = true, $targetFragment = 'before') { + $this->targetFragment = $targetFragment; + $this->useToggle = $useToggle; + } + + public function augmentColumns($field, &$cols) { + if(!in_array('Select', $cols)) $cols[] = 'Select'; + } + + public function getColumnsHandled($field) { + return array('Select'); + } + + public function getColumnContent($field, $record, $col) { + if($record->canView()) { + $data = new ArrayData(array( + 'ID' => $record->ID + )); + return $data->renderWith('GridFieldSelectColumn'); + } + } + + public function getColumnAttributes($field, $record, $col) { + return array('class' => 'col-select'); + } + + public function getColumnMetadata($gridField, $col) { + return array('title' => ""); + } +} Index: articles/code/ArticlesAdmin.php =================================================================== --- articles/code/ArticlesAdmin.php (revision 268) +++ articles/code/ArticlesAdmin.php (revision 271) @@ -35,6 +35,7 @@ public function init() { parent::init(); Requirements::javascript('articles/javascript/ArticlesAdmin_GridField.js'); + Requirements::add_i18n_javascript('articles/javascript/lang'); Requirements::css('articles/css/ArticlesAdmin-GridField.css'); }