😁Blade components for your layout full (ok)
https://beyondco.de/blog/blade-components-for-your-layout
Full Doc: https://laravel.com/docs/9.x/blade#layouts-using-components
Ví dụ đã triển khai dùng để làm mẫu
Source code tham khảo
V1. List

C:\xampp8\htdocs\testauth\resources\views\banner\index.blade.php
@extends('layouts.main')
@section('class','banner')
@section('main')
<div class="tab-content rounded-bottom bg-white">
<div class="tab-pane p-3 active preview text-center" role="tabpanel">
<table class="table table-bordered">
<x-tables.banner.thead>
<x-tables.banner.th />
</x-tables.banner.thead>
<x-tables.banner.tbody>
<x-tables.banner.tr :datas=$banners :type=$type />
</x-tables.banner.tbody>
</table>
{!! $banners->links() !!}
</div>
</div>
@endsection
C:\xampp8\htdocs\testauth\resources\views\components\tables\banner\thead.blade.php
<thead class="bg-dark text-white">
<tr>
{{ $slot }}
</tr>
</thead>
C:\xampp8\htdocs\testauth\resources\views\components\tables\banner\th.blade.php
@props([
"width"=> "150px",
"labels" =>
[
"ID"=> [
"breakwidth" => false,
"breakcolspan" => false,
"colspan"=> 2
],
"表示順"=> [
"breakwidth" => false,
"breakcolspan" => false,
"colspan"=> 2
],
"登録内容"=> [
"breakwidth" => true,
"breakcolspan" => true,
"colspan"=> 2
]
]
])
@foreach ($labels as $label => $value)
<th scope="col" @if(!$value['breakwidth'])width={{$width}}@endif @if($value['breakcolspan']) colspan={{$value['colspan']}}@endif>{{ $label }}</th>
@endforeach
C:\xampp8\htdocs\testauth\resources\views\components\tables\banner\tbody.blade.php
<tbody>
{{ $slot }}
</tbody>
<style>
.banner__box h3 {
margin-bottom: 20px;
}
.banner__tag {
margin-bottom: 60px;
}
.banner__time span {
font-weight: 700;
}
</style>
C:\xampp8\htdocs\testauth\resources\views\components\tables\banner\tr.blade.php
@foreach ($datas as $data)
<tr>
<td>{{ $data['id'] }}</td>
<td>{{ $data['sort_no'] }}</td>
<td width="300px">
<img class="img-fluid" src="{{ $data['image_url'] }}" alt="Image">
</td>
<td class="text-start banner__box">
<p>{{ $data['publish_start'] }} ~ {{ $data['publish_end'] }}</p>
<h3><a href="{{ route('banner.type.edit.id',[$type,$data['id']]) }}">{{ $data['title'] }}</a></h3>
<x-tables.tags.group>
<x-tables.tags.tag />
</x-tables.tags.group>
<x-tables.times.group>
<x-tables.times.create-update />
</x-tables.times.group>
</td>
</tr>
@endforeach
C:\xampp8\htdocs\testauth\resources\views\components\tables\tags\group.blade.php
<div {{ $attributes->merge(['class' => 'd-flex flex-wrap banner__tag']) }}>
{{ $slot }}
</div>
C:\xampp8\htdocs\testauth\resources\views\components\tables\tags\tag.blade.php
@props([
'tags' => ["レギュラー","レギュラー"]
])
@foreach ($tags as $tag)
<span {{ $attributes->merge(['class' => 'rounded-pill border px-2 me-2 mb-2']) }}>{{ $tag }}</span>
@endforeach
C:\xampp8\htdocs\testauth\resources\views\components\tables\times\group.blade.php
<div {{ $attributes->merge(['class' => 'd-flex justify-content-between banner__time']) }}>
{{ $slot }}
</div>
C:\xampp8\htdocs\testauth\resources\views\components\tables\times\create-update.blade.php
@props([
'datas' => ["登録日時"=>"2022-12-12 07:18:06","更新日時"=>"2022-12-12 07:18:06"]
])
@foreach ($datas as $label => $data)
<small>
<span {{ $attributes->merge(['class' => 'start me-1']) }}">{{ $label }}</span>
{{ $data }}
</small>
@endforeach
V2. Edit

