Custom Bindings
WakaPAC's binding system is fully extensible. You can register custom binding types that integrate seamlessly with built-in bindings, enabling you to create reusable, declarative UI behaviors specific to your application.
Why Custom Bindings?
Custom bindings provide:
- Reusable encapsulation: Define behavior once, apply it consistently across your application
- Declarative templates: Replace imperative DOM manipulation with descriptive binding names
- Uniform syntax: Custom bindings use identical
data-pac-bindsyntax as built-in bindings - Framework independence: Extend functionality without modifying WakaPAC core
Registration API
Function Signature
wakaPAC.registerBinding('bindingName', function(context, element, value) {
// Your binding implementation
});
The handler function receives:
- context: The Context instance managing this component. Contains
context.abstraction(the reactive object),context.container(the root DOM element), and other internal properties. Most custom bindings only need thevalueparameter - element: The DOM element this binding is attached to
- value: The evaluated result of the binding expression from
data-pac-bind
Execution Timing
Your handler executes:
- Immediately during component initialization when WakaPAC scans the DOM
- Automatically whenever the binding expression's dependencies change (reactive updates)
wakaPAC(). Bindings registered after initialization won't be recognized.
Basic Example: Tooltip Binding
A simple binding that manages the title attribute:
<script>
// Register before wakaPAC() initialization
wakaPAC.registerBinding('tooltip', function(context, element, value) {
if (value) {
element.setAttribute('title', value);
} else {
element.removeAttribute('title');
}
});
// Initialize component
wakaPAC('#app', {
helpText: 'Click to submit the form',
updateHelp() {
this.helpText = 'Processing...';
}
});
</script>
<div id="app">
<button data-pac-bind="tooltip: helpText, click: updateHelp">Submit</button>
</div>
When helpText changes, the tooltip updates automatically—no manual DOM manipulation required.
Advanced Examples
Conditional Auto-Focus
Focus an element when a reactive condition becomes true:
<script>
wakaPAC.registerBinding('autoFocus', function(context, element, shouldFocus) {
if (shouldFocus) {
// Defer to next tick to ensure DOM visibility
setTimeout(() => element.focus(), 0);
}
});
wakaPAC('#app', {
searchVisible: false,
toggleSearch() {
this.searchVisible = !this.searchVisible;
}
});
</script>
<div id="app">
<button data-pac-bind="click: toggleSearch">Toggle Search</button>
<div data-pac-bind="visible: searchVisible">
<input data-pac-bind="autoFocus: searchVisible" placeholder="Search...">
</div>
</div>
Why setTimeout? The visible binding may not have finished showing the element when autoFocus runs. Deferring to the next event loop tick ensures the element is focusable.
Animated Number Transitions
Smoothly animate numeric value changes:
<script>
wakaPAC.registerBinding('animateNumber', function(context, element, targetValue) {
// Cancel any in-progress animation
if (element._animationInterval) {
clearInterval(element._animationInterval);
}
const currentValue = parseInt(element.textContent) || 0;
const targetNumber = parseInt(targetValue) || 0;
// No animation needed if values match
if (currentValue === targetNumber) {
return;
}
const difference = targetNumber - currentValue;
const stepSize = difference / 20; // 20 animation frames
let frameCount = 0;
element._animationInterval = setInterval(() => {
frameCount++;
element.textContent = Math.round(currentValue + (stepSize * frameCount));
// Complete animation after 20 frames
if (frameCount >= 20) {
clearInterval(element._animationInterval);
element.textContent = targetNumber; // Ensure exact final value
delete element._animationInterval;
}
}, 50); // 50ms × 20 frames = 1 second total
});
wakaPAC('#app', {
score: 0,
addPoints(amount) {
this.score += amount;
}
});
</script>
<div id="app">
<h2>Score: <span data-pac-bind="animateNumber: score">0</span></h2>
<button data-pac-bind="click: () => addPoints(10)">+10 Points</button>
<button data-pac-bind="click: () => addPoints(50)">+50 Points</button>
<button data-pac-bind="click: () => addPoints(100)">+100 Points</button>
</div>
Implementation notes:
- Stores animation state on the element itself (
element._animationInterval) to handle rapid updates - Cancels previous animations to prevent overlapping transitions
- Ensures final value matches target exactly by setting it explicitly after animation completes
Best Practices
State Management
If your binding needs to track state between updates, store it directly on the element (e.g., element._animationInterval). This prevents state collisions when the same binding is used on multiple elements.
Cleanup
Always clean up resources (intervals, timeouts, event listeners) from previous binding executions before starting new ones. Your handler runs on every reactive update, so leaked resources accumulate quickly.
Synchronization
Use setTimeout(..., 0) when your binding depends on other bindings completing first (like waiting for visible to show an element before focusing it).
Naming
Use descriptive names that clearly indicate what the binding does. Prefer verbs or adjectives: autoFocus, tooltip, animateNumber, not vague names like helper or util.