Глава 5.6: Отображение данных через Blade + Fetch
Время изучения: 50 минут
1. Гибридный подход: Лучшее из двух миров
Мы можем построить страницу двумя способами:
- Полный Server-Side Rendering (SSR): Laravel генерирует весь HTML, включая список планет. Для любого обновления (удаления, добавления) страница полностью перезагружается.
- Полный Client-Side Rendering (CSR): Laravel отдает пустую HTML-"оболочку", а JavaScript запрашивает все данные у API и рендерит их на клиенте. (Это подход Single Page Application - SPA).
Наш выбор — гибридный подход:
- Первая загрузка (SSR): Laravel немедленно отдает страницу с уже готовым списком планет. Это быстро и хорошо для SEO. Пользователь сразу видит контент.
- Последующие действия (CSR): JavaScript перехватывает действия пользователя (нажатия кнопок) и взаимодействует с API, обновляя только нужные части страницы, без полной перезагрузки.
💡 Космическая аналогия:
При входе на мостик вам сразу выдают основную навигационную карту, напечатанную в ЦУП (SSR). Она уже у вас в руках, не нужно ждать. Но затем вы активируете "живой режим" на своем планшете (CSR), и он начинает получать обновления от спутников в реальном времени, перерисовывая объекты на вашей карте.
2. Шаг 1: Подготовка страницы
Мы будем работать с нашей страницей списка планет resources/views/planets/index.blade.php
. Она уже умеет отображать данные, переданные из контроллера. Теперь мы добавим на нее элементы управления, которые будут работать через JS.
Добавим кнопку "Обновить список" и контейнер для уведомлений:
<div class="controls">
<h2>Список всех известных планет</h2>
<button id="refresh-btn">Обновить через API</button>
</div>
<div id="notification-area" class="notification"></div>
<hr>
{{-- Этот div будет нашим контейнером для динамического обновления --}}
<div id="planet-list-container" class="planet-list">
{{-- Включаем "дочерний" вид, который рендерит начальный список --}}
@include('planets.partials.list', ['planets' => $planets])
</div>
Обратите внимание на @include('planets.partials.list', ...)
. Мы вынесли логику отображения списка в отдельный, переиспользуемый файл.
Шаг 2: Создание переиспользуемого "частичного" вида (Partial)
Выносить повторяющиеся части в отдельные файлы — хорошая практика.
Создайте файл resources/views/planets/partials/list.blade.php
:
@forelse($planets as $planet)
<div class="planet-card" id="planet-card-{{ $planet->id }}">
<h3>{{ $planet->name }}</h3>
<p>Солнечная система: {{ $planet->solar_system }}</p>
<p>Диаметр: {{ number_format($planet->size_km, 0, '.', ' ') }} км</p>
<a href="{{ route('planets.show', $planet) }}">Узнать больше →</a>
<button class="delete-btn" data-id="{{ $planet->id }}" data-url="{{ route('api.planets.destroy', $planet) }}">
Списать
</button>
</div>
@empty
<p>В базе данных нет ни одной планеты.</p>
@endforelse
- Важно: Обратите внимание, что URL для кнопки удаления теперь генерируется для API-маршрута:
route('api.planets.destroy', $planet)
. Для этого убедитесь, что вroutes/api.php
у вас есть именованный ресурс:Route::apiResource('planets', ...)->name('api.planets');
Шаг 3: Написание JavaScript для динамического обновления
Теперь самое интересное. Создадим JavaScript, который будет по кнопке запрашивать свежий список планет у API и перерисовывать его.
Создайте файл public/js/planet-manager.js
и подключите его в layouts/app.blade.php
.
document.addEventListener('DOMContentLoaded', () => {
const refreshBtn = document.getElementById('refresh-btn');
const planetListContainer = document.getElementById('planet-list-container');
const notificationArea = document.getElementById('notification-area');
// Функция для показа уведомлений
function showNotification(message, isError = false) {
notificationArea.textContent = message;
notificationArea.className = isError ? 'notification error' : 'notification success';
setTimeout(() => {
notificationArea.textContent = '';
notificationArea.className = 'notification';
}, 3000);
}
// Функция для отрисовки одной карточки планеты
function createPlanetCardHtml(planet) {
// ВАЖНО: Мы генерируем такой же HTML, как и в нашем partial-виде
return `
<div class="planet-card" id="planet-card-${planet.id}">
<h3>${planet.name}</h3>
<p>Солнечная система: ${planet.solar_system}</p>
<p>Диаметр: ${new Intl.NumberFormat().format(planet.size_km)} км</p>
<a href="/planets/${planet.id}">Узнать больше →</a>
<button class="delete-btn" data-id="${planet.id}" data-url="/api/planets/${planet.id}">
Декомиссия (JS)
</button>
</div>
`;
}
// Функция для запроса и перерисовки списка планет
async function fetchAndRenderPlanets() {
showNotification('Запрашиваю свежие данные с орбитальных спутников...');
try {
const response = await fetch('/api/planets', {
headers: { 'Accept': 'application/json' }
});
if (!response.ok) {
throw new Error('Ошибка сети при получении данных.');
}
const planets = await response.json(); // Laravel по умолчанию вернет { data: [...] } для пагинированного ресурса
planetListContainer.innerHTML = ''; // Очищаем старый список
if (planets.data.length === 0) {
planetListContainer.innerHTML = '<p>В базе данных нет ни одной планеты.</p>';
} else {
planets.data.forEach(planet => {
const cardHtml = createPlanetCardHtml(planet);
planetListContainer.innerHTML += cardHtml;
});
}
showNotification('Данные успешно обновлены!', false);
} catch (error) {
console.error('Ошибка при обновлении списка планет:', error);
showNotification(error.message, true);
}
}
// Вешаем обработчик на кнопку
if (refreshBtn) {
refreshBtn.addEventListener('click', fetchAndRenderPlanets);
}
// Сюда можно будет перенести логику удаления из прошлой главы,
// чтобы весь JS был в одном месте.
});
3. Финальная проверка
- Запустите сервер (
php artisan serve
или убедитесь, что Herd работает). - Пересоздайте базу, если нужно:
php artisan migrate:fresh --seed
. - Откройте страницу
/planets
в браузере.- Вы должны сразу увидеть список планет, сгенерированный сервером.
- Нажмите кнопку "Обновить через API".
- Вы увидите уведомление о загрузке.
- Список должен на мгновение исчезнуть и появиться снова, но на этот раз он будет сгенерирован JavaScript-ом на основе данных, полученных от API.
Вы успешно реализовали гибридную модель!
Квиз для закрепления
🚀 Поздравляем с завершением Главы 5!
Вы прошли огромный путь от основ Blade до создания интерактивных гибридных страниц. Вы научились:
- Создавать и использовать Blade-шаблоны и макеты.
- Организовывать веб-маршруты и контроллеры для CRUD-операций.
- Защищать веб-формы и AJAX-запросы с помощью CSRF-токенов.
- Интегрировать JavaScript для динамического взаимодействия с API без перезагрузки страницы.
Ваш Центр управления полетами полностью функционален, безопасен и интерактивен. Вы готовы к следующему большому этапу — сравнению этого подхода с другими фреймворками и изучению лучших практик для production.