When creating a CRUD Panel, your EntityCrudController
(where Entity = your model name) is extending CrudController
. Inside it, we've already provided the logic for the most important operations, you just need to enable or configure them. Also, you can easily add custom operations.
Operations enabled by default:
Operations disabled by default:
Admins are allowed to do an operation or not using a very simple system: $crud
holds an array with all operations they can perform. By default it will look like this:
public $access = [
'list',
'create',
'update',
'delete'
/* 'revisions', reorder', 'show', 'details_row' */
];
You can easily add or remove elements to this access array in your setup()
method, or your custom methods, using:
$this->crud->allowAccess('operation');
$this->crud->allowAccess(['list', 'update', 'delete']);
$this->crud->denyAccess('operation');
$this->crud->denyAccess(['update', 'create', 'delete']);
$this->crud->hasAccess('operation'); // returns true/false
$this->crud->hasAccessOrFail('create'); // throws 403 error
$this->crud->hasAccessToAll(['create', 'update']); // returns true/false
$this->crud->hasAccessToAny(['create', 'update']); // returns true/false
Thanks to Backpack's simple architecture, each CRUD panel uses a controller and a route, that are placed inside your project. That means you hold the keys to how this controller works.
To add an operation to an EntityCrudController
, you can just:
routes/backpack/custom.php
that points to a new method in that controller;EntityCrudController
;Take a look at the examples below for a better picture and code examples.
Since you're creating a new operation, in terms of restricting access you can:
update
, he's ok to perform custom operations);$this->crud->allowAccess('moderate')
in your setup()
method, then check for access to that operation using $this->crud->hasAccess('moderate')
;Let's say we have a UserCrudController
and we want to create a simple Clone
operation, which would create another entry with the same info. So very similar to Delete
. What we need to do is:
routes/backpack/custom.php
:Route::post('user/{id}/clone', 'UserCrudController@clone');
UserCrudController
:public function clone($id)
{
$this->crud->hasAccessOrFail('create');
$clonedEntry = $this->crud->model->findOrFail($id)->replicate();
return (string) $clonedEntry->push();
}
resources\views\vendor\backpack\crud\buttons\clone.blade.php
file:@if ($crud->hasAccess('create'))
<a href="javascript:void(0)" onclick="cloneEntry(this)" data-route="{{ url($crud->route.'/'.$entry->getKey().'/clone') }}" class="btn btn-xs btn-default" data-button-type="clone"><i class="fa fa-clone"></i> Clone</a>
@endif
<script>
if (typeof cloneEntry != 'function') {
$("[data-button-type=clone]").unbind('click');
function cloneEntry(button) {
// ask for confirmation before deleting an item
// e.preventDefault();
var button = $(button);
var route = button.attr('data-route');
$.ajax({
url: route,
type: 'POST',
success: function(result) {
// Show an alert with the result
new PNotify({
title: "Entry cloned",
text: "A new entry has been added, with the same information as this one.",
type: "success"
});
// Hide the modal, if any
$('.modal').modal('hide');
crud.table.ajax.reload();
},
error: function(result) {
// Show an alert with the result
new PNotify({
title: "Cloning failed",
text: "The new entry could not be created. Please try again.",
type: "warning"
});
}
});
}
}
// make it so that the function above is run after each DataTable draw event
// crud.addFunctionToDataTablesDrawEventQueue('cloneEntry');
</script>
UserCrudController::setup()
:$this->crud->addButtonFromView('line', 'clone', 'clone', 'beginning');
Of course, if you plan to re-use this operation on another EntityCrudController, it's a good idea to isolate the method inside a trait, then use that trait on each EntityCrudController where you want the operation to work.
Let's say we have a UserCrudController
and we want to create a simple Moderate
operation, where we show a form where the admin can add his observations and what not. In this respect, it should be similar to Update
- the button should lead to a separate form, then that form will probably have a Save button. So when creating the methods, we should look at CrudController::edit()
and CrudController::updateCrud()
for working examples.
What we need to do is:
routes/backpack/custom.php
:Route::get('user/{id}/moderate', 'UserCrudController@getModerateForm');
Route::post('user/{id}/moderate', 'UserCrudController@postModerateForm');
UserCrudController
:public function getModerateForm($id)
{
$this->crud->hasAccessOrFail('update');
// get the info for that entry
$this->data['entry'] = $this->crud->getEntry($id);
$this->data['crud'] = $this->crud;
$this->data['title'] = 'Moderate '.$this->crud->entity_name;
return view('vendor.backpack.crud.moderate', $this->data);
}
public function postModerateForm(Request $request = null)
{
$this->crud->hasAccessOrFail('update');
// TODO: do whatever logic you need here
// ...
// You can use
// - $this->crud
// - $this->crud->getEntry($id)
// - $request
// ...
// show a success message
\Alert::success('Moderation saved for this entry.')->flash();
return \Redirect::to($this->crud->route);
}
/resources/views/vendor/backpack/crud/moderate.php
blade file, which shows the moderate form and what not. Best to start from the edit.blade.php
file and customize:@extends('backpack::layout')
@section('header')
<section class="content-header">
<h1>
<span class="text-capitalize">{{ $crud->entity_name_plural }}</span>
<small>{{ trans('backpack::crud.edit').' '.$crud->entity_name }}.</small>
</h1>
<ol class="breadcrumb">
<li><a href="{{ url(config('backpack.base.route_prefix'),'dashboard') }}">{{ trans('backpack::crud.admin') }}</a></li>
<li><a href="{{ url($crud->route) }}" class="text-capitalize">{{ $crud->entity_name_plural }}</a></li>
<li class="active">Moderate</li>
</ol>
</section>
@endsection
@section('content')
<div class="row">
<div class="col-md-8 col-md-offset-2">
<!-- Default box -->
@if ($crud->hasAccess('list'))
<a href="{{ url($crud->route) }}"><i class="fa fa-angle-double-left"></i> {{ trans('backpack::crud.back_to_all') }} <span>{{ $crud->entity_name_plural }}</span></a><br><br>
@endif
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Moderate</h3>
</div>
<div class="box-body row display-flex-wrap" style="display: flex;flex-wrap: wrap;">
Something in the box body
</div><!-- /.box-body -->
<div class="box-footer">
Something in the box footer
</div><!-- /.box-footer-->
</div><!-- /.box -->
</form>
</div>
</div>
@endsection
resources\views\vendor\backpack\crud\buttons\moderate.blade.php
file:@if ($crud->hasAccess('update'))
<a href="{{ url($crud->route.'/'.$entry->getKey().'/moderate') }}" class="btn btn-xs btn-default"><i class="fa fa-list"></i> Moderate</a>
@endif
UserCrudController::setup()
:$this->crud->addButtonFromView('line', 'moderate', 'moderate', 'beginning');
Of course, if you plan to re-use this operation on another EntityCrudController, it's a good idea to isolate the method inside a trait, then use that trait on each EntityCrudController where you want the operation to work.
Say we want to create a Clone
button which clones multiple entries at the same time. So very similar to our Bulk Delete
. What we need to do is:
$this->crud->enableBulkActions()
to make the checkboxes show up;
Create a new button and add it to our buttom stack:
@if ($crud->hasAccess('create') && $crud->bulk_actions)
<a href="javascript:void(0)" onclick="bulkCloneEntries(this)" class="btn btn-default bulk-button"><i class="fa fa-clone"></i> Clone</a>
@endif
@push('after_scripts')
<script>
if (typeof bulkCloneEntries != 'function') {
function bulkCloneEntries(button) {
if (typeof crud.checkedItems === 'undefined' || crud.checkedItems.length == 0)
{
new PNotify({
title: "{{ trans('backpack::crud.bulk_no_entries_selected_title') }}",
text: "{{ trans('backpack::crud.bulk_no_entries_selected_message') }}",
type: "warning"
});
return;
}
var message = "Are you sure you want to clone these :number entries?";
message = message.replace(":number", crud.checkedItems.length);
// show confirm message
if (confirm(message) == true) {
var ajax_calls = [];
var clone_route = "{{ url($crud->route) }}/bulk-clone";
// submit an AJAX delete call
$.ajax({
url: clone_route,
type: 'POST',
data: { entries: crud.checkedItems },
success: function(result) {
// Show an alert with the result
new PNotify({
title: "Entries cloned",
text: crud.checkedItems.length+" new entries have been added.",
type: "success"
});
crud.checkedItems = [];
crud.table.ajax.reload();
},
error: function(result) {
// Show an alert with the result
new PNotify({
title: "Cloning failed",
text: "One or more entries could not be created. Please try again.",
type: "warning"
});
}
});
}
}
}
</script>
@endpush
setup()
method, add this button to the bottom stack:$this->crud->addButtonFromView('bottom', 'bulk_clone', 'bulk_clone', 'end');
public function bulkClone()
{
$this->crud->hasAccessOrFail('create');
$entries = $this->request->input('entries');
$clonedEntries = [];
foreach ($entries as $key => $id) {
if ($entry = $this->crud->model->find($id)) {
$clonedEntries[] = $entry->replicate()->push();
}
}
return $clonedEntries;
}
CRUD::resource('monster', 'MonsterCrudController')->with(function() {
Route::post('monster/bulk-clone', 'MonsterCrudController@bulkClone');
});
Now there's a Clone button on our bottom stack, that works as expected for multiple entries.
The button makes one call for all entries, and only triggers one notification. If you would rather make a call for each entry, you can use something like below:
@if ($crud->hasAccess('create') && $crud->bulk_actions)
<a href="javascript:void(0)" onclick="bulkCloneEntries(this)" class="btn btn-default"><i class="fa fa-clone"></i> Clone</a>
@endif
@push('after_scripts')
<script>
if (typeof bulkCloneEntries != 'function') {
function bulkCloneEntries(button) {
if (typeof crud.checkedItems === 'undefined' || crud.checkedItems.length == 0)
{
new PNotify({
title: "{{ trans('backpack::crud.bulk_no_entries_selected_title') }}",
text: "{{ trans('backpack::crud.bulk_no_entries_selected_message') }}",
type: "warning"
});
return;
}
var message = "Are you sure you want to clone these :number entries?";
message = message.replace(":number", crud.checkedItems.length);
// show confirm message
if (confirm(message) == true) {
var ajax_calls = [];
// for each crud.checkedItems
crud.checkedItems.forEach(function(item) {
var clone_route = "{{ url($crud->route) }}/"+item+"/clone";
// submit an AJAX clone call
ajax_calls.push($.ajax({
url: clone_route,
type: 'POST',
success: function(result) {
// Show an alert with the result
new PNotify({
title: "Entry cloned",
text: "A new entry has been added, with the same information as this one.",
type: "success"
});
},
error: function(result) {
// Show an alert with the result
new PNotify({
title: "Cloning failed",
text: "The new entry could not be created. Please try again.",
type: "warning"
});
}
}));
});
$.when.apply(this, ajax_calls).then(function ( ajax_calls ) {
crud.checkedItems = [];
crud.table.ajax.reload();
});
}
}
}
</script>
@endpush
Then you'll love our premium add-ons - productivity tools and tons of new features.