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,
]
]);
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