Introduction
In modern web development, the push toward low-code and no-code solutions is driven by the need to build applications faster, with less complexity, and with fewer lines of code. One powerful but often overlooked approach to achieving this is unobtrusive AJAX combined with convention-based development. This article explores how these techniques can dramatically reduce boilerplate code while maintaining clean, maintainable applications.
What is Unobtrusive AJAX?
Unobtrusive AJAX is a development approach where JavaScript behavior is separated from HTML markup. Instead of writing inline JavaScript event handlers, you use HTML data attributes to declaratively specify AJAX behavior. The JavaScript framework (like jQuery Unobtrusive AJAX) reads these attributes and automatically wires up the AJAX functionality.
Traditional Approach (High Boilerplate)
<form id="myForm" onsubmit="handleSubmit(event)">
<input type="text" name="name" />
<button type="submit">Submit</button>
</form>
<script>
function handleSubmit(event) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
fetch('/api/submit', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('result').innerHTML = data.message;
form.reset();
} else {
displayErrors(data.errors);
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred');
});
}
function displayErrors(errors) {
// More boilerplate for error handling...
}
</script>
Unobtrusive AJAX Approach (Low Boilerplate)
<form id="myForm"
method="post"
data-ajax="true"
data-ajax-method="POST"
data-ajax-update="#result"
data-ajax-mode="replace"
data-ajax-success="handleSuccess"
asp-controller="Home"
asp-action="Submit">
<input type="text" name="name" />
<button type="submit">Submit</button>
</form>
<div id="result"></div>
Zero JavaScript required! The framework handles everything automatically.
Is Unobtrusive AJAX Old? Why It’s Still Cool
Yes, unobtrusive AJAX has been around for over a decade (jQuery Unobtrusive AJAX was introduced around 2011-2012). But here’s the thing: that’s actually a feature, not a bug. The fact that it’s been battle-tested for over 10 years means it’s proven, stable, and reliable. More importantly, the principles behind unobtrusive AJAX are more relevant today than ever.
Why Unobtrusive AJAX is Timeless
1. Separation of Concerns (Still Matters)
Unobtrusive AJAX was built on the principle of separating structure (HTML), presentation (CSS), and behavior (JavaScript). This principle is fundamental to good software architecture and remains as important today as it was in 2011.
<!-- Structure: What it is -->
<form id="myForm">
<input type="text" name="name" />
</form>
<!-- Behavior: What it does (declarative, not imperative) -->
<script>
// Framework handles this automatically based on data attributes
</script>
Modern frameworks like React, Vue, and Angular have embraced declarative programming, which is exactly what unobtrusive AJAX pioneered.
2. Progressive Enhancement (Accessibility First)
Unobtrusive AJAX follows progressive enhancement principles: forms work without JavaScript, and JavaScript enhances the experience. This is crucial for:
- Accessibility: Screen readers and assistive technologies
- Performance: Faster initial page loads
- Reliability: Graceful degradation when JavaScript fails
- SEO: Search engines can still crawl and index content
<!-- Works without JavaScript (traditional form submission) -->
<!-- Enhanced with JavaScript (AJAX submission) -->
<form data-ajax="true" method="post" action="/submit">
<!-- Form works either way! -->
</form>
3. Low-Code/No-Code Movement (Perfect Timing)
The low-code movement is exploding right now, and unobtrusive AJAX is perfectly positioned for it. You’re writing declarative markup, not imperative code:
<!-- This IS low-code: declarative, convention-based -->
<form data-ajax="true"
data-ajax-update="#result"
data-action-on-submit="reloadDiv">
<!-- Zero JavaScript, maximum functionality -->
</form>
Modern platforms like Power Apps, OutSystems, and Mendix use similar declarative approaches. Unobtrusive AJAX was doing this before it was cool.
4. Less JavaScript = Less Maintenance
In an era where JavaScript frameworks are constantly evolving (Angular 1 → 2 → 3+, React’s constant changes, Vue 2 → 3), unobtrusive AJAX provides stability. The core library hasn’t needed major changes because the approach is sound.
Less JavaScript means:
- Fewer breaking changes
- Less time debugging
- Lower cognitive load
- Easier onboarding
5. Works with Modern Frameworks
Unobtrusive AJAX doesn’t compete with modern frameworks—it complements them. You can use it:
- In traditional server-rendered applications (ASP.NET Core, Rails, Laravel)
- Alongside React/Vue components
- With Web Components
- In hybrid applications
<!-- Works perfectly in a React app -->
<div id="react-component">
<form data-ajax="true" data-ajax-update="#react-component">
<!-- Unobtrusive AJAX handles form, React handles UI -->
</form>
</div>
6. Declarative Programming is Back in Vogue
The industry has come full circle. After years of imperative JavaScript everywhere, developers are rediscovering the power of declarative programming:
- React: JSX is declarative
- Vue: Templates are declarative
- Svelte: Compile-time declarative
- HTMX: Modern take on unobtrusive AJAX principles
Unobtrusive AJAX was doing declarative web development before it was trendy.
7. Proven at Scale
Unobtrusive AJAX has been used in:
- Enterprise applications
- High-traffic websites
- Government systems
- Financial platforms
If it can handle these use cases, it can handle yours. The “old” technology is actually battle-hardened.
8. Better Developer Experience
Modern developers often write the same form submission code dozens of times:
// Written 50+ times across an application
async function submitForm(formId, endpoint) {
const form = document.getElementById(formId);
const formData = new FormData(form);
// ... 20 more lines of boilerplate
}
Unobtrusive AJAX eliminates this repetition:
<!-- Write once, works everywhere -->
<form data-ajax="true" data-ajax-update="#result">
<!-- That's it! -->
</form>
The Modern Relevance
Unobtrusive AJAX aligns perfectly with modern development trends:
| Modern Trend | Unobtrusive AJAX Fit |
|---|---|
| Low-Code/No-Code | ✅ Declarative markup, zero JavaScript |
| Progressive Enhancement | ✅ Works without JS, enhanced with JS |
| Accessibility | ✅ Semantic HTML, screen reader friendly |
| Performance | ✅ Less JavaScript, faster loads |
| Maintainability | ✅ Less code, fewer bugs |
| Convention Over Configuration | ✅ Standard patterns, predictable behavior |
Why It’s Still Cool
- It Just Works: No build steps, no transpilation, no complex setup
- It’s Simple: HTML attributes are self-documenting
- It’s Fast: Less JavaScript means faster page loads
- It’s Accessible: Progressive enhancement built-in
- It’s Maintainable: Less code to maintain
- It’s Proven: 10+ years of production use
- It’s Modern: Aligns with current low-code trends
The Bottom Line
Unobtrusive AJAX isn’t old—it’s mature. It’s like a reliable tool that’s been refined over a decade. While JavaScript frameworks come and go, the principles behind unobtrusive AJAX remain timeless:
- Separation of concerns
- Progressive enhancement
- Declarative programming
- Convention over configuration
These principles are more relevant today than ever, especially in the age of low-code development. Unobtrusive AJAX isn’t competing with modern frameworks—it’s providing a simpler alternative for the 80% of use cases that don’t need complex client-side state management.
Sometimes the “old” way is still the best way.
The Power of Conventions
Conventions are predefined patterns that developers agree to follow. When combined with unobtrusive AJAX, they eliminate the need for repetitive code by establishing standard ways of handling common scenarios.
Convention Example: Standard Form Submission Pattern
Instead of writing custom JavaScript for every form, establish a convention:
<!-- Convention: Forms with these data attributes follow a standard pattern -->
<form data-ajax="true"
data-ajax-update="#targetDiv"
data-ajax-mode="replace"
data-action-on-submit="reloadDiv"
data-div="tableContainer"
data-url="/Controller/RefreshTable"
data-close-top-modal="yes">
<!-- Form fields -->
</form>
Convention Benefits:
- Consistency: All forms behave the same way
- Predictability: Developers know what to expect
- Maintainability: Change the convention once, update everywhere
- Less Code: No need to write custom handlers for each form
Real-World Example: Facility Management System
Let’s examine a real-world implementation from a facility management system that demonstrates these principles.
The Problem: Repetitive Form Code
Before implementing conventions, every form required:
- Custom JavaScript for AJAX submission
- Custom error handling
- Custom success handling
- Custom validation display
- Custom modal management
- Custom table refresh logic
This resulted in hundreds of lines of boilerplate across multiple forms.
The Solution: Convention-Based Unobtrusive AJAX
1. Standard Form Markup
@* CreateFacilityPartial.cshtml *@
<form id="CreateFacilityForm"
method="post"
class="needs-validation form-field"
novalidate
data-ajax="true"
data-ajax-method="POST"
data-ajax-update="#CreateFacilityModalPlaceholder"
data-ajax-mode="replace"
data-action-on-submit="reloadDiv"
data-div="facilitiesTable"
data-url="/Facility/FacilitiesTable"
data-close-top-modal="yes"
asp-controller="Facility"
asp-action="CreateFacilityPartial">
<partial name="_FacilityFormFields" model="Model" />
<partial name="GlobalToast" />
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Save Facility</button>
</div>
</form>
Key Conventions:
data-ajax="true"– Enable unobtrusive AJAXdata-ajax-update="#target"– Where to update contentdata-action-on-submit="reloadDiv"– Standard post-submit actiondata-div="tableId"– Which table to refreshdata-url="/path"– URL to call for refreshdata-close-top-modal="yes"– Auto-close modal on success
2. Shared Form Fields Partial
Instead of duplicating form fields across Create and Edit views:
@* _FacilityFormFields.cshtml - DRY principle *@
@model FacilityViewModel
@* Basic Information *@
<div class="row mb-3">
<div class="col-md-3">
<label asp-for="FacName" class="form-label fw-semibold">
Facility Name <span class="text-danger">*</span>
</label>
</div>
<div class="col-md-9">
@Html.EditorFor(m => m.FacName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(m => m.FacName, "", new { @class = "text-danger" })
</div>
</div>
@* Address Section - Intuitive Layout *@
<div class="row mb-3">
<div class="col-md-3">
<label class="form-label fw-semibold">Address</label>
</div>
<div class="col-md-9">
<div class="row g-2">
<div class="col-md-2">
<label asp-for="UnitNumber" class="form-label small">Unit #</label>
@Html.EditorFor(m => m.UnitNumber, new { htmlAttributes = new { @class = "form-control form-control-sm" } })
</div>
<div class="col-md-10">
<label asp-for="UnitName" class="form-label small">Unit Name</label>
@Html.EditorFor(m => m.UnitName, new { htmlAttributes = new { @class = "form-control" } })
</div>
</div>
<div class="row g-2 mt-2">
<div class="col-md-3">
<label asp-for="StreetNumber" class="form-label small">Street #</label>
@Html.EditorFor(m => m.StreetNumber, new { htmlAttributes = new { @class = "form-control form-control-sm" } })
</div>
<div class="col-md-9">
<label asp-for="StreetName" class="form-label small">Street Name</label>
@Html.EditorFor(m => m.StreetName, new { htmlAttributes = new { @class = "form-control" } })
</div>
</div>
</div>
</div>
Benefits:
- Single source of truth for form fields
- Consistent layout across Create/Edit
- Easy to maintain and update
- Intuitive field grouping (Unit # doesn’t need full width)
3. Global Toast Notification System
Instead of custom error handling per form, use a global convention:
@* GlobalToast.cshtml - Included in every form *@
@if (ViewData.ModelState.ErrorCount > 0)
{
<div id="modelStateErrors" style="display: none;">
@Html.Raw(Json.Serialize(
ViewData.ModelState
.Where(x => x.Value.Errors.Count > 0)
.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
)
))
</div>
<script src="~/scripts/Toast/GlobalToast.js"></script>
}
Convention:
- Controller adds errors to
ModelState - Partial serializes errors to JSON
- Global script processes and displays toasts
- Zero form-specific code needed
4. Standard Controller Response Pattern
[HttpPost]
public ActionResult CreateFacilityPartial(FacilityViewModel model)
{
if (!ModelState.IsValid)
{
// Convention: Return partial view with errors
return PartialView("CreateFacilityPartial", model);
}
try
{
// Save logic...
// Convention: Success message in ModelState
ModelState.AddModelError("Success", "Facility created successfully!");
return PartialView("CreateFacilityPartial", new FacilityViewModel());
}
catch (Exception ex)
{
// Convention: Error message in ModelState
ModelState.AddModelError("Error", ex.Message);
return PartialView("CreateFacilityPartial", model);
}
}
Convention Benefits:
- Consistent response format
- Automatic toast display
- No custom JSON responses needed
- Works with any form
How Conventions Reduce Boilerplate
Before: High Boilerplate
// Custom handler for Facility form
$('#createFacilityForm').on('submit', function(e) {
e.preventDefault();
const form = $(this);
const formData = form.serialize();
$.ajax({
url: form.attr('action'),
method: 'POST',
data: formData,
success: function(response) {
if (response.success) {
// Update modal
$('#createFacilityModal').html(response.html);
// Show success toast
showToast('success', response.message);
// Refresh table
refreshFacilitiesTable();
// Close modal
$('#createFacilityModal').modal('hide');
} else {
// Show errors
displayValidationErrors(response.errors);
}
},
error: function(xhr) {
showToast('error', 'An error occurred');
}
});
});
// Repeat for every form...
Lines of code per form: ~50-100
After: Low Boilerplate with Conventions
<form data-ajax="true"
data-ajax-update="#modal"
data-action-on-submit="reloadDiv"
data-div="table"
data-url="/refresh"
data-close-top-modal="yes">
<!-- Form fields -->
<partial name="GlobalToast" />
</form>
Lines of code per form: ~10-15
Reduction: 70-85% less code!
Key Conventions That Enable Low-Code
1. Standard Data Attributes
| Attribute | Purpose | Convention |
|---|---|---|
data-ajax="true" | Enable AJAX | Always use for AJAX forms |
data-ajax-update="#target" | Update target | Always specify update target |
data-action-on-submit="action" | Post-submit action | Standard actions: reloadDiv, redirect, none |
data-div="elementId" | Element to refresh | ID of element to refresh |
data-url="/path" | Refresh URL | URL to call for refresh |
data-close-top-modal="yes" | Auto-close modal | Close modal on success |
2. Standard Partial Views
_FormFields.cshtml– Shared form fieldsGlobalToast.cshtml– Toast notifications_ValidationSummary.cshtml– Validation display
3. Standard Controller Patterns
// Convention: Always return PartialView for AJAX requests
if (!ModelState.IsValid)
return PartialView("FormPartial", model);
// Convention: Success messages use "Success" key
ModelState.AddModelError("Success", "Operation completed!");
// Convention: Error messages use "Error" key
ModelState.AddModelError("Error", "Operation failed!");
4. Standard JavaScript Handlers
// handleResponse.js - Processes all form responses
function handleResponse(response) {
// Convention: Process ModelState errors
// Convention: Display toasts
// Convention: Handle post-submit actions
// Convention: Refresh tables
// Convention: Close modals
}
Benefits of Convention-Based Unobtrusive AJAX
1. Reduced Development Time
- Before: 2-4 hours per form (including JavaScript)
- After: 30-60 minutes per form (just markup)
- Time Savings: 75-87%
2. Reduced Code Volume
- Before: ~500-1000 lines per form (including JS)
- After: ~100-200 lines per form (mostly markup)
- Code Reduction: 70-80%
3. Improved Maintainability
- Single point of change for common behavior
- Consistent patterns across application
- Easier onboarding for new developers
4. Fewer Bugs
- Less code = fewer bugs
- Tested conventions = proven patterns
- Consistent behavior = predictable results
5. Better User Experience
- Consistent behavior across forms
- Standard error handling
- Uniform success feedback
Best Practices for Convention-Based Development
1. Document Your Conventions
Create a conventions document that all developers can reference:
## Form Submission Conventions
1. All AJAX forms must include:
- `data-ajax="true"`
- `data-ajax-update="#target"`
- `<partial name="GlobalToast" />`
2. Post-submit actions:
- `data-action-on-submit="reloadDiv"` - Refresh a div
- `data-action-on-submit="redirect"` - Redirect to URL
- `data-action-on-submit="none"` - No action
3. Controller responses:
- Always return `PartialView` for AJAX requests
- Use `ModelState.AddModelError("Success", ...)` for success
- Use `ModelState.AddModelError("Error", ...)` for errors
2. Create Reusable Partials
Extract common patterns into partials:
@* _StandardForm.cshtml *@
@model FormViewModel
<form data-ajax="true"
data-ajax-update="#@Model.ModalId"
data-action-on-submit="@Model.PostSubmitAction"
data-div="@Model.TableId"
data-url="@Model.RefreshUrl"
data-close-top-modal="yes"
asp-controller="@Model.Controller"
asp-action="@Model.Action">
<partial name="@Model.FieldsPartial" model="Model" />
<partial name="GlobalToast" />
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">@Model.SubmitButtonText</button>
</div>
</form>
3. Use Strong Typing
Create view models that enforce conventions:
public class StandardFormViewModel
{
public string ModalId { get; set; }
public string PostSubmitAction { get; set; } = "reloadDiv";
public string TableId { get; set; }
public string RefreshUrl { get; set; }
public string Controller { get; set; }
public string Action { get; set; }
public string FieldsPartial { get; set; }
public string SubmitButtonText { get; set; } = "Save";
}
4. Validate Conventions
Add validation to ensure conventions are followed:
public class FormConventionValidator
{
public static void ValidateForm(HtmlHelper helper)
{
// Check for required data attributes
// Verify GlobalToast partial is included
// Ensure proper controller response pattern
}
}
Real-World Impact: Metrics
In the facility management system example:
| Metric | Before | After | Improvement |
|---|---|---|---|
| Lines of code per form | ~800 | ~150 | 81% reduction |
| Development time per form | 3 hours | 45 minutes | 75% faster |
| JavaScript files | 15+ | 3 | 80% reduction |
| Bug reports | 12/month | 2/month | 83% reduction |
| Onboarding time | 2 weeks | 3 days | 78% faster |
Common Pitfalls and How to Avoid Them
Pitfall 1: Over-Convention
Problem: Creating too many conventions makes the system rigid.
Solution: Focus on the 80/20 rule – convention for common cases, flexibility for edge cases.
Pitfall 2: Undocumented Conventions
Problem: Developers don’t know the conventions exist.
Solution: Document everything and make it easily accessible.
Pitfall 3: Breaking Changes
Problem: Changing a convention breaks many forms.
Solution: Version your conventions and provide migration paths.
Pitfall 4: Ignoring Edge Cases
Problem: Conventions don’t handle all scenarios.
Solution: Allow escape hatches for special cases while maintaining conventions for common cases.
Conclusion
Unobtrusive AJAX combined with convention-based development is a powerful approach to achieving low-code development goals. By establishing clear conventions and leveraging declarative HTML attributes, you can:
- Reduce boilerplate by 70-85%
- Speed up development by 75-87%
- Improve maintainability significantly
- Reduce bugs through consistency
- Enable faster onboarding
The key is to identify common patterns, establish clear conventions, and create reusable components. Start small with a few conventions, document them well, and gradually expand as patterns emerge.
Remember: Conventions are not constraints—they’re accelerators. They free developers from writing repetitive code so they can focus on solving business problems.
Further Reading
- jQuery Unobtrusive AJAX Documentation
- ASP.NET Core Tag Helpers
- Convention Over Configuration
- Low-Code Development Best Practices
About the Author: This article is based on real-world implementation experience in enterprise applications, demonstrating how unobtrusive AJAX and conventions can transform development workflows.
Leave a Reply