DEV Community

loading...
Cover image for  Creating Your First Blog With TALL - Part Five

Creating Your First Blog With TALL - Part Five

Alhassan Kamil
I am a software developer with experience in mobile and web development. Currently focused on Flutter, PHP/Laravel, Vue.js and API design and development.
Originally published at blog.bandughana.com ・9 min read

Hello and welcome to this episode of the tutorial series. In the previous episode you learnt how to create factories and seed our database with sample data. This episode will begin with building the UI part of our blog using Laravel Livewire components.

At the end of the tutorial, you should be able to:

  • Describe what a Livewire component is.
  • Create and modify Livewire components to suit your needs.
  • Use Tailwind CSS utility classes to practically layout elements in your Blade view files.

As usual, fasten up your seat belt and let's dive in. :)

Note: There is a starter project up to this episode which can be accessed on Github here

Introduction

Part Two Recap

Remember in part two of this tutorial series we configured the public facing routes to return the default Laravel Jetstream welcome page. If you also remember, there were exactly two routes for the public facing pages of our blog - the home and the category pages. The homepage will be a listing of our blog posts while the category page will list all blog posts of a particular category. You can always read part two of this tutorial series here if you missed that part.

You're going to work on those two pages and replace the welcome page with a more interesting listing of your blog posts for this episode, albeit with introduction to some Tailwind CSS utilities. OK. Read on.

Introduction to Livewire Components

Livewire components are reusable pieces of code that you can define once and use in different parts of your application. They are just like Laravel components but comes with the power of both Laravel and Livewire. By default, a Livewire component is created with a corresponding view file. Livewire component classes are also placed in the app/Http/Livewire directory while their corresponding views are placed in the resources/views/livewire directory.

Creating Livewire Components

Livewire components are created by running the make:livewire or livewire:make Artisan commands in your terminal:

php artisan make:livewire PostItem
// or ...
php artisan livewire:make PostItem
Enter fullscreen mode Exit fullscreen mode

This creates the most basic Livewire component, which extends the base Livewire Component class and contains a render method - used for rendering views and inline text:

<?php // tall-blog/app/Http/Livewire/PostItem.php 
namespace App\\Http\\Livewire;

use Livewire\\Component;

class PostItem extends Component {
    public function render() {
        return view('livewire.post-item');
    }
}
Enter fullscreen mode Exit fullscreen mode

and the corresponding view:

{{\-- tall-blog/resources/views/livewire/post-item.blade.php --}}
<div>
    {{\-- If you look to others for fulfillment, you will never truly be fulfilled. --}}
</div>
Enter fullscreen mode Exit fullscreen mode

Adding the --inline option tells Livewire that your component is an inline component and won't be returning any view in its render method. So this:

php artisan make:livewire PostItem --inline
Enter fullscreen mode Exit fullscreen mode

yields something like this:

...
public function render() {
    return <<<'blade'
            <div></div>
        blade;
}
...
Enter fullscreen mode Exit fullscreen mode

It is important to understand that public properties in a Livewire component are readily available in the component's view file, so you don't need to pass it through the view method like you're used to do if you've worked with pure Laravel controllers:

// This:
class PostItem extends Component {
    public $post;
    public function render() {
        return view('livewire.post-item');
    }
}