C:\xampp8\htdocs\testauth\resources\views\components\inputs\group.blade.php
@props([
'label',
'for',
'class' => 'btn-dark'
])
<div class="input-group">
<label for="{{ $for }}" {{ $attributes->merge(['class' => "btn $class px-5 rounded-2"]) }}>
{{ $label }}
</label>
{{ $slot }}
</div>
C:\xampp8\htdocs\testauth\resources\views\components\inputs\text.blade.php
@props([
'for',
'type' => 'text',
'value' => '',
'width' => '250px'
])
<input id="{{ $for }}" name="{{ $for }}" style="width:{{$width}};" type="{{ $type }}" value="{{ $value }}" {{ $attributes->merge(['class' => 'form-control']) }} />
C:\xampp8\htdocs\testauth\resources\views\components\selects\group.blade.php
@props([
'for' => 'target',
'width' => '250px'
])
<select style="width:{{$width}};" {{ $attributes->merge(['class' => 'form-select']) }} aria-label="Default select example" id="{{ $for }}" name="{{ $for }}">
{{ $slot }}
</select>
C:\xampp8\htdocs\testauth\resources\views\components\checkboxs\group.blade.php
<div {{ $attributes->merge(['class' => 'text-start']) }}>
{{ $slot }}
</div>
C:\xampp8\htdocs\testauth\resources\views\components\checkboxs\checkbox.blade.php
@props([
"name" => 'checks',
"checkboxs" => ["ゴールド"=>1,"レギュラー"=>2,"ジュニア" => 3, "カジュアルレギュラー"=>4,"無料"=>5,"アカデミー"=>6],
])
<div {{ $attributes->merge(['class' => 'form-check d-inline-flex']) }}>
@foreach ($checkboxs as $label => $value)
<input class="form-check-input" type="checkbox" name="{{ $name }}" id="{{ $name . $value }}" value="{{ $value }}">
<label {{ $attributes->merge(['class' => 'form-check-label me-5 ms-2']) }} for="{{ $name . $value }}">ゴールド</label>
@endforeach
</div>
C:\xampp8\htdocs\testauth\resources\views\components\buttons\group.blade.php
<div {{ $attributes->merge(['class' => 'form-check form-check-block text-center']) }}>
{{ $slot }}
</div>
C:\xampp8\htdocs\testauth\resources\views\components\buttons\button.blade.php
@props([
"type" => "submit",
"width" => "120px",
"label" => "登録"
])
<button type="{{ $type }}" style="width: {{ $width }};" {{ $attributes->merge(['class' => 'btn']) }}>{{ $label }}</button>
1. Blade Components for your Layout

php artisan make:component Layout
C:\xampp8\htdocs\testauth\app\View\Components\Layout.php
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class Layout extends Component
{
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.layout');
}
}
C:\xampp8\htdocs\testauth\resources\views\components\layout.blade.php
<!-- /views/components/layout.blade.php -->
<!DOCTYPE html>
<html>
<head>
@yield('scripts')
</head>
<body>
@yield('content')
</body>
</html>
C:\xampp8\htdocs\testauth\resources\views\welcome.blade.php
<x-layout>
@section('scripts')
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
@endsection
@section('content')
<div>My Page content is here</div>
@endsection
</x-layout>
2. Named Slots

C:\xampp8\htdocs\testauth\app\View\Components\Layout.php
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class Layout extends Component
{
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.layout');
}
}
C:\xampp8\htdocs\testauth\resources\views\components\layout.blade.php
<!-- /views/components/layout.blade.php -->
<!DOCTYPE html>
<html>
<head>
{{ $scripts }}
</head>
<body>
{{ $slot }}
</body>
</html>
C:\xampp8\htdocs\testauth\resources\views\welcome.blade.php
<x-layout>
<x-slot name="scripts">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
</x-slot>
<div>My Page content is here</div>
</x-layout>
3. Renderless Blade components

C:\xampp8\htdocs\testauth\app\View\Components\Section.php
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class Section extends Component
{
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return function ($componentData) {
return <<<BLADE
@section("{$componentData['attributes']->get('name')}")
{$componentData['slot']}
@endsection
BLADE;
};
}
}
C:\xampp8\htdocs\testauth\app\View\Components\Layout.php
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class Layout extends Component
{
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.layout');
}
}
C:\xampp8\htdocs\testauth\resources\views\welcome.blade.php
<x-layout>
<x-section name="scripts">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
</x-section>
<div>My Page content is here</div>
</x-layout>
4. Props

