Skip to the content.

10.1 JavaScript Framework - KMP Frontend Architecture

Note: This documentation has been fact-checked against the actual KMP codebase as of July 19, 2025, to ensure accuracy of implementation details, code examples, and configuration.

Overview

KMP uses a modern JavaScript architecture built on Stimulus.js and Turbo (from the Hotwired suite) for interactive frontend functionality. This provides a progressive enhancement approach where JavaScript controllers are bound to HTML elements through data attributes, creating a clean separation between markup and behavior.

Architecture Components

Core Technologies

Main Entry Point

app/assets/js/index.js

The main JavaScript entry point that initializes the entire frontend system:

// Core framework imports
import 'bootstrap';
import * as Turbo from "@hotwired/turbo"
import { Application, Controller } from "@hotwired/stimulus"
import KMP_utils from './KMP_utils.js';

// Global availability
window.KMP_utils = KMP_utils;
window.Stimulus = Application.start();

// Controller registration system
// load all the controllers that have registered in the window.Controllers object
for (var controller in window.Controllers) {
    Stimulus.register(controller, window.Controllers[controller]);
}

// Bootstrap tooltip activation
//activate boostrap tooltips
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))

Key Features:

  1. Global Utilities: Makes KMP_utils available across the application
  2. Stimulus Application Start: Initializes the Stimulus framework
  3. Dynamic Controller Registration: Registers all controllers from the global window.Controllers object
  4. Bootstrap Integration: Automatically initializes Bootstrap tooltips

KMP Utilities

app/assets/js/KMP_utils.js

A collection of utility functions used throughout the KMP application:

