Skip to content

Capítulo 5.6: Exibição de Dados via Blade + Fetch

Tempo de estudo: 50 minutos


1. Abordagem Híbrida: O Melhor de Dois Mundos

Podemos construir uma página de duas maneiras:

  1. Renderização Completa no Servidor (SSR): Laravel gera todo o HTML, incluindo a lista de planetas. Para qualquer atualização (exclusão, adição), a página é completamente recarregada.
  2. Renderização Completa no Cliente (CSR): Laravel entrega um "esqueleto" HTML vazio, e o JavaScript solicita todos os dados da API e os renderiza no cliente. (Esta é a abordagem Single Page Application - SPA).

Nossa escolha — a abordagem híbrida:

  • Primeira carga (SSR): Laravel entrega imediatamente a página com a lista de planetas já pronta. Isso é rápido e bom para SEO. O usuário vê o conteúdo imediatamente.
  • Ações subsequentes (CSR): O JavaScript intercepta as ações do usuário (cliques de botões) e interage com a API, atualizando apenas as partes necessárias da página, sem recarga completa.

💡 Analogia Espacial:

Ao entrar na ponte de comando, você recebe imediatamente o mapa de navegação principal, impresso no Centro de Controle da Missão (SSR). Ele já está em suas mãos, não há necessidade de esperar. Mas então você ativa o "modo ao vivo" em seu tablet (CSR), e ele começa a receber atualizações dos satélites em tempo real, redesenhando os objetos em seu mapa.


2. Passo 1: Preparação da Página

Trabalharemos com nossa página de lista de planetas resources/views/planets/index.blade.php. Ela já é capaz de exibir dados passados do controlador. Agora adicionaremos a ela elementos de controle que funcionarão via JS.

Adicione um botão "Atualizar lista" e um contêiner para notificações:

    <div class="controls">
        <h2>Lista de todos os planetas conhecidos</h2>
        <button id="refresh-btn">Atualizar via API</button>
    </div>
    <div id="notification-area" class="notification"></div>
    <hr>
    {{-- Esta div será nosso contêiner para atualização dinâmica --}}
    <div id="planet-list-container" class="planet-list">
        {{-- Inclui a view "filha" que renderiza a lista inicial --}}
        @include('planets.partials.list', ['planets' => $planets])
    </div>

Observe @include('planets.partials.list', ...). Extraímos a lógica de exibição da lista para um arquivo separado e reutilizável.


Passo 2: Criação de uma View "Parcial" Reutilizável (Partial)

Extrair partes repetidas para arquivos separados é uma boa prática.

Crie o arquivo 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>Sistema Solar: {{ $planet->solar_system }}</p>
        <p>Diâmetro: {{ number_format($planet->size_km, 0, '.', ' ') }} km</p>
        <a href="{{ route('planets.show', $planet) }}">Saber mais &rarr;</a>
        <button class="delete-btn" data-id="{{ $planet->id }}" data-url="{{ route('api.planets.destroy', $planet) }}">
            Desativar
        </button>
    </div>
@empty
    <p>Não há nenhum planeta no banco de dados.</p>
@endforelse
  • Importante: Observe que a URL para o botão de exclusão agora é gerada para a rota da API: route('api.planets.destroy', $planet). Para isso, certifique-se de que em routes/api.php você tenha um recurso nomeado: Route::apiResource('planets', ...)->name('api.planets');

Passo 3: Escrita de JavaScript para Atualização Dinâmica

Agora o mais interessante. Criaremos um JavaScript que, ao clicar em um botão, solicitará uma lista atualizada de planetas da API e a redesenhará.