C:\xampp8\htdocs\testauth\resources\views\welcome.blade.php
<x-layout>
<x-section name="scripts">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
</x-section>
<div>My Page content is here</div>
@props(['disabled' => true])
<input {{ $disabled ? 'disabled' : '' }} {!! $attributes->merge(['class' => 'rounded-md shadow-sm border-gray-300'])
!!}>
</x-layout>
5. Quick Introduction to Anonymous Blade Components In Laravel 7
5.1. Giao diện thường

C:\xampp8\htdocs\testauth\routes\web.php
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('profile');
});
Route::get('profile', function () {
return view('profile');
});
C:\xampp8\htdocs\testauth\resources\views\layouts\main.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel 7 Blade Components</title>
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class="h-screen w-screen flex items-center justify-center">
@yield('content')
</body>
</html>
C:\xampp8\htdocs\testauth\resources\views\profile.blade.php
@extends('layouts.main')
@section('content')
<form style=" width: 50%; ">
<div>
<div>
<h3 class="text-lg leading-6 font-medium text-gray-900">
Personal Information
</h3>
</div>
<div class="mt-6 sm:mt-5">
<div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
<label for="first_name" class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
First name
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<div class="rounded-md shadow-sm">
<input id="first_name"
class="bg-white border border-gray-200 py-2 px-3 rounded-lg w-full block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5" />
</div>
</div>
</div>
<div class="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
<label for="last_name" class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
Last name
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<div class="rounded-md shadow-sm">
<input id="las_name"
class="bg-white border border-gray-200 py-2 px-3 rounded-lg w-full block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5" />
</div>
</div>
</div>
<div class="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
<label for="email" class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
Email address
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<div class="mt-1 sm:mt-0 sm:col-span-2">
<div class="rounded-md shadow-sm">
<input id="email" type="email"
class="bg-white border border-gray-200 py-2 px-3 rounded-lg w-full block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5" />
</div>
</div>
</div>
</div>
<div class="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
<label for="street_address" class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
Street address
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<div class="rounded-md shadow-sm">
<input id="street_address"
class="bg-white border border-gray-200 py-2 px-3 rounded-lg w-full block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5" />
</div>
</div>
</div>
<div class="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
<label for="city" class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
City
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<div class="rounded-md shadow-sm">
<input id="city"
class="bg-white border border-gray-200 py-2 px-3 rounded-lg w-full block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5" />
</div>
</div>
</div>
<div class="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
<label for="state" class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
State / Province
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<div class="rounded-md shadow-sm">
<input id="state"
class="bg-white border border-gray-200 py-2 px-3 rounded-lg w-full block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5" />
</div>
</div>
</div>
<div class="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
<label for="zip" class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
ZIP / Postal
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<div class="rounded-md shadow-sm">
<input id="zip"
class="bg-white border border-gray-200 py-2 px-3 rounded-lg w-full block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5" />
</div>
</div>
</div>
</div>
</div>
<div class="mt-8 border-t border-gray-200 pt-5">
<div class="flex justify-end">
<span class="ml-3 inline-flex rounded-md shadow-sm">
<button type="submit"
class="inline-flex justify-center py-2 px-4 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition duration-150 ease-in-out">
Save
</button>
</span>
</div>
</div>
</form>
@endsection
5.2. Giao diện dùng Balde Components
C:\xampp8\htdocs\testauth\routes\web.php
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('profile');
});
Route::get('profile', function () {
return view('profile');
});
C:\xampp8\htdocs\testauth\resources\views\profile.blade.php
@extends('layouts.main')
@section('content')
<form style=" width: 50%; ">
<div>
<div>
<h3 class="text-lg leading-6 font-medium text-gray-900">
Personal Information
</h3>
</div>
<div class="space-y-6 sm:mt-5">
<x-inputs.group label="First Name" for="first_name">
<x-inputs.text for="first_name" />
</x-inputs.group>
<x-inputs.group label="Last Name" for="last_name">
<x-inputs.text for="last_name" />
</x-inputs.group>
<x-inputs.group label="Email address" for="email">
<x-inputs.text for="email" type="email" />
</x-inputs.group>
<x-inputs.group label="Street address" for="street_address">
<x-inputs.text for="street_address" />
</x-inputs.group>
<x-inputs.group label="City" for="city">
<x-inputs.text for="city" />
</x-inputs.group>
<x-inputs.group label="State" for="state">
<x-inputs.text for="state" />
</x-inputs.group>
<x-inputs.group label="Zip" for="zip">
<x-inputs.text for="zip" />
</x-inputs.group>
</div>
</div>
<div class="mt-8 border-t border-gray-200 pt-5">
<div class="flex justify-end">
<span class="ml-3 inline-flex rounded-md shadow-sm">
<button type="submit"
class="inline-flex justify-center py-2 px-4 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition duration-150 ease-in-out">
Save
</button>
</span>
</div>
</div>
</form>
@endsection
C:\xampp8\htdocs\testauth\resources\views\components\inputs\group.blade.php
@props(['label', 'for'])
<div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
<label for="{{ $for }}" class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
{{ $label }}
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
{{ $slot }}
</div>
</div>
C:\xampp8\htdocs\testauth\resources\views\components\inputs\text.blade.php
@props([
'for'
])
<div class="rounded-md shadow-sm">
<input id="{{ $for }}" class="bg-white border border-gray-200 py-2 px-3 rounded-lg w-full block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5" />
</div>

