Laravel is a full-stack server-side rendered framework. And if you want, you can use the Laravel stack to create your whole application from database migrations to the HTML being rendered in the browser.
In my journey to learn Laravel, I have started writing some test apps using the blade syntax built into Laravel. This is in many ways like using a preprocessor for your HTML.
Doing this has allowed me to connect with the HTML roots in a way that was never appealing to do in the React apps I write in my day to day work.
It has even allowed me to see the beauty in server-side rendered pages. And the beauty in trying to optimize them.
In this blog post, I want to try and tackle a basic but very tangible problem.
Imagine that we have a site, where we can view issues, and for these issues create tasks for them.
When browsing a specific issue, I want to allow the user to create a new task, either for that specific issue, or a different one. In the form, I need the server to provide the possible issues to link the task to, and also in which states the task can be in.
Problems?
I want the user to be able to see the page, without the form
Alrighty then, let's crack on.
First of all, I will need a blade partial view and controller action that can return my form.
That looks like this:
// _create_task_partial.blade.php
<form action="/tasks" method="post">
<x-text-input label="Task title" id="title_id" name="title" type="text">
<x-slot name="error">
@error('status', 'issue_id', 'title')
{{$message}}
@enderror
</x-slot>
</x-text-input>
@csrf
<x-select name="status" id="status_id" label="Status">
@foreach($statuses as $status)
<option value="{{$status}}">{{$status}}</option>
@endforeach
</x-select>
<x-select name="issue_id" id="issue" label="Issue">
@foreach($issues as $issue)
@if($issue->selected)
<option selected="selected" value="{{$issue->id}}">{{$issue->title}}</option>
@else
<option value="{{$issue->id}}">{{$issue->title}}</option>
@endif
@endforeach
</x-select>
<button type="submit">Submit</button>
</form>
Most of this reads pretty easy, but notice that this view is expecting a variable called $issue
and $status
, so which ever controller action creates this view, will need to provide these.
Let us see that controller action then!
public function create()
{
return view('tasks.create', [
'statuses' => [
'new', 'started', 'done', 'in_review'
],
'issues' => Issue::all()->map(function ($issue) {
return (object)[
'id' => $issue->id,
'title' => $issue->title,
'selected' => $issue->id === (int)request('issue')
];
})
]);
}
Boy, this is some terse PHP. Let's go through it.
To fetch the issues already present in the database, I am using an Eloquent data model called Issue, and through that I am telling it I want all of them. And of all of those, I want to map them to consist of an id, title and whether or not it should be preselected. For the last check, I am using a helper function called request('issue')
which will check the request for either a query param or body with the key of issue. Since this is a get request, it will be the previous case.
And that is most of the work done.
Show us in action now, please!
Let's strip out most of my issue templating, and focus on just fetching the partial.
// issues/show.blade.php
// ... showing all issues
<div id="create-task-form-target">
<!-- -->
</div>
<script>
(function fetchTaskCreationForm(){
fetch('/tasks').then(response=> response.text()).then(html=>{
document.getElementById('create-task-form-target').innerHTML = html
})
})()
</script>
And boom, in comes the form, but only after the initial page load.
This might be an expensive calculation, which took some seconds to finish, no need for our user to wait for that.
And actually, we can take this a step further. How about we cache it?
This way, that IF this page has already been to the page with issues and fetched the form, we cache it for 10 seconds, and if you refresh the page during those 10 seconds, they form will be returned on the initial payload, and not loaded in later. Let's see how:
First of all, our create method, which returns the HTML for the form, should cache the entire HTML payload.
public function create()
{
return Cache::remember('tasks.create', Carbon::parse('10 seconds'), function () {
return view('tasks.create', [
'statuses' => [
'new', 'started', 'done', 'in_review'
],
'issues' => Issue::all()->map(function ($issue) {
return (object)[
'id' => $issue->id,
'title' => $issue->title,
'selected' => $issue->id === (int)request('issue')
];
})
])->render();
});
}
Luckily, laravel makes that easy with the Cache interface.
Next, we should have our issues/show.blade.php
use this cache if present, otherwise render the data needed for the partial.
public function show(Issue $issue)
{
return view('issues.show', [
'issue' =>tap($issue)->load('tasks'),
'form' => Cache::get('tasks.create')
]);
}
Notice that I am adding a key to the view called form, and I am assigning it to the same name as I saved the cache in previously. And it really is that easy.
@if($form)
{!! $form !!}
@else
<div id="create-task-form-target">
<!-- -->
</div>
<script>
(function fetchTaskCreationForm() {
fetch('/tasks/create').then(response => response.text()).then(html => {
document.getElementById('create-task-form-target').innerHTML = html
})
})()
</script>
@endif
Normally, you would not embed a script tag inside your rendered HTML, but for the sake of this blog, it will do just fine.
A nifty side effect of this, is that if you have any test validating that your view gets the correct data, all of these changes would be reflected in your tests, if using React/Vue as the frontend engine, these things would have to be tested separately.
For instance if I misspelled issue to isue in the template
That's all for now, I hope you enjoyed reading this, and maybe this gave you some inspiration to same fun things to do with Laravel
Top comments (0)