stimulus-invoke v1.0.0
Stimulus Invoke
Fill the gap between Hotwire Turbo Frames/Streams responses and Stimulus controllers to replace Rails UJS.
It allows to invoke any Stimulus actions of any Stimulus controllers, anywhere in the DOM, automatically with the help of a Custom Element <stimulus-invoke>
.
This package should be use in last resort. Basically when you need to interact with advanced/complex Stimulus controllers. Here some use cases:
- Hide a modal when rendering a Turbo Stream response (where
modal
is a Stimulus controller andhide
is one of its actions):<turbo-stream target="user_modal_1" action="append"> <template> <stimulus-invoke action="modal#hide"></stimulus-invoke> </template> </turbo-stream>
Call a
checkbox-list#checkboxAdded
Stimulus action when a new checkbox (who is a target of thecheckbox-list
Stimulus controller) is added by a Turbo Stream response:<turbo-stream target="users_table_body" action="append"> <template> <tr id="user_2"> <td> <input type="checkbox" name="ids[]" value="2" data-checkbox-list-target="checkbox"> User 2 </td> </tr> <stimulus-invoke action="checkbox-list#checkboxAdded" source="#user_2 [data-checkbox-list-target~=checkbox]" ></stimulus-invoke> </template> </turbo-stream>
Behind the scene no magics 💫, it encapsulates a Stimulus controller who can interact with others Stimulus controllers.
- Installation
- Configuration
stimulus-invoke
custom elementStimulusInvoke
controller- Running examples
- Running tests
- Changelog
- Getting Help & Contributing Back
- Acknowledgments
Installation
npm
npm install stimulus-invoke
yarn
yarn add stimulus-invoke
Configuration
Using webpack
Stimulus Invoke integrates nicely with the webpack asset packager.
// src/application.js
import { Application } from '@hotwired/stimulus'
import { definitionsFromContext } from '@hotwired/stimulus-webpack-helpers'
import { StimulusInvokeController, StimulusInvokeElement } from 'stimulus-invoke'
window.Stimulus = Application.start()
// Load Stimulus controllers
const context = require.context('./controllers', true, /\.js$/)
Stimulus.load(definitionsFromContext(context))
// Register StimulusInvokeController
Stimulus.register('invoke', StimulusInvokeController)
// Register the <stimulus-invoke> custom element
StimulusInvokeElement.define()
Using Without a Build System
If you prefer not to use a build system, you can load Stimulus Invoke with UMD or ECMAScript module
With UMD
You can load Stimulus Invoke in a <script>
tag and it will be globally available through the window.StimulusInvoke
object:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/@hotwired/stimulus@3.0.1/dist/stimulus.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stimulus-invoke/dist/stimulus-invoke.umd.min.js"></script>
<script>
(() => {
const application = Stimulus.Application.start()
application.register("hello", class extends Stimulus.Controller {
static get targets() {
return [ "name" ]
}
world() {
this.nameTarget.value = 'hello#world method invoked!' // test
}
})
// Register StimulusInvokeController
application.register('invoke', StimulusInvoke.StimulusInvokeController)
})()
addEventListener('DOMContentLoaded', () => {
// Register the <stimulus-invoke> custom element
StimulusInvoke.StimulusInvokeElement.define()
})
</script>
</head>
<body>
<div data-controller="hello">
<input data-hello-target="name" type="text">
<stimulus-invoke
action="hello#world"
source="[data-hello-target~=name]"
keep
></stimulus-invoke>
</div>
</body>
</html>
With ECMAScript module
You can load Stimulus Invoke in a <script type="module">
tag:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script type="module">
import { Application, Controller } from 'https://cdn.skypack.dev/@hotwired/stimulus@3.0.1?min'
import { StimulusInvokeController, StimulusInvokeElement } from 'https://cdn.skypack.dev/stimulus-invoke?min'
window.Stimulus = Application.start()
Stimulus.register('hello', class extends Controller {
static targets = [ 'name' ]
world() {
this.nameTarget.value = 'hello#world method invoked!' // test
}
})
// Register StimulusInvokeController
Stimulus.register('invoke', StimulusInvokeController)
addEventListener('DOMContentLoaded', () => {
// Register the <stimulus-invoke> custom element
StimulusInvokeElement.define()
})
</script>
</head>
<body>
<div data-controller="hello">
<input data-hello-target="name" type="text">
<stimulus-invoke
action="hello#world"
source="[data-hello-target~=name]"
keep
></stimulus-invoke>
</div>
</body>
</html>
stimulus-invoke
custom element
HTML attributes
Attribute | Description | Notes | Possible values |
---|---|---|---|
action | The Stimulus action to invoke, similar to a Stimulus data-action attribute, but without the event part | required | At least one Stimulus action must be provide, but you can pass multiple actions separated by spaces (e.g. <stimulus-invoke action="class-attribute#toggle selectbox#open"></stimulus-invoke> ) |
target | The DOM element(s) target. Should be an element connected to the Stimulus controller given in the action attribute or any of its sub elements | optional By default it will find the closest element connected to the Stimulus controller given in the action attribute. | It can be a DOM id (e.g. target="user_1" ) or a CSS selector who return all matches elements (e.g. <stimulus-invoke action="class-attribute#toggle" target=".user"></stimulus-invoke> ) |
source | The DOM element who dispatch the event (event.target and event.currentTarget ) linked to the Stimulus action . Should be a sub element (i.e. a Stimulus target) of an element connected to the Stimulus controller given in the action attribute | optional | It can be a DOM id (e.g. source="user_1" ) or a CSS selector who return only the first element (e.g. <stimulus-invoke action="class-attribute#toggle" source="[data-checkbox-list-target~=checkbox]"></stimulus-invoke> ) |
disabled | Prevents any Stimulus action invocation when present | optional | e.g. <stimulus-invoke action="class-attribute#toggle" disabled></stimulus-invoke> |
on | Event type who invoke the Stimulus action given in the action attribute | optional By default the action is invoked when the <stimulus-invoke> is connected to the DOM. When the on attribute is set the <stimulus-invoke> element is not automatically removed, but you can set the once attribute to automatically remove it | e.g. <stimulus-invoke action="flash-message#close" on="click" target=".flash-message"><button>Close All</button></stimulus-invoke> You can pass multiple event types separated by spaces (e.g. <stimulus-invoke action="menu#toggle" on="mouseenter mouseleave" target="main_menu"><button>Help</button></stimulus-invoke> ) |
params | Pass parameters to the invoked Stimulus action | optional Accept JSON Array encoded value only (i.e. JSON.stringify(array) ) | e.g. <stimulus-invoke action="selectbox#search" params="[\"value\"]"></stimulus-invoke> |
keep | Preserve the <stimulus-invoke> element after invoking the Stimulus action given in the action attribute. Enabled by default if the on attribute is set | optional Opposite attribute of the once attribute, both attributes shouldn't be present at the same time | e.g. <stimulus-invoke action="modal#hide" keep></stimulus-invoke> |
once | Auto remove the <stimulus-invoke> element after invoking the Stimulus action given in the action attribute. Enabled by default if the on attribute is not set | optional Opposite attribute of the keep attribute, both attributes shouldn't be present at the same time | e.g. <stimulus-invoke action="flash-message#close" on="click" target=".flash-message" once><button>Close All</button></stimulus-invoke> |
data-controller-name | Advanced usage. If you want to change the default encapsulate Stimulus controller name | optional If not set, it'll use the StimulusInvokeElement.config.controllerName value, which is invoke by default | e.g. <stimulus-invoke action="class-attribute#toggle" data-controller-name="custom-invoke"></stimulus-invoke> |
data-controller-action | Advanced usage. If you want to change the default encapsulate Stimulus action name | optional If not set, it'll use the StimulusInvokeElement.config.controllerName value with the connected event and the StimulusInvokeElement.config.controllerName value with the apply action, which is invoke:connected->invoke#apply by default | e.g. <stimulus-invoke action="class-attribute#toggle" data-controller-action="custom-event->invoke#customAction"></stimulus-invoke> |
Custom element configuration
Property | Description | Notes | Possible values |
---|---|---|---|
controllerName | Advanced usage. If you want to change the default linked Stimulus controller name | Default value: invoke | e.g. StimulusInvokeElement.config.controllerName = 'invoker' |
tagName | Advanced usage. If you want to change the default <stimulus-invoke> tag name | Default value: stimulus-invoke | e.g. StimulusInvokeElement.config.tagName = 'stimulus-invoker' |
controllerTagName | Advanced usage. If you want to change the default nested Stimulus controller tag name | Default value: span | e.g. StimulusInvokeElement.config.controllerTagName = 'div' |
Use case with webpack
// src/application.js
import { Application } from '@hotwired/stimulus'
import { definitionsFromContext } from '@hotwired/stimulus-webpack-helpers'
import { StimulusInvokeController, StimulusInvokeElement } from 'stimulus-invoke'
window.Stimulus = Application.start()
// Load Stimulus controllers
const context = require.context('./controllers', true, /\.js$/)
Stimulus.load(definitionsFromContext(context))
// Register StimulusInvokeController
Stimulus.register('invoker', StimulusInvokeController)
// Config and register the <stimulus-invoker> custom element
// Overwrite the default linked Stimulus controller name, 'invoke' by default
StimulusInvokeElement.config.controllerName = 'invoker'
// Overwrite the default <stimulus-invoke> tag name, 'stimulus-invoke' by default
StimulusInvokeElement.config.tagName = 'stimulus-invoker'
// Overwrite the default nested Stimulus controller tag name, 'span' by default
StimulusInvokeElement.config.controllerTagName = 'div'
// Register the <stimulus-invoker> custom element
StimulusInvokeElement.define()
<!-- src/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="/src/application.js" defer></script>
</head>
<body>
<div data-controller="hello">
<input data-hello-target="name" type="text">
<stimulus-invoker
action="hello#world"
source="[data-hello-target~=name]"
></stimulus-invoker>
</div>
</body>
</html>
Properties
IMPORTANT: To access properties from a <stimulus-invoke>
element, you need to use the StimulusInvokeElementReactive
class instead of the StimulusInvokeElement
class.
In your main javascript file (e.g. src/application.js
), you can replace:
//...
import { StimulusInvokeController, StimulusInvokeElement } from 'stimulus-invoke'
//...
StimulusInvokeElement.define()
//...
By:
//...
import { StimulusInvokeController, StimulusInvokeElementReactive } from 'stimulus-invoke'
//...
StimulusInvokeElementReactive.define()
//...
Attribute | Type | Notes | Possible values |
---|---|---|---|
action | string | required | e.g. myStimulusInvokeElement.action = 'class-attribute#toggle' |
target | string | optional | e.g. myStimulusInvokeElement.target = '.user' |
source | string | optional | e.g. myStimulusInvokeElement.source = 'user_1' |
on | string | optional | e.g. myStimulusInvokeElement.on = 'click' |
params | string | optional | e.g. myStimulusInvokeElement.params = "[\"value\"]" |
disabled | boolean | optional | e.g. myStimulusInvokeElement.disabled = true |
keep | boolean | optional | e.g. myStimulusInvokeElement.keep = true |
once | boolean | optional | e.g. myStimulusInvokeElement.once = true |
Methods
IMPORTANT: To access methods from a <stimulus-invoke>
element, you need to use the StimulusInvokeElementReactive
class instead of the StimulusInvokeElement
class, see the Properties section for more info.
Attribute | Description | Possible values |
---|---|---|
apply() | Advanced usage. Manually invoke the Stimulus action given in the action attribute | e.g. myStimulusInvokeElement.apply() |
destroy() | Advanced usage. Destroy the nested invoke Stimulus controller and the <stimulus-invoke> custom element | e.g. myStimulusInvokeElement.destroy() |
StimulusInvoke
controller
Controller configuration
Property | Description | Notes | Possible values |
---|---|---|---|
domAccess | Advanced usage. Allow to invoke any DOM Element methods, ⚠️ use at your own risk ⚠️ | Type: boolean Default value: false | e.g. StimulusInvokeController.config.domAccess = true |
domIdentifier | Very advanced usage. Useful only if domAccess is true and you already have a real Stimulus controller named DOM . Special Stimulus controller identifier to use DOM elements as Stimulus controllers | Type: string Default value: DOM | e.g. StimulusInvokeController.config.domIdentifier = 'native-element' |
propertyWritable | Advanced usage. Allow to set value for any Stimulus Controller or DOM Element properties, ⚠️ use at your own risk ⚠️ | Type: boolean Default value: false | e.g. StimulusInvokeController.config.propertyWritable = true |
Use case with webpack
// src/application.js
import { Application } from '@hotwired/stimulus'
import { definitionsFromContext } from '@hotwired/stimulus-webpack-helpers'
import { StimulusInvokeController, StimulusInvokeElement } from 'stimulus-invoke'
window.Stimulus = Application.start()
// Load Stimulus controllers
const context = require.context('./controllers', true, /\.js$/)
Stimulus.load(definitionsFromContext(context))
// Config and register StimulusInvokeController
// Allow to invoke any DOM Element methods,
// use at your own risk, false by default
StimulusInvokeController.config.domAccess = true
// Allow to set value for any Stimulus Controller or DOM Element properties,
// use at your own risk, false by default
StimulusInvokeController.config.propertyWritable = true
// Register StimulusInvokeController
Stimulus.register('invoke', StimulusInvokeController)
// Register the <stimulus-invoke> custom element
StimulusInvokeElement.define()
<!-- src/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="/src/application.js" defer></script>
</head>
<body>
<div>
<details id="menu">
<summary>Menu</summary>
Something small enough to escape casual notice.
</details>
<stimulus-invoke
on="click"
action="DOM#click"
target="#menu > summary"
>
<button>Toggle menu</button>
</stimulus-invoke>
</div>
</body>
</html>
Running examples
You can test Stimulus Invoke locally before adding this library to one of your projects.
To test Stimulus Invoke examples, you need to install Node.js on your computer, then follow these instructions:
- Clone this repository:
git clone https://gitlab.com/efficiently/stimulus-invoke
cd stimulus-invoke/
- Once in your local
stimulus-invoke
directory, run:
npm install
npm run fresh-build
- Launch the local web server:
npm start
Then you can open in your Web browser this URL: http://localhost:8000
For each examples, you should read the source of the web page to better understand what's going on. Or open the corresponding HTML file, here.
Running tests
You can run Stimulus Invoke tests locally.
To run tests, you need to install Node.js on your computer, then follow these instructions:
- Clone this repository:
git clone https://gitlab.com/efficiently/stimulus-invoke
cd stimulus-invoke/
- Once in your local
stimulus-invoke
directory, run:
npm install
npm run fresh-build
- Run tests:
npm test
Changelog
See CHANGELOG.md file for reference.
Getting Help & Contributing Back
Have a question about Stimulus Invoke? Connect with other Stimulus developers on the Hotwire Discourse community forum.
Find a bug? Head over to our issue tracker and we'll do our best to help. We love pull/merge requests, too!
We expect all Stimulus Invoke contributors to abide by the terms of our Code of Conduct.
Acknowledgments
Stimulus Invoke is MIT-licensed open-source software.
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago