ibrows/xeditable-bundle
Symfony bundle that brings X-editable inline editing to your forms and Twig templates, letting users edit fields in place via popups, tooltips, and AJAX saves. Ships form type extensions, Twig helpers, and asset wiring for fast setup.
First, the reality check. ibrows/xeditable-bundle is a Symfony2 bundle (uses Symfony\Bundle\FrameworkBundle, Twig form themes, symfony/http-kernel ~2.x). Do not composer require it into a Laravel app — you'll get constraint conflicts and an autoload prefix (Ibrows\Bundle\...) with no service container to bind it to. The repo is effectively abandoned (0 stars, 0 dependents, no recent activity), so treat it as reference documentation, not a dependency.
What to read first, in order:
Resources/views/Form/ — the x-editable Twig form theme. This is the actual value: it shows how data-* attributes (data-url, data-pk, data-type, data-name, data-source) are rendered from a form field.Resources/public/js/ — the JS bootstrap that calls $().editable({...}) with the global defaults.First use case: inline-edit one scalar field on a "show" page — e.g. users.email.
Minimal Laravel equivalent:
// routes/web.php
Route::patch('/users/{user}/inline', [InlineEditController::class, 'update'])->name('inline.update');
{{-- resources/views/users/show.blade.php --}}
<a href="#"
class="editable"
data-name="email"
data-type="text"
data-pk="{{ $user->id }}"
data-url="{{ route('inline.update', $user) }}"
data-title="Edit email">{{ $user->email }}</a>
$('.editable').editable({
ajaxOptions: {
type: 'PATCH',
headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content },
},
success: function (res) { if (!res.success) return res.errors; },
});
Install x-editable itself (x-editable on npm, or the Bootstrap 3 build from CDN) — not the bundle.
Validate and respond with the shape x-editable expects. It checks HTTP status first, then a body. Return 200 with {success:true} on success, and 422 (or 200 with {success:false, errors:{field:'msg'}}) on failure:
class InlineEditController extends Controller
{
public function update(Request $request, User $user)
{
$field = $request->input('name');
abort_unless(in_array($field, ['email','name','phone']), 422, 'Field not editable');
$validated = $request->validate([
$field => ['required', 'string', 'max:255'],
]);
$user->update($validated);
return response()->json(['success' => true]);
}
}
Whitelist editable fields per model. The bundle's approach (a form type mapping) translates cleanly into a $inlineEditable array or a dedicated InlineEditable interface on the model. Never $user->update($request->all()) — pk and name are user-controlled.
Validate pk against the route model, not the payload. x-editable sends pk in the request; ignore it and rely on route-model binding ({user}). This prevents ID-spoofing in the same request.
Select/enum fields. Use data-type="select" with data-source as a JSON map so Laravel validation rules (Rule::in) line up:
data-type="select"
data-source="{{ json_encode(['active' => 'Active', 'banned' => 'Banned']) }}"
data-value="{{ $user->status }}"
Date fields. Set data-format="yyyy-mm-dd" (x-editable's moment-style tokens differ from Bootstrap Datepicker's) and always Carbon::parse($request->input($field)) server-side — string formats arriving are inconsistent.
CSRF. x-editable posts JSON with Content-Type: application/json. VerifyCsrfToken checks the X-CSRF-TOKEN header, which ajaxOptions.headers handles. If you have multiple editables on a page, define a single $.fn.editable.defaults.ajaxOptions at boot rather than per-element.
Reusable Blade component to mirror the bundle's form theme:
{{-- resources/views/components/editable.blade.php --}}
@props(['model', 'name', 'type' => 'text', 'value' => null, 'source' => null])
<a href="#"
data-name="{{ $name }}"
data-type="{{ $type }}"
data-pk="{{ $model->getKey() }}"
data-url="{{ route('inline.update', $model) }}"
@if($source) data-source="{{ json_encode($source) }}" @endif
{{ $attributes }}>{{ $value ?? $model->{$name} }}</a>
Bulk / table-row editing. The bundle ships this pattern too — pass an array of pk and update each in a loop, returning {success:true, errors:[]}.
composer require.bootstrap-editable (the maintained fork), toast-ui/editor for rich text, or a small Alpine x-data inline-edit component. Porting the response contract is the reusable part; the JS library is the debt.success vs. HTTP status. x-editable treats non-2xx as a transport error and shows the raw text. Many implementations return 200 with {success:false, errors:{...}}; validate your handler actually produces that shape, or the error toast will read "Unprocessable Content".data-url must be exact. Laravel's route() with trailing slashes vs. x-editable's relative URL resolution is a common silent 404. Log the request in storage/logs/laravel.log and confirm in DevTools → Network → Payload.{name, value, pk} — the server looks up the field by name. If you use a custom name (e.g. data-name="email_address") the column in the DB must match or be explicitly mapped.type: 'PUT'/'PATCH' in ajaxOptions, Laravel's _method spoofing does not apply to JSON bodies — declare the route as Route::patch and let the header do the work; don't rely on method spoofing.yyyy-mm-dd; moment.js and jQuery UI datepicker interpret tokens differently. Set format explicitly on both client and server and never trust the string.php artisan route:list --name=inline — confirm the route accepts the method; (3) validate() errors — dump $request->all(); (4) CSRF — 419 means the header wasn't sent, not a code bug.display callback (format the value server-side and return an HTML snippet — sanitize it), inputclass (custom input types), and a per-field validate callback for client-side pre-flight before hitting the server. Keep server-side validation authoritative.How can I help you explore Laravel packages today?