## Getting Started
### Minimal Setup
1. **Installation**
Add the bundle via Composer (updated for 4.0.3 compatibility):
```bash
composer require becklyn/rad-bundle:^4.0
Register the bundle in config/bundles.php (unchanged):
return [
// ...
Becklyn\RadBundle\BecklynRadBundle::class => ['all' => true],
];
First Use Case: AJAX Response
Inject AjaxResponseBuilder into a controller (updated for 4.0.3):
use Becklyn\RadBundle\AjaxResponseBuilder;
class MyController extends AbstractController
{
public function ajaxAction(AjaxResponseBuilder $builder): Response
{
return $builder
->ok()
->data(['key' => 'value'])
->build(); // 4.0.3 enforces stricter response validation
}
}
Use the mojave client (ensure version ^3.2+ for compatibility):
const response = await fetch('/ajax-endpoint');
const result = await mojave.ajaxResponse(response);
if (!result.ok) {
console.error(`Status: ${result.status || 'unknown'}`);
}
Form Extensions
Extend a form type with RadFormExtension (4.0.3 adds lazyValidation option):
use Becklyn\RadBundle\Form\Extension\RadFormExtension;
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addExtension(new RadFormExtension([
'lazyValidation' => true, // NEW: Validate on blur by default
]));
}
}
Consistent Response Handling
Use AjaxResponseBuilder with 4.0.3's stricter validation:
return $builder
->ok()
->status('custom-status') // Must be non-empty string
->data(['items' => $items]) // Must be JSON-serializable
->message(['text' => 'Success!', 'impact' => 'positive']) // NEW: 'impact' is required
->build();
Frontend Integration
Pair with mojave@^3.2 for TypeScript (4.0.3 adds result.meta support):
if (result.meta?.redirect) {
window.location.href = result.meta.redirect;
}
if (result.message?.impact) {
mojave.toast(result.message.text, result.message.impact);
}
Error Handling
Return non-ok responses with descriptive status (4.0.3 reserves these):
return $builder
->fail()
->status('invalid-data') // Must be one of: ['invalid-data', 'validation-error', 'auth-failed', 'server-error']
->data(['errors' => $errors])
->build();
Dynamic Field Validation
Use RadFormExtension with 4.0.3's lazyValidation:
$builder->add('email', EmailType::class, [
'constraints' => [new NotBlank(), new Email()],
'attr' => ['data-rad-lazy' => true], // NEW: Opt-in per-field lazy validation
]);
Custom JavaScript Events
Attach events via RadFormExtension (4.0.3 adds debounce option):
$builder->addExtension(new RadFormExtension([
'events' => [
'submit' => [
'handler' => 'myCustomSubmitHandler',
'debounce' => 300, // NEW: Debounce in ms
],
],
]));
Nested Form Support
Extend nested forms with 4.0.3's partialUpdate:
$builder->add('address', AddressType::class, [
'entry_options' => [
'rad' => [
'ajax' => true,
'partialUpdate' => true, // NEW: Only update changed fields
],
],
]);
Symfony UX Turbo/Stimulus
Combine with Symfony UX (4.0.3 adds turbo:replace support):
// Controller
return $builder
->ok()
->data(['html' => $this->renderView('partial.html.twig')])
->meta(['turboAction' => 'replace']) // NEW: Turbo-specific meta
->build();
// Stimulus controller
connect() {
this.fetch('/ajax-endpoint').then(response => {
if (response.meta?.turboAction === 'replace') {
this.element.replaceWith(response.data.html);
}
});
}
API Platform
Override serializeContext with 4.0.3's ajax_groups:
public function serializeContext(Operation $operation, array $uriVariables = [], array $context = []): array
{
$context['groups'] = ['ajax', 'default'];
$context['ajax_groups'] = ['items']; // NEW: Separate AJAX serialization groups
return parent::serializeContext($operation, $uriVariables, $context);
}
Custom Twig Functions
Extend Twig with 4.0.3's ajax_include:
{{ ajax_include('partial.html.twig', {
'data': {'key': 'value'},
'meta': {'turboAction': 'replace'}
}) }}
Deprecation Warning
becklyn/rad v8+.class CustomResponseBuilder extends AjaxResponseBuilder
{
public function __construct()
{
parent::__construct(); // Must call parent constructor
}
}
TypeScript Mismatch
mojave@^3.2+:
// Handle unknown statuses
if (!['ok', 'invalid-data', 'validation-error', 'auth-failed', 'server-error'].includes(result.status)) {
throw new Error(`Unknown status: ${result.status}`);
}
Form Extension Conflicts
lazyValidation is now true by default.config/packages/rad_form.yaml:
rad_form:
default_lazy_validation: false
AJAX Redirects
meta.redirect (deprecated redirect field):
if (result.meta?.redirect) {
window.location.href = result.meta.redirect;
}
Response Validation
Use var_dump($builder->getResponse()) (4.0.3 adds validate() method):
$response = $builder->build();
$builder->validate($response); // Throws \InvalidArgumentException on errors
Frontend Errors Check for these 4.0.3-specific issues:
result.ok must be a boolean.result.status must be a reserved string (see above).result.data must be a plain object/array (no resources).result.meta must be a plain object.Form Events
Debug with 4.0.3's rad:debug event:
document.addEventListener('rad:debug', (e) => {
console.log('Rad event:', e.detail);
});
Default AJAX Protocol
200 for all responses, but:
ok: false responses trigger rad:error event in frontend.ok: false with 200 status.Twig Autoescape
Disable autoescaping for AJAX HTML (4.0.3 adds ajax_safe filter):
{{ render(controller('AppController::ajaxPartial'))|ajax_safe }}
CSRF Protection
SameSite cookie support. Update mojave:
mojave.fetch('/submit', {
credentials: 'include', // NEW: Required for SameSite cookies
headers: {
'X-Requested-With':
How can I help you explore Laravel packages today?