😁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 - The Illuminate\View\ComponentAttributeBag instance that holds all of our component attributes

  • slot - A Illuminate\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