Skip to content

Глава 5.6: Отображение данных через Blade + Fetch

Время изучения: 50 минут


1. Гибридный подход: Лучшее из двух миров

Мы можем построить страницу двумя способами:

  1. Полный Server-Side Rendering (SSR): Laravel генерирует весь HTML, включая список планет. Для любого обновления (удаления, добавления) страница полностью перезагружается.
  2. Полный 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) }}">Узнать больше &rarr;</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}">Узнать больше &rarr;</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. Финальная проверка

  1. Запустите сервер (php artisan serve или убедитесь, что Herd работает).
  2. Пересоздайте базу, если нужно: php artisan migrate:fresh --seed.
  3. Откройте страницу /planets в браузере.
    • Вы должны сразу увидеть список планет, сгенерированный сервером.
  4. Нажмите кнопку "Обновить через API".
    • Вы увидите уведомление о загрузке.
    • Список должен на мгновение исчезнуть и появиться снова, но на этот раз он будет сгенерирован JavaScript-ом на основе данных, полученных от API.

Вы успешно реализовали гибридную модель!


Квиз для закрепления

1. Что такое гибридный подход к рендерингу (SSR + CSR)?

2. Какой основной плюс первоначальной серверной отрисовки (SSR)?

3. Зачем в примере используется `@include('planets.partials.list')`?

4. В JavaScript-коде мы дублируем HTML-структуру карточки. Какой может быть более продвинутый способ этого избежать?

5. Почему важно, чтобы API (`/api/planets`) и JavaScript-код (`createPlanetCardHtml`) генерировали консистентные данные/HTML?


🚀 Поздравляем с завершением Главы 5!

Вы прошли огромный путь от основ Blade до создания интерактивных гибридных страниц. Вы научились:

  • Создавать и использовать Blade-шаблоны и макеты.
  • Организовывать веб-маршруты и контроллеры для CRUD-операций.
  • Защищать веб-формы и AJAX-запросы с помощью CSRF-токенов.
  • Интегрировать JavaScript для динамического взаимодействия с API без перезагрузки страницы.

Ваш Центр управления полетами полностью функционален, безопасен и интерактивен. Вы готовы к следующему большому этапу — сравнению этого подхода с другими фреймворками и изучению лучших практик для production.