Here I am going to walk through an example of how to use macros and additional views to get rid of logic in our Laravel views.
Let’s pretend we have a form to create a user and one of the fields we can edit is first_name. So we might expect some HTML like below
<div class="control-group">
<label class="control-label" for="inputFirstName">First Name</label>
<div class="controls">
<input type="text" id="inputFirstName" placeholder="First name" value="<?= $user->first_name ?>">
<?php if ($errors->has('first_name'): ?>
<div class="errors">
<?= $errors->first('first_name') ?>
</div>
<?php endif ?>
</div>
</div>
I can simplify the conditional with a macro called show_message_when.
<div class="control-group">
<label class="control-label" for="inputFirstName">First Name</label>
<div class="controls">
<input type="text" id="inputFirstName" placeholder="First name" value="<?= $user->first_name ?>">
<?= HTML::show_message_when('first_name', $errors) ?>
</div>
</div>
And voila! No more logic in our views, it’s all evaluating variables now and all the logic has been extracted away. This makes testing a breeze, as we don’t have to worry about conditional structure in our view - assuming the variables are in place then the system just works.
This is a simple view in the first place but we can always extract out conditional logic from a view. That really only leaves foreach/loop logic, but that doesn’t bother me as much as conditionals so I usually leave those in place if they are simple. You could though create a macro to help with loops if you really wanted to do so. Let’s see another before and after example…
** Before **
<div class="container">
<?php if (!$users): ?>
<div class="empty row">No users found</div>
<?php endif ?>
<table class="table">
<th>
<td>Id</td>
<td>First Name</td>
<td>Last Name</td>
<td>Score</td>
</th>
<?php foreach ($users as $user): ?>
<tr>
<td class="id"><?= $user->id ?></td>
<td class="first_name"><?= $user->first_name ?></td>
<td class="last_name"><?= $user->last_name ?></td>
<td class="score"><?= $user->rank * $user->proficiency *
($user->updated_at->diffInDays() - $user->created_at->diffInDays())
* 1 / Globals::average_rank() </td>
</tr>
<?php endforeach ?>
</table>
<div class="my pagination row">
<?= $users->links() ?>
</div>
</div>
After
<div class="container">
<?= HTML::not_found($users, 'No users found!') ?>
<table class="table">
<th>
<td>Id</td>
<td>First Name</td>
<td>Last Name</td>
<td>Score</td>
</th>
<?= HTML::foreach('user', 'user.itemview', $users) ?>
</table>
<div class="my pagination row">
<?= $users->links() ?>
</div>
</div>
And in app/views/user/itemview.php
you might have something like this
<tr>
<td class="id"><?= $user->id ?></td>
<td class="first_name"><?= $user->first_name ?></td>
<td class="last_name"><?= $user->last_name ?></td>
<td class="score"><?= $user->score ?> </td>
</tr>
So you’re probably wondering what these macros look like? Well, I have made a macros.php gist to share with you guys. Now send me a beer. ^_^
/**
* HTML::show_message_when('first_name', $errors)
*/
HTML::macro('show_message_when', function show_message_when($name, $errors, $attributes = array())
{
$attributes_string = "";
$content_string = "";
$attributes['class'] = isset($attributes['class']) ?: "";
$attributes['class'] .= " $name";
if ($errors->has($name))
{
$attributes['class'] .= " alert";
$attributes['class'] .= " alert-danger";
$content_string = $errors->first($name);
}
foreach ($attributes as $key => $value) {
$attributes_string = " $key = \"" . $value . "\"";
}
return "<div $attributes_string>$content_string</div>";
});
/**
* HTML::foreach('user', 'user.itemview', $users)
*/
HTML::macro('foreach', function($name, $view, $items)
{
var html = "";
foreach ($items as $item)
{
html .= View::make($view, [$name => $item]);
}
return html;
});
/**
* HTML::not_found($users, 'No users found!')
*/
HTML::macro('not_found', function($items, $message = 'None found!')
{
return '<div class="empty row">' . $message . '</div>';
});
So that about wraps it up. I use macros all the time in my views. You don’t always have to use HTML::macro
either. In fact, one macro I use often is just a normal function which I call like active(...)
or HTML::active(...)
and it allows me to set a class to active when we are visiting the right place.
<li class="<?= active("foobar.index") ?>">
<a href="<?= route("foobar.index") ?>">
<i class="fa fa-eye"></i>
<span class="hidden-sm">My Watchlists</span>
</a>
</li>
If you didn’t get anything else out of this besides the fact that you can use macros to keep your views clean then I think I can pat myself on the back. Now go, change the world my friend! My next blog will be talking about using presenters to keep views clean in the hexagonal pattern.