One-way data binding controller for stimulus.js
Stimulus controller to provide one-way data bindings from form input.
Automatically sets bindings on connection to the DOM, and can perform updates
on any event via a data-action attribute.
``shell`
$ yarn add stimulus-data-bindings
Register the controller with Stimulus:
`javascript
// application.js
import { Application } from "@hotwired/stimulus";
import DataBindingController from "stimulus-data-bindings";
const application = Application.start();
application.register("data-binding", DataBindingController);
`
Initialize the controller on a container element, then add bindings:
`html`
All input elements to be bound require at least a data-action calling theupdate method, a data-binding-target specifying the target element, and either a data-binding-property to set a property on the target, or a data-binding-attribute to set an attribute.
Optionally data-binding-condition can be passed, to only set the property/attribute if the condition evaluates to true. Also data-binding-value can be passed, to set the value of the target property/attribute.
multiple properties and attributes can be used separated by a space, as well as multiple targets.
Most data attributes (property, attribute, value, and condition) can be set on the target. When set on both the target and the source, target will override the source.
e.g: data-binding-debug="true"
Set this to true to have helpful debug information logged to the console. We strongly recommend having this switched on when setting up your bindings, then set to false or remove when you're happy its working.
e.g: data-binding-target="my-target"
The target element(s) to alter. any element that has a data-binding-ref of this value will match. Multiple elements can match.
e.g: data-binding-property="textContent"
The property(s) of the target element to set.
Security Note: Only safe properties are allowed. Dangerous properties like innerHTML, outerHTML, onclick, src, href, and any property starting with on are automatically blocked to prevent XSS attacks. See the Security section for allowed properties.
e.g: data-binding-attribute="disabled hidden"
The attribute(s) of the target element to set.
Security Note: Only safe attributes are allowed. Dangerous attributes like onclick, onerror, src, href, and any attribute starting with on are automatically blocked to prevent XSS attacks. All data-* attributes are allowed. See the Security section for allowed attributes.
e.g: data-binding-class="text-red-500 line-through"
The classes to be added/removed from the target element.
e.g: data-binding-value="$source.value"
The value to set the target attribute/property to. Note that this field evaluates an expression, this expression has access to $source (the element this is defined on) and $target (the target element).
If you would like to pass in a string, you would have to do this: data-binding-value="'my string'".
If this value is not set, the attribute will be set/removed without a value.
e.g: data-binding-condition="$source.value === 'hello world'"
The condition to check whether or not to set the target attribute/property/class. This evaluates an expression which has access to the value of the source element as $source and the target element as $target.
Both data-binding-value and data-binding-condition support JavaScript-like expressions. Here's what's available:
- $source - The source element (the element where the binding is defined)
- $target - The target element (the element that will be modified)
You can access properties and attributes of elements using dot notation:
- $source.value - The value of an input, textarea, or select element$source.checked
- - The checked state of a checkbox or radio input$source.dataset.foo
- - Access data attributes (e.g., data-foo="bar" becomes $source.dataset.foo)$target.dataset.id
- - Access target element's data attributes
- === - Strict equality!==
- - Strict inequality>
- - Greater than<
- - Less than>=
- - Greater than or equal<=
- - Less than or equal
- && - Logical AND||
- - Logical OR!
- - Logical NOT (negation)
- Strings: Use single or double quotes - 'hello' or "world"5
- Numbers: , 3.14, -10true
- Booleans: , falsenull
- Null/Undefined: , undefined
You can use template literals (backticks) in data-binding-value to create dynamic strings:
`htmlHello ${$source.value}!
data-binding-value=""`
Use parentheses to group expressions and control evaluation order:
`html`
data-binding-condition="($source.value === 'a' || $source.value === 'b') &&
$source.checked"
e.g: data-binding-initial="true"data-binding-initial="false"
e.g:
If this is true data-binding#update will be called on load to set an initial value. Set to false to prevent this behaviour.
e.g: data-binding-event="change input"
You can set one or more events to be triggered when the data-binding-property is changed. This is sometimes required because programmatically assigned values do not trigger events on an element, whereas physically updating the value in the UI does.
- Note events will not be triggered on changes to data-binding-attribute. Only property changes will trigger an event.
`htmledited-${$source.value}
type="text"
data-action="change->data-binding#update"
data-binding-target="foo"
data-binding-property="textContent"
data-binding-value=""
/>
$3
`html
type="number"
data-action="change->data-binding#update"
data-binding-target="foo"
data-binding-condition="$source.value > 5"
data-binding-attribute="data-large"
/>
`$3
`html
type="checkbox"
data-action="change->data-binding#update"
data-binding-target="foo"
data-binding-condition="$source.checked"
data-binding-class="badger bodger"
/>
`$3
`html
type="checkbox"
data-action="change->data-binding#update"
data-binding-target="foo"
data-binding-condition="!$source.checked"
data-binding-attribute="disabled hidden"
/>
`$3
`html
type="checkbox"
data-action="change->data-binding#update"
data-binding-target="foo"
/> data-binding-ref="foo"
data-binding-condition="!$source.checked"
data-binding-attribute="disabled"
>
I will be disabled when checked
$3
`html
data-action="change->data-binding#update"
data-binding-condition="$source.value !== $target.dataset.id"
data-binding-target="bar"
data-binding-attribute="hidden"
>
`$3
`html
type="radio"
value="A"
checked
data-action="change->data-binding#update"
data-binding-target="radio"
data-binding-attribute="hidden"
data-binding-initial="false"
/>
type="radio"
value="B"
data-action="change->data-binding#update"
data-binding-target="radio"
data-binding-attribute="hidden"
data-binding-initial="false"
/>
Showing A
data-binding-ref="radio"
data-binding-condition="$source.value !== 'B'"
hidden
>
Showing B
$3
`html
data-action="click->data-binding#update"
data-binding-target="amount"
data-binding-property="value"
data-binding-initial="false"
data-binding-value="$source.dataset.fullAmount"
data-full-amount="12.99"
>
Full amount
`$3
`html
data-action="click->data-binding#update"
data-binding-target="amount"
data-binding-property="value"
data-binding-initial="false"
data-binding-value="$source.dataset.fullAmount"
data-full-amount="12.99"
data-binding-event="change input"
>
Full amount
`Security
This library is designed with security in mind and implements several protections against XSS (Cross-Site Scripting) attacks:
$3
ā
Content Security Policy (CSP) Compliant: The library does not use
eval(), Function(), or any other dynamic code execution methods. It uses a custom expression parser that is safe even with strict CSP policies.$3
The library uses a whitelist approach to prevent XSS attacks:
#### Allowed Properties
The following properties are safe to use with
data-binding-property:-
textContent - Safe text content
- value - Form input values
- checked - Checkbox/radio state
- disabled - Disabled state
- readOnly - Read-only state
- hidden - Visibility state
- tabIndex - Tab order
- ariaLabel, ariaHidden, ariaExpanded, ariaChecked, ariaDisabled, ariaSelected - ARIA attributes
- ariaValueText, ariaValueNow, ariaValueMin, ariaValueMax - ARIA value attributes#### Blocked Properties
The following properties are blocked for security:
-
innerHTML, outerHTML, insertAdjacentHTML - Can inject scripts
- onclick, onerror, onload, etc. - Event handlers
- src, href, action - Can execute JavaScript via URLs
- Any property starting with on - Event handlers
- Any property containing HTML - HTML manipulation#### Allowed Attributes
The following attributes are safe to use with
data-binding-attribute:-
hidden - Visibility
- disabled, readonly - Form states
- tabindex - Tab order
- role - ARIA role
- aria-label, aria-hidden, aria-expanded, aria-checked, aria-disabled, aria-selected - ARIA attributes
- aria-valuetext, aria-valuenow, aria-valuemin, aria-valuemax - ARIA value attributes
- All data-* attributes - Any attribute starting with data- is allowed#### Blocked Attributes
The following attributes are blocked for security:
-
onclick, onerror, onload, etc. - Event handlers
- src, href, action - Can execute JavaScript via URLs
- style - Can inject CSS that executes JavaScript (in some contexts)
- Any attribute starting with on - Event handlers$3
1. Never trust user input: Even though the library blocks dangerous properties/attributes, always validate and sanitize user input on the server side.
2. Use safe properties: Prefer
textContent over innerHTML for displaying user content.3. Validate expressions: The library validates expressions at initialization, but always review your
data-binding-value and data-binding-condition expressions.4. Monitor console errors: The library logs security violations to the console. Monitor these in development and production.
5. Keep dependencies updated: Regularly update this library and other dependencies to receive security patches.
$3
If you discover a security vulnerability, please report it responsibly. Do not open a public issue. Instead, contact the maintainers privately.
Contributing
Fork the project.
Install dependencies
`shell
$ yarn install
`Start the test watcher
`shell
$ yarn test:watch
`Running one-off test runs can be done with:
`shell
$ yarn test
``Write some tests, and add your feature. Send a PR.