V dnešním díle si připravíme layout našeho blogu a zobrazíme si všechny příspěvky z databáze, které jsme si vytvořili v minulém díle. Vysvětlíme si jak šablony v Laravel vypadají a co všechno s nimi můžeme udělat a co v nich můžeme využít. Pro frontend budeme využívat framework Bootstrap v4 a pro jednoduchost si ho rovnou budeme linkovat z oficiálního CDN. Při dalším vývoji doporučuji využít node.js a jeho npm pro řešení balíčků frontendu (něco jako Composer pro backend).

Laravel je postaven na architektuře Model-View-Controller, který nám definuje tři základní části aplikace. První část, tedy model, jsme si již vytvořili v minulém dílu. Model má zodpovídat za komunikaci se zdrojovými úložišti, v našem případě s MySQL databází. V tomto díle si představíme druhou část architektury a tou je View, česky pohled. Ta má jednoduchou funkci - prezentovat data uživateli.

Blade

Pro generování html stránek využívá Laravel šablonovací systém Blade. V něm je možné psát jak html kód, tak i PHP kód. Prakticky všechny Blade pohledy jsou zkompilovány do klasického PHP kódu a poté kešovány do doby, než jsou změněny. Blade pohledy mají příponu .blade.php a jsou typicky uloženy v adresáři resources/views. Jako úvodní stránku tam najdeme welcome.blade.php, kterou můžeme klidně smazat a místo ní si pěkně zobrazíme všechny příspěvky.

Dědičnost šablon

Mezi hlavními výhodami šablon Blade je možnost využít jejich dědičností a sekcí. Jako první si definujeme layout, který budeme využívat pro všechny ostatní šablony. Je však dobrým zvykem rozdělit si layout do více samostatných logických částí a ty si udržovat zvlášť. A k tomu využijeme právě sekce. Vytvoříme si nový layout resources/views/layouts/app.blade.php s tímto obsahem:

<!DOCTYPE html>
<html>

<head>
    @include('includes.head')
</head>

<body>

@include('includes.navigation')

<div class="container">
    <div class="row">
        <div class="col-md-8 offset-md-2">
            @yield('content')
        </div>
    </div>
</div>

@include('includes.footer')
@include('includes.scripts')

</body>
</html>

Helpery @include() nám pomáhají vkládat jiné šablony, kdy cestu k nim uvádíme pomocí tečkové anotace. Jinak funguje helper @yield(), přesněji řečeno funguje přesně naopak. Tato sekce je dynamická a neobsahuje žádnou konkrétní šablonu. V případě, že bychom chtěli něco vložit do této sekce, pak musíme v šabloně dědit z tohoto layoutu a uvést sekci, která se právě zde vloží. To si ale předvedeme později v tomto článku.

Head

Jako první si vytvoříme šablonu resources/views/includes/head.blade.php  a vložíme zde vše co potřebujeme zobrazit v headeru:

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">

