Blok 2 · rozdział 5
Układy Blade i komponenty
Menu ma być w jednym pliku, karta szczytu na liście też.
Służą do tego układ (@extends) i komponent Blade (<x-karta-szczytu>).
W tym rozdziale przejdziesz też składnię Blade: warunki, kilka pętli i @switch.
Po tym rozdziale powinieneś umieć
- złożyć układ z miejscem
@yield('content'); - podpiąć własny CSS z katalogu
public; - przełączyć listę i kartę na
@extends; - użyć w widoku
@if,@foreach,@for,@whilei@switchoraz powiedzieć, czym@forelseróżni się od@foreach; - wyjąć powtarzany HTML do komponentu anonimowego i przekazać mu dane przez
@props.
1. Układ strony
Układ nazywamy korona, nie app. W bloku 4 Breeze utworzy własny
layouts/app.blade.php. Osobna nazwa chroni dzisiejszą pracę przed nadpisaniem.
Plik resources/views/layouts/korona.blade.php:
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@yield('tytul', 'Korona Gór Polski')</title>
<link rel="stylesheet" href="{{ asset('css/korona.css') }}">
</head>
<body>
<header class="pasek">
<div class="pasek-wnetrze">
<a class="znak" href="{{ route('start') }}">Korona Gór Polski</a>
<nav class="menu">
<a href="{{ route('start') }}">Start</a>
<a href="{{ route('szczyty.index') }}">Szczyty</a>
</nav>
</div>
</header>
<main class="strona">
@yield('content')
</main>
</body>
</html>
@yield('content') to miejsce na środek strony. Widok potomny wstawi tu swój HTML.
@yield('tytul', 'Korona Gór Polski') ma drugi argument: tytuł zapasowy.
Gdy potomek nie poda sekcji tytul, w karcie przeglądarki zostaje „Korona Gór Polski”.
public. Przy artisan serve
asset('css/korona.css') wskazuje na http://127.0.0.1:8000/css/korona.css.
2. Plik CSS
Skopiuj materialy/korona.css z folderu kursu do
C:\laragon\www\korona\public\css\korona.css
(katalog css utwórz sam). To zwykły arkusz, bez Tailwind i bez Node.
Node dojdzie dopiero z Breeze.
Po zapisaniu pliku odśwież dowolną stronę, która już ma układ, i w źródle HTML sprawdź adres w <link>.
Ma prowadzić do http://127.0.0.1:8000/css/korona.css, a otwarty bezpośrednio ma pokazać treść arkusza, nie 404.
Klasy z tego pliku, których użyjesz zaraz, to pasek, menu, strona, siatka i karta.
3. Widoki dziedziczą układ
Lista, po skróceniu, zaczyna się tak:
@extends('layouts.korona')
@section('tytul', 'Szczyty')
@section('content')
<h1>Szczyty Korony</h1>
<div class="siatka">
@forelse ($szczyty as $szczyt)
<article class="karta">
<h2>
<a href="{{ route('szczyty.show', $szczyt['slug']) }}">{{ $szczyt['nazwa'] }}</a>
</h2>
<p class="meta">{{ $szczyt['pasmo'] }} · {{ $szczyt['wysokosc'] }} m</p>
</article>
@empty
<p>Brak szczytów na liście.</p>
@endforelse
</div>
@endsection
@extends wybiera ramkę. Są dwie postaci sekcji.
Krótka, @section('tytul', 'Szczyty'), wstawia jedną wartość i nie ma @endsection.
Długa, @section('content') … @endsection, wstawia cały blok HTML w miejsce @yield('content').
Artykuł w pętli jest na razie zwykłym HTML-em. W podrozdziale 5 podmienisz go na komponent, żeby lista i ewentualna druga lista nie kopiowały tego samego znacznika.
resources/views/start.blade.php po przepięciu nie ma własnego <html>:
@extends('layouts.korona')
@section('tytul', 'Start')
@section('content')
<h1>Korona Gór Polski</h1>
<p class="lead">Lista szczytów, które zbierasz w tej aplikacji.</p>
<p><a href="{{ route('szczyty.index') }}">Przejdź do szczytów</a></p>
@endsection
To samo zrób w szczyty/show.blade.php. Zostaje środek, ramkę daje układ:
@extends('layouts.korona')
@section('tytul')
{{ $szczyt['nazwa'] }}
@endsection
@section('content')
<p><a href="{{ route('szczyty.index') }}">Wróć do listy</a></p>
<h1>{{ $szczyt['nazwa'] }}</h1>
<p>{{ $szczyt['pasmo'] }} · {{ $szczyt['wysokosc'] }} m n.p.m.</p>
@endsection
Odśwież /, /szczyty i /szczyty/rysy.
Na każdej stronie ma być ten sam zielony pasek z napisem „Korona Gór Polski”.
W źródle strony nie ma dwóch znaczników <html>.
@include('fragment') wkleja inny widok i widzi zmienne rodzica.
Komponent ma jawne wejście: dostaje tylko to, co mu przekażesz. Przy karcie szczytu bierz komponent.
4. Składnia Blade
W rozdziale 3 zostały {{ }}, @if i @forelse.
Tu przejdziesz resztę konstrukcji, które czytasz w cudzych widokach i których sam użyjesz przy formularzu.
Lista Korony zostaje przy @forelse. Przykłady lądują na osobnej stronie, żeby dało się je odświeżyć i wyrzucić bez ruszania kart szczytów.
W kontrolerze dopisz metodę. Korzysta z tej samej tablicy co lista:
public function skladnia()
{
return view('skladnia', [
'szczyty' => $this->listaSzczytow(),
]);
}
W routes/web.php, obok tras szczytów:
Route::get('/skladnia', [SzczytController::class, 'skladnia'])->name('skladnia');
Utwórz resources/views/skladnia.blade.php. Każdy kolejny fragment wklejaj przed @endsection.
Po każdym zapisie odśwież http://127.0.0.1:8000/skladnia.
W źródle strony mają być gotowe listy i akapity. Dyrektyw @if ani @foreach tam nie ma — Laravel zamienił je na PHP, zanim HTML wyszedł z serwera.
@extends('layouts.korona')
@section('tytul', 'Składnia Blade')
@section('content')
<h1>Składnia Blade</h1>
@endsection
@if, @elseif, @else, @unless
@if wypisuje tę gałąź, której warunek jest prawdziwy. @elseif sprawdza kolejny warunek, gdy wcześniejsze nie pasowały.
@else obejmuje pozostałe przypadki. @unless działa odwrotnie niż @if: treść pokazuje się, gdy warunek nie jest spełniony.
@php
$wysokosc = 1725;
@endphp
<h2>Warunki</h2>
@if ($wysokosc >= 2000)
<p>{{ $wysokosc }} m — próg dwóch tysięcy.</p>
@elseif ($wysokosc >= 1000)
<p>{{ $wysokosc }} m — od 1000 do 1999.</p>
@else
<p>{{ $wysokosc }} m — poniżej 1000.</p>
@endif
@unless ($wysokosc >= 2000)
<p>@unless: to nie jest próg 2000 m.</p>
@endunless
Przy 1725 widzisz zdanie „od 1000 do 1999” i zdanie z @unless.
Zmień $wysokosc na 2499, odśwież: zostaje pierwsza gałąź, a akapit z @unless znika.
Potem możesz wrócić do 1725.
@php … @endphp wstawia zwykły PHP. Tu służy tylko przykładowi, żeby nie dokładać trasy dla jednej liczby.
Liczenie listy, szukanie slugu i walidacja zostają w kontrolerze. Widok Korony nie obrasta takimi blokami.
@isset i @empty
@isset($zmienna) wchodzi, gdy zmienna istnieje i nie jest null.
@empty($zmienna) wchodzi, gdy jest pusta: null, "", 0 albo pusta tablica.
Gdy szczyt nie ma jeszcze opisu, @isset nic nie wypisuje.
@php
$opis = null;
@endphp
<h2>isset i empty</h2>
@isset($opis)
<p>Opis: {{ $opis }}</p>
@else
<p>Brak opisu — @isset nie wszedł.</p>
@endisset
@empty($opis)
<p>@empty też widzi null.</p>
@endempty
@foreach i $loop
@foreach przechodzi po tablicy tak jak foreach w PHP.
Wewnątrz pętli Blade dodaje obiekt $loop. Numeru wiersza nie liczysz sam.
<h2>@foreach</h2>
<ol>
@foreach ($szczyty as $szczyt)
<li>
{{ $loop->iteration }}. {{ $szczyt['nazwa'] }}
@if ($loop->first) — pierwszy @endif
@if ($loop->last) — ostatni @endif
</li>
@endforeach
</ol>
Na stronie powinny być cztery pozycje. Przy Rysach dopisek „pierwszy”, przy Łysicy „ostatni”.
$loop->iteration liczy od 1, $loop->index od 0, $loop->count to długość tablicy.
@continue przeskakuje do następnego obrotu. @break wychodzi z pętli.
To te same słowa co w PHP, z małpą na początku, żeby Blade ich nie wypisał jako tekst.
<h2>Tylko od 1000 m</h2>
<ul>
@foreach ($szczyty as $szczyt)
@if ($szczyt['wysokosc'] < 1000)
@continue
@endif
<li>{{ $szczyt['nazwa'] }}</li>
@endforeach
</ul>
Na liście są Rysy, Babia Góra i Śnieżka. Łysica (614 m) wypada przez @continue.
@forelse a @foreach
@foreach po pustej tablicy nie wypisuje nic. Nie ma gdzie dać zdania „brak danych”.
@forelse ma gałąź @empty na ten przypadek. Dlatego lista szczytów w projekcie używa @forelse, a nie gołego @foreach.
<h2>Pusta tablica</h2>
@foreach ([] as $pozycja)
<p>Tego akapitu nie będzie.</p>
@endforeach
@forelse ([] as $pozycja)
<p>{{ $pozycja }}</p>
@empty
<p>@forelse: lista jest pusta, więc widzisz tę gałąź.</p>
@endforelse
@for
@for liczy sam, bez tablicy. Ma te same trzy części co pętla for w PHP: start, warunek, krok.
Do wypisania szczytów i tak bierzesz @foreach. @for zostawiasz, gdy znasz liczbę obrotów, a nie kolekcję.
<h2>@for</h2>
<ul>
@for ($nr = 1; $nr <= 3; $nr++)
<li>Miejsce {{ $nr }}</li>
@endfor
</ul>
Trzy pozycje: Miejsce 1, Miejsce 2, Miejsce 3.
@while
@while kręci się, dopóki warunek jest prawdziwy. Sam nie przechodzi do następnego elementu tablicy — musisz zmienić zmienną w środku.
Gdy o tym zapomnisz, warunek zostaje prawdziwy i strona wisi, aż PHP przerwie skrypt limitem czasu.
<h2>@while</h2>
@php
$zostalo = 3;
@endphp
<ul>
@while ($zostalo > 0)
<li>Zostaje {{ $zostalo }}</li>
@php
$zostalo = $zostalo - 1;
@endphp
@endwhile
</ul>
Widzisz „Zostaje 3”, potem 2, potem 1. Na liście szczytów tej pętli nie używasz.
@switch
@switch porównuje jedną wartość z kolejnymi @case.
@break kończy dopasowany przypadek, tak jak break w PHP. Bez niego program wszedłby też w następny @case.
@default obejmuje wartości, które nie pasują do żadnego przypadku.
<h2>@switch</h2>
<ul>
@foreach ($szczyty as $szczyt)
<li>
{{ $szczyt['nazwa'] }} —
@switch($szczyt['pasmo'])
@case('Tatry')
najwyższe pasmo na tej liście
@break
@case('Karkonosze')
Karkonosze
@break
@default
inne pasmo
@endswitch
</li>
@endforeach
</ul>
Rysy dostają tekst o Tatrach, Śnieżka o Karkonoszach, Babia Góra i Łysica wpadają w @default.
Przy dwóch możliwościach zwykle czytelniejszy jest @if. @switch przydaje się, gdy jedno pole ma kilka stałych wartości, tak jak tutaj pasmo.
Wyrażenie, klasa, checkbox
W {{ }} może stanąć wyrażenie, nie tylko zmienna. Operator ? : wybiera jeden z dwóch napisów.
@class składa atrybut class. Zwykły napis dodaje zawsze, a wpis z warunkiem tylko wtedy, gdy warunek jest prawdziwy.
<h2>Klasa i checkbox</h2>
@foreach ($szczyty as $szczyt)
<p @class(['karta', 'wysoki' => $szczyt['wysokosc'] >= 1000])>
{{ $szczyt['nazwa'] }} —
{{ $szczyt['wysokosc'] >= 1000 ? 'od 1000 m' : 'poniżej 1000 m' }}
</p>
<label>
<input type="checkbox" @checked($szczyt['wysokosc'] >= 2000) @disabled(true)>
próg 2000 m (pole wyłączone, tylko do odczytu przykładu)
</label>
@endforeach
Łysica nie dostaje klasy wysoki i ma tekst „poniżej 1000 m”.
Checkbox jest zaznaczony tylko przy Rysach, bo tylko ten wiersz ma co najmniej 2000 m.
@disabled(true) dopisuje atrybut disabled, więc pola nie da się kliknąć — to podgląd dyrektywy, nie element formularza z rozdziału 6.
@selected działa tak samo przy <option>. Użyjesz go przy planie wyjścia.
/skladnia może zostać. Nie wpinasz jej w menu.
Lista, karta i formularz jej nie potrzebują. Na liście Korony zostaje @forelse.
5. Komponent karty
Komponent anonimowy to sam plik Blade, bez klasy PHP.
Nazwa pliku resources/views/components/karta-szczytu.blade.php
daje znacznik <x-karta-szczytu>.
@props(['szczyt'])
<a class="karta" href="{{ route('szczyty.show', $szczyt['slug']) }}">
<h2>{{ $szczyt['nazwa'] }}</h2>
<p class="meta">
{{ $szczyt['pasmo'] }}
· <span class="wysokosc">{{ $szczyt['wysokosc'] }} m</span>
</p>
</a>
Dwukropek w :szczyt="$szczyt" oznacza „przekaż wyrażenie PHP”, nie tekst.
Bez dwukropka szczyt="rysy" byłoby napisem złożonym z pięciu znaków, a komponent szukałby klucza nazwa w napisie i widok by się wyłożył.
W szczyty/index.blade.php podmień artykuł w pętli na jeden znacznik:
@forelse ($szczyty as $szczyt)
<x-karta-szczytu :szczyt="$szczyt" />
@empty
<p>Brak szczytów na liście.</p>
@endforelse
Odśwież /szczyty. Cztery karty są linkami, pasmo i wysokość siedzą w komponencie.
Nazwa pliku z myślnikiem staje się nazwą znacznika. Katalog components jest umowny: Blade szuka w nim sam, bez prefiksu w nazwie widoku.
{{ $slot }}
$slot to miejsce w komponencie na HTML, który wpiszesz między znacznikiem otwierającym a zamykającym.
Tej zmiennej nie tworzysz w kontrolerze i nie przekazujesz jej dwukropkiem, tak jak :szczyt.
Blade sam wkłada w {{ $slot }} to, co stoi w środku znacznika.
Dopisz jedną linię w karta-szczytu.blade.php, pod akapitem z pasmem. Cały plik wygląda wtedy tak:
@props(['szczyt'])
<a class="karta" href="{{ route('szczyty.show', $szczyt['slug']) }}">
<h2>{{ $szczyt['nazwa'] }}</h2>
<p class="meta">
{{ $szczyt['pasmo'] }}
· <span class="wysokosc">{{ $szczyt['wysokosc'] }} m</span>
</p>
{{ $slot }}
</a>
Nazwa, pasmo i wysokość biorą się z tablicy $szczyt.
Linia {{ $slot }} jest osobnym miejscem: na dodatkową treść z widoku, który kartę wywołuje.
Na próbę otwórz znacznik na liście i wstaw w środek akapit. Zwykły ukośnik na końcu znika, bo znacznik ma teraz środek:
<x-karta-szczytu :szczyt="$szczyt">
<p class="meta">Wpis z listy</p>
</x-karta-szczytu>
Dla Rysów przeglądarka dostaje mniej więcej taki HTML. Akapit „Wpis z listy” stoi tam, gdzie w komponencie było {{ $slot }}:
<a class="karta" href="http://127.0.0.1:8000/szczyty/rysy">
<h2>Rysy</h2>
<p class="meta">
Tatry
· <span class="wysokosc">2499 m</span>
</p>
<p class="meta">Wpis z listy</p>
</a>
Odśwież /szczyty. Pod wysokością każdej karty widać „Wpis z listy”.
Potem na liście wróć do postaci bez środka:
<x-karta-szczytu :szczyt="$szczyt" />
Środek jest pusty, więc {{ $slot }} nic nie wypisuje. Zostają nazwa, pasmo i wysokość.
Linię {{ $slot }} w pliku komponentu zostaw. Inny widok będzie mógł wstawić własne zdanie i nie skopiuje całej karty.
$slot a @props
@props(['szczyt']) to dane podane atrybutem, na przykład :szczyt="$szczyt".
{{ $slot }} to HTML wpisany między <x-karta-szczytu> a </x-karta-szczytu>.
Przy <x-karta-szczytu :szczyt="$szczyt" /> atrybut jest, a środek jest pusty.
$szczyt['nazwa'] i $szczyt['wysokosc']. Model użyje $szczyt->nazwa i $szczyt->wysokosc_m — w tabeli kolumna ma przyrostek _m.
Poprawisz wtedy ten jeden komponent i widok karty, nie każde miejsce z osobna — o ile lista już woła komponent.
Klasowy komponent (php artisan make:component) ma osobny plik PHP.
Na kursie zostajemy przy anonimowym: karta nie liczy, tylko rysuje.
Livewire w bloku 5 to inny rodzaj komponentu — ten żyje na serwerze i reaguje na wpisywanie.
Gdy układ, strona /skladnia i komponent karty działają:
git add .
git status
git commit -m "Układ, składnia Blade i komponent karty"
Przed commitem git status nie pokazuje .env.