I needed a form that sends an email to each user, directly from the admin panel. I already had a Users CRUD, so here's how I added a fo...
I needed a form that sends an email to each user, directly from the admin panel. I already had a Users CRUD, so here's how I added a form that allows the admin to send a quick email to a user. I'll help you create the same EmailOperation
I created, or any operation that needs a custom form, really.
What do we need, from an admin’s perspective?
a button, that shows up next to each User in the table, to allow them to get to the form:
a form, that allows them to type out their Subject and Message for that user:
Let’s go ahead and do that, using Backpack features. We can load all of those custom features in a separate file, by creating a new Backpack operation.
I recommend you read all the way to the end, to understand how and why everything was done. But if you just want the working files, you can find them in this gist.
First, we need to create a custom operation. If you've installed backpack/generators, you can generate an empty operation trait using:
php artisan backpack:crud-operation Email
This will generate app/Http/Controllers/Admin/Operations/EmailOperation.php
, but inside it, we need to change a few things to get it working the way we want it.
protected function setupEmailRoutes($segment, $routeName, $controller){
- Route::get($segment . '/email', [
+ Route::get($segment . '/{id}/email', [
'as' => $routeName.'.email',
'uses' => $controller.'@email',
'operation' => 'email',
]);
}
public function email(){
$this->crud->hasAccessOrFail('email');
$this->data['crud'] = $this->crud;
- $this->data['title'] = $this->crud->getTitle() ?? 'email '.$this->crud->entity_name;
+ $this->data['title'] = $this->crud->getTitle() ?? 'Email '.$this->crud->entity_name;
+ $this->data['entry'] = $this->crud->getCurrentEntry();
}
resources/views/vendor/backpack/crud/buttons/email.blade.php
:@if ($crud->hasAccess('email'))
<a href="{{ url($crud->route.'/'.$entry->getKey().'/email') }}" class="btn btn-sm btn-link"><i class="la la-envelope"></i> Email</a>
@endif
setupEmailDefaults()
by simply uncommenting this line:protected function setupEmailDefaults(){
$this->crud->allowAccess('email');
$this->crud->operation('email', function () {
$this->crud->loadDefaultOperationSettingsFromConfig();
});
$this->crud->operation('list', function () {
// $this->crud->addButton('top', 'email', 'view', 'crud::buttons.email');
- // $this->crud->addButton('line', 'email', 'view', 'crud::buttons.email');
+ $this->crud->addButton('line', 'email', 'view', 'crud::buttons.email');
});
}
Let’s look at the email() method – it loads an “email” blade file. I'd rather give it a new name:
public function email(){
- return view("crud::operations.email", $this->data);
+ return view("crud::operations.email_form", $this->data);
}
Let’s create this blade file (resources/views/vendor/backpack/crud/operations/email_form.blade.php
), where we are going to extend Backpack's blank layout, to keep the look-and-feel of the admin panel:
@extends(backpack_view('blank'))
@section('content')
{{-- TODO: SHOW FORM HERE --}}
@endsection
Let's add our brand-new operation to the CrudController:
class UserCrudController extends CrudController{
+ use \App\Http\Controllers\Admin\Operations\EmailOperation;
}
Boom! We have reached halfway with the operation: it's usable in the browser, but... it does nothing:
Now, let's fill the content section of the email_form.php
blade file with the actual HTML code that shows a form. Also, let's fill the header section with breadcrumbs:
@extends(backpack_view('blank'))
@php
$defaultBreadcrumbs = [
trans('backpack::crud.admin') => url(config('backpack.base.route_prefix'), 'dashboard'),
$crud->entity_name_plural => url($crud->route),
'Email' => false,
];
// if breadcrumbs aren't defined in the CrudController, use the default breadcrumbs
$breadcrumbs = $breadcrumbs ?? $defaultBreadcrumbs;
@endphp
@section('header')
<section class="container-fluid">
<h2>
<span class="text-capitalize">Send Email</span>
<small>Sending email to {!! $entry->name !!}.</small>
@if ($crud->hasAccess('list'))
<small>
<a href="{{ url($crud->route) }}" class="d-print-none font-sm">
<i
class="la la-angle-double-{{ config('backpack.base.html_direction') == 'rtl' ? 'right' : 'left' }}"></i>
{{ trans('backpack::crud.back_to_all') }}
<span>{{ $crud->entity_name_plural }}</span>
</a>
</small>
@endif
</h2>
</section>
@endsection
@section('content')
<div class="row">
<div class="col-md-8 bold-labels">
@if ($errors->any())
<div class="alert alert-danger pb-0">
<ul class="list-unstyled">
@foreach ($errors->all() as $error)
<li><i class="la la-info-circle"></i> {{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="post" action="">
@csrf
<div class="card">
<div class="card-body row">
<div class="form-group col-md-4">
<label>From</label>
<input type="text" name="from" value="{{ old('from', config('mail.from.address')) }}" class="form-control @error('from') is-invalid @enderror">
@error('from')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div>
<div class="form-group col-md-4">
<label>To</label>
<input type="text" name="to" value="{{ $entry->email }}" readonly="readonly" disabled="disabled" class="form-control">
</div>
<div class="form-group col-md-4">
<label>Reply To</label>
<input type="text" name="reply_to" value="{{ old('reply_to',backpack_user()->email) }}" class="form-control @error('reply_to') is-invalid @enderror">
@error('reply_to')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div>
<div class="form-group col-sm-12">
<label>Subject</label>
<input type="text" name="subject" value="{{ old('subject') }}" class="form-control @error('subject') is-invalid @enderror">
@error('subject')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div>
<div class="form-group col-sm-12">
<label>Message</label>
<textarea name="message" class="form-control @error('message') is-invalid @enderror">{{ old('message') }}</textarea>
@error('message')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div>
</div>
</div>
<div class="d-none" id="parentLoadedAssets">[]</div>
<div id="saveActions" class="form-group">
<input type="hidden" name="_save_action" value="send_email">
<button type="submit" class="btn btn-success">
<span class="la la-save" role="presentation" aria-hidden="true"></span>
<span data-value="send_email">Send Email</span>
</button>
<div class="btn-group" role="group">
</div>
<a href="{{ url($crud->route) }}" class="btn btn-default"><span class="la la-ban"></span>
Cancel</a>
</div>
</form>
</div>
</div>
@endsection
Now let's define a POST route, that leads to a postEmailForm()
method inside EmailOperation.php
, that validates and processes the form:
protected function setupEmailRoutes($segment, $routeName, $controller){
Route::get($segment . '/{id}/email', [
'as' => $routeName.'.email',
'uses' => $controller.'@email',
'operation' => 'email',
]);
+ Route::post($segment . '/{id}/email', [
+ 'as' => $routeName . '.email-send',
+ 'uses' => $controller . '@postEmailForm',
+ 'operation' => 'email',
+ ]);
}
Let’s fill in the logic, so it actually processes an email form and sends that email:
use Illuminate\Support\Facades\Mail;
use Illuminate\Http\Request;
use Exception;
use Validator;
use Alert;
public function postEmailForm(Request $request){
// run validation
$validator = Validator::make($request->all(), [
'from' => 'required|email',
'reply_to' => 'nullable|email',
'subject' => 'required',
'message' => 'required'
]);
if($validator->fails())
return redirect()->back()->withErrors($validator)->withInput();
$entry = $this->crud->getCurrentEntry();
try {
// send the actual email
Mail::raw($request['message'], function ($message) use ($entry, $request) {
$message->from($request->from);
$message->replyTo($request->reply_to);
$message->to($entry->email, $entry->name);
$message->subject($request['subject']);
});
Alert::success('Mail Sent')->flash();
return redirect(url($this->crud->route));
} catch (Exception $e) {
// show a bubble with the error message
Alert::error("Error, " . $e->getMessage())->flash();
return redirect()->back()->withInput();
}
}
That's it, here's what we're getting from the above:
Again, if you just want to copy-paste, you can find the final code here 💻
Thanks for reading this far 😀 – I hope you learned something new. Let us know what you think in the comments below.
Subscribe to our "Article Digest". We'll send you a list of the new articles, every week, month or quarter - your choice.
What do you think about this?
Wondering what our community has been up to?