Crie o arquivo public/js/planet-manager.js e inclua-o em 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');

    // Função para exibir notificações
    function showNotification(message, isError = false) {
        notificationArea.textContent = message;
        notificationArea.className = isError ? 'notification error' : 'notification success';
        setTimeout(() => {
            notificationArea.textContent = '';
            notificationArea.className = 'notification';
        }, 3000);
    }

    // Função para renderizar um único cartão de planeta
    function createPlanetCardHtml(planet) {
        // IMPORTANTE: Geramos o mesmo HTML que em nossa view parcial
        return `
            <div class="planet-card" id="planet-card-${planet.id}">
                <h3>${planet.name}</h3>
                <p>Sistema Solar: ${planet.solar_system}</p>
                <p>Diâmetro: ${new Intl.NumberFormat().format(planet.size_km)} km</p>
                <a href="/planets/${planet.id}">Saber mais &rarr;</a>
                <button class="delete-btn" data-id="${planet.id}" data-url="/api/planets/${planet.id}">
                    Desativar (JS)
                </button>
            </div>
        `;
    }

    // Função para solicitar e redesenhar a lista de planetas
    async function fetchAndRenderPlanets() {
        showNotification('Solicitando novos dados de satélites orbitais...');
        try {
            const response = await fetch('/api/planets', {
                headers: { 'Accept': 'application/json' }
            });

            if (!response.ok) {
                throw new Error('Erro de rede ao obter dados.');
            }

            const planets = await response.json(); // Laravel por padrão retornará { data: [...] } para um recurso paginado

            planetListContainer.innerHTML = ''; // Limpa a lista antiga

            if (planets.data.length === 0) {
                planetListContainer.innerHTML = '<p>Não há nenhum planeta no banco de dados.</p>';
            } else {
                planets.data.forEach(planet => {
                    const cardHtml = createPlanetCardHtml(planet);
                    planetListContainer.innerHTML += cardHtml;
                });
            }
            showNotification('Dados atualizados com sucesso!', false);
        } catch (error) {
            console.error('Erro ao atualizar a lista de planetas:', error);
            showNotification(error.message, true);
        }
    }

    // Adiciona o manipulador de eventos ao botão
    if (refreshBtn) {
        refreshBtn.addEventListener('click', fetchAndRenderPlanets);
    }

    // A lógica de exclusão do capítulo anterior pode ser movida para cá,
    // para que todo o JS esteja em um só lugar.
});

3. Verificação Final

  1. Inicie o servidor (php artisan serve ou certifique-se de que o Herd esteja funcionando).
  2. Recrie o banco de dados, se necessário: php artisan migrate:fresh --seed.
  3. Abra a página /planets no navegador.
    • Você deve ver imediatamente a lista de planetas, gerada pelo servidor.
  4. Clique no botão "Atualizar via API".
    • Você verá uma notificação de carregamento.
    • A lista deve desaparecer por um momento e reaparecer, mas desta vez será gerada por JavaScript com base nos dados obtidos da API.

Você implementou com sucesso o modelo híbrido!


Quiz para Fixação

1. O que é uma abordagem de renderização híbrida (SSR + CSR)?

2. Qual é a principal vantagem da renderização inicial no servidor (SSR)?

3. Por que `@include('planets.partials.list')` é usado no exemplo?

4. No código JavaScript, duplicamos a estrutura HTML do cartão. Qual seria uma maneira mais avançada de evitar isso?

5. Por que é importante que a API (`/api/planets`) e o código JavaScript (`createPlanetCardHtml`) gerem dados/HTML consistentes?

🚀 Parabéns pela conclusão do Capítulo 5!

Você percorreu um longo caminho, desde os fundamentos do Blade até a criação de páginas híbridas interativas. Você aprendeu a:

  • Criar e usar templates e layouts Blade.
  • Organizar rotas da web e controladores para operações CRUD.
  • Proteger formulários da web e requisições AJAX com tokens CSRF.
  • Integrar JavaScript para interação dinâmica com a API sem recarregar a página.

Seu Centro de Controle de Voo está totalmente funcional, seguro e interativo. Você está pronto para a próxima grande etapa — comparar esta abordagem com outros frameworks e aprender as melhores práticas para produção.