Using Stimulus
In a decision to move OpenProject towards the Hotwire approach, we introduced Stimulus.js to replace a collection of dynamically loaded custom JavaScript files used to sprinkle some interactivity.
This guide will outline how to add controllers and the conventions around it. This is not a documentation of stimulus itself. Use their documentation instead: https://stimulus.hotwired.dev
Adding controllers
All controllers live under frontend/src/stimulus/controllers/
. The naming convention is <controller-name>.controller.ts
, meaning to dasherize the name of the controller. This makes it easier to generate names and classes using common IDEs.
If you want to add a common pattern, manually register the controller under frontend/src/stimulus/setup.ts
. Often you’ll want to have a dynamically loaded controller instead though.
Dynamically loaded controllers
To dynamically load a controller, it needs to live under frontend/src/stimulus/controllers/dynamic/<controller-name>.controller.ts
.
In DOM, you’ll tell the application the controller is dynamically loaded using the data-application-target="dynamic"
attribute. It tells the application controller (frontend/src/stimulus/controllers/op-application.controller.ts
) we load on every page on body to dynamically import and load the controller named users
.
<div data-controller="users" data-application-target="dynamic">
</div>
Namespacing dynamic controllers
If you want to organize your dynamic controllers in a subfolder, use the double dash convention of stimulus. For example, adding a new admin controller settings
, you’d do the following:
- Add the controller under
frontend/src/stimulus/controllers/dynamic/admin/settings.controller.ts
- Specify the controller name with a double dash for each folder
<div data-controller="admin--settings" data-application-target="dynamic">
</div>
You need to take care to prefix all actions, values etc. with the exact same pattern, e.g., data-admin--settings-target="foobar"
.
Requiring a page controller
If you have a single controller used in a partial, we have added a helper to use in a partial in order to append a controller to the #content-wrapper
tag. This is useful if your template doesn’t have a single DOM root. For example, to load the dynamic project-storage-form
controller and provide a custom value to it:
<% content_controller 'project-storage-form',
dynamic: true,
'project-storage-form-folder-mode-value': @project_storage.project_folder_mode %>