Fields may be specified via the scaffold.fields
configuration key. By
default, this will contain a list of all columns associated with the Table being
in scope. To limit the fields used, simply specify an array of fields:
$action = $this->Crud->action();
$action->setConfig('scaffold.fields', ['title', 'description']);
If you wish to use the default automatic field population functionality but want
to specify settings for a few of the fields, you can use the
scaffold.field_settings
configuration key:
$action = $this->Crud->action();
$action->setConfig('scaffold.field_settings', [
'title' => [
// options here
]
]);
You may also use the scaffold.fields_blacklist
configuration key to remove
fields from the output if you are using the default automatic field population
functionality:
$action = $this->Crud->action();
$action->setConfig('scaffold.fields_blacklist', ['created', 'modified']);
Note
This functionality currently only applies to index
and view
pages.
The most immediate way of formatting a field is by passing a callable function
or object to the formatter
option. Callable functions or objects will
receive 5 arguments:
$name
The name of the field to be displayed
$value
The value of the field that should be outputted
$entity
The entity object from which the field was extracted
$options
An array of options passed to the CrudView helper when the field is being processed
$View
The view object in use during formatting
For example, imagine that when displaying the published_time
property, we
wanted to also display who approved the article:
$action = $this->Crud->action();
$action->setConfig('scaffold.fields', [
'title',
'published_time' => [
'formatter' => function ($name, $value, $entity) {
return $value->nice() . sprintf(' (Approved by %s)', $entity->approver->name);
}
],
]);
You may also specify formatters using the scaffold.field_settings
configuration key. This is useful if you want to display all fields but wish to
only configure the settings for one or two.
$action = $this->Crud->action();
$action->setConfig('scaffold.field_settings', [
'published_time' => [
'formatter' => function ($name, Time $value, Entity $entity) {
return $value->nice() . sprintf(' (Approved by %s)', $entity->approver->name);
}
],
]);
In some cases, it may be useful to access a helper within the callable. For instance, you might want to create a link:
$action = $this->Crud->action();
$action->setConfig('scaffold.fields', [
'title',
'external_id' => [
'formatter' => function ($name, $value, $entity, $options, $View) {
return $View->Html->link($name, sprintf('https://example.com/view/%d', $value));
}
],
]);
You can also keep your code DRY by configuring the CrudViewHelper
to use
a callable formatter based on column type. For .e.g.
// In controller action or preferably in beforeRender()
$this->viewBuilder()->setHelpers([
'CrudView' => [
'className' => 'CrudView.CrudView',
'fieldFormatters' => [
// Key can be any valid column type of table schema.
'datetime' => function ($name, $value, $entity, $options, $View) {
return $View->Time->nice($value);
},
'boolean' => function ($name, $value, $entity, $options, $View) {
return $value ? 'Yes' : 'No';
},
],
],
]);
Note
This functionality currently only applies to index
and view
pages.
Sometimes you want to execute more complex formatting logic, that may involve
the use of view helpers or outputting HTML. Since building HTML outside of the
view layer is not ideal, you can use the element
formatter for any of your
fields.
For example, consider this example where we want to link the published_time
to the same index action by passing some search arguments:
$action = $this->Crud->action();
$action->setConfig('scaffold.fields', [
'published_time' => [
'formatter' => 'element',
'element' => 'search/published_time',
'action' => 'index'
]
]);
We have instructed the formatter to use search/published_time
element. Then,
it is just a matter of creating the element file with the right content:
// templates/element/search/published_time.ctp
echo $this->Html->link($value->timeAgoInWords(), [
'action' => $options['action'],
'published_time' => $value->format('Y-m-d')
]);
After this, when displaying the published_time
field, there will the will be
a link similar to this one:
<a href="/articles?published_time=2015-06-23">4 days ago</a>
Element files will have available at least the following variables:
$value
: The value of the field
$field
: The name of the field it is intended to be rendered
$context
: The entity from which the value came from
$options
: The array of options associated to the field as passed in scaffold.fields
By default sorting links are generated for index page table’s column headers
using the PaginatorHelper
. You can disable the link generation by using
the disableSort
option:
$action = $this->Crud->action();
$action->setConfig('scaffold.fields', [
'title' => [
'disableSort' => true,
]
]);
By default, the included index buttons are generated based on the mapped Crud
actions. You can customize available buttons by using the scaffold.actions
key:
$action = $this->Crud->action();
// restrict to just the add button, which will show up globally
$action->setConfig('scaffold.actions', [
'add'
]);
// restrict to just the delete/edit/view actions, which are scoped to entities
$action->setConfig('scaffold.actions', [
'delete',
'edit',
'view',
]);
You can also specify configuration for actions, which will be used when generating action buttons.
$action = $this->Crud->action();
$action->setConfig('scaffold.actions', [
'duplicate' => [
// An alternative title for the action
'link_title' => 'Duplicate this record',
// A url that this action should point to
'url' => ['action' => 'jk-actually-this-action'],
// The HTTP method to use. Defaults to GET. All others result in
// a ``FormHelper::postLink``
'method' => 'POST',
// Whether to scope the action to a single entity or the entire table
// Options: ``entity``, ``table``
'scope' => 'entity',
// All other options are passed in as normal to the options array
'other' => 'options',
]
]);
For entity-scoped actions, we will append the primaryKey
of the record to
the link by default:
$action = $this->Crud->action();
// For the PostsController, will generate
// /posts/translate/english/1
$action->setConfig('scaffold.actions', [
'translate' => [
'url' => ['action' => 'translate', 'english']
]
]);
We can specify the token :primaryKey:
. Rather than appending the
primaryKey
, we will replace this token in the url as many times as
specified.
$action = $this->Crud->action();
// For the PostsController, will generate
// /posts/translate/1/english
$action->setConfig('scaffold.actions', [
'translate' => [
'url' => ['action' => 'translate', ':primaryKey:', 'english']
]
]);
If you wish to blacklist certain action buttons from showing up, you can use the
scaffold.actions_blacklist
configuration key. This can be useful when many
Crud action classes are mapped but should not all be shown on the main UI.
$action = $this->Crud->action();
$action->setConfig('scaffold.actions_blacklist', ['add', 'delete']);
By default, we blacklist the action which is mapped to Crud.LookupAction
.
As this action is meant to be used solely for autocompletion, it cannot be removed
from the scaffold.actions_blacklist
list.
You can group actions together using Action Groups. This will generate a
dropdown for the group, and can be controlled by the scaffold.action_groups
configuration key.
$action = $this->Crud->action();
$action->setConfig('scaffold.actions', ['view', 'edit', 'delete']);
$action->setConfig('scaffold.action_groups', [
'Actions' => [
'view',
'edit',
'delete',
],
]);
All actions specified in an action group must be included in the
scaffold.actions
key.
You can specify multiple action groups:
$action = $this->Crud->action();
$action->setConfig('scaffold.actions', ['view', 'edit', 'delete', 'disable', 'delete']);
$action->setConfig('scaffold.action_groups', [
'Actions' => [
'view',
'edit',
'delete',
],
'Destructive Actions' => [
'disable',
'delete',
]
]);
Finally, you can also set configuration for each entry in an action group:
$action = $this->Crud->action();
$action->setConfig('scaffold.actions', ['view', 'edit', 'delete', 'english', 'spanish']);
$action->setConfig('scaffold.action_groups', [
'Actions' => [
'view',
'edit',
'delete',
],
'Translate' => [
'english' => [
'url' => ['action' => 'translate', 'english']
],
'spanish' => [
'url' => ['action' => 'translate', 'spanish']
],
]
]);
The Crud plugin provides bulk actions which can be easily used with crud view.
To set up crud action in controller do something like this in initialize method.
$this->loadComponent('Crud.Crud', [
'actions' => [
'deleteAll' => [
'className' => 'Crud.Bulk/Delete',
],
]
]);
Once a bulk action has been mapped, the scaffold.bulk_actions
configuration
key can be specified. The scaffold.bulk_actions
configuration key takes an
array of key/value pairs, where the key is the url and the value is the title.
$action = $this->Crud->action();
$action->setConfig('scaffold.bulk_actions', [
Router::url(['action' => 'deleteAll']) => __('Delete records'),
]);
In some cases, it is helpful to show quick links to pre-filtered datasets.
Rather than force users to select all the filters, CrudView enables the ability
to display “Finder Scope” links via the scaffold.index_finder_scopes
configuration key. These are output below the action header, above the data that
is being paginated.
The scaffold.index_finder_scopes
option takes an array of finder scope data.
Each sub-array should contain title
and finder
parameters.
$this->Crud->action()->setConfig('scaffold.index_finder_scopes', [
[
'title' => __('All'),
'finder' => 'all',
],
[
'title' => __('Active'),
'finder' => 'active',
],
[
'title' => __('Inactive'),
'finder' => 'inactive',
],
]);
The all
finder scope is special. This scope will be displayed by default,
and should always be included in the scope list. It is not automatically
injected.
Selecting a finder scope will reset any other querystring arguments. Selecting
the all
finder scope will result in being redirected to a page without
querystring arguments.
Selecting a finder scope will not automatically apply the find to your paginated result-set. This must be done manually.
Note
This example assumes a simple blog application is being modified, with a
posts
database table containing the fields id
, active
,
title
, body
, and created
.
Once a finder scope is selected, it must still be applied to the paginated result-set. This can be done in the mapped action as follows:
public function index()
{
$this->Crud->action()->setConfig('scaffold.index_finder_scopes', [
[
'title' => __('All'),
'finder' => 'all',
],
[
'title' => __('Active'),
'finder' => 'active',
],
[
'title' => __('Inactive'),
'finder' => 'inactive',
],
]);
// We don't need to check for `all` as it is the default findMethod
if (in_array($this->request->getQuery('finder'), ['active', 'inactive'])) {
$this->Crud->action()->config('findMethod', $this->request->getQuery('finder'));
}
return $this->Crud->execute();
}
Now that the findMethod
can be mapped, the respective custom find methods
must be created in the PostsTable
class.
use Cake\ORM\Query;
use Cake\ORM\Table;
class PostsTable extends Table
{
public function findActive(Query $query, array $options)
{
$query->where([$this->aliasField('active') => true]);
return $query;
}
public function findInactive(Query $query, array $options)
{
$query->where([$this->aliasField('active') => false]);
return $query;
}
}
The ViewSearch
listener generates filter inputs for filtering records on your
index action. It requries friendsofcake/search <https://packagist.org/packages/friendsofcake/search>
to be installed and filters configured for your model using the search manager.
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Controller\AppController;
class SamplesController extends AppController
{
public function initialize(): void
{
parent::initialize();
// Enable PrgComponent so search form submissions
// properly populate querystring parameters for the SearchListener
$this->loadComponent('Search.Prg', [
'actions' => [
'index',
],
]);
}
public function index()
{
// Enable the SearchListener
$this->Crud->addListener('search', 'Crud.Search', [
// The search behavior collection to use. Default "default".
'collection' => 'admin',
]);
// Enable the ViewSearch listener
$this->Crud->addListener('viewSearch', 'CrudView.ViewSearch', [
// Indicates whether is listener is enabled.
'enabled' => true,
// Whether to use auto complete for select fields. Default `true`.
// This requires you have `Crud.Lookup` action enabled for that
// related model's controller.
// http://crud.readthedocs.io/en/latest/actions/lookup.html
'autocomplete' => true,
// Whether to use selectize for select fields. Default `true`.
'selectize' => true,
// The search behavior collection to use. Default "default".
'collection' => 'default',
// Config for generating filter controls. If `null` the
// filter controls will be derived based on filter collection.
// You can use "form" key in filter config to specify control options.
// Default `null`.
'fields' => [
// Key should be the filter name.
'filter_1' => [
// Any option which you can use Form::control() options.
],
// Control options for other filters.
]
]);
return $this->Crud->execute();
}
}
Here’s an e.g. of how configure filter controls options through search manager itself:
<?php
declare(strict_types=1);
namespace App\Model\Table;
use Cake\ORM\Table;
class SamplesTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->addBehavior('Search.Search');
$this->searchManager()
->useCollection('default')
->add('q', 'Search.Like', [
'field' => ['title', 'body'],
'form' => [
'data-foo' => 'bar'
]
])
->add('category_id', 'Search.Value', [
'form' => [
'type' => 'select',
'class' => 'no-selectize'
]
]);
}
}
Sometime you may want more than one index page for a resource to represent
different views to the user. If multiple index pages exist, CrudView will
automatically build links at the top of the index
page. Including multiple
views is simple and requires setting the index
view in your action.
$action = $this->Crud->action();
$action->view('index');
The scaffold.index_formats
configuration key can be used to customize
“Download Links”. These are alternative methods of displaying the current index
page, and can be used to expose the paginated data in JSON, XML, or other
formats. The output of each format can be customized to your specifications.
The scaffold.index_formats
option takes an array of download format data.
Each sub-array should contain title
and url
parameters.
use Cake\Routing\Router;
// link to the current page, except with extensions `json` or `xml`
// include the querystring argument as specified or you will lose any
// currently applied filters
$action = $this->Crud->action();
$action->setConfig('scaffold.index_formats', [
[
'title' => 'JSON',
'url' => ['_ext' => 'json', '?' => $this->request->getQueryParams()]
],
[
'title' => 'XML',
'url' => Router::url(['_ext' => 'xml', '?' => $this->request->getQueryParams()])
],
]);
Download links are displayed near the bottom-left of the index page and will open in a new window.
Note
This example assumes a simple blog application is being modified, with a
posts
database table containing the fields id
, active
,
title
, body
, and created
.
To implement a simple csv download link, the friendsofcake/cakephp-csvview
plugin should be installed. This plugin will handle the actual rendering of
csv files at the CakePHP view layer.
composer require friendsofcake/cakephp-csvview:~3.0
Next, the csv
extension must be connected so that it can be properly parsed.
This can be done by modifying the config/routes.php
file. Below is a
semi-complete example:
Router::scope('/', function (RouteBuilder $routes) {
$routes->extensions(['csv']);
// other routes go here
});
To complete the initial setup, the RequestHandler should be notified to use the
CsvView.View
class whenever an extension of csv
is detected. The
following can be added to the AppController::initialize()
to do
application-wide:
$this->loadComponent('RequestHandler', [
'viewClassMap' => ['csv' => 'CsvView.Csv']
]);
Once the initial setup of the CsvView plugin is complete, the index()
action
can be modified to add a CSV Download Link.
public function index()
{
// only show the id, title, and created fields for csv output
if ($this->request->getParam('_ext') === 'csv') {
$this->set('_serialize', ['posts']);
$this->set('_extract', ['id', 'active', 'title', 'created']);
}
$this->Crud->action()->setConfig('scaffold.index_formats', [
[
'title' => 'CSV',
'url' => ['_ext' => 'csv', '?' => $this->request->getQueryParams()]
],
]);
return $this->Crud->execute();
}
The following custom view blocks are available for use within forms:
form.sidebar
: Rendered on the side of a form. Will also change the form
width.
form.before_create
: Rendered before FormHelper::create()
is called.
form.after_create
: Rendered after FormHelper::create()
is called.
form.before_end
: Rendered before FormHelper::end()
is called.
form.after_end
: Rendered after FormHelper::end()
is called. Used by embedded Form::postLink()
calls.
All the CrudView templates are built from several elements that can be
overridden by creating them in your own templates/element
folder. The
following sections will list all the elements that can be overridden for each
type of action.
In general, if you want to override a template, it is a good idea to copy the
original implementation from
vendor/friendsofcake/crud-view/templates/element
Create templates/element/action-header.ctp
to have full control over
what is displayed at the top of the page. This is shared across all page
types.
Create templates/element/form/buttons.ctp
to change what is displayed
for form submission. You can expect the $formSubmitButtonText
and
$formSubmitExtraButtons
variables to be available