В прошлом веб-разработчиков больше волновали функции программ и информация, которую они могли передать, а вот на внешний вид веб-страниц они не обращали внимание. Не то что бы они в этом виноваты, ведь в те времена не было таких технологий, которые бы позволяли разрабатывать сложные дизайны веб-страниц. Те же, которые могли сделать что-то магическое, были слишком трудны в использовании.
Важность хлебных крошек для сайта
В настоящее время наблюдается совершенно иная ситуация. В веб-разработках этап UI/UX дизайна также важен, как планирование логики серверной части программы, на основе чего все и работает. Существует огромный спектр общепринятых практик в веб-дизайне, при использовании которых программа обязательно получит ярлык «дружелюбной к пользователю». Еще больше существует ошибок в дизайне, которых должны избегать все.
Люди ненавидят, когда на сайтах неожиданно выскакивают непрошенные всплывающие окна. Я тоже их недолюбливаю, но не только эти окна способны выбесить пользователя. Неспособность понять, где находится юзер, и ориентироваться на сайте сравнима с тем, чтобы потеряться в огромном супермаркете. Ощущается та же беспомощность.
Это, конечно, может быть серьезной проблемой, но необязательно до этого доводить! Хлебные крошки могут оказаться наиболее простым решением, позволяющим пользователям наслаждаться удобной веб-навигацией.
Хлебные крошки создают систему навигации, которая помогает пользователям узнать их настоящее положение в отношении других веб-страниц сайта. Такое название система получила от знаменитой сказки про Гензеля и Гретель, ведь происходит примерно то же самое. Эта система как бы оставляет за собой дорожку, благодаря которой пользователи не теряются, а потому получают хорошие впечатления от сайта. Если на веб-сайте используются хлебные крошки, будет также уменьшено количество действий, которые юзеру нужно предпринять для того, чтобы добраться до нужной страницы.
Разработка хлебных крошек в Laravel - достаточно простая задача. Существует пакет, который занимается большей частью логики. Мы рассмотрим, как пользоваться этим пакетом и применить все его лучшие функции.
Установка приложения Laravel
Весь туториал рассчитан на разработчиков, работающих с Laravel.
С помощью composer мы будем работать с пакетом Laravel Breadcrumbs. Мы напишем код, в котором динамически обработаются сервисы навигации хлебных крошек, причем рендеринг меняется в зависимости от того, какую страницу просматривает пользователь.
Готовый код, показанный в статье, доступен на github.
Ради этой статьи мы создадим приложение Laravel заново.
laravel new breadcrumbs
Возможен и другой вариант:
composer create-project --prefer-dist laravel/laravel breadcrumbs
Далее вводим пакет Laravel Breadcrumbs, вводя следующую команду:
composer require davejamesmiller/laravel-breadcrumbs
Создание HTTP роутинга
Давайте создадим и назовем HTTP маршруты в файле routes/web.php
. В более поздних частях этой статьи мы будем обращаться к ним по именам.
Для этого примера нужны следующие маршруты:
routes/web.php
Route::get('/', [
'as' => 'home',
'uses' => 'MainController@home'
]);
Route::get('/continent/{name}', [
'as' => 'continent',
'uses' => 'MainController@continent'
]);
Route::get('/country/{name}', [
'as' => 'country',
'uses' => 'MainController@country'
]);
Route::get('/city/{name}', [
'as' => 'city',
'uses' => 'MainController@city'
]);
Чтобы продемонстрировать силу хлебных крошек мы разработаем небольшое приложение, в котором будет происходит регистрация континента, страны и города. С помощью атрибута hasMany
мы укажем, что на континенте много стран, а в стране - много городов.
Для создания этих модель нужно ввести такие команды:
php artisan make:model Continent
php artisan make:model Country
php artisan make:model City
Также создадим файлы миграции для каждой модели:
php artisan make:migration continents
php artisan make:migration countries
php artisan make:migration cities
Далее определим отношения в каждом классе моделей:
app/Continent.php
namespace App;
use App\Country;
use Illuminate\Database\Eloquent\Model;
class Continent extends Model
{
public function country(){
return $this->hasMany(Country::class);
}
}
app/Country.php
namespace App;
use App\City;
use App\Continent;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
protected $guarded = [];
public function city(){
return $this->hasMany(City::class);
}
public function continent(){
return $this->belongsTo(Continent::class);
}
}
app/City.php
namespace App;
use App\Country;
use Illuminate\Database\Eloquent\Model;
class City extends Model
{
protected $guarded = [];
public function country(){
return $this->belongsTo(Country::class);
}
}
Обратите внимание на то, что guarded присвоено значение пустого массива. Это сделано потому, что мы собираемся сделать что-то вроде массового присвоения во время наполнения СУБД данными. Когда бы вы ни производили подобные изменения с файлом модели, не забывайте их отменить перед тем, как приложение попадет на продакшен.
Теперь создадим контроллер, который будет ответственен за HTTP запросы, которые будет получать приложение. Мы назовем его MainController и будем использовать разные действия (methods), чтобы вводить (compact) вхождения в модели с обратной выборкой, причем в каждой будут содержаться разные хлебные крошки.
Чтобы создать этот контроллер, нужно написать следующую команду:
php artisan make:controller MainController
Теперь пропишем действия (methods), которые будут возвращать выборки после обработки HTTP запросов.
app/Http/Controllers/MainControllers.php
namespace App\Http\Controllers;
use App\Continent;
use App\Country;
use App\City;
use Illuminate\Http\Request;
class MainController extends Controller
{
public function home(){
return view('home');
}
public function continent($name){
$continent = Continent::where('name', $name)->first();
return view('continent', compact('continent'));
}
public function country($name){
$country = Country::where('name', $name)->first();
return view('country', compact('country'));
}
public function city($name){
$city = City::where('name', $name)->first();
return view('city', compact('city'));
}
}
Мы включили отношения между моделями, так что мы можем изучить возможности сервиса Laravel Breadcrumbs в контексте динамических ссылок и относительных параметров.
Теперь создадим выборки для моделей, только что созданных нами. Добавим 4 новых файла в директории resources/views
. Мы назовем их так:
home.blade.php
continent.blade.php
country.blade.php
city.blade.php
Эти имена достаточно интуитивны. Первый содержит фронтенд-код для домашней страницы, второй - для страницы континентов, третий - для стран, четвертый - для городов.
Отлично, теперь наполним эти выборки кодом.
resources/views/home.blade.php
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
</head>
<body>
{{ Breadcrumbs::render('home') }}
</body>
</html>
resources/views/continent.blade.php
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
</head>
<body>
{{ Breadcrumbs::render('continent', $continent) }}
</body>
</html>
resources/views/country.blade.php
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
</head>
<body>
{{ Breadcrumbs::render('country', $country->continent, $country) }}
</body>
</html>
resources/views/city.blade.php
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
</head>
<body>
{{ Breadcrumbs::render('city', $city->country->continent, $city->country, $city) }}
</body>
</html>
У моделей Continent и Country используются отношения «один ко многим». Многие города принадлежат одной стране, а многие страны принадлежат одному континенту. Континент – «дед» города, а страна - его «родитель». В этой статье мы не будем углубляться в логику отношений в Laravel, однако эту информацию можно почерпнуть самостоятельно.
Наконец, модифицируем файлы миграции, чтобы они определили правильную структуру таблиц в базе данных.
database/migrations/2017_11_02_092826_continents.php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class Continents extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('continents', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}
database/migrations/2017_11_02_092835_countries.php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class Countries extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('countries', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('continent_id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}
database/migrations/2017_11_02_092845_cities.php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class Cities extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('cities', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('country_id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}
Миграция может проводиться с использованием этой команды:
php artisan migrate
Давайте добавим в базу данных информацию, чтобы можно было протестировать приложение. Для этого создадим 3 файла factory (по одному на каждую модель) и обновим метод run файла DatabaseSeeder.php (уже существует) в директории database/seeds
.
Чтобы создать их, нужно записать такие команды:
php artisan make:factory ContinentFactory --model=Continent
php artisan make:factory CountryFactory --model=Country
php artisan make:factory CityFactory --model=City
Они создадут следующие файлы соответственно:
database/factories/ContinentFactory.php
use Faker\Generator as Faker;
$factory->define(App\Continent::class, function (Faker $faker) {
return [
//
];
});
database/factories/CountryFactory.php
use Faker\Generator as Faker;
$factory->define(App\Country::class, function (Faker $faker) {
return [
//
];
});
database/factories/CityFactory.php
use Faker\Generator as Faker;
$factory->define(App\City::class, function (Faker $faker) {
return [
//
];
});
Наконец, обновим файл DatabaseSeeder.php, который занимается всеми свежими вхождениями приложения Laravel. Мы используем эти файлы для введения 3 записей в базу данных. Мы добавим Africa (модель континента), South Africa (модель страны), Johannesburg (модель города) и уточним их отношения:
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
factory(App\City::class)->create([
'name' => 'Johannesburg',
'country_id' => function(){
return factory(App\Country::class)->create([
'name' => 'South Africa',
'continent_id' => function(){
return factory(App\Continent::class)->create([
'name' => 'Africa' ])->id;
}
])->id;
}
]);
}
}
Провести сидинг базы данных можно этой командой:
php artisan db:seed --class=DatabaseSeeder
Установка файла хлебных крошек
В директории рутов нужно создать новый файл breadcrumbs.php. К нему мы будем обращаться каждый раз при обработке хлебной крошки, так как он показывает Laravel, как обращаться с этой информацией. Без этого файла Laravel будет выдавать ошибку каждый раз, когда будет сталкиваться с вызовом функции хлебных крошек.
Первая функция хлебных крошек, которая будет определена в новом файле breadcrumbs.php, будет сделана для домашней страницы. Хлебная крошка «home» будет загружаться при посещении рута с именем «home».
routes/breadcrumbs.php
Breadcrumbs::register('home', function ($breadcrumbs) {
$breadcrumbs->push('Home', route('home'));
});
Рендеринг статической хлебной крошки
В вышенаписанном коде можно увидеть, что класс Breadcrumbs используется для вызова статического метода. Он регистрирует новую хлебную крошку с именем home и вызывает функцию-замыкание. Та берет параметр $breadcrumbs и помещает в URL новое значение хлебной крошки.
$breadcrumbs->push('Home', route('home'));
«Home» в методе push
код написан хардкодом. Это то, что будет появляться при рендеринге хлебной крошки на домашней или любой другой выборке, в которой хлебная крошка Home
нужна для отображения цепи навигации.
route('home')
возвращает URL «home» и будет ссылкой, к которой ведет Home
при обработке в любой выборке.
Наконец, в коде домашней выборки проводим рендеринг хлебной крошки.
resources/views/home.blade.php
{{ Breadcrumbs::render('home') }}
Метод render
получает имя хлебной крошки для отображения в домашней выборке. В более сложных цепях навигации, которые включают отношения между БД, функция render
принимает столько вхождений Models в качестве аргументов, сколько требуется для рендеринга полной цепи навигации хлебной крошки.
Об этом мы подробнее поговорим далее.
Рендеринг динамической хлебной крошки
Что же произойдет, если в веб-приложении мы не знаем точные атрибуты страниц, которые будут посещаться пользователем? Например, у нас есть веб-сайт, на котором отображается информация о континентах, странах и городах. Мы не сможем записать хардкодом название любого континента, страны и города в файл routes/breadcrumbs.php
, так как мы никогда не узнаем заранее, что будет просматривать юзер.
Чтобы можно было обрабатывать динамические хлебные крошки в реальном времени, можно прописать файл routes/breadcrumbs.php
так, чтобы он эффективно работал с динамическими Models в нашем приложении и создавал цепь навигации хлебных крошек, пока юзеры исследуют более глубокие слои программы.
Как говорилось ранее, в модели континента много стран, а в стране много городов. Зная это, мы можем создать интерфейс хлебной крошки, который выглядит так, если мы посетим Johannesburg, принадлежащий South Africa, где последняя принадлежит Africa.
Чтобы сгенерировать цепь навигации хлебных крошек, используя отношения между моделями, как выше, начнем с написания кода, который регистрирует хлебную крошку continent:
routes/breadcrumbs.php
Breadcrumbs::register('continent', function ($breadcrumbs, $continent) {
$breadcrumbs->parent('home');
$breadcrumbs->push($continent->name, route('continent', [
'name' => $continent->name
]));
});
Мы регистрируем хлебную крошку continent и передаем closure. Последняя принимает новый параметр $continent
. Далее хлебная крошка home назначается «родителем». Наконец, присваиваются имя и URL хлебной крошки $continent
.
Для обработки хлебной крошки в выборке континента напишем этот кусок кода:
resources/views/continent.blade.php
{{ Breadcrumbs::render('continent', $continent) }}
Метод render получает имя хлебной крошки в качестве первого аргумента. Он также получает аргумент $continent
, который поможет в упрощении вхождения модели континента до базовых параметров, таких как имя и URL.
На моем локальном компьютере после посещения http://127.0.0.1:8000/continent/africa
откроется страница континента Африки с таким интерфейсом хлебной крошки:
Рендеринг полной цепи навигации
Мы рассмотрели код, в котором обрабатываются хлебные крошки по отдельности. Теперь посмотрим на код и логику, нужные для обработки всей цепи. В этой статье мы обработаем страницу города, которая относится к странице страны, а та - к континенту, последняя - к домашней странице.
Это финальный код файла routes/breadcrumbs.php
для этой статьи. Он может увеличиваться в зависимости от того, чего вы пытаетесь добиться.
routes/breadcrumbs.php
Breadcrumbs::register('home', function ($breadcrumbs) {
$breadcrumbs->push('Home', route('home'));
});
Breadcrumbs::register('continent', function ($breadcrumbs, $continent) {
$breadcrumbs->parent('home');
$breadcrumbs->push($continent->name, route('continent', [
'name' => $continent->name
]));
});
Breadcrumbs::register('country', function ($breadcrumbs, $continent, $country) {
$breadcrumbs->parent('continent', $continent);
$breadcrumbs->push($country->name, route('country', [
'name' => $country->name
]));
});
Breadcrumbs::register('city', function ($breadcrumbs, $continent, $country, $city) {
$breadcrumbs->parent('country', $continent, $country);
$breadcrumbs->push($city->name, route('city', [
'name' => $city->name
]));
});
Мы добавили еще 2 хлебные крошки: country и city. Они нужны для того, чтобы все хлебные крошки могли эффективно связываться друг с другом, когда выборка вызывает цепь навигации, в которой больше одной крошки.
Методы для регистрации и вывода хлебных крошек country и city те же, что и для continent. Мы не будем рассматривать их подробно.
Чтобы обработать хлебную крошку в коде выборки для страницы city, нужно вставить в нужное место этот кусок кода:
resources/views/city.blade.php
{{ Breadcrumbs::render('city', $city->country->continent, $city->country, $city) }}
Метод render получает имя хлебной крошки в качестве первого аргумента. Далее он получает аргумент $city->country->continent
, затем получается результат вхождения модели континента. Также метод получает аргумент $city->country
, который является страной, к которой принадлежит город, и вхождение $city
.
Вот и все. Мы полностью разработали полностью функциональную динамическую навигацию хлебных крошек всего в нескольких строках кода.
Заключение
В мире, в котором пользователи интернет-служб ищут самые простые продукты, рано или поздно разработчикам бы пришлось добавить хлебные крошки. Эта статья показала, что всего с несколькими строками кода можно создать полностью работающую службу навигации. С пакетом Laravel хлебные крошки можно создать еще много всего (например, можно влиять на стиль и разметку). Изучайте его мощь самостоятельно.