Appearance
Contact
TIP
- We'll use the integrated MailHog service to send (fake) mails in a development and testing environments
- You can access the MailHog mailbox in the browser at
http://localhost:8025
(or click on the @-icon in the footer menu)
- The contact page contains some static content and a (dynamic) contact form
- In web.php, we keep the route as a view route to resources/views/contact.blade.php and embed the Livewire component in that view
Preparation
Create a ContactForm component
- Create a new Livewire ContactForm component with
php artisan livewire:make ContactForm
- app/Livewire/Admin/ContactForm.php (the component class)
- resources/views/livewire/admin/contact-form.blade.php (the component view)
- Refactor the contact.blade.php view and embed the ContactForm component in it
- The page contains some static content and embeds (on line 8) the ContactForm component
php
<x-vinylshop-layout>
<x-slot name="title">The Vinyl Shop: contact info</x-slot>
<x-slot name="subtitle">Contact info</x-slot>
<div class="grid grid-cols-4 gap-4">
<x-tmk.section class="col-span-4 lg:col-span-3 lg:order-2">
{{-- embed the Livewire ContactForm component --}}
@livewire('contact-form')
</x-tmk.section>
<section class="col-span-4 lg:col-span-1 lg:order-1">
<h3>The Vinyl Shop</h3>
<p>Kleinhoefstraat 4</p>
<p class="pb-2 border-b">2440 Geel - Belgium</p>
<p class="flex items-center pt-2 cursor-pointer">
<x-phosphor-phone-call class="w-6 mr-2 text-gray-400"/>
<a href="tel:+3214562310" class="mr-2">+32(0)14/56.23.10</a>
</p>
<p class="flex items-center pt-2 cursor-pointer">
<x-heroicon-o-envelope-open class="w-6 mr-2 text-gray-400"/>
<a href="mailto:info@thevinylshop.com">info@thevinylshop.com</a>
</p>
</section>
</div>
{{-- <p>The Vinyl Shop</p>
<p><a href="mailto:info@thevinylshop.com">info@thevinylshop.com</a></p> --}}
</x-vinylshop-layout>
<x-vinylshop-layout>
<x-slot name="title">The Vinyl Shop: contact info</x-slot>
<x-slot name="subtitle">Contact info</x-slot>
<div class="grid grid-cols-4 gap-4">
<x-tmk.section class="col-span-4 lg:col-span-3 lg:order-2">
{{-- embed the Livewire ContactForm component --}}
@livewire('contact-form')
</x-tmk.section>
<section class="col-span-4 lg:col-span-1 lg:order-1">
<h3>The Vinyl Shop</h3>
<p>Kleinhoefstraat 4</p>
<p class="pb-2 border-b">2440 Geel - Belgium</p>
<p class="flex items-center pt-2 cursor-pointer">
<x-phosphor-phone-call class="w-6 mr-2 text-gray-400"/>
<a href="tel:+3214562310" class="mr-2">+32(0)14/56.23.10</a>
</p>
<p class="flex items-center pt-2 cursor-pointer">
<x-heroicon-o-envelope-open class="w-6 mr-2 text-gray-400"/>
<a href="mailto:info@thevinylshop.com">info@thevinylshop.com</a>
</p>
</section>
</div>
{{-- <p>The Vinyl Shop</p>
<p><a href="mailto:info@thevinylshop.com">info@thevinylshop.com</a></p> --}}
</x-vinylshop-layout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Basic scaffolding
- Open app/Livewire/ContactForm.php and resources/views/livewire/contact-form.php
- Line 10 - 16: the form has three input fields (
name
,email
andmessage
) so we need three public properties in the component with there validation rules - Line 18 - 22: the form has a submit button, so we need a method for this
sendEmail()
- At this moment, the method only dumps the values of the three public properties to the screen
php
<?php
namespace App\Livewire;
use Livewire\Attributes\Validate;
use Livewire\Component;
class ContactForm extends Component
{
// public properties
#[Validate('required|min:3|max:50')]
public $name;
#[Validate('required|email')]
public $email;
#[Validate('required|min:10|max:500')]
public $message;
// send email
public function sendEmail()
{
dump($this->name, $this->email, $this->message);
}
public function render()
{
return view('livewire.contact-form');
}
}
<?php
namespace App\Livewire;
use Livewire\Attributes\Validate;
use Livewire\Component;
class ContactForm extends Component
{
// public properties
#[Validate('required|min:3|max:50')]
public $name;
#[Validate('required|email')]
public $email;
#[Validate('required|min:10|max:500')]
public $message;
// send email
public function sendEmail()
{
dump($this->name, $this->email, $this->message);
}
public function render()
{
return view('livewire.contact-form');
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Real-time form validation
- We always need to validate the form input fields before we can send the email
- Livewire has multiple built-in validation systems, and this time we will use the real-time validation
- Let's try three real-time validation possibilities:
- Line 6: add the
wire:model
directive with thelive
modifier to thename
property
(thelive
modifier will be triggered on every keystroke) - Line 13: add the
wire:model
directive with theblur
modifier to theemail
property
(theblur
modifier will be triggered when the input field loses focus) - Line 20: add the
wire:model
directive with thelive.debounce.500ms
modifier to themessage
property
(thelive.debounce.500ms
modifier will be triggered 500ms after the last keystroke)
- Line 6: add the
php
<form wire:submit="sendEmail()">
<div class="grid grid-cols-2 gap-4">
<div class="col-span-2 md:col-span-1">
<x-label for="name" value="Name"/>
<x-input type="text" id="name" name="name" class="block mt-1 w-full"
wire:model.live="name"
placeholder="Your name"/>
<x-input-error for="name" class="mt-2"/>
</div>
<div class="col-span-2 md:col-span-1">
<x-label for="email" value="Email"/>
<x-input type="email" id="email" name="email" placeholder="Your email"
wire:model.blur="email"
class="block mt-1 w-full"/>
<x-input-error for="email" class="mt-2"/>
</div>
<div class="col-span-2">
<x-label for="message" value="Message"/>
<x-tmk.form.textarea id="message" name="message" rows="5" placeholder="Your message"
wire:model.live.debounce.500ms="message"
class="block mt-1 w-full">
</x-tmk.form.textarea>
<x-input-error for="message" class="mt-2"/>
</div>
<div class="col-span-2">
<x-tmk.form.button class="col-span-2">send message</x-tmk.form.button>
</div>
</div>
</form>
<form wire:submit="sendEmail()">
<div class="grid grid-cols-2 gap-4">
<div class="col-span-2 md:col-span-1">
<x-label for="name" value="Name"/>
<x-input type="text" id="name" name="name" class="block mt-1 w-full"
wire:model.live="name"
placeholder="Your name"/>
<x-input-error for="name" class="mt-2"/>
</div>
<div class="col-span-2 md:col-span-1">
<x-label for="email" value="Email"/>
<x-input type="email" id="email" name="email" placeholder="Your email"
wire:model.blur="email"
class="block mt-1 w-full"/>
<x-input-error for="email" class="mt-2"/>
</div>
<div class="col-span-2">
<x-label for="message" value="Message"/>
<x-tmk.form.textarea id="message" name="message" rows="5" placeholder="Your message"
wire:model.live.debounce.500ms="message"
class="block mt-1 w-full">
</x-tmk.form.textarea>
<x-input-error for="message" class="mt-2"/>
</div>
<div class="col-span-2">
<x-tmk.form.button class="col-span-2">send message</x-tmk.form.button>
</div>
</div>
</form>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Disable the submit button
- Remember that we added a
disabled
and thecolor
attributes to ourx-tmk.form.button
component - We can use the
disabled
attribute on the submit button to disable the button when the validation is not successful
- Line 10: add a public property
$canSubmit
with a default value offalse
- Line 15: set the
$canSubmit
property tofalse
if the full validation is successful - Line 17 - 18: set the
$canSubmit
property totrue
if the 'error bag' is empty
php
class ContactForm extends Component
{
// public properties
#[Validate('required|min:3|max:50')]
public $name;
#[Validate('required|email')]
public $email;
#[Validate('required|min:10|max:500')]
public $message;
public $canSubmit = false;
// disable submit button until all fields are valid
public function updated($propertyName, $propertyValue)
{
$this->canSubmit = false;
$this->validateOnly($propertyName);
if(count($this->getErrorBag()->all()) === 0)
$this->canSubmit = true;
}
public function sendEmail() { ... }
public function render() { ... }
}
class ContactForm extends Component
{
// public properties
#[Validate('required|min:3|max:50')]
public $name;
#[Validate('required|email')]
public $email;
#[Validate('required|min:10|max:500')]
public $message;
public $canSubmit = false;
// disable submit button until all fields are valid
public function updated($propertyName, $propertyValue)
{
$this->canSubmit = false;
$this->validateOnly($propertyName);
if(count($this->getErrorBag()->all()) === 0)
$this->canSubmit = true;
}
public function sendEmail() { ... }
public function render() { ... }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Sending email
- After configuring our mail settings, we set up a mail template, pass some data to this template and send the mail itself
configure mail settings
- The mail is already set up for you in the
.env
file - The
.env
file contains the following settings for the mail service:- Line 2: replace mailpit with localhost
- Line 7: replace "hello@example.com" with your mailadres e.g. "info@vinyl_shop.test"
bash
MAIL_MAILER=smtp
MAIL_HOST=localhost # replace 'mailpit' with 'localhost'
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="info@vinyl_shop.test" # replace "hello@example.com" with your mailadres e.g. "info@vinyl_shop.test"
MAIL_FROM_NAME="${APP_NAME}"
MAIL_MAILER=smtp
MAIL_HOST=localhost # replace 'mailpit' with 'localhost'
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="info@vinyl_shop.test" # replace "hello@example.com" with your mailadres e.g. "info@vinyl_shop.test"
MAIL_FROM_NAME="${APP_NAME}"
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
WARNINGS
- If your variable contains spaces, surround it with quotes. E.g. MAIL_FROM_NAME="John Doe"
- If you want a REAL email address to test certain functionalities, use Mailinator e.g.
john.doe@mailinator.com
.
Emails sent to Mailinator are public but temporary and will disappear after a few hours.
Create mailable
- In Laravel, each type of email sent by your application is represented as a mailable class
- Create a new mailable class with an associated view with the command:
bash
php artisan make:mail ContactMail --markdown=emails.contact
php artisan make:mail ContactMail --markdown=emails.contact
1
- The ContactMail.php mailable class is located in the app/Mail directory
- The contact.blade.php view is located in the resources/views/emails directory
- The --markdown option will create a markdown based view
(Besides a Markdown-based mail template, used in this site, you can also opt for HTML or text-based templates for the mail)
Send your first email
- Send an email inside the
sendMail()
method from ourContactForm
component and open MailHog to see the result
- Line 23: make a new variable
$template
of theContactMail
mailable class - Line 24: the variable
$to
contains the email address (first parameter) and the name (second parameter) of the person who receives the email - Line 25: add the recipient using the
to()
method (on theMail
facade) - Line 26: send the mail using the
send()
method with$template
as parameter
php
// send email
<?php
namespace App\Livewire;
use App\Mail\ContactMail;
use Illuminate\Mail\Mailables\Address;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Mail;
class ContactForm extends Component
{
...
// send email
public function sendEmail()
{
$this->validate();
// send email
$template = new ContactMail();
$to = new Address($this->email, $this->name);
Mail::to($to)
->send($template);
// show a success toast
$this->dispatch('swal:toast', [
'background' => 'success',
'html' => "<p class='font-bold mb-2'>Dear $this->name,</p>
<p>Thank you for your message.<br>We'll contact you as soon as possible.</p>",
]);
// reset all public properties
$this->reset();
}
...
}
// send email
<?php
namespace App\Livewire;
use App\Mail\ContactMail;
use Illuminate\Mail\Mailables\Address;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Mail;
class ContactForm extends Component
{
...
// send email
public function sendEmail()
{
$this->validate();
// send email
$template = new ContactMail();
$to = new Address($this->email, $this->name);
Mail::to($to)
->send($template);
// show a success toast
$this->dispatch('swal:toast', [
'background' => 'success',
'html' => "<p class='font-bold mb-2'>Dear $this->name,</p>
<p>Thank you for your message.<br>We'll contact you as soon as possible.</p>",
]);
// reset all public properties
$this->reset();
}
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
REMARKS
- You are not limited to the
to()
recipients. You can also add thecc()
andbcc()
recipients - All these methodes can handle multiple recipients with an array of recipients
- The second parameter (the name) of the
new Address()
method is optional - If you don't need the name of the recipient, you can omit the
new Address()
and just pass the email address as a string,
e.g.$to = 'john.doe@exmpale.com';
is the same as$to = new Address('john.doe@exmpale.com');
- Example:
php
// send email
$template = new ContactMail();
$to_1 = new Address($this->email, $this->name); // email + name
$to_2 = new Address('user2@example.com'); // email only
$to_3 = 'user3@example.com'; // email only (same as above)
Mail::to([$to_1, $to_2, $to_3])
->cc(['user3@example.com', 'user4@example.com'])
->bcc(['user5@example.com', 'user6@example.com'])
->send($template);
// send email
$template = new ContactMail();
$to_1 = new Address($this->email, $this->name); // email + name
$to_2 = new Address('user2@example.com'); // email only
$to_3 = 'user3@example.com'; // email only (same as above)
Mail::to([$to_1, $to_2, $to_3])
->cc(['user3@example.com', 'user4@example.com'])
->bcc(['user5@example.com', 'user6@example.com'])
->send($template);
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- All other mail settings (
subject
,body
,from
,replayTo
, ...) can be configured in theContactMail
class
Pass data to the mailable and update the mail template
- Add the data you want to use in the email as parameters to
new ContactMail()
to inject it into the constructor of mailable class and make it available in the mail template- ContactForm component: add an array with the data you want to inject into the constructor of the mailable (and use in the mail template)
- ContactMail mailable: add a public property (e.g.
$data
) and assign the injected data via te constructor to it - Update the Contact view and use the data from the mailable in the mail template
- fromName and fromEmail will be used to override the default from address in the mailable class
- subject will be used to override the default subject in the mailable class
- name, email and message will be used in the body of the email
php
public function sendEmail()
{
$this->validate();
// send email
$template = new ContactMail([
'fromName' => 'The Vinyl Shop - Info',
'fromEmail' => 'info@thevinylshop.com',
'subject' => 'The Vinyl Shop - Contact Form',
'name' => $this->name,
'email' => $this->email,
'message' => $this->message,
]);
$to = new Address($this->email, $this->name);
Mail::to($to)
->send($template);
// show a success toast
$this->dispatch('swal:toast', [
'background' => 'success',
'html' => "<p class='font-bold mb-2'>Dear $this->name,</p>
<p>Thank you for your message.<br>We'll contact you as soon as possible.</p>",
]);
// reset all public properties
$this->reset();
}
public function sendEmail()
{
$this->validate();
// send email
$template = new ContactMail([
'fromName' => 'The Vinyl Shop - Info',
'fromEmail' => 'info@thevinylshop.com',
'subject' => 'The Vinyl Shop - Contact Form',
'name' => $this->name,
'email' => $this->email,
'message' => $this->message,
]);
$to = new Address($this->email, $this->name);
Mail::to($to)
->send($template);
// show a success toast
$this->dispatch('swal:toast', [
'background' => 'success',
'html' => "<p class='font-bold mb-2'>Dear $this->name,</p>
<p>Thank you for your message.<br>We'll contact you as soon as possible.</p>",
]);
// reset all public properties
$this->reset();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Extra features
- Check Laravel's mail documentation to discover extra mail features, e.g:
- add attachments
- publish and change the default mailable template (
x-mail::message
) - use an HTML-based or plain text mail template
- Publish the default mail component with the command
php artisan vendor:publish --tag=laravel-mail
so you can change the layout of the mail template - ...
Show error bag (optional)
- Instead of showing each error message individually, we can show all errors in one single error block
- To reuse the error block on different pages, we can best make a component for it
- Create a new errorbag component inside the component's folder: resources/views/components/tmk/errorbag.blade.php
- The component don't need extra properties or a slot because all the logic is inside the component itself
- Add some extra code to the component:
- Now we can re-use this component on every page where we want bundle the errors into a single error block
php
@if ($errors->any())
<x-tmk.alert type="danger">
<x-tmk.list>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</x-tmk.list>
</x-tmk.alert>
@endif
@if ($errors->any())
<x-tmk.alert type="danger">
<x-tmk.list>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</x-tmk.list>
</x-tmk.alert>
@endif
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
EXERCISE: Add a contact field to the form
- Add a dropdown list to the form in order to choose a specific contact
- The list contains four options: Select a contact (
value=""
), Info, Billing and Support - This is a required field, so add it to your validation
- Depending on the choice of the user, the from (and carbon copy) address should be adjusted:
- info@thevinylshop.com with the name The Vinyl Shop - Info
- billing@thevinylshop.com with the name The Vinyl Shop - Billing
- or support@thevinylshop.com with the name The Vinyl Shop - Support