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
- Stimulus.js (
@hotwired/stimulus
^3.2.2) - Main JavaScript framework for DOM interaction - Turbo (
@hotwired/turbo
^8.0.4) - Fast navigation and form submission handling - Bootstrap 5 (
bootstrap
^5.3.6) - UI component framework and utilities - Laravel Mix - Asset compilation and bundling system
- Popper.js (
popper.js
^1.16.1) - Tooltip and popover positioning - Guifier (
guifier
^1.0.32) - Additional UI components
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:
- Global Utilities: Makes
KMP_utils
available across the application - Stimulus Application Start: Initializes the Stimulus framework
- Dynamic Controller Registration: Registers all controllers from the global
window.Controllers
object - 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 = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
"/": '/',
};
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:
urlParam(name)
: Extracts URL parameters from the current pagesanitizeString(str)
: Sanitizes HTML strings to prevent XSS attackssanitizeUrl(str)
: URL-encodes special characters for safe URL construction
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:
window.Controllers["ac"] = AutoComplete;
(auto-complete functionality)window.Controllers["modal-opener"] = ModalOpener;
(modal opening functionality)
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:
- Dynamic Discovery: Automatically finds all
*-controller.js
files - Bundling: Combines controllers into
controllers.js
- Vendor Extraction: Separates vendor libraries into
core.js
- CSS Compilation: Processes CSS files
- Versioning: Adds cache-busting hashes
- Source Maps: Generates debugging maps
Framework Benefits
Progressive Enhancement
- Graceful Degradation: Pages work without JavaScript
- Enhanced Experience: JavaScript adds interactivity
- Accessibility: Maintains semantic HTML structure
Performance
- Turbo Navigation: Fast page transitions without full reloads
- Selective Loading: Controllers only load when needed
- Asset Optimization: Bundling and minification reduce load times
Developer Experience
- Convention over Configuration: Clear patterns and structure
- Component Isolation: Controllers are self-contained
- Hot Reloading: Development changes reflected immediately
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
- Create Controller File: Place in
app/assets/js/controllers/name-controller.js
- Follow Naming Convention: Use kebab-case with
-controller.js
suffix - Register Controller: Add to global
window.Controllers
object - 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
- Single Responsibility: Each controller handles one specific behavior
- State Management: Use controller properties for component state
- Event Handling: Prefer declarative action binding over imperative listeners
- Error Handling: Always handle async operation errors
Performance Optimization
- Lazy Loading: Load controllers only when needed
- Efficient Selectors: Use targets instead of querySelector
- Memory Management: Clean up in disconnect() method
- Debouncing: Use delays for expensive operations
Security Considerations
- Input Sanitization: Always sanitize user input using
KMP_utils
- CSRF Protection: Include CSRF tokens in all mutations
- XSS Prevention: Avoid innerHTML with user data
- URL Validation: Validate URLs before navigation
Troubleshooting
Common Issues
- Controller Not Loading: Check registration in
window.Controllers
- Targets Not Found: Verify data-*-target attributes match target names
- Actions Not Firing: Confirm data-action syntax and method names
- Value Changes Ignored: Ensure value callbacks are properly named
Debug Tools
- Stimulus Inspector: Browser extension for debugging Stimulus applications
- Console Logging: Use
console.log
in controller methods - Breakpoints: Set breakpoints in browser developer tools
- Network Tab: Monitor AJAX requests and responses
Future Considerations
Potential Enhancements
- TypeScript Integration: Type safety for larger applications
- Testing Framework: Comprehensive test suite for controllers
- Performance Monitoring: Track controller performance metrics
- Progressive Web App: Service worker integration for offline functionality
This JavaScript framework provides a solid foundation for KMP’s interactive features while maintaining simplicity, performance, and developer productivity.