5.3 Passing In Default Data

C:\xampp8\htdocs\testauth\routes\web.php
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
$data = [
'first_name' => 'Shane',
'last_name' => 'Rosenthal',
'email' => 'srosenthal82@gmail.com',
];
return view('profile', compact('data'));
});
Route::get('profile', function () {
return view('profile');
});
C:\xampp8\htdocs\testauth\resources\views\profile.blade.php
@extends('layouts.main')
@section('content')
<form style=" width: 50%; ">
<div>
<div>
<h3 class="text-lg leading-6 font-medium text-gray-900">
Personal Information
</h3>
</div>
<div class="space-y-6 sm:mt-5">
<x-inputs.group label="First Name" for="first_name">
<x-inputs.text for="first_name" :value="$data['first_name']" />
</x-inputs.group>
<x-inputs.group label="Last Name" for="last_name">
<x-inputs.text for="last_name" :value="$data['last_name']" />
</x-inputs.group>
<x-inputs.group label="Email address" for="email">
<x-inputs.text for="email" :value="$data['email']" />
</x-inputs.group>
<x-inputs.group label="Street address" for="street_address">
<x-inputs.text for="street_address" />
</x-inputs.group>
<x-inputs.group label="City" for="city">
<x-inputs.text for="city" />
</x-inputs.group>
<x-inputs.group label="State" for="state">
<x-inputs.text for="state" />
</x-inputs.group>
<x-inputs.group label="Zip" for="zip">
<x-inputs.text for="zip" />
</x-inputs.group>
</div>
</div>
<div class="mt-8 border-t border-gray-200 pt-5">
<div class="flex justify-end">
<span class="ml-3 inline-flex rounded-md shadow-sm">
<button type="submit"
class="inline-flex justify-center py-2 px-4 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition duration-150 ease-in-out">
Save
</button>
</span>
</div>
</div>
</form>
@endsection
C:\xampp8\htdocs\testauth\resources\views\components\inputs\group.blade.php
@props(['label', 'for'])
<div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
<label for="{{ $for }}" class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
{{ $label }}
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
{{ $slot }}
</div>
</div>
C:\xampp8\htdocs\testauth\resources\views\components\inputs\text.blade.php
@props([
'for',
'type' => 'text',
'value' => ''
])
<div class="rounded-md shadow-sm">
<input id="{{ $for }}" type="{{ $type }}" value="{{ $value }}" {{ $attributes->merge(['class' => 'bg-white border border-gray-200 py-2 px-3 rounded-lg w-full block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5']) }}
/>
</div>

