Skip to content

Chapter 5.3: Embedding JavaScript in Laravel Views

Study time: 50 minutes


1. Two Approaches to JavaScript on the Web

So far, we have been working with Server-Side Rendering (SSR) — the server (Laravel) generated the finished HTML (via Blade) and sent it to the browser. This is great for SEO and fast initial loading.

Now we will add Client-Side Interactions — the browser, having already loaded the page, will execute JavaScript code to:

  • Send requests to our API without reloading the page.
  • Dynamically update parts of the page (e.g., adding a new planet to the list).
  • Display notifications and modal windows.

💡 Space Analogy:

Imagine that SSR is receiving a complete map of a star system, printed at Mission Control (the server). You see all the objects at the time of printing.

Client-Side JS is your personal tablet (the browser) that communicates with satellites (the API) in real time and updates the position of objects on your map, without requesting a new paper map from Mission Control.


2. Where to Store and How to Include JS Code

As we have already found out, all public assets (CSS, JS, images) should be located in the public folder.

The correct structure:

  1. Source files: All your main JS code should be in public/js/. For example, public/js/planets.js.
  2. Inclusion in Blade: Use the asset() helper to generate the correct URL.

Example of inclusion in the layouts/app.blade.php layout:

<!DOCTYPE html>
<html>
<head>
    {{-- ... --}}
</head>
<body>
    {{-- ... header and main ... --}}

    <footer>
        <p>&copy; {{ date('Y') }} Space Command.</p>
    </footer>

    {{-- It is better to include scripts at the end of the body to speed up page rendering --}}
    <script src="{{ asset('js/planets.js') }}"></script>
    @stack('scripts') {{-- Create a "slot" for scripts of a specific page --}}
</body>
</html>

  • @stack('scripts') is a powerful Blade directive. It allows child views to "push" their own JS code into this place. This is useful when one page needs a unique script, and another does not.

3. Example: "Delete" Button with Confirmation

Let's add a "Delete" button for each planet on the planet list page (planets/index.blade.php) that will work via JavaScript and the Fetch API.

Step 1: Add the button to resources/views/planets/index.blade.php

Modify the planet card by adding a button with data-attributes:

{{-- ... inside the @forelse loop ... --}}
<div class="planet-card" id="planet-card-{{ $planet->id }}">
    <h3>{{ $planet->name }}</h3>
    <p>Solar System: {{ $planet->solar_system }}</p>
    <p>Diameter: {{ number_format($planet->size_km, 0, '.', ' ') }} km</p>
    <a href="/planets/{{ $planet->id }}">Learn more &rarr;</a>
    <button class="delete-btn" data-id="{{ $planet->id }}" data-url="/api/planets/{{ $planet->id }}">
        Decommission
    </button>
</div>
<!-- ... Before the closing body tag ... -->
<script src="{{ asset('js/planets.js') }}" defer></script>

  • id="planet-card-{{ $planet->id }}" is a unique ID for the entire card so that we can remove it from the DOM.
  • data-id and data-url are a convenient way to pass data from PHP (Blade) to JavaScript.

Step 2: Write the JavaScript logic

Create a file public/js/planets.js and add the following code to it:

document.addEventListener('DOMContentLoaded', () => {
    // Find all "Delete" buttons
    const deleteButtons = document.querySelectorAll('.delete-btn');

    deleteButtons.forEach(button => {
        button.addEventListener('click', async (event) => {
            const planetId = event.target.dataset.id;
            const apiUrl = event.target.dataset.url;

            if (!confirm(`Are you sure you want to decommission the planet with ID ${planetId}? This action is irreversible.`)) {
                return; // The user clicked "Cancel"
            }

            try {
                // Send a DELETE request to our API
                const response = await fetch(apiUrl, {
                    method: 'DELETE',
                    headers: {
                        'Accept': 'application/json',
                        // We will add a CSRF token here later
                    }
                });

                if (response.status === 204) { // 204 No Content - successful deletion
                    // Remove the planet card from the page
                    const cardToRemove = document.getElementById(`planet-card-${planetId}`);
                    if (cardToRemove) {
                        cardToRemove.remove();
                    }
                    alert('Planet successfully decommissioned.');
                } else {
                    // If the API returned an error
                    const errorData = await response.json();
                    alert(`Error: ${errorData.message || 'Failed to delete the planet.'}`);
                }
            } catch (error) {
                console.error('Error sending the request:', error);
                alert('A network error occurred. Please try again.');
            }
        });
    });
});

Now, if you refresh the /planets page, you will see "Decommission" buttons, and clicking them will trigger our JavaScript code!


4. Passing Data from Blade to JavaScript

Sometimes you need to pass not just a string to JS, but a whole array or object.

The wrong way (vulnerable):

let planets = {{ $planets }}; // This will lead to a syntax error and is insecure

The right way (via JSON): Use the @json directive. It safely converts a PHP array/object into a valid JSON object.

Example in planets/index.blade.php:

@extends('layouts.app')
{{-- ... --}}
@section('content')
    {{-- ... --}}
@endsection

@push('scripts') {{-- "Push" our script into the @stack('scripts') slot in the layout --}}
<script>
    // Laravel safely converts the $planets collection into a JSON array
    const planetsData = @json($planets);

    // Now we can work with this array in JS
    console.log('Planet data passed from Blade:', planetsData);
    alert(`Loaded ${planetsData.length} planets!`);
</script>
@endpush

  • @push('scripts') places the content inside @stack('scripts') in layouts/app.blade.php. This allows you to add scripts only to the pages where they are really needed.

Quiz to Reinforce

1. Where in a Laravel project should public JS and CSS files be stored?

2. Which Blade helper is used to correctly generate URLs to assets (JS, CSS)?

3. What does the `@push('scripts')` / `@stack('scripts')` pair of directives do?

4. How do you safely pass a PHP array `$data` to JavaScript from Blade?

5. Why is it recommended to include JS scripts at the end of the body tag?


🚀 Chapter Summary:

You have learned to breathe life into static Blade pages by adding client-side logic. Key skills:

  • Proper organization and inclusion of JS files in a Laravel project.
  • Using data-* attributes to pass data from HTML to JS.
  • Dynamic interaction with the API using Fetch without reloading the page.
  • Safely passing PHP variables to JavaScript using the @json directive.
  • Organizing scripts using @push and @stack.

Your "control panels" have become interactive. However, our modifying requests (POST, PUT, DELETE) are currently vulnerable. In the next chapter, we will add a crucial security mechanism — CSRF tokens.