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-bind syntax as built-in bindings
  • Framework independence: Extend functionality without modifying WakaPAC core
When to create custom bindings: If you're duplicating DOM manipulation logic, need specialized UI behavior beyond built-in bindings, or want to abstract complex interactions into simple declarative names, create a custom binding.

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 the value parameter
  • 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)
Registration order matters: Register custom bindings before calling 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.