C:\xampp8\htdocs\lva\app\View\Components\Alert.php
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class Alert extends Component
{
public $type;
public $message;
public function __construct($type, $message)
{
$this->type = $type;
$this->message = $message;
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.alert');
}
}
C:\xampp8\htdocs\lva\resources\views\welcome.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
</head>
<body class="antialiased">
@php
$message = "Messsss";
@endphp
<x-alert type="error" :message="$message" />
</body>
</html>
C:\xampp8\htdocs\lva\resources\views\components\alert.blade.php
<div class="alert alert-{{ $type }}">
{{ $message }}
</div>
6.1. Áp dụng

C:\xampp8\htdocs\testauth\resources\views\layouts\main.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
@include('inner.head')
@yield('style')
</head>
<body>
@include('layouts.sidebar')
<div class="wrapper d-flex flex-column min-vh-100 bg-light">
@include('layouts.header')
<div class="body flex-grow-1 px-3">
<div class="container-lg">
@yield('main')
</div>
</div>
@include('layouts.footer')
</div>
@include('inner.script')
@yield('script')
</body>
</html>
C:\xampp8\htdocs\testauth\resources\views\banner\index.blade.php
@extends('layouts.main')
@section('main')
<div class="tab-content rounded-bottom">
<div class="tab-pane p-3 active preview" role="tabpanel" id="preview-91">
<table class="table table-bordered text-center">
@php
$width = ["","15%",""];
$colspan = ["","",2];
$label = ["ID","表示順","登録内容"];
@endphp
<x-tables.th :count="3" :width=$width :colspan=$colspan :label=$label></x-tables.th>
<x-tables.td :data=$data></x-tables.td>
</table>
</div>
</div>
@endsection
C:\xampp8\htdocs\testauth\resources\views\components\tables\th.blade.php
<thead class="table-dark">
<tr>
@for ($i = 0; $i < $count; $i++)
<th scope="col" width="{{ $width[$i] }}" colspan="{{ $colspan[$i] }}">{{ $label[$i] }}</th>
@endfor
</tr>
</thead>
C:\xampp8\htdocs\testauth\resources\views\components\tables\td.blade.php
<tbody>
@foreach ($data as $dta)
<tr>
@foreach ($dta as $dt)
<td scope="row">{{ $dt }}</td>
@endforeach
</tr>
@endforeach
</tbody>
C:\xampp8\htdocs\testauth\app\Http\Controllers\BannerController.php
public function index()
{
$data = [
[
1,
"Mark 1",
"Otto 1",
"@mdo 1"
],
[
2,
"Mark 2",
"Otto 2",
"@mdo 2"
]
];
return view('banner.index')->with(compact('data'));
}
Blade Components for your Layout
In the latest blog post I showed you how Laravels Blade view components work and how you can make use of them. In this part of the series, I want to focus on how we can use these components throughout our application - starting with the very basic: your layout.
In a typical Laravel application, you might have a file called layouts/app.blade.php
that all of your other views extend from.
Such a view could look like this:
<!DOCTYPE html>
<html>
<head>
@yield('scripts')
</head>
<body>
@yield('content')
</body>
</html>
And in your actual pages, you will extend
that view and provide your content within the content
section. Here's an example welcome.blade.php
file:
@extends('layouts.app')
@section('scripts')
<!-- Some JS and styles -->
@endsection
@section('content')
<div>My Page content is here</div>
@endsection
So far so good, so let's see how we can make use of our newly learned Blade components to refactor this.
First of all, we are going to create a new component called "Layout":
php artisan make:component Layout
Next, we can take all of our existing layout view code and place it inside of our new layout component view:
<!-- /views/components/layout.blade.php -->
<!DOCTYPE html>
<html>
<head>
@yield('scripts')
</head>
<body>
@yield('content')
</body>
</html>
To make this work, the only thing we need to do in our welcome.blade.php
file is, instead of extending our layout - we can just wrap all of the content inside of our new layout component, like this:
<x-layout>
@section('scripts')
<!-- Some JS and styles -->
@endsection
@section('content')
<div>My Page content is here</div>
@endsection
</x-layout>
This is already pretty good, but as we have seen in the previous post, Laravel provides a $slot
variable that automatically holds all of the content that was placed inside of the blade component tags. So lets go and change our layout to make use of the $slot
variable, instead of yielding a content section:
<!-- /views/components/layout.blade.php -->
<!DOCTYPE html>
<html>
<head>
@yield('scripts')
</head>
<body>
{{ $slot }}
</body>
</html>
Instead of using the @section
directive, we can now simply pass the data as is:
<x-layout>
@section('scripts')
<!-- Some JS and styles -->
@endsection
<div>My Page content is here</div>
</x-layout>
Alright - so we got rid of the content section and wrapped everything into our layout component. That's already pretty cool and I think this improves the readability of our view component. Because of the indentation it is immediately clear which layout is being used. This scripts
section is still bothering me though. So let's see how we can get rid of this as well - and there are multiple options.
Named Slots
The first option that we have is to make use of a "named slot". We already discussed the $slot
variable, that will automatically be populated with everything within the component HTML, but we can also manually specify a slot name. This can be done using the x-slot
tag - which itself is sort of a pre-defined Blade component that comes out of the box with Laravel.
By passing a name
property to our x-slot
, we can make the data available within a variable with the same name as our name
attribute.
Let's modify our layout component to make use of a new $scripts
variable:
<!-- /views/components/layout.blade.php -->
<!DOCTYPE html>
<html>
<head>
{{ $scripts }}
</head>
<body>
{{ $slot }}
</body>
</html>
To pass this variable to our view, we can now pass it into a scripts
slot:
<x-layout>
<x-slot name="scripts">
<!-- Some JS and styles -->
</x-slot>
<div>My Page content is here</div>
</x-layout>
Refresh the page, and you can see that everything works as expected. With this, we introduced an error though, as our $scripts
variable is now mandatory within our layout - so if a view would not provide a scripts
slot, you would get an error "Undefined variable: scripts".
While this works with a named slot, we no longer have the ability to append to the scripts section from within multiple of our views/components - because we don't have a real "section" anymore.
So what if we want to get rid of the @section
directives and use a component for this instead? First of, lets get our @yield
directive back into our layout component:
<!DOCTYPE html>
<html>
<head>
@yield('scripts')
</head>
<body>
{{ $slot }}
</body>
</html>
Alright - now lets see how we can make use of this section, without using a directive.
Renderless Blade components
When you create a custom Blade component, the class has a render
method, that by default returns a string containing the name of the view for the given component:
namespace App\View\Components;
use Illuminate\View\Component;
class Alert extends Component
{
public function render()
{
return view('components.alert');
}
}
There's another way of returning a view though, which is by returning a callable, that is then going to return the actual view. In this case, our component only holds the data/state that was provided for the component, but it doesn't actually return a view - which is great for our section use case.
So lets create a new view component called Section
:
php artisan make:component Section
Now instead of returning a view, we are actually going to return a function - that will return the @section
directive for us:
namespace App\View\Components;
use Illuminate\View\Component;
class Section extends Component
{
public function render()
{
return function ($componentData) {
return <<<BLADE
@section("{$componentData['attributes']->get('name')}")
{$componentData['slot']}
@endsection
BLADE;
};
}
}
Alright - so lets break this render method down line by line.
The render
method returns a callable with one parameter - an array holding the component data. This array has the following data and keys available:
componentName
- The name for the component, in our case "section"attributes
- TheIlluminate\View\ComponentAttributeBag
instance that holds all of our component attributesslot
- AIlluminate\Support\HtmlString
object with the string that was passed to the default slot
Inside of this callable, we are returning a string - this string is escaped using PHPs Heredoc Syntax.
Inside of this string, we can now return any Blade code that we want - so in our case, we are opening a @section
directive, with the name
attribute that was provided in the <x-section
tag. Inside of that @section
directive, we pass the slot of our component (and cast it to a string, as it's a HtmlString object). And last but not least, we close the section directive.
To make use of this renderless component, we can now rewrite our page view like this:
<x-layout>
<x-section name="scripts">
<!-- Some JS and styles -->
</x-section>
<div>My Page content is here</div>
</x-layout>
Yay - we did it! We got rid of our ugly directive and have successfully refactored it to make use of a custom blade component.
Is this better than using the directives? You'll have to decide for yourself - but I hope that this inspired you to take a look at renderless Blade components, as well as how you can use components for your traditional layout extensions.
Last updated
Was this helpful?