Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

hammamzarefa's avatar

How to Implement Inline Frontpage Editing in Laravel (Similar to WPBakery)?

I'm working on a Laravel project where I need to allow admins to edit the frontpage directly from the frontend, similar to how WPBakery works in WordPress. The goal is for admins to see the page as a regular user would, but with the ability to edit specific sections directly on the page. If an admin clicks on an image, an image upload dialog should open, allowing them to select and upload a new image. If an admin clicks on a text section, a rich text editor (WYSIWYG) should appear. If an admin clicks on a yes/no option, a radio button or toggle switch should be displayed based on the field type in the database.

0 likes
4 replies
LaryAI's avatar
Level 58

To implement inline frontpage editing in Laravel, similar to WPBakery in WordPress, you can follow these steps:

  1. Set Up Authentication and Authorization: Ensure that only admins can access the inline editing features.

  2. Frontend Setup: Use JavaScript libraries to handle inline editing. Libraries like ContentTools for text editing, Dropzone.js for image uploads, and custom JavaScript for toggles can be useful.

  3. Backend Setup: Create API endpoints in Laravel to handle the updates made by the admin.

Here’s a step-by-step guide:

Step 1: Authentication and Authorization

Ensure you have authentication set up in your Laravel application. Use Laravel's built-in authentication scaffolding or a package like Laravel Breeze or Laravel Jetstream.

// In your routes/web.php
Route::middleware(['auth', 'can:edit-content'])->group(function () {
    Route::get('/admin/frontpage', [FrontpageController::class, 'edit'])->name('frontpage.edit');
    Route::post('/admin/frontpage/update', [FrontpageController::class, 'update'])->name('frontpage.update');
});

Step 2: Frontend Setup

Include the necessary JavaScript libraries in your Blade template.

<!-- In your resources/views/layouts/app.blade.php -->
<!DOCTYPE html>
<html lang="en">
<head>
    <!-- Other head elements -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/content-tools/1.6.10/content-tools.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/content-tools/1.6.10/content-tools.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.9.2/min/dropzone.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.9.2/min/dropzone.min.css">
</head>
<body>
    @yield('content')
    @auth
        <script src="{{ asset('js/inline-editing.js') }}"></script>
    @endauth
</body>
</html>

Step 3: Inline Editing JavaScript

Create a JavaScript file to handle the inline editing.

// In your public/js/inline-editing.js
document.addEventListener('DOMContentLoaded', function() {
    if (document.querySelector('body').classList.contains('admin')) {
        ContentTools.IMAGE_UPLOADER = function (dialog) {
            var image, xhr, xhrComplete, xhrProgress;

            // Define functions to handle image upload
            xhrProgress = function (event) {
                dialog.progress((event.loaded / event.total) * 100);
            };

            xhrComplete = function (event) {
                var response;

                if (event.target.readyState != 4) {
                    return;
                }

                if (parseInt(event.target.status) == 200) {
                    response = JSON.parse(event.target.responseText);
                    image = {
                        size: response.size,
                        url: response.url
                    };
                    dialog.save(image.url, image.size);
                } else {
                    new ContentTools.FlashUI('no');
                }
            };

            dialog.addEventListener('imageuploader.fileready', function (event) {
                var formData;

                xhr = new XMLHttpRequest();
                xhr.upload.addEventListener('progress', xhrProgress);
                xhr.addEventListener('readystatechange', xhrComplete);

                xhr.open('POST', '/admin/frontpage/upload-image', true);
                xhr.setRequestHeader('X-CSRF-TOKEN', document.querySelector('meta[name="csrf-token"]').getAttribute('content'));

                formData = new FormData();
                formData.append('image', event.detail().file);
                xhr.send(formData);
            });

            dialog.addEventListener('imageuploader.save', function () {
                dialog.busy(true);
            });

            dialog.addEventListener('imageuploader.cancelupload', function () {
                if (xhr) {
                    xhr.upload.removeEventListener('progress', xhrProgress);
                    xhr.removeEventListener('readystatechange', xhrComplete);
                    xhr.abort();
                }

                dialog.state('empty');
            });

            dialog.addEventListener('imageuploader.clear', function () {
                dialog.clear();
                image = null;
            });
        };

        var editor = ContentTools.EditorApp.get();
        editor.init('*[data-editable]', 'data-name');

        editor.addEventListener('saved', function (ev) {
            var name, payload, regions, xhr;

            regions = ev.detail().regions;
            if (Object.keys(regions).length == 0) {
                return;
            }

            payload = new FormData();
            for (name in regions) {
                if (regions.hasOwnProperty(name)) {
                    payload.append(name, regions[name]);
                }
            }

            xhr = new XMLHttpRequest();
            xhr.open('POST', '/admin/frontpage/update');
            xhr.setRequestHeader('X-CSRF-TOKEN', document.querySelector('meta[name="csrf-token"]').getAttribute('content'));

            xhr.addEventListener('load', function () {
                new ContentTools.FlashUI(xhr.status == 200 ? 'ok' : 'no');
            });

            xhr.send(payload);
        });
    }
});

Step 4: Backend Setup

Create the necessary controller methods to handle the updates.

// In your app/Http/Controllers/FrontpageController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class FrontpageController extends Controller
{
    public function edit()
    {
        // Load the frontpage content
        $content = Frontpage::first();
        return view('admin.frontpage.edit', compact('content'));
    }

    public function update(Request $request)
    {
        // Update the frontpage content
        $content = Frontpage::first();
        $content->update($request->all());
        return response()->json(['status' => 'success']);
    }

    public function uploadImage(Request $request)
    {
        // Handle image upload
        if ($request->hasFile('image')) {
            $path = $request->file('image')->store('images', 'public');
            return response()->json(['url' => asset('storage/' . $path)]);
        }
        return response()->json(['status' => 'error'], 400);
    }
}

Step 5: Blade Template

Update your Blade template to include editable regions.

<!-- In your resources/views/admin/frontpage/edit.blade.php -->
@extends('layouts.app')

@section('content')
<div class="container">
    <div data-editable data-name="header">
        {!! $content->header !!}
    </div>
    <div data-editable data-name="body">
        {!! $content->body !!}
    </div>
    <div data-editable data-name="footer">
        {!! $content->footer !!}
    </div>
</div>
@endsection

Step 6: Database Migration

Ensure your database has the necessary fields to store the content.

// In your database/migrations/xxxx_xx_xx_create_frontpages_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateFrontpagesTable extends Migration
{
    public function up()
    {
        Schema::create('frontpages', function (Blueprint $table) {
            $table->id();
            $table->text('header')->nullable();
            $table->text('body')->nullable();
            $table->text('footer')->nullable();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('frontpages');
    }
}

This setup provides a basic inline editing functionality for a Laravel application. You can extend it further by adding more features and refining the user experience.

jlrdw's avatar

Also there are many lessons here on laracasts on javascript, some are free.

1 like

Please or to participate in this conversation.