// is the same as this:
class PostItem extends Component {
    public $post;
    public function render() {
        return view('livewire.post-item', ['post' => $post]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Rendering Livewire Components

Livewire components are meant to be reusable. As a result, you can use them anywhere you would a Laravel component. Rendering a component can be done by either using the <livewire:component-name /> tag syntax or by using the @livewire('component-name') blade directive syntax:

<div>
    <livewire:post-item />
</div>
{{-- Or --}}
<div>
    @livewire('post-item')
</div>
Enter fullscreen mode Exit fullscreen mode

Passing Parameters to Components

You can pass parameters to a component by specifying those parameters like so:

<livewire:post-item :post="$post" />
{{-- or --}}
@livewire('post-item', ['post' => $post])
Enter fullscreen mode Exit fullscreen mode

Now the $post variable will be available in the PostItem component and view.

Accessing Route Parameters

In a situation whereby you need to access route parameters like you would in a traditional Laravel controller, Livewire allows you to do that in the mount method:

class MyComponent extends Component {
    public $userId;

    public function mount($userId) {
        $this->userId = $userId;
    }

    public function render() {
        return view('livewire.my-component');
    }
}
Enter fullscreen mode Exit fullscreen mode

This feature makes it so powerful for Livewire components to mimic the behavior of Laravel controllers and also makes it easy to make full page components.

Creating Our Blog's Components

Now that you have a solid understanding of Livewire components, let's move on to work on our blog. If you haven't yet done so from above, create your first Livewire component for the project by running this command in your terminal:

php artisan make:livewire PostItem
Enter fullscreen mode Exit fullscreen mode

This component represents one post item from a list in the home and category pages. Open the component class in app/Http/Livewire/PostItem.php and add a public $post property to the beginning. The component class should look similar to this:

<?php // tall-blog/app/Http/Livewire/PostItem.php
namespace App\Http\Livewire;

use Livewire\Component;

class PostItem extends Component {
    public $post;

    public function render() {
        return view('livewire.post-item');
    }
}
Enter fullscreen mode Exit fullscreen mode

As pointed out above, the $post property will now be readily available in the view file for use since it is declared public. Open the corresponding blade view file in resources/views/livewire/post-item.blade.php. Make sure it contains the following:

{{-- tall-blog/resources/views/livewire/post-item.blade.php --}}
<article class="flex flex-col mb-2 rounded-md shadow-md md:mb-0">
    <a href="{{ route('post-detail', ['slug' => $post->slug]) }}">
        <img src='{{ asset("storage/posts/$post->featured_image") }}' 
            alt="{{ $post->title }}" class="w-full h-56 rounded-t-md">
    </a>
    <div class="p-3">
        <h3 class="text-lg font-semibold text-gray-900">
            <a href="{{ route('post-detail', ['slug' => $post->slug]) }}">
                {{ $post->title }}
            </a>
        </h3>
        <p class="text-gray-800">
            <a href="{{ route('post-detail', ['slug' => $post->slug]) }}">
                {{ $post->excerpt }}
            </a>
        </p>
        <div class="flex flex-row justify-between mt-2">
            <a href="{{ route('category', ['category' => $post->category]) }}" 
                 class="px-2 text-sm text-indigo-900 bg-indigo-100 rounded">
                {{ $post->category }}
            </a>
            <small>
                {{ $post->published_date }}
            </small>
        </div>
    </div>
</article>
Enter fullscreen mode Exit fullscreen mode

First of all, you can see that we're making use of the $post variable in this file, which contains a blog post entry. We're utilizing Tailwind CSS class utilities to style the root <article> element. In this case, we have laid its contents in a Flexbox with a flex-direction of col - meaning we want to lay the contents from the top to bottom. mb-2 is a utility class for 8-pixels margin-bottom value with rounded-md and shadow-md representing medium rounded and medium shadowed shapes respectively.

Next is the anchor element (a tag). We're referencing a named Route to route to the post detail page(using the route helper function) which uses the post's slug to construct the link. Remember we didn't create the post detail route so you would need to add it just below the homepage route:

Route::get('/', function () {
     return view('welcome');
})->name('home');

// Add below route
Route::get('{slug}', function ($slug) {
    return view('welcome');
})->name('post-detail');
Enter fullscreen mode Exit fullscreen mode

Next in the tree is the image, whose width we have made full (100%) with w-full and whose height we have decided will be h-56 (equivalent to 224px) with its top rounded. The post image's src attribute points to an image in the posts folder which is in the storage symlink we created earlier. asset is also one of the helper functions that allows you to display assets(css, images, js) files in your blade views. The file itself is uploaded in the posts folder while its name is saved into the database.

The next block contains the title, excerpt, category and published date all wrapped in a nicely padded(with p-3) div element. Both the title and excerpt use a different shade of gray text color from Tailwind CSS's color utilities. The last thing worth noting is the fact that the category and published date of our post are also wrapped in a Flexbox div container which runs from left to right (row) with a justify-between utility which ensures that these two elements are placed at the extreme ends of both left and right of the containing div element.


The post item component is now done. Let's create another component that will list all the posts on the homepage. This component will fetch all the posts from the database and display each in the post item component we created above. Enter the command to create the show-posts component:

php artisan make:livewire ShowPosts
Enter fullscreen mode Exit fullscreen mode

Open the created component at app/Http/Livewire/ShowPosts.php and make sure its contents is equal to this:

<?php // tall-blog/app/Http/Livewire/ShowPosts.php
namespace App\Http\Livewire;

use App\Models\Post;
use Livewire\Component;

class ShowPosts extends Component {
    public $posts;

    public function mount() {
        $this->posts = Post::where('is_published', true)->get();
    }
    public function render() {
        return view('livewire.show-posts')->layout("layouts/guest");
    }
}
Enter fullscreen mode Exit fullscreen mode

The $posts property holds all our posts from the database, which we fetch when this component is mounted (this is why the query is put in the mount method). We're only interested in posts that are published. We have also specified the layout to use (i.e guest layout file). By default, Livewire uses the resources/views/layouts/app.blade.php layout file. However, because Jetstream adds authentication and other checks to that layout, using the app layout file without those checks in place will lead to errors, that's the reason we're using the resources/views/layouts/guest.blade.php layout file.

Now open the corresponding resources/views/livewire/show-posts.bladed.php blade view and enter the following in it:

<div class="gap-4 m-2 md:grid md:grid-cols-2 lg:grid-cols-4">
    @foreach ($posts as $post)
        @livewire('post-item', ['post' => $post], key($post->id))
    @endforeach
</div>
Enter fullscreen mode Exit fullscreen mode

This one is simpler than you might have guessed. We're looping through the $posts collection and rendering each using the PostItem component we created earlier, passing the $post to its post parameter and giving its key the post id. The key is used to uniquely differentiate the current post item from the others and is very necessary when you, for example, want to toggle a post's visibility.

On the Tailwind part, we're displaying the posts in

  1. a block container on small screens,
  2. a grid container with two columns in medium screen devices and
  3. a grid container with four columns in large+ screen devices

All these with a 16-px gap between each item. Of course, and an 8-pixel margin.

Now change the homepage route to the following:

...
Route::get('/', App\Http\Livewire\ShowPosts::class)->name('home');
...
Enter fullscreen mode Exit fullscreen mode

Hook up your terminal, run php artisan serve and open your browser to http://127.0.0.1:8000 and you should see posts in the homepage:

Blog Homepage

Now create the last Livewire component, which will display all posts from a particular category, by running this Artisan command:

php artisan make:livewire CategoryPosts --inline
Enter fullscreen mode Exit fullscreen mode

We have made the CategoryPosts component inline because it is going to reuse the show-posts component view file. Since there is nothing different between the two except filtering from the database, it is simpler using this approach.

Modify the CategoryPosts component to match the following:

<?php 
// tall-blog/app/Http/Livewire/CategoryPosts.php

namespace App\Http\Livewire;

use App\Models\Post;
use Livewire\Component;

class CategoryPosts extends Component {
    public $posts;

    public function mount($category) {
        $this->posts = Post::where('category', $category)
             ->where('is_published', true)
             ->get();
    }
    public function render() {
        return view('livewire.show-posts')
                 ->layout("layouts/guest");
    }
}
Enter fullscreen mode Exit fullscreen mode

This is almost the same as the ShowPosts component except that we've changed the query to fetch based on the given category query string passed to the mount method. Change the category route to the following:

...
Route::get('categories/{category}', CategoryPosts::class)->name('category');
...
Enter fullscreen mode Exit fullscreen mode

Start the built-in PHP server if it's not running:

php artisan serve
Enter fullscreen mode Exit fullscreen mode

Now go back to your homepage and click on any category. It should send you to the category page displaying posts in the same category:

Category Page Showing the est Category

This brings us to the end of this episode. I hope you really enjoyed the tutorial and I'd love to see you reading the next episode. We post updates on both our Twitter and Facebook pages. If you don't mind, you can follow any of our social media pages to get notified when a new post is published.

Twitter: https://twitter.com/bandughana

Facebook: https://www.facebook.com/bandughana

Thank you and see you for the next post.

Discussion (2)

Collapse
johnjrhunglin profile image
johnjrhunglin

Dear Alhassan:

The tutorial is wonderful. However, I got this error when follow the part5 instructions.
Missing required parameter for [Route: post-detail] [URI: {slug}] [Missing parameter: slug]

I appreciate you can give me hits. Thanks.

Collapse
nayi10 profile image
Alhassan Kamil Author

@johnjehunglin sorry for the late reply. I'm very busy recently and actually never thought I'd receive notifications again.

But coming to your issue, did you pass on the slug for the detail route as required below:

Route::get('{slug}', function ($slug) {
return view('welcome');
})->name('post-detail');

There may be one of two problems: either the slug column is missing from your posts table or the slug wasn't passed. If you read the tutorial series to the end, you'd see that I later modified the posts table to accommodate the slug column.

Forem Open with the Forem app