AJAX Navigation Configuration
Learn how to configure and customize AJAX navigation in your Odac application.
Prerequisites
Before configuring AJAX navigation, ensure your skeleton template is properly set up. See the Quick Start guide for skeleton setup instructions.
Key requirements:
- Skeleton placeholders must be UPPERCASE and wrapped in HTML tags (e.g.,
<main></main>) - Controller keys are lowercase (e.g.,
content: 'home') - Frontend selectors target the HTML tags (e.g.,
content: 'main'targets<main>tag)
Mapping example:
- Skeleton:
<main></main> - Controller:
content: 'home' - Frontend:
update: { content: 'main' }
Configuration Options
Basic Configuration
The simplest way to enable AJAX navigation:
Odac.action({
navigate: 'main' // Update <main> element
})
Full Configuration
All available options:
Odac.action({
navigate: {
// Which links to intercept
links: 'a[href^="/"]', // Default: all internal links
// Which elements to update
update: {
content: 'main',
header: 'header',
sidebar: '#sidebar'
},
// Callback after navigation
on: function(page, variables) {
console.log('Navigated to:', page)
console.log('Server data:', variables)
}
}
})
Link Selectors
Default Behavior
By default, all internal links (starting with /) are intercepted:
navigate: true // Uses 'a[href^="/"]'
Custom Selectors
Target specific links:
navigate: {
links: 'nav a', // Only navigation links
update: 'main'
}
navigate: {
links: 'a.ajax-link', // Only links with class
update: 'main'
}
Excluding Links
Exclude specific links from AJAX navigation:
<!-- Using data attribute -->
<a href="/download.pdf" data-navigate="false">Download</a>
<!-- Using CSS class -->
<a href="/logout" class="no-navigate">Logout</a>
Both methods work automatically - no additional configuration needed!
Element Updates
Single Element
Update one element:
navigate: 'main' // Shorthand
// or
navigate: {
update: 'main'
}
Multiple Elements
Update multiple elements simultaneously:
navigate: {
update: {
content: 'main',
header: 'header',
sidebar: '#sidebar',
breadcrumb: '.breadcrumb'
}
}
The keys (content, header, etc.) must match the view parts defined in your controller:
// In controller
odac.View.set({
header: 'main',
content: 'about',
sidebar: 'main'
})
Callbacks
Navigation Callback
Execute code after each navigation:
navigate: {
update: 'main',
on: function(page, variables) {
// page: current page name (e.g., 'about')
// variables: data from server
// Update page title
document.title = variables.title || page
// Track analytics
if (window.gtag) {
gtag('event', 'page_view', {
page_path: window.location.pathname
})
}
// Update active navigation
updateActiveNav()
}
}
Page-Specific Actions
Run code for specific pages:
Odac.action({
navigate: {
update: 'main',
on: (page, vars) => console.log('Navigated to:', page)
},
page: {
index: function(variables) {
console.log('Home page loaded')
},
about: function(variables) {
console.log('About page:', variables.title)
}
}
})
Disabling Navigation
Disable Completely
Odac.action({
navigate: false // No AJAX navigation
})
Disable for Specific Links
<!-- Data attribute -->
<a href="/download" data-navigate="false">Download</a>
<!-- CSS class -->
<a href="/logout" class="no-navigate">Logout</a>
<!-- External links (automatic) -->
<a href="https://example.com">External</a>
Server-Side Configuration
Sending Variables
In your controller, send data to the client:
module.exports = function(Odac) {
// Set variables for AJAX responses
odac.set({
title: 'About Page',
user: {name: 'John', role: 'admin'},
stats: {views: 1234}
}, true) // true = include in AJAX
odac.View.skeleton('main')
odac.View.set({
header: 'main',
content: 'about',
footer: 'main'
})
}
Accessing Variables
Access server variables in your callback:
navigate: {
update: 'main',
on: function(page, variables) {
console.log(variables.title) // 'About Page'
console.log(variables.user.name) // 'John'
console.log(variables.stats.views) // 1234
}
}
Advanced Examples
Loading Indicator
Show a loading spinner during navigation:
Odac.action({
navigate: {
update: 'main',
on: (page, vars) => {
hideLoadingSpinner()
}
},
click: {
'a[href^="/"]': function(e) {
if (!this.hasAttribute('data-navigate') &&
!this.classList.contains('no-navigate')) {
showLoadingSpinner()
}
}
}
})
function showLoadingSpinner() {
document.getElementById('spinner').style.display = 'block'
}
function hideLoadingSpinner() {
document.getElementById('spinner').style.display = 'none'
}
Progress Bar
Animated progress bar:
let progressBar = document.getElementById('progress')
Odac.action({
navigate: {
update: 'main',
on: () => {
progressBar.style.width = '100%'
setTimeout(() => {
progressBar.style.width = '0%'
}, 300)
}
},
click: {
'a[href^="/"]': function() {
if (!this.hasAttribute('data-navigate')) {
progressBar.style.width = '30%'
}
}
}
})
Conditional Navigation
Confirm before navigating:
Odac.action({
navigate: {
update: 'main'
},
click: {
'a[href^="/"]': function(e) {
if (hasUnsavedChanges() &&
!this.hasAttribute('data-navigate')) {
if (!confirm('You have unsaved changes. Continue?')) {
e.preventDefault()
e.stopPropagation()
}
}
}
}
})
Best Practices
- Keep it Simple: Start with minimal configuration
- Progressive Enhancement: Ensure links work without JavaScript
- Loading States: Always show loading indicators
- Error Handling: Handle failed requests gracefully
- SEO: Ensure content is accessible to search engines
- Performance: Only update necessary elements
Troubleshooting
Links Not Working
- Check that
navigateis enabled - Verify link selector matches your links
- Check browser console for errors
Elements Not Updating
- Ensure element selectors are correct
- Verify view parts are defined in controller
- Check that skeleton has correct placeholders
Variables Not Available
- Confirm
Odac.set(data, true)hastrueparameter - Check variables are set before
View.print()
Next Steps
- Learn about Form Handling
- Explore API Requests
- Check Advanced Usage