<title>Blog - @yield('title')</title>

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<link rel="stylesheet" href="{{ asset('css/custom.css') }}">```
Abychom neměli pouze statický title na všech stránkách tak opět využijeme yield, kterému budeme předávat parametr v každé šabloně a zobrazíme tak různý název stránek. Helper `asset()```  nám umožňuje automaticky vkládat cestu do `/public```  v rootu projektu. A právě zde si vytvoříme css soubor `public/css/custom.css```  s vlastními styly. Nám tam bude stačit pouze tento obsah:
<pre class="lang:css decode:true">body {
  padding-top: 70px;
}

Navigace

Připravíme si navigaci, kde budeme mít několik statických stránek a také si připravíme zobrazení dropdown v případě, že bychom byli přihlášeni. Vytvoříme si šablonu v includes/navigation.blade.php:

<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
    <div class="container">
        <a class="navbar-brand" href="#">Blog</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarResponsive">
            <ul class="navbar-nav ml-auto">
                <li class="nav-item active">
                    <a class="nav-link" href="#">Domů <span class="sr-only">(current)</span> </a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">O nás</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">Kontakt</a>
                </li>
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" data-toggle="dropdown" id="login" href="#" role="button" aria-haspopup="true" aria-expanded="false"> Login </a>
                    <div class="dropdown-menu" aria-labelledby="login">
                        <a class="dropdown-item" href="#">Odhlášení</a>
                    </div>
                </li>
            </ul>
        </div>
    </div>
</nav>

Footer

Bylo by také fajn mít na každé stránce stejnou patičku, takže si nadefinujeme v šabloně includes/footer.blade.php následující obsah:

<footer class="py-5 bg-dark">
    <div class="container">
        <p class="m-0 text-center text-white">Copyright &amp;copy; Blog 2018</p>
    </div>
</footer>

Skripty

Poslední část layoutu bude obsahovat javascriptové skripty, které potřebujeme načíst. Soubor includes/scripts.blade.php bude vypadat takto:

<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>

Výpis článků

Jak jsem již psal v předchozí části, helper yield()  nám umožňuje vkládat jakýkoliv obsah kdy nám stačí pouze označit jeho sekci. Abych to lépe upřesnil, tak si vytvoříme šablonu resources/views/post/index.blade.php s následujícím obsahem:

@extends('layouts.app')

@section('title', 'Příspěvky')

@section('content')

    @foreach($posts as $post)
        <div class="card mb-4">
            <img class="card-img-top" src="http://placehold.it/750x300" alt="Card image cap">
            <div class="card-body">
                <h2 class="card-title">{{ $post->title }}</h2>
                <p class="card-text">{{ $post->content }}</p>
                <a href="#" class="btn btn-primary">Číst dále &amp;rarr;</a>
            </div>
        </div>
    @endforeach
@endsection

Celý kód by měl být poměrně čitelný, pro jistotu ho ale postupně vysvětlím. Helper extends()  využívá právě dědičnosti a definuje nám, jaký layout se má použít. Opět se zde využívá tečková anotace, tedy šablona app.blade.php se vyhledá v adresáři layouts (defaultně se předpokládá, že pohledy jsou v resources/views/). Vše co se nachází v layoutu automaticky dědí i tato šablona, včetně jejich proměnných.

Dále se zde využívá helper section(), který spolupracuje se dříve zmíněným helperem yield(). Jako první parametr se uvádí název sekce, druhý parametr je obsah (string, proměnná, atd.). Díky názvu sekce se vyhledá helper yield (i v layoutu, který figuruje prakticky jako rodič), v tomto případě @yield('title'), a vykreslí se jeho parametr. Tuto sekci využíváme v html části <header>, kde název stránky "Příspěvky" vypisujeme do title.

Sekce content se také vykresluje v helperu @yield('content') a poté si pomocí helperu foreach (prakticky klasicky foreach jak ho známe z nativního PHP) zobrazíme všechny příspěvky z proměnné $post, kterou si nadefinujeme hned v další části.

Routa pro příspěvky

Poslední části tohoto dílu je nadefinování si routy pro zobrazení příspěvků. Nebudu zacházet do detailu a ani to nijak podrobněji rozebírat, vše si více ukážeme v dalším dílu. Pro teď nám bude stačit si otevřít soubor routes/web.php a upravit ho:

/*
|--------------------------------------------------------------------------
| 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!
|
*/

use App\Post;

Route::get('/', function () {
    return view('post.index', ['posts' => Post::all()]);
});

Tato definice nám jednoduše říká, že jakmile provedeme HTTP požadavek typu GET na adresu našeho projektu (např. http://localhost), pak Laravel vrátí šablonu resources/views/post/index.blade.php a předá jí do proměnné $posts všechny příspěvky, které si vytáhneme z databáze pomocí metody all() z našeho modelu App\Post, který je odpovědný za komunikaci s databází (a který jsme si vytvořili v minulém díle).

Závěr

V tomto dílu jsem se pokusil vysvětlit základy Blade šablon a vytvořili jsme si základní layout našeho blogu. Také jsme si vykreslili všechny příspěvky a trochu narazili na routování. Výsledek pak vypadá takto:

alt text

A právě téma routování bude součástí dalšího dílu, kdy si vytvoříme nové routy a pomocí controllerů budeme zajišťovat jejich business logiku a hrát tak roli mezi pohledy a modely. Vytvoříme si vlastní kontaktní formulář a budeme provádět validaci přijatých dat.

Není vám něco jasné, nefunguje nebo mám někde chybu? Napište do komentářů!

Zdrojové kódy pro tento díl naleznete na GitHubu

Užitečné odkazy: