Installation:
composer require bluetea/ajax-response-bundle
Add to config/bundles.php (Laravel 5.5+):
BlueTea\AjaxResponseBundle\BlueTeaAjaxResponseBundle::class => ['all' => true],
Configure JMS Serializer (required dependency): Install via Composer:
composer require jms/serializer-bundle
Add to config/app.php under providers:
JMS\SerializerBundle\JMSSerializerBundle::class,
First Use Case: Create a controller method to return an AJAX response:
use BlueTea\AjaxResponseBundle\Response\AjaxResponse;
public function exampleAction()
{
return new AjaxResponse([
'success' => true,
'message' => 'Operation completed',
'data' => ['id' => 1, 'name' => 'Test']
]);
}
Frontend Integration: Include required JS libraries (adjust paths as needed):
<script src="vendor/jquery/dist/jquery.min.js"></script>
<script src="vendor/jquery-ui/ui/minified/jquery-ui.min.js"></script>
<script src="vendor/blockui/jquery.blockUI.js"></script>
<script src="vendor/pnotify/pnotify.core.js"></script>
<script src="vendor/pnotify/pnotify.buttons.js"></script>
<script src="vendor/pnotify/pnotify.callbacks.js"></script>
Response Structure:
Always return AjaxResponse objects with a consistent structure:
return new AjaxResponse([
'success' => bool, // Required
'message' => string, // Optional but recommended
'data' => array|object, // Serialized via JMS
'errors' => array // Optional error details
]);
Controller Integration:
Use dependency injection for the AjaxResponse class:
public function __construct(AjaxResponse $ajaxResponse)
{
$this->ajaxResponse = $ajaxResponse;
}
Form Handling: Validate forms and return structured errors:
$validator = $this->validator->validate($data, $constraints);
if ($validator->count()) {
return $this->ajaxResponse->error(
'Validation failed',
$validator->getErrorsAsArray()
);
}
API Endpoints:
Use for single-purpose AJAX actions (e.g., save, delete, search):
Route::post('/api/save', [Controller::class, 'saveAction']);
Global AJAX Setup: Attach a handler to all AJAX requests:
$(document).ajaxStart(function() {
$.blockUI({ message: 'Processing...' });
}).ajaxStop(function() {
$.unblockUI();
});
Response Handling: Use a unified response parser:
function handleAjaxResponse(response) {
if (response.success) {
$.pnotify({
title: 'Success',
text: response.message,
type: 'success'
});
if (response.data) {
// Handle data (e.g., update UI)
}
} else {
$.pnotify({
title: 'Error',
text: response.message || 'An error occurred',
type: 'error',
buttons: {
sticker: false
}
});
if (response.errors) {
console.error(response.errors);
}
}
}
Form Submission: Override default form submission:
$('form.ajax-form').submit(function(e) {
e.preventDefault();
$.ajax({
url: $(this).attr('action'),
method: 'POST',
data: $(this).serialize(),
success: handleAjaxResponse
});
});
Dynamic Content Loading: Use for partial updates (e.g., modals, tables):
$.ajax({
url: '/api/load-data',
data: { id: 123 },
success: function(response) {
if (response.success) {
$('#target-element').html(response.data.html);
}
}
});
Laravel-Specific Adjustments:
Request with Laravel’s Illuminate\Http\Request.$validator = Validator::make($request->all(), [
'email' => 'required|email',
]);
Routing: Prefix AJAX routes for clarity:
Route::prefix('api')->group(function() {
Route::post('/users', [UserController::class, 'store']);
});
Middleware:
Apply ajax middleware to restrict routes:
Route::post('/admin/ajax', [AdminController::class, 'action'])
->middleware('ajax');
Testing:
Mock AjaxResponse in PHPUnit:
$mockResponse = $this->getMockBuilder(AjaxResponse::class)
->disableOriginalConstructor()
->getMock();
JMS Serializer Issues:
@Ignore annotations:
use JMS\Serializer\Annotation as Serializer;
class User {
/** @Serializer\Ignore */
private $apiToken;
}
CSRF Token Mismatch:
Request.Route::post('/ajax-endpoint', [Controller::class, 'action'])
->middleware('throttle:60,1')->middleware('ajax');
Frontend JS Conflicts:
// webpack.mix.js
mix.copy('node_modules/jquery/dist/jquery.min.js', 'public/js/vendor/jquery.min.js');
Caching Headers:
$response->header('Cache-Control', 'no-cache, no-store, max-age=0');
Inspect Responses: Use browser dev tools to verify response structure:
$.ajax({
// ...
success: function(response) {
console.log('Raw response:', response);
}
});
Symfony Event Listener:
The bundle’s AjaxResponseListener may interfere with Laravel’s event system. Disable it if needed by overriding the bundle’s configuration.
Log Serialization Errors: Wrap JMS serialization in a try-catch:
try {
$serializer->serialize($data, 'json');
} catch (\Exception $e) {
\Log::error('Serialization failed: ' . $e->getMessage());
return $this->ajaxResponse->error('Internal error', ['serialization' => $e->getMessage()]);
}
Custom Response Types:
Extend AjaxResponse to add domain-specific methods:
class ApiAjaxResponse extends AjaxResponse {
public function apiSuccess($data, $meta = [])
{
return $this->createResponse([
'success' => true,
'data' => $data,
'meta' => $meta,
'timestamp' => now()->toIso8601String()
]);
}
}
Frontend Templates: Use Laravel Blade for dynamic JS templates:
<script type="text/template" id="user-template">
@include('partials.user-card', ['user' => $user])
</script>
WebSocket Integration: Combine with Laravel Echo/Pusher for real-time updates:
Echo.channel('user-updates')
.listen('UserUpdated', (data) => {
handleAjaxResponse(data);
});
Testing Utilities: Create a trait for testing AJAX responses:
trait TestsAjaxResponses {
protected function assertAjaxSuccess($response, $expectedData = [])
{
$response->assertJsonStructure([
'success', 'message', 'data' => $expectedData
]);
$response->assertJson(['success' => true]);
}
}
How can I help you explore Laravel packages today?