10. JavaScript Development with Stimulus
10.1 Introduction to Stimulus
KMP uses the Stimulus JavaScript framework to enhance the user interface with dynamic behavior. Stimulus is a modest framework designed to augment your HTML with just enough JavaScript to make it interactive.
Stimulus works by automatically connecting DOM elements to JavaScript objects. When you add data-controller
attributes to your HTML, Stimulus automatically instantiates the corresponding controller class and keeps it connected to the element.
10.2 Controller Organization
In KMP, all Stimulus controllers for the main application should be stored in the app/assets/js/controllers
directory with filenames that follow the pattern {name}-controller.js
. For example:
app/assets/js/controllers/nav-bar-controller.js
app/assets/js/controllers/member-card-profile-controller.js
app/assets/js/controllers/auto-complete-controller.js
For plugin-specific controllers, use:
plugins/PluginName/assets/js/controllers/{name}-controller.js
Each controller follows the Stimulus naming convention, where the filename corresponds to the controller identifier used in the HTML. This ensures consistency and automatic registration in the build process.
Here’s an example of a typical Stimulus controller:
// app/assets/js/controllers/example-controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["output"]
connect() {
console.log("Controller connected to element")
}
greet() {
this.outputTarget.textContent = "Hello, Stimulus!"
}
}
In your HTML, you would connect this controller like this:
<div data-controller="example">
<button data-action="click->example#greet">Greet</button>
<span data-example-target="output"></span>
</div>
10.3 Development Workflow
KMP uses Laravel Mix (based on webpack) to compile JavaScript and CSS assets. The workflow for developing JavaScript in KMP is as follows:
- Make sure you have Node.js installed and run
npm install
in theapp
directory to install dependencies - Start the development server using
npm run watch
- Make changes to your JavaScript files in the
app/assets/js
directory - The watch process will automatically detect changes, recompile assets, and refresh your browser
The npm run watch
command keeps running and watches for changes in your JavaScript and CSS files. It will automatically recompile and update your browser when you save changes to any asset file.
# Navigate to app directory
cd app
# Install dependencies (if not already installed)
npm install
# Start the watch process
npm run watch
10.4 Asset Management
KMP uses Laravel Mix and webpack to manage frontend assets. All source JavaScript and CSS files should be stored in the following directories:
- JavaScript:
app/assets/js/
- CSS:
app/assets/css/
These files are then compiled and output to the public directory for deployment:
- JavaScript: compiled to
app/webroot/js/
- CSS: compiled to
app/webroot/css/
Webpack automatically bundles your JavaScript modules and their dependencies into optimized packages. The build process is configured in app/webpack.mix.js
.
Adding a New Stimulus Controller
To add a new Stimulus controller:
- For the main app, create a new file in
app/assets/js/controllers/
namedyour-feature-controller.js
. - For a plugin, create it in
plugins/PluginName/assets/js/controllers/
. - Implement your controller using the Stimulus pattern.
- The controller will be automatically included in the build process.
The webpack configuration automatically detects all files ending with -controller.js
in the appropriate controllers directory and includes them in the build.
Main JavaScript Entry Points
The main JavaScript entry points are:
app/assets/js/index.js
: The main application JavaScriptapp/assets/js/controllers/*-controller.js
: Individual Stimulus controllers
These files are compiled into the final JavaScript bundles that are loaded by the application.
10.5 Core Stimulus Controllers
KMP includes several Stimulus controllers that provide essential functionality across the application. These controllers are organized by functionality and serve as both working components and examples of Stimulus patterns.
10.5.1 Form Management Controllers
App Setting Form Controller (app-setting-form-controller.js
)
Purpose: Manages application settings forms with validation and submission handling.
Features:
- Form submission control with preventDefault
- Submit button state management
- Form validation integration
Targets:
submitBtn
: The submit button elementform
: The form element to submit
Usage Example:
<form data-controller="app-setting-form" data-app-setting-form-target="form">
<!-- form fields -->
<button type="button"
data-app-setting-form-target="submitBtn"
data-action="click->app-setting-form#submit"
disabled>
Save Settings
</button>
</form>
Key Methods:
submit(event)
: Handles form submission with proper event handlingenableSubmit()
: Enables and focuses the submit button
Note: This controller uses CommonJS require syntax rather than ES6 imports.
Branch Links Controller (branch-links-controller.js
)
Purpose: Manages dynamic collection of branch links with URL validation and type categorization.
Features:
- Dynamic link addition and removal
- URL sanitization using KMP_utils
- Link type management with Bootstrap icons
- Duplicate prevention
- JSON form field integration
Targets:
new
: Input field for new URLsformValue
: Hidden field containing JSON datadisplayList
: Container for displaying current linkslinkType
: Element showing current link type
Usage Example:
<div data-controller="branch-links">
<div class="input-group mb-3">
<input type="url" data-branch-links-target="new" placeholder="Enter URL" class="form-control">
<button type="button" data-action="click->branch-links#add" class="btn btn-primary">
Add Link
</button>
</div>
<div data-branch-links-target="displayList"></div>
<input type="hidden" name="branch_links" data-branch-links-target="formValue">
</div>
Key Methods:
add(event)
: Adds new link with validation and sanitizationremove(event)
: Removes link from collectionsetLinkType(event)
: Changes link type with icon updatecreateListItem(item)
: Creates DOM elements for link display
10.5.2 Data Input Controllers
Auto Complete Controller (auto-complete-controller.js
)
Purpose: Advanced autocomplete functionality with AJAX search, keyboard navigation, and dynamic options.
Features:
- Remote data loading via AJAX
- Local data filtering from datalist
- Keyboard navigation (arrow keys, enter, escape)
- Custom value support with
allowOther
option - Hidden field integration for form submission
- Debounced search to prevent excessive requests
- Accessibility features with ARIA attributes
Targets:
input
: The visible input fieldhidden
: Hidden field for form submission valuehiddenText
: Hidden field for display textresults
: Container for autocomplete resultsdataList
: Optional local data sourceclearBtn
: Button to clear selection
Values:
url
: AJAX endpoint for remote dataminLength
: Minimum characters before search (default: 1)delay
: Debounce delay in milliseconds (default: 300)allowOther
: Allow custom values not in listsubmitOnEnter
: Submit form on Enter keyqueryParam
: Query parameter name (default: “q”)
Usage Example:
<div data-controller="ac"
data-ac-url-value="/api/members/search"
data-ac-min-length-value="2"
data-ac-allow-other-value="false">
<input type="text"
data-ac-target="input"
placeholder="Search members..."
class="form-control">
<input type="hidden" name="member_id" data-ac-target="hidden">
<input type="hidden" data-ac-target="hiddenText">
<ul data-ac-target="results" class="list-group" hidden></ul>
<button type="button" data-ac-target="clearBtn" class="btn btn-sm">Clear</button>
</div>
Key Methods:
fetchResults(query)
: Handles AJAX or local searchcommit(selected)
: Processes selection and updates form fieldsclear()
: Resets the autocomplete to empty state- Keyboard handlers:
onArrowDownKeydown
,onArrowUpKeydown
,onEnterKeydown
,onEscapeKeydown
Events:
autocomplete.change
: Fired when selection changesready
: Fired when controller is initializedloadstart/load/loadend
: AJAX loading events
10.5.3 File Management Controllers
CSV Download Controller (csv-download-controller.js
)
Purpose: Handles CSV file downloads with AJAX fetch and browser download management.
Features:
- AJAX file retrieval to avoid page navigation
- Blob processing for client-side download
- Custom filename support
- Error handling with user feedback
- Automatic cleanup of temporary URLs
Values:
url
: Download endpoint URLfilename
: Custom filename for download
Targets:
button
: Optional specific button target
Usage Example:
<!-- Method 1: Using href attribute -->
<a href="/export/members.csv"
data-controller="csv-download"
data-csv-download-filename-value="members_export.csv"
class="btn btn-primary">
Download CSV
</a>
<!-- Method 2: Using data attributes -->
<button data-controller="csv-download"
data-csv-download-url-value="/api/export/branches"
data-csv-download-filename-value="branches.csv"
data-action="click->csv-download#download"
class="btn btn-success">
Export Branches
</button>
Key Methods:
download(event)
: Handles the complete download workflow- Error handling with user-friendly alert messages
- Automatic DOM cleanup after download
Image Preview Controller (image-preview-controller.js
)
Purpose: Provides image preview functionality for file uploads.
Features:
- Real-time image preview on file selection
- Object URL management for blob display
- Loading state management
- Automatic cleanup of temporary URLs
Targets:
file
: File input elementpreview
: Image element for preview displayloading
: Loading indicator element
Usage Example:
<div data-controller="image-preview">
<input type="file"
accept="image/*"
data-image-preview-target="file"
data-action="change->image-preview#preview"
class="form-control">
<div data-image-preview-target="loading" class="spinner-border">
Loading...
</div>
<img data-image-preview-target="preview"
class="preview-image"
alt="Preview"
hidden>
</div>
Key Methods:
preview(event)
: Handles file selection and creates preview using Object URLs
Kanban Controller (kanban-controller.js
)
Purpose: Implements drag-and-drop Kanban board functionality with server synchronization.
Features:
- Drag-and-drop card movement between columns
- Server-side position updates via AJAX
- Visual feedback during drag operations
- Position restoration on failed moves
- CSRF token integration
- Custom validation callbacks
Targets:
card
: Draggable card elementscolumn
: Sortable column containers
Values:
csrfToken
: CSRF token for secure AJAX requestsurl
: API endpoint for position updates
Usage Example:
<div data-controller="kanban"
data-kanban-url-value="/api/kanban/update"
data-kanban-csrf-token-value="<%= $this->request->getAttribute('csrfToken') %>">
<div class="kanban-column sortable" data-col="todo" data-kanban-target="column">
<div class="card"
data-kanban-target="card"
data-rec-id="1"
data-stack-rank="100"
draggable="true"
data-action="dragstart->kanban#grabCard dragend->kanban#dropCard">
<div class="card-body">Task 1</div>
</div>
</div>
<div class="kanban-column sortable" data-col="in-progress" data-kanban-target="column">
<!-- More cards -->
</div>
</div>
Key Methods:
grabCard(event)
: Initiates drag operation and stores original positiondropCard(event)
: Completes drag operation and syncs with serverprocessDrag(event, isDrop)
: Handles drag logic and position calculationsrestoreOriginalPosition()
: Restores card to original position on failureregisterBeforeDrop(callback)
: Allows custom validation before drop operations
10.5.4 Interface Controllers
10.5.4 Interface Controllers
Filter Grid Controller (filter-grid-controller.js
)
Purpose: Manages grid filtering with automatic form submission.
Features:
- Automatic form submission on filter changes
- Integration with CakePHP pagination
- Grid refresh without page reload
Usage Example:
<form data-controller="filter-grid" method="get">
<input type="text" name="search" placeholder="Filter results...">
<select name="status" data-action="change->filter-grid#submitForm">
<option value="">All</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
</form>
Key Methods:
submitForm(event)
: Submits the form to refresh grid data
Modal Opener Controller (modal-opener-controller.js
)
Purpose: Programmatically opens Bootstrap modals based on value changes.
Features:
- Automatic modal triggering via value changes
- Integration with Bootstrap modal system
- Event-driven modal activation
Values:
modalBtn
: ID of modal button element to trigger
Usage Example:
<!-- Modal trigger controller -->
<div data-controller="modal-opener"
data-modal-opener-modal-btn-value="">
<!-- This controller watches for modalBtn value changes -->
</div>
<!-- Hidden modal trigger button -->
<button id="hidden-modal-trigger"
data-bs-toggle="modal"
data-bs-target="#confirmModal"
style="display: none;">
</button>
<!-- Modal -->
<div class="modal fade" id="confirmModal">
<div class="modal-dialog">
<!-- Modal content -->
</div>
</div>
<script>
// Trigger modal programmatically
const controller = document.querySelector('[data-controller="modal-opener"]');
controller.setAttribute('data-modal-opener-modal-btn-value', 'hidden-modal-trigger');
</script>
Key Methods:
modalBtnValueChanged()
: Automatically triggered when modalBtn value changes, clicks the specified modal button
Navigation Bar Controller (nav-bar-controller.js
)
Purpose: Manages navigation bar interactions with expand/collapse state tracking.
Features:
- Navigation item expand/collapse state tracking
- AJAX requests for state persistence
- Automatic event listener management
- Server-side state synchronization
Targets:
navHeader
: Navigation header elements with expand/collapse functionality
Usage Example:
<nav data-controller="nav-bar">
<button class="nav-link"
data-nav-bar-target="navHeader"
data-expand-url="/nav/expand/123"
data-collapse-url="/nav/collapse/123"
aria-expanded="false"
data-bs-toggle="collapse"
data-bs-target="#navSection123">
Section Header
</button>
<div id="navSection123" class="collapse">
<!-- Navigation content -->
</div>
</nav>
Key Methods:
navHeaderClicked(event)
: Handles navigation header clicks and sends state to servernavHeaderTargetConnected(event)
: Sets up event listeners for header elementsnavHeaderTargetDisconnected(event)
: Cleanup event listeners
Detail Tabs Controller (detail-tabs-controller.js
)
Purpose: Manages tabbed interfaces for detailed views with URL state management.
Features:
- URL-based tab state management
- Browser history integration
- Automatic first tab selection
- Turbo frame reload integration
- Scroll position management
Targets:
tabBtn
: Tab button elementstabContent
: Tab content areas
Values:
updateUrl
: Whether to update URL on tab changes (default: true)
Usage Example:
<div data-controller="detail-tabs" data-detail-tabs-update-url-value="true">
<ul class="nav nav-tabs">
<li class="nav-item">
<button class="nav-link"
id="nav-profile-tab"
data-detail-tabs-target="tabBtn">
Profile
</button>
</li>
<li class="nav-item">
<button class="nav-link"
id="nav-history-tab"
data-detail-tabs-target="tabBtn">
History
</button>
</li>
</ul>
<!-- Tab content areas -->
<div class="tab-content">
<turbo-frame id="profile-frame" data-detail-tabs-target="tabContent">
<!-- Profile content -->
</turbo-frame>
<turbo-frame id="history-frame" data-detail-tabs-target="tabContent">
<!-- History content -->
</turbo-frame>
</div>
</div>
Key Methods:
tabBtnTargetConnected(event)
: Sets up tab button and handles URL-based tab selectiontabBtnClicked(event)
: Handles tab clicks and updates URL statetabBtnTargetDisconnected(event)
: Cleanup event listeners
Modal Opener Controller (modal-opener-controller.js
)
Purpose: Manages Bootstrap modal dialogs with dynamic content loading.
[This controller would need to be analyzed for full documentation]
Detail Tabs Controller (detail-tabs-controller.js
)
Purpose: Manages tabbed interfaces for detailed views with URL state management.
Features:
- URL-based tab state management
- Browser history integration
- Automatic first tab selection
- Turbo frame reload integration
- Scroll position management
Targets:
tabBtn
: Tab button elementstabContent
: Tab content areas
Values:
updateUrl
: Whether to update URL on tab changes (default: true)
Usage Example:
<div data-controller="detail-tabs" data-detail-tabs-update-url-value="true">
<ul class="nav nav-tabs">
<li class="nav-item">
<button class="nav-link"
id="nav-profile-tab"
data-detail-tabs-target="tabBtn">
Profile
</button>
</li>
<li class="nav-item">
<button class="nav-link"
id="nav-history-tab"
data-detail-tabs-target="tabBtn">
History
</button>
</li>
</ul>
<!-- Tab content areas -->
<div class="tab-content">
<turbo-frame id="profile-frame" data-detail-tabs-target="tabContent">
<!-- Profile content -->
</turbo-frame>
<turbo-frame id="history-frame" data-detail-tabs-target="tabContent">
<!-- History content -->
</turbo-frame>
</div>
</div>
Key Methods:
tabBtnTargetConnected(event)
: Sets up tab button and handles URL-based tab selectiontabBtnClicked(event)
: Handles tab clicks and updates URL statetabBtnTargetDisconnected(event)
: Cleanup event listeners
Guifier Controller (guifier-controller.js
)
Purpose: Integrates the Guifier library for dynamic form generation and JSON data editing.
Features:
- Dynamic form generation from JSON schema
- Real-time data updates
- Fullscreen editing interface
- Automatic container collapse
- Change event propagation
Targets:
hidden
: Hidden input field containing JSON datacontainer
: Container element for Guifier interface
Values:
type
: Data type for Guifier (e.g., “json”, “yaml”)
Usage Example:
<div data-controller="guifier-control"
data-guifier-control-type-value="json">
<div data-guifier-control-target="container" id="guifier-container"></div>
<input type="hidden"
name="settings_data"
data-guifier-control-target="hidden"
value='{"key": "value"}'>
</div>
Key Methods:
connect()
: Initializes Guifier instance with configuration and change handlers
10.5.5 Member Management Controllers
10.5.5 Member Management Controllers
Member Card Profile Controller (member-card-profile-controller.js
)
Purpose: Manages dynamic member profile card generation with multi-card layout support.
Features:
- AJAX data loading for member profile information
- Multi-card layout with automatic overflow handling
- Dynamic content organization by plugin sections
- Card height management and space calculation
- Real-time membership and background check status
Targets:
cardSet
: Container for all profile cardsfirstCard
: Primary card templatename
,scaName
,branchName
: Member identification fieldsmembershipInfo
,backgroundCheck
: Status information fieldslastUpdate
: Timestamp fieldloading
: Loading indicatormemberDetails
: Main details container
Values:
url
: API endpoint for member data
Usage Example:
<div data-controller="member-card-profile"
data-member-card-profile-url-value="/api/members/123/card-data">
<div data-member-card-profile-target="loading" class="spinner-border">
Loading...
</div>
<div data-member-card-profile-target="memberDetails" class="member-profile" hidden>
<div data-member-card-profile-target="cardSet" class="card-set">
<div data-member-card-profile-target="firstCard" class="auth_card">
<div class="cardbox">
<h2 data-member-card-profile-target="name"></h2>
<h3 data-member-card-profile-target="scaName"></h3>
<p data-member-card-profile-target="branchName"></p>
<div data-member-card-profile-target="membershipInfo"></div>
<div data-member-card-profile-target="backgroundCheck"></div>
<div data-member-card-profile-target="lastUpdate"></div>
</div>
</div>
</div>
</div>
</div>
Key Methods:
loadCard()
: Initiates AJAX request and populates member datastartCard()
: Creates new card when current card is fullappendToCard(element, minSpace)
: Adds content with overflow managementusedSpaceInCard()
: Calculates current card space usage
Member Mobile Card Profile Controller (member-mobile-card-profile-controller.js
)
Purpose: Specialized mobile interface for member profile cards with PWA integration.
Features:
- Mobile-optimized card layout with Bootstrap styling
- PWA readiness integration
- Responsive table layout for member data
- Dynamic card generation for plugin sections
- Automatic data formatting and organization
Targets:
cardSet
: Container for mobile cardsname
,scaName
,branchName
: Member identificationmembershipInfo
,backgroundCheck
: Status fieldslastUpdate
: Update timestamploading
: Loading state indicatormemberDetails
: Main content container
Values:
url
: API endpoint for member datapwaReady
: PWA readiness flag
Usage Example:
<div data-controller="member-mobile-card-profile"
data-member-mobile-card-profile-url-value="/api/members/123/mobile"
data-member-mobile-card-profile-pwa-ready-value="false">
<div data-member-mobile-card-profile-target="loading" class="text-center">
<div class="spinner-border"></div>
</div>
<div data-member-mobile-card-profile-target="memberDetails" hidden>
<div class="text-center mb-3">
<h1 data-member-mobile-card-profile-target="name"></h1>
<h2 data-member-mobile-card-profile-target="scaName"></h2>
<h3 data-member-mobile-card-profile-target="branchName"></h3>
<p data-member-mobile-card-profile-target="membershipInfo"></p>
<p data-member-mobile-card-profile-target="backgroundCheck"></p>
<small data-member-mobile-card-profile-target="lastUpdate"></small>
</div>
<div data-member-mobile-card-profile-target="cardSet"></div>
</div>
</div>
Key Methods:
loadCard()
: Loads and formats member data for mobile displaystartCard(title)
: Creates new mobile-optimized card sectionspwaReadyValueChanged()
: Responds to PWA state changes
Member Mobile Card PWA Controller (member-mobile-card-pwa-controller.js
)
Purpose: Manages Progressive Web App functionality for offline member card access.
Features:
- Service Worker registration and management
- Offline/online status monitoring
- URL caching for offline access
- Automatic refresh when online
- Status indicator management
Targets:
urlCache
: Cache configuration datastatus
: Online/offline status indicatorrefreshBtn
: Manual refresh button
Values:
swUrl
: Service Worker script URL
Usage Example:
<div data-controller="member-mobile-card-pwa"
data-member-mobile-card-pwa-sw-url-value="/service-worker.js">
<script type="application/json" data-member-mobile-card-pwa-target="urlCache">
["/api/members/profile", "/offline-assets/"]
</script>
<div class="d-flex align-items-center mb-3">
<span class="badge me-2" data-member-mobile-card-pwa-target="status">Checking...</span>
<button class="btn btn-sm btn-outline-primary"
data-member-mobile-card-pwa-target="refreshBtn"
data-action="click->member-mobile-card-pwa#refreshPageIfOnline">
Refresh
</button>
</div>
</div>
Key Methods:
manageOnlineStatus()
: Sets up online/offline monitoring and Service WorkerupdateOnlineStatus()
: Updates UI based on connection statusrefreshPageIfOnline()
: Refreshes page when connection is available
Member Unique Email Controller (member-unique-email-controller.js
)
Purpose: Validates email uniqueness during member registration/editing with real-time feedback.
Features:
- Real-time email uniqueness validation
- AJAX validation against server endpoint
- Visual feedback with Bootstrap validation classes
- Original email comparison for edits
- Custom validity messaging
Values:
url
: Email validation endpoint
Usage Example:
<input type="email"
name="email"
class="form-control"
data-controller="member-unique-email"
data-member-unique-email-url-value="/api/members/check-email"
data-original-value="current@email.com"
required>
<div class="invalid-feedback">
This email address is already taken.
</div>
<div class="valid-feedback">
Email address is available.
</div>
Key Methods:
checkEmail(event)
: Validates email uniqueness via AJAX- Automatic Bootstrap validation class management
- Custom validity message setting
Member Verify Form Controller (member-verify-form-controller.js
)
Purpose: Handles member verification form workflows with conditional field management.
Features:
- Conditional field enabling/disabling
- Parent member verification toggle
- Membership information toggle
- Form field state management
Targets:
scaMember
: SCA member checkbox fieldmembershipNumber
: Membership number fieldmembershipExpDate
: Membership expiration field
Usage Example:
<form data-controller="member-verify-form">
<div class="form-check">
<input type="checkbox"
data-action="change->member-verify-form#toggleParent"
class="form-check-input">
<label class="form-check-label">Has Parent/Guardian</label>
</div>
<input type="text"
data-member-verify-form-target="scaMember"
placeholder="Parent/Guardian Name"
class="form-control">
<div class="form-check">
<input type="checkbox"
data-action="change->member-verify-form#toggleMembership"
class="form-check-input">
<label class="form-check-label">Has Membership</label>
</div>
<input type="text"
data-member-verify-form-target="membershipNumber"
placeholder="Membership Number"
class="form-control">
<input type="date"
data-member-verify-form-target="membershipExpDate"
class="form-control">
</form>
Key Methods:
toggleParent(event)
: Enables/disables parent/guardian fieldtoggleMembership(event)
: Enables/disables membership fields
Modal Opener Controller (modal-opener-controller.js
)
Purpose: Programmatically opens Bootstrap modals based on value changes.
Features:
- Automatic modal triggering via value changes
- Integration with Bootstrap modal system
- Event-driven modal activation
Values:
modalBtn
: ID of modal button element to trigger
Usage Example:
<!-- Modal trigger controller -->
<div data-controller="modal-opener"
data-modal-opener-modal-btn-value="">
<!-- This controller watches for modalBtn value changes -->
</div>
<!-- Hidden modal trigger button -->
<button id="hidden-modal-trigger"
data-bs-toggle="modal"
data-bs-target="#confirmModal"
style="display: none;">
</button>
<!-- Modal -->
<div class="modal fade" id="confirmModal">
<div class="modal-dialog">
<!-- Modal content -->
</div>
</div>
<script>
// Trigger modal programmatically
const controller = document.querySelector('[data-controller="modal-opener"]');
controller.setAttribute('data-modal-opener-modal-btn-value', 'hidden-modal-trigger');
</script>
Key Methods:
modalBtnValueChanged()
: Automatically triggered when modalBtn value changes, clicks the specified modal button
10.5.6 Administrative Controllers
Permission Add Role Controller (permission-add-role-controller.js
)
Purpose: Manages adding roles to permissions with form validation.
Features:
- Role selection validation
- Submit button state management
- Automatic focus management
Targets:
role
: Role selection elementform
: Form elementsubmitBtn
: Submit button
Usage Example:
<form data-controller="permission-add-role" data-permission-add-role-target="form">
<select data-permission-add-role-target="role"
data-action="change->permission-add-role#checkSubmitEnable"
name="role_id">
<option value="">Select a role...</option>
<option value="role_1">Administrator</option>
<option value="role_2">Member</option>
</select>
<button type="submit"
data-permission-add-role-target="submitBtn"
disabled>
Add Role
</button>
</form>
Key Methods:
checkSubmitEnable()
: Validates role selection and enables/disables submit button
Permission Manage Policies Controller (permission-manage-policies-controller.js
)
Purpose: Manages comprehensive permission policy matrix with class/method granular control.
Features:
- Batch checkbox processing for performance
- Class-level and method-level permission management
- Indeterminate state handling for partial selections
- Loading overlay for large policy matrices
- AJAX persistence for permission changes
Targets:
policyClass
: Class-level permission checkboxespolicyMethod
: Method-level permission checkboxes
Values:
url
: API endpoint for permission updates
Usage Example:
<div data-controller="permission-manage-policies"
data-permission-manage-policies-url-value="/api/permissions/update"
class="permissions-matrix">
<!-- Class-level checkbox -->
<input type="checkbox"
data-permission-manage-policies-target="policyClass"
data-class-name="MemberPolicy"
data-permission-id="123"
class="form-check-input">
<!-- Method-level checkboxes -->
<input type="checkbox"
data-permission-manage-policies-target="policyMethod"
data-class-name="MemberPolicy"
data-permission-id="123"
data-method-name="view"
class="form-check-input">
<input type="checkbox"
data-permission-manage-policies-target="policyMethod"
data-class-name="MemberPolicy"
data-permission-id="123"
data-method-name="edit"
class="form-check-input">
</div>
Key Methods:
connect()
: Initializes batch processing with loading overlayclassClicked(event)
: Handles class-level permission togglemethodClicked(event)
: Handles method-level permission changescheckClass(className, permissionId)
: Updates class checkbox based on method states
Role Add Member Controller (role-add-member-controller.js
)
Purpose: Manages adding members to roles with member validation and branch requirements.
Features:
- Member selection validation with ID parsing
- Optional branch requirement validation
- Submit button state management
- Automatic focus handling
Targets:
scaMember
: Member selection elementform
: Form elementsubmitBtn
: Submit buttonbranch
: Optional branch selection
Usage Example:
<form data-controller="role-add-member" data-role-add-member-target="form">
<select data-role-add-member-target="scaMember"
data-action="change->role-add-member#checkSubmitEnable"
name="member_id">
<option value="">Select a member...</option>
<option value="member_123">John Doe</option>
<option value="member_456">Jane Smith</option>
</select>
<!-- Optional branch selection -->
<select data-role-add-member-target="branch"
data-action="change->role-add-member#checkSubmitEnable"
name="branch_id">
<option value="">Select branch...</option>
<option value="1">Local Branch</option>
</select>
<button type="submit"
data-role-add-member-target="submitBtn"
disabled>
Add Member
</button>
</form>
Key Methods:
checkSubmitEnable()
: Validates member selection and optional branch requirement
Role Add Permission Controller (role-add-permission-controller.js
)
Purpose: Manages adding permissions to roles with permission validation.
Features:
- Permission selection validation with ID parsing
- Submit button state management
- Automatic focus handling
- Form validation integration
Targets:
permission
: Permission selection elementform
: Form elementsubmitBtn
: Submit button
Usage Example:
<form data-controller="role-add-permission" data-role-add-permission-target="form">
<select data-role-add-permission-target="permission"
data-action="change->role-add-permission#checkSubmitEnable"
name="permission_id">
<option value="">Select a permission...</option>
<option value="permission_1">View Members</option>
<option value="permission_2">Edit Members</option>
</select>
<button type="submit"
data-role-add-permission-target="submitBtn"
disabled>
Add Permission
</button>
</form>
Key Methods:
checkSubmitEnable()
: Validates permission selection and enables/disables submit button
Revoke Form Controller (revoke-form-controller.js
)
Purpose: Manages revocation forms with reason validation and outlet communication.
Features:
- Outlet-based communication with other controllers
- Reason validation for revocation actions
- Form state management
- ID value management through outlets
Targets:
submitBtn
: Submit buttonreason
: Reason text fieldid
: Hidden ID field
Values:
url
: Revocation endpoint
Outlets:
outlet-btn
: Communication with outlet button controllers
Usage Example:
<form data-controller="revoke-form"
data-revoke-form-url-value="/api/revoke"
data-revoke-form-outlet-btn-outlet="#revoke-buttons">
<input type="hidden" data-revoke-form-target="id" name="record_id">
<textarea data-revoke-form-target="reason"
data-action="input->revoke-form#checkReadyToSubmit"
placeholder="Enter reason for revocation..."
class="form-control"
required></textarea>
<button type="submit"
data-revoke-form-target="submitBtn"
class="btn btn-danger"
disabled>
Revoke
</button>
</form>
<!-- Outlet buttons container -->
<div id="revoke-buttons">
<button data-controller="outlet-btn"
data-outlet-btn-btn-data-value='{"id": "123"}'
data-action="click->outlet-btn#fireNotice">
Revoke Item 123
</button>
</div>
Key Methods:
setId(event)
: Receives ID from outlet button communicationscheckReadyToSubmit()
: Validates reason field and enables/disables submitoutletBtnOutletConnected/Disconnected()
: Manages outlet communication lifecycle
Note: This controller demonstrates the outlet pattern for inter-controller communication.
Outlet Button Controller (outlet-button-controller.js
)
Purpose: Provides inter-controller communication through Stimulus outlets with data passing.
Features:
- Data validation and requirement checking
- Event dispatching for controller communication
- Dynamic button state management
- Custom event listener management
Values:
btnData
: Object containing button-specific datarequireData
: Whether data is required for button functionality
Usage Example:
<button data-controller="outlet-btn"
data-outlet-btn-btn-data-value='{"id": "123", "type": "member"}'
data-outlet-btn-require-data-value="true"
data-action="click->outlet-btn#fireNotice"
class="btn btn-primary">
Process Member 123
</button>
<script>
// Listening controller setup
const form = document.querySelector('[data-controller="revoke-form"]');
form.addEventListener('outlet-btn:outlet-button-clicked', (event) => {
console.log('Received data:', event.detail);
});
</script>
Key Methods:
btnDataValueChanged()
: Updates button state based on data availabilityaddBtnData(data)
: Programmatically sets button datafireNotice(event)
: Dispatches custom event with button dataaddListener(callback)
/removeListener(callback)
: Manages event listeners
10.5.7 Session Management Controllers
Session Extender Controller (session-extender-controller.js
)
Purpose: Extends user sessions to prevent timeout during active use.
Features:
- Automatic session extension alerts
- Configurable timeout intervals (25 minutes default)
- AJAX session refresh
- Timer management and cleanup
Values:
url
: Session extension endpoint
Usage Example:
<div data-controller="session-extender"
data-session-extender-url-value="/auth/extend-session">
<!-- This controller runs in background -->
</div>
Key Methods:
urlValueChanged()
: Sets up timer and handles session extension workflow- Timer automatically shows alert at 25-minute intervals
- Fetches session extension endpoint after user confirmation
10.5.8 Utility Controllers
Select All Switch List Controller (select-all-switch-list-controller.js
)
Purpose: Provides “select all” functionality for checkbox lists with Bootstrap switches.
Features:
- Automatic “Select All” checkbox generation
- Bidirectional checkbox synchronization
- Bootstrap form-switch styling integration
- Dynamic state management
Usage Example:
<div data-controller="select-all-switch">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="items[]" value="1">
<label class="form-check-label">Item 1</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="items[]" value="2">
<label class="form-check-label">Item 2</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="items[]" value="3">
<label class="form-check-label">Item 3</label>
</div>
<!-- Select All checkbox will be automatically inserted here -->
</div>
Key Methods:
connect()
: Creates “Select All” checkbox and sets up event listenersupdateSelectAll(event)
: Synchronizes individual checkboxes with “Select All” state
10.6 Controller Development Patterns
Standard Controller Structure
All KMP Stimulus controllers follow this standard pattern:
import { Controller } from "@hotwired/stimulus"
class YourController extends Controller {
// Define targets - elements your controller interacts with
static targets = ["input", "output"]
// Define values - properties that can be set from HTML
static values = {
url: String,
delay: { type: Number, default: 300 }
}
// Define outlets - connections to other controllers
static outlets = ["other-controller"]
// Initialize function (optional)
initialize() {
// Setup code here
}
// Connect function - runs when controller connects to DOM
connect() {
// Connection code here
}
// Event handler methods
handleEvent(event) {
// Handle events
}
// Disconnect function - cleanup when controller disconnects
disconnect() {
// Cleanup code here
}
}
// Register with global controllers registry
if (!window.Controllers) {
window.Controllers = {};
}
window.Controllers["your-controller"] = YourController;
Import/Require Inconsistencies
Note: Some controllers in the codebase use CommonJS require
syntax instead of ES6 import
statements. This inconsistency should be addressed:
- ES6 Import (Recommended):
import { Controller } from "@hotwired/stimulus"
- CommonJS Require:
const { Controller } = require("@hotwired/stimulus")
Controllers currently using require
include:
app-setting-form-controller.js
session-extender-controller.js
These should be updated to use ES6 imports for consistency.
Security Considerations
When developing controllers that handle user input:
- Always sanitize URLs using
KMP_utils.sanitizeUrl()
- Validate input before processing using native HTML5 validation or custom logic
- Use CSRF tokens for state-changing AJAX requests
- Sanitize strings using
KMP_utils.sanitizeString()
for display - Handle errors gracefully with user-friendly feedback
Performance Best Practices
- Debounce expensive operations like AJAX calls (see auto-complete example)
- Clean up event listeners in the
disconnect()
method - Use efficient DOM operations - batch updates when possible
- Implement lazy loading for data-heavy components
Code Quality Notes
Issues Found in Current Codebase:
- Some controllers have incomplete
disconnect()
methods (e.g.,csv-download-controller.js
) - The
auto-complete-controller.js
has duplicate getter methods that may cause issues - Import statement inconsistency across controllers (mix of ES6 and CommonJS)
These issues should be addressed to improve code reliability and maintainability.
10.7 Examples from the Codebase
KMP includes several Stimulus controllers that serve as good examples:
app-setting-form-controller.js
: Simple form managementauto-complete-controller.js
: Complex AJAX interactions with accessibilitybranch-links-controller.js
: Dynamic list management with validationcsv-download-controller.js
: File handling and download managementmember-card-profile-controller.js
: Component state management
Examining these controllers can provide insight into how to implement common patterns in Stimulus.
For more best practices and coding standards, see the KMP CakePHP & Stimulus.JS Best Practices.
10.8 Asset Management
KMP uses Laravel Mix (based on webpack) to manage frontend assets with sophisticated build optimization and automated controller discovery.
10.8.1 Build System Architecture
The asset compilation system is configured in webpack.mix.js
and provides:
- Automatic Controller Discovery: Recursively finds all
-controller.js
files - Code Splitting: Separates vendor libraries from application code
- Asset Versioning: Cache-busting for production deployments
- Source Maps: Development debugging support
- CSS Processing: SASS compilation and optimization
10.8.2 Webpack Configuration
The build system automatically discovers Stimulus controllers across the application:
// webpack.mix.js - Controller Discovery
function getJsFilesFromDir(startPath, skiplist, filter, callback) {
// Recursively scans directories for controller files
// Skips node_modules and webroot directories
// Finds all files matching '-controller.js' pattern
}
// Scans main app and plugins for controllers
getJsFilesFromDir('./assets/js', skipList, '-controller.js', callback);
getJsFilesFromDir('./plugins', skipList, '-controller.js', callback);
Build Outputs:
webroot/js/controllers.js
- All Stimulus controllers bundled togetherwebroot/js/index.js
- Main application JavaScript entry pointwebroot/js/core.js
- Vendor libraries (Bootstrap, Turbo, Stimulus)webroot/css/*.css
- Compiled CSS files
10.8.3 NPM Scripts
The package.json
defines comprehensive build and test workflows:
Development Scripts:
npm run dev # Development build
npm run watch # Watch files and rebuild on changes
npm run watch-poll # Watch with polling (for Docker/VM)
npm run hot # Hot module reloading
Production Scripts:
npm run prod # Production build with optimization
npm run production # Alias for prod
Testing Scripts:
npm run test # Run all tests (JS + UI)
npm run test:js # Jest JavaScript tests
npm run test:ui # Playwright end-to-end tests
npm run test:security # Security vulnerability checks
10.8.4 Dependencies Management
Core Framework Dependencies:
@hotwired/stimulus
- Stimulus JavaScript framework@hotwired/turbo
- Turbo navigation and cachingbootstrap
- UI framework and componentsguifier
- Dynamic form generation library
Development Dependencies:
laravel-mix
- Asset compilation wrapper for webpackjest
- JavaScript testing framework@playwright/test
- End-to-end testing frameworkplaywright-bdd
- Behavior-driven development for Playwright
10.8.5 Asset Organization
JavaScript Structure:
app/assets/js/
├── index.js # Main entry point
├── KMP_utils.js # Global utilities
└── controllers/ # Stimulus controllers
├── app-setting-form-controller.js
├── auto-complete-controller.js
└── ... # All other controllers
plugins/*/assets/js/controllers/ # Plugin-specific controllers
CSS Structure:
app/assets/css/
├── app.css # Main application styles
├── signin.css # Authentication page styles
├── cover.css # Landing page styles
└── dashboard.css # Dashboard-specific styles
10.8.6 Build Process Optimization
Code Splitting Strategy:
- Vendor Bundle (
core.js
): Bootstrap, Turbo, Stimulus, and other vendor libraries - Controllers Bundle (
controllers.js
): All Stimulus controllers from app and plugins - Application Bundle (
index.js
): Main application logic and utilities
Benefits:
- Vendor libraries cached separately from application code
- Controllers can be updated without affecting vendor cache
- Smaller incremental updates for better performance
10.8.7 Development Workflow
Setting Up Development Environment:
cd app
npm install # Install dependencies
npm run watch # Start development with file watching
Adding New Assets:
- Stimulus Controllers: Place in
assets/js/controllers/
with-controller.js
suffix - CSS Files: Add to
assets/css/
and reference inwebpack.mix.js
- JavaScript Modules: Import into
index.js
or controller files as needed
Automatic Discovery:
- Controllers are automatically discovered and bundled
- No manual webpack entry configuration required
- Plugin controllers automatically included in build
10.8.8 Production Deployment
Production Build Process:
npm run production
Production Optimizations:
- Minification: JavaScript and CSS are minified
- Tree Shaking: Unused code is removed
- Asset Versioning: Files include content hashes for cache busting
- Source Maps: Available for production debugging if needed
Output Files:
webroot/js/
├── controllers.js # All Stimulus controllers
├── index.js # Main application code
├── core.js # Vendor libraries
├── manifest.json # Laravel Mix manifest for versioning
└── runtime.js # Webpack runtime
webroot/css/
├── app.css # Main styles
├── signin.css # Authentication styles
└── ... # Other CSS files
10.8.9 Browser Support
The build system targets modern browsers with the following browserlist configuration:
"browserslist": ["defaults"]
This provides support for:
- Chrome: Last 2 versions
- Firefox: Last 2 versions
- Safari: Last 2 versions
- Edge: Last 2 versions
- Covers >95% of global browser usage
10.8.10 Performance Considerations
Bundle Size Optimization:
- Vendor libraries extracted to separate bundle for better caching
- Controllers bundled together for optimal loading
- Source maps available for development debugging
Loading Strategy:
- Core vendor libraries loaded first
- Controllers loaded separately to allow for independent updates
- CSS loaded asynchronously to prevent render blocking
10.8.11 Troubleshooting
Common Build Issues:
- Controller Not Loading: Ensure filename ends with
-controller.js
- Build Errors: Check for syntax errors in controller files
- Asset Not Found: Verify file paths in
webpack.mix.js
- Memory Issues: Use
--max-old-space-size=4096
for large projects
Debug Commands:
npm run dev # Development build with detailed output
npm run watch # Watch mode shows file changes
npm run test:js # Run JavaScript tests
File Watching Issues:
- Use
npm run watch-poll
for Docker/VM environments - Ensure file permissions allow webpack to watch files
- Check that
skipList
in webpack.mix.js doesn’t exclude your files