export default {
    // URL parameter extraction
    urlParam(name) {
        var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
        var result = null;
        if (results) {
            result = decodeURIComponent(results[1]);
        }
        return result;
    },

    // HTML string sanitization
    sanitizeString(str) {
        const map = {
            '&': '&',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;',
            "'": '&#x27;',
            "/": '&#x2F;',
        };
        const reg = /[&<>"'/]/ig;
        return str.replace(reg, (match) => (map[match]));
    },

    // URL string sanitization
    sanitizeUrl(str) {
        const map = {
            '<': '%3C',
            '>': '%3E',
            '"': '%22',
            "'": '%27',
            ' ': '%20',
        };
        const reg = /[<>"' ]/ig;
        return str.replace(reg, (match) => (map[match]));
    }
};

Utility Functions:

Stimulus.js Integration

Controller Registration Pattern

KMP uses a centralized controller registration system. Controllers register themselves in the global window.Controllers object:

// Example controller registration (from individual controller files)
if (!window.Controllers) {
    window.Controllers = {};
}
window.Controllers["controller-name"] = ControllerClass;

Note: Controller names in the registration often use abbreviated forms (e.g., "ac" for AutoComplete) rather than full descriptive names. The actual registration examples from KMP controllers:

Standard Controller Structure

All KMP Stimulus controllers follow this pattern:

import { Controller } from "@hotwired/stimulus"

class ExampleController extends Controller {
    // Define targets - elements your controller interacts with
    static targets = ["input", "output", "button"]
    
    // Define values - properties that can be set from HTML
    static values = {
        url: String,
        delay: { type: Number, default: 300 },
        enabled: { type: Boolean, default: true }
    }
    
    // Define outlets - connections to other controllers
    static outlets = ["other-controller"]
    
    // Initialize function (optional)
    initialize() {
        this._privateState = {};
    }
    
    // Connect function - runs when controller connects to DOM
    connect() {
        // Setup event listeners, initialize state
    }
    
    // Event handler methods
    handleEvent(event) {
        // Handle DOM events
    }
    
    // Value change callbacks
    urlValueChanged(newValue, oldValue) {
        // React to value changes
    }
    
    // Target connected/disconnected callbacks
    inputTargetConnected(target) {
        // React to target connections
    }
    
    // Disconnect function - cleanup when controller disconnects
    disconnect() {
        // Cleanup event listeners, timers, etc.
    }
}

// Register controller globally
if (!window.Controllers) {
    window.Controllers = {};
}
window.Controllers["example"] = ExampleController;

HTML Integration

Controllers are bound to HTML elements using data attributes:

<div data-controller="example" 
     data-example-url-value="/api/endpoint"
     data-example-delay-value="500"
     data-example-enabled-value="true">
     
  <!-- Targets -->
  <input data-example-target="input" type="text">
  <div data-example-target="output"></div>
  
  <!-- Actions -->
  <button data-action="click->example#handleEvent" 
          data-example-target="button">
    Submit
  </button>
</div>

Asset Compilation

app/webpack.mix.js

Laravel Mix configuration for asset compilation and bundling:

const mix = require('laravel-mix');
const webpack = require('webpack');
const fs = require('fs');
const path = require('path');

// Dynamic controller discovery
function getJsFilesFromDir(startPath, skiplist, filter, callback) {
    if (!fs.existsSync(startPath)) {
        console.log("Directory not found: ", startPath);
        return;
    }

    const files = fs.readdirSync(startPath);
    files.forEach(file => {
        const filename = path.join(startPath, file);
        if (skiplist.some((skip) => filename.includes(skip))) {
            return;
        }
        const stat = fs.lstatSync(filename);
        if (stat.isDirectory()) {
            getJsFilesFromDir(filename, skipList, filter, callback); // Recursive call
        } else if (filename.endsWith(filter)) {
            callback(filename);
        }
    });
}

// Collect all controller files
const files = []
const skipList = ['node_modules', 'webroot'];

// Find controllers in main app and plugins
getJsFilesFromDir('./assets/js', skipList, '-controller.js', (filename) => {
    files.push(filename);
});
getJsFilesFromDir('./plugins', skipList, '-controller.js', (filename) => {
    files.push(filename);
});

// Mix configuration
mix.setPublicPath('./webroot')
    .js(files, 'webroot/js/controllers.js')                    // Bundle all controllers
    .js('assets/js/index.js', 'webroot/js')                   // Main entry point
    .extract([                                                 // Extract vendor libraries
        'bootstrap', 
        'popper.js', 
        '@hotwired/turbo', 
        '@hotwired/stimulus', 
        '@hotwired/stimulus-webpack-helpers'
    ], 'webroot/js/core.js')
    .webpackConfig({
        devtool: "source-map",
        optimization: {
            runtimeChunk: true
        },
        plugins: [
            new webpack.ProvidePlugin({
                'bootstrap': 'bootstrap',
            }),
        ],
    })
    .css('assets/css/app.css', 'webroot/css')                 // Compile main CSS
    .css('assets/css/signin.css', 'webroot/css')              // Compile signin CSS
    .css('assets/css/cover.css', 'webroot/css')               // Compile cover CSS
    .css('assets/css/dashboard.css', 'webroot/css')           // Compile dashboard CSS
    .version()                                                 // Add version hashing
    .sourceMaps();                                             // Generate source maps

Build Process:

  1. Dynamic Discovery: Automatically finds all *-controller.js files
  2. Bundling: Combines controllers into controllers.js
  3. Vendor Extraction: Separates vendor libraries into core.js
  4. CSS Compilation: Processes CSS files
  5. Versioning: Adds cache-busting hashes
  6. Source Maps: Generates debugging maps

Framework Benefits

Progressive Enhancement

Performance

Developer Experience

Integration with CakePHP

View Integration

JavaScript controllers integrate seamlessly with CakePHP views:

// In CakePHP template
echo $this->Html->div(
    '',
    $content,
    [
        'data-controller' => 'auto-complete',
        'data-auto-complete-url-value' => $this->Url->build([
            'controller' => 'Members',
            'action' => 'search'
        ])
    ]
);

AJAX Integration

Controllers make AJAX requests to CakePHP controllers:

// In Stimulus controller
async fetchData() {
    try {
        const response = await fetch(this.urlValue, {
            headers: {
                'Accept': 'application/json',
                'X-Requested-With': 'XMLHttpRequest'
            }
        });
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const data = await response.json();
        this.updateUI(data);
    } catch (error) {
        console.error('Fetch error:', error);
    }
}

CSRF Protection

CSRF tokens are automatically included in AJAX requests:

// CSRF token handling
const csrfToken = document.querySelector('meta[name="csrfToken"]').content;

fetch(url, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrfToken
    },
    body: JSON.stringify(data)
});

Development Workflow

Creating New Controllers

  1. Create Controller File: Place in app/assets/js/controllers/name-controller.js
  2. Follow Naming Convention: Use kebab-case with -controller.js suffix
  3. Register Controller: Add to global window.Controllers object
  4. Test Integration: Verify controller loads and functions correctly

Testing Controllers

Controllers can be tested using Jest:

// Example controller test
import { Application } from "@hotwired/stimulus"
import ExampleController from "../controllers/example-controller.js"

describe("ExampleController", () => {
    let application;
    
    beforeEach(() => {
        application = Application.start();
        application.register("example", ExampleController);
    });
    
    it("should handle events correctly", () => {
        // Test implementation
    });
});

Best Practices

Controller Design

Performance Optimization

Security Considerations

Troubleshooting

Common Issues

  1. Controller Not Loading: Check registration in window.Controllers
  2. Targets Not Found: Verify data-*-target attributes match target names
  3. Actions Not Firing: Confirm data-action syntax and method names
  4. Value Changes Ignored: Ensure value callbacks are properly named

Debug Tools

Future Considerations

Potential Enhancements

This JavaScript framework provides a solid foundation for KMP’s interactive features while maintaining simplicity, performance, and developer productivity.