A modest JavaScript framework for the HTML you already have

Overview

Stimulus Stimulus

A modest JavaScript framework for the HTML you already have

Stimulus is a JavaScript framework with modest ambitions. It doesn't seek to take over your entire front-end—in fact, it's not concerned with rendering HTML at all. Instead, it's designed to augment your HTML with just enough behavior to make it shine. Stimulus pairs beautifully with Turbo to provide a complete solution for fast, compelling applications with a minimal amount of effort.

How does it work? Sprinkle your HTML with controller, target, and action attributes:

<div data-controller="hello">
  <input data-hello-target="name" type="text">

  <button data-action="click->hello#greet">Greet</button>

  <span data-hello-target="output"></span>
</div>

Then write a compatible controller. Stimulus brings it to life automatically:

// hello_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ "name", "output" ]

  greet() {
    this.outputTarget.textContent =
      `Hello, ${this.nameTarget.value}!`
  }
}

Stimulus continuously watches the page, kicking in as soon as attributes appear or disappear. It works with any update to the DOM, regardless of whether it comes from a full page load, a Turbo page change, or an Ajax request. Stimulus manages the whole lifecycle.

You can write your first controller in five minutes by following along in the Stimulus Handbook.

You can read more about why we created this new framework in The Origin of Stimulus.

Installing Stimulus

Stimulus integrates with the webpack asset packager to automatically load controller files from a folder in your app.

You can use Stimulus with other asset packaging systems, too. And if you prefer no build step at all, just drop a <script> tag on the page and get right down to business.

See the Installation Guide for detailed instructions.

Getting Help & Contributing Back

Looking for the docs? Once you've read through the Handbook, consult the Stimulus Reference for API details.

Have a question about Stimulus? 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 requests, too!

We expect all Stimulus contributors to abide by the terms of our Code of Conduct.

Acknowledgments

Stimulus is MIT-licensed open-source software from Basecamp, the creators of Ruby on Rails.

Continuous integration VMs generously provided by Sauce Labs.


© 2020 Basecamp, LLC.

Comments
  • The Values and Classes APIs

    The Values and Classes APIs

    This pull request introduces two new APIs to Stimulus: Values and Classes. These APIs are designed to improve upon, and ultimately obviate, the current Data Map API. We plan to ship them together in the upcoming Stimulus 2.0 release.

    Values

    Most uses of the Data Map API in Basecamp fall under the following categories:

    • Storing small strings, such as URLs, dates, or color values
    • Keeping track of a numeric index into a collection
    • Bootstrapping a controller with a JSON object or array
    • Conditioning behavior on a per-controller basis

    However, the Data Map API only works with string values. That means we must manually convert to and from other types as needed. The Values API handles this type conversion work automatically.

    Value properties

    The Values API adds support for a static values object on controllers. The keys of this object are Data Map keys, and the values declare their data type:

    export default class extends Controller {
      static values = {
        url: String,
        refreshInterval: Number,
        loadOnConnect: Boolean
      }
    
      connect() {
        if (this.loadOnConnectValue) {
          this.load()
        }
      }
    
      async load() {
        const response = await fetch(this.urlValue)
        // ...
        setTimeout(() => this.load(), this.refreshIntervalValue)
      }
    }
    

    Supported types and defaults

    This pull request implements support for five built-in types:

    Type | Serialized attribute value | Default value ---- | -------------------------- | ------------- Array | JSON.stringify(array) | [] Boolean | boolean.toString() | false Number | number.toString() | 0 Object | JSON.stringify(object) | {} String | Itself | ""

    Each type has a default value. If a value is declared in a controller but its associated data attribute is missing, the getter property will return its type's default.

    Controller properties

    Stimulus automatically generates three properties for each entry in the object:

    Type | Kind | Property name | Effect ---- | ---- | ------------- | ----------- Boolean, Number, Object, String | Getter | this.[name]Value | Reads data-[identifier]-[name]-value Array | Getter | this.[name]Values | Reads data-[identifier]-[name]-values Boolean, Number, Object, String | Setter | this.[name]Value= | Writes data-[identifier]-[name]-value Array | Setter | this.[name]Values= | Writes data-[identifier]-[name]-values Boolean, Number, Object, String | Existential | this.has[Name]Value | Tests for presence of data-[identifier]-[name]-value Array | Existential | this.has[Name]Values | Tests for presence of data-[identifier]-[name]-values

    Note that array values are always pluralized, both as properties and as attributes.

    Value changed callbacks

    In addition to value properties, the Values API introduces value changed callbacks. A value changed callback is a specially named method called by Stimulus whenever a value's data attribute is modified.

    To observe changes to a value, define a method named [name]ValueChanged(). For example, a slideshow controller with a numeric index property might define an indexValueChanged() method to display the specified slide:

    export default class extends Controller {
      static values = { index: Number }
    
      indexValueChanged() {
        this.showSlide(this.indexValue)
      }
    
      // ...
    }
    

    Stimulus invokes each value changed callback once when the controller is initialized, and again any time the value's data attribute changes.

    Even if a value's data attribute is missing when the controller is initialized, Stimulus will still invoke its value changed callback. Use the existential property to determine whether the data attribute is present.


    Classes

    Another common use of the Data Map API is to store CSS class names.

    For example, Basecamp's copy-to-clipboard controller applies a CSS class to its element after a successful copy. To avoid inlining a long BEM string in our controller, and to keep things loosely coupled, we declare the class in a data-clipboard-success-class attribute:

    <div data-controller="clipboard"
         data-clipboard-success-class="copy-to-clipboard--success">
    

    and access it using this.data.get("successClass") in the controller:

    this.element.classList.add(this.data.get("successClass"))
    

    The Classes API formalizes and refines this pattern.

    Class properties

    The Classes API adds a static classes array on controllers. As with targets, Stimulus automatically adds properties for each class listed in the array:

    // clipboard_controller.js
    export default class extends Controller {
      static classes = [ "success", "supported" ]
    
      initialize() {
        if (/* ... */) {
          this.element.classList.add(this.supportedClass)
        }
      }
    
      copy() {
        // ...
        this.element.classList.add(this.successClass)
      }
    }
    

    Kind | Property name | Effect ---- | ------------- | ----------- Getter | this.[name]Class | Reads the data-[identifier]-[name]-class attribute Existential | this.has[Name]Class | Tests whether the data-[identifier]-[name]-class attribute is present

    Declarations are assumed to be present

    When you access a class property in a controller, such as this.supportedClass, you assert that the corresponding data attribute is present on the controller element. If the declaration is missing, Stimulus throws a descriptive error:

    Screenshot showing error message: "Missing attribute 'data-clipboard-supported-class'"

    If a class is optional, you must first use the existential property (e.g. this.hasSupportedClass) to determine whether its declaration is present.



    Unifying target attributes

    We've made a change to the target attribute syntax to align them with values and classes, and also to make the controller identifier more prominent by moving it into the attribute name.

    The original syntax is:

    <div data-target="[identifier].[name]">
    

    and the updated syntax is:

    <div data-[identifier]-target="[name]">
    

    The original syntax is supported but deprecated

    Stimulus 2.0 will support both syntaxes, but using the original syntax will display a deprecation message in the developer console. We intend to remove the original syntax in Stimulus 3.0.


    Try it out in your application

    Update the Stimulus entry in package.json to point to the latest development build:

    "stimulus": "https://github.com/stimulusjs/dev-builds/archive/b8cc8c4/stimulus.tar.gz"
    
    opened by sstephenson 59
  • Communicating between controllers

    Communicating between controllers

    I know this isn't exactly an issue, but I think it is a good question. I think of your controllers as controllers for components on a page. How would you suggest to communicate between more controllers?

    I have two components List (data rendered in a list) and ListFilter (bunch of selects and text inputs). List is loaded using AJAX. It should be reloaded every time ListFilter changes. I would like to use separate controllers for List and ListFilter to keep files small and organized.

    So, how to trigger a List reload when ListFilter changes?

    opened by vojtad 43
  • Pass data attributes as action method argument

    Pass data attributes as action method argument

    Very often the question comes up on how to pass arguments to Stimulus action methods.

    Global parameters can be passed to the action using the data API but when a controller has multiple elements and individual parameters (an id by example) for each of those elements, it is a common practice to pass the value using the standard dataset API: event.currentTarget.dataset.id

    One typical example would be for a set of buttons with ids and associated fetch actions

    <div data-controller="items" data-items-base-url="https://api.stimulus.org/upvote/">
      <button data-action="items#upvote" data-id='1'></button>
      <button data-action="items#upvote" data-id='2'></button>
      <button data-action="items#upvote" data-id='3'></button>
    </div>
    
    ...
    upvote(e) {
      const id = e.currentTarget.dataset.id
      fetch(this.baseUrl + id, { method: "POST" })
    }
    
    get baseUrl() {
      return this.data.get("baseUrl")
    }
    ...
    

    Proposal

    This PR is a draft proposal for a more concise API by passing all data attributes value of the currentTarget as a parameter of the action.

    action(event, data) {}
    

    Using basic destructuring the above example can be simplified like this:

    ...
    upvote(e, { id }) {
      fetch(this.baseUrl + id, { method: "POST" })
    }
    ...
    

    I think this could make it simpler to understand and avoids confusions around target and currentTarget. If the element is of a type HTMLFormElement we could also pass the value at the same time.

    Just wanted to propose this for now if you feel this could make sense, I ll add some associated test

    opened by adrienpoly 33
  • Introduce once and passive events to actions

    Introduce once and passive events to actions

    As per the discussion in this thread, this pull request introduces two new options for events attached to an action

    • once
    • passive
    addEventListener('click', action, {passive: false, once: false})
    

    The proposed API is the following

    <!--passive: true-->
    <div data-action="touchmove.passive->controller#method"></div>
    <div data-action="[email protected]>controller#method"></div>
    
    <!--once: true-->
    <button data-action="click.once->controller#vote"></button>
    <div data-action="[email protected]>controller#delete"></div>
    

    Defaults events

    Defaults events with options are not supported

    an action like this

    <button data-action="controller#method">
    

    must be written like this to pass an option:

    <button data-action="click.once->controller#method">
    

    considerations: I see two possible syntaxes to support default event with options

    <button data-action="once.controller#method">
    
    <button data-action="controller#method.once">
    // and this would be generalized 
    <div data-action="touchmove->controller#method.passive"></div>
    <div data-action="touchmove@window->controller#method.passive"></div>
    

    open to discussion....

    Tests

    I added a new test case for those events in src/tests/cases/event_options_tests.ts. The tests for once events are ok IMHO but I am having a hard time testing the passive event

    When I test in a real app the passive event I can get it to work but when I log as below an action in my test suite I always get non-passive events.

    logPassive(event: Event) {
        event.preventDefault()
        if (event.defaultPrevented) {
         //this is not a passive event
          this.recordAction("passive", event, false)
        } else {
          //this is a passive event
          this.recordAction("passive", event, true)
        }
      }
    

    Will keep investigating, maybe I am missing something here. Could it be that the chrome headless version does not support passive event???

    Example

    once a dev-build is available (not sure how new build are trigged) here is a test example https://glitch.com/edit/#!/stimulus-event-options?path=src/controllers/options_controller.js:15:23

    Still to do…

    • [x] fix test for passive events
    • [x] do we need to handle default event with a simpler API?
    • [x] key/value syntax to support all event listener options?
    • [x] Polyfill for ie11? potential solution but it does not support handleEvent
    • [x] documentation
    opened by adrienpoly 27
  • Add modifier to filter keyboard events.

    Add modifier to filter keyboard events.

    from https://github.com/hotwired/stimulus/issues/440

    There are many cases where we want to support keyboard operations as part of accessibility.

    In such cases, determining which key is pressed each time and filtering it in the function may promote a divergence between the function name and the behavior when adhering to the teaching that declarative function names should be given.

    prevTab(evt) {
      if (evt.key ! == 'ArrowLeft') { return; } // here
      ...
    }
    

    So, I thought it might be possible to express this filtering on the HTML side to make the function implementation more straightforward.

    <button type="button" role="tab" data-action="keydown.left->tabs#prevTab">xxx</button>
    //  syntax: `data-action="{event}.{filter_modifier}->{controller}#{action}"`
    

    I know that there is a third party library that does this filter on the HTML side, using a different attribute value than data-action. However, I'm hoping that this can be integrated into data-action as a Stimulus feature to make this simpler to achieve.

    const keyMappings: { [key: string]: string } = {
      "enter":  "Enter",
      "tab":    "Tab",
      "esc":    "Escape",
      "space":  " ",
      "up":     "ArrowUp",
      "down":   "ArrowDown",
      "left":   "ArrowLeft",
      "right":  "ArrowRight",
      "home":   "Home",
      "end":    "End"
    }
    

    Here is a mapping of the keys supported by the filter modifier. (I selected the keys that are often used mainly for accessibility support.)

    opened by NakajimaTakuya 22
  • Outlets API

    Outlets API

    This pull request introduces a new Outlets API to Stimulus. Heavily influenced by HEY's Stimulus Outlet Properties.

    Outlets

    The concept of outlets is very similar to Stimulus Targets. While a target is a specifically marked element within the scope of the controller element, an outlet is a reference to one or many other Stimulus Controller instances on the same page.

    The important difference is that outlets don't necessarily have to be within the scope of the controller element, as they can be anywhere on the page.

    Outlet declaration

    The Outlets API adds support for a static outlets array on controllers. This array declares which other controller identifiers can be used as outlets on this controller:

    // list_controller.js
    
    export default class extends Controller {
      static outlets = [ "item" ]
    
      connect () {
        this.itemOutlets.forEach(item => ...)
      }
    }
    

    Attributes and selectors

    In order to declare a controller instance as an outlet on the "host" controller you need the add a data-attribute to the host controller element in the form of:

    data-[identifier]-[outlet]-outlet="[selector]"
    
    <div id="pagination" data-controller="pagination">
      <!-- ... -->
    </div>
    
    <!-- ... -->
    
    <div class="list" data-controller"list" data-list-pagination-outlet="#pagination">
      <!-- ... -->
    </div>
    

    If you try to declare an element as an outlet which doesn't have the corresponding data-controller element on it, Stimulus will throw an exception:

    Missing "data-controller=pagination" attribute on outlet element for "list" controller`
    

    The selector can be any valid CSS selector.

    <div class="item" data-controller="item">Item 1</div>
    <div class="item" data-controller="item">Item 2</div>
    <div class="item" data-controller="item">Item 3</div>
    <div class="item" data-controller="item">Item 4</div>
    
    <!-- ... -->
    
    <div class="list" data-controller"list" data-list-item-outlet=".item">
      <!-- ... -->
    </div>
    

    Controller properties

    Stimulus automatically generates five properties for every outlet identifier in the array:

    | Kind | Property name | Return Type | Effect | ---- | ------------- | ----------- | ----------- | Existential | has[Name]Outlet | Boolean | Tests for presence of a name outlet | Singular | [name]Outlet | Controller | Returns the Controller instance of the first name outlet or throws an exception if none is present | Plural | [name]Outlets | Array<Controller> | Returns the Controller instances of all name outlets | Singular | [name]OutletElement | Element | Returns the Controller Element of the first name outlet or throws an exception if none is present | Plural | [name]OutletElements | Array<Element> | Returns the Controller Element's of all name outlets

    Accessing controllers and elements

    Since you get back a Controller instance from the properties you are also able to access the Values, Classes, Targets and all of the other properties and functions that controller instance defines, for example:

    this.itemOutlet.idValue
    this.itemOutlet.childrenTargets
    this.itemOutlet.activeClasses
    

    You are also able to invoke any of the functions the controller defines.

    // item_controller.js
    
    export default class extends Controller {
      markAsDone(event) {
        // ...
      }
    }
    
    // list_controller.js
    
    export default class extends Controller {
      static outlets = [ "item" ]
    
      markAllAsDone(event) {
        this.itemOutlets.forEach(item => item.markAsDone(event))
      }
    }
    

    Similarly with the Outlet Element:

    this.imageOutletElement.dataset.value
    this.imageOutletElement.getAttribute("src")
    this.imageOutletElements.map(image => image.id)
    

    Outlet callbacks

    Outlet callbacks are specially named functions called by Stimulus to let you respond to whenever an outlet is added or removed.

    To observe outlet changes, define a function named [name]OutletConnected() or [name]OutletDisconnected().

    // list_controller.js
    
    export default class extends Controller {
      static outlets = [ "item" ]
    
      itemOutletConnected(outlet, element) {
        // ...
      }
    
      itemOutletDisconnected(outlet, element) {
        // ...
      }
    }
    

    Outlets are assumed to be present

    When you access an outlet property in a controller, you assert that at least one corresponding outlet is present. If the declaration is missing are no matching outlet is found Stimulus will throw an exception:

    Missing outlet element "item" for "list" controller
    

    Optional outlets

    If an outlet is optional, you must first check if an outlet is present using the existential property:

    if (this.hasItemOutlet) {
      doSomethingWith(this.itemOutlet)
    }
    

    Resolves https://github.com/hotwired/stimulus/issues/35 and Resolves https://github.com/hotwired/stimulus/issues/552.

    Feedback and any other ideas are very welcome!

    opened by marcoroth 20
  • Custom default values for the Values API

    Custom default values for the Values API

    Hey there!

    I teamed up with @leastbad to implement custom default values for the Values API as discussed in #335.

    The original syntax was proposed by @dancallaghan here and enhanced by @paulozoom here.

    You can still define your values with the type, so the syntax is fully backwards-compatible. Also I've added some tests to cover the most common scenarios.

    Example

    Custom default values are perfect so you don't need to write getters (or similar constructs) like this:

    // demo_controller.js
    
    import { Controller } from 'stimulus'
    
    export default class extends Controller {
      static values = {
        url: String,
        interval: Number,
        params: Object,
        clicked: Boolean,
        ids: Array
      }
    
      connect () {
        console.log(this.url, this.interval, this.params, this.clicked, this.ids)
      }
      
      get url() {
        return this.urlValue || '/bill'
      }
      
      get interval() {
        return this.intervalValue || 5
      }
      
      get params() {
        return this.paramsValue || { name: 'sue' }
      }
     
     get clicked() {
        return this.clickedValue || true
      }
    
      get ids() {
        return this.idsValue || [1]
      }
    }
    
    // "/bill" - 5 - { name: "sue" } - true - [1]
    

    The above example can be simplified to this:

    // demo_controller.js
    
    import { Controller } from 'stimulus'
    
    export default class extends Controller {
      static values = {
        url: '/bill',
        interval: 5,
        params: { name: 'sue' },
        clicked: true,
        ids: [1]
      }
    
      connect () {
        console.log(this.urlValue, this.intervalValue, this.paramsValue, this.clickedValue, this.idsValue)
      }
    }
    
    // "/bill" - 5 - { name: "sue" } - true - [1]
    

    Override custom default values

    You can override the custom default values as you already do with the current Value API.

    <div data-controller="demo"
      data-demo-url-value="/jack"
      data-demo-interval-value="10"
      data-demo-params-value='{ "name": "ruby" }'
      data-demo-clicked-value="false"
      data-demo-ids-value="[2, 3, 4]">
    </div>
    

    We are happy to add docs as well if the implementation and the syntax is fine! Thank you!

    Resolves #335.

    opened by marcoroth 19
  • Class constructor Controller cannot be invoked without 'new' (3.0.0-beta1)

    Class constructor Controller cannot be invoked without 'new' (3.0.0-beta1)

    Just had a go at updating our Stimulus 2.x app to the beta. We're using Rails/Webpacker.

    I think there's some missing parts of the docs around this change (in the push to importmaps) so I did have to guess some of the new Webpack configuration code. Namely - definitionsFromContext used to come from stimulus/webpack-helpers and I think it now comes from just @hotwired/stimulus.

    import { Application } from "@hotwired/stimulus";
    import { definitionsFromContext } from "@hotwired/stimulus";
    
    const application = Application.start();
    const context = require.context("./controllers", true, /\.js$/);
    application.load(definitionsFromContext(context));
    

    This runs, however in the browser I get the following error - following the stacktrace it occurs in each of my controller modules.

    Uncaught (in promise) TypeError: Class constructor Controller cannot be invoked without 'new'
        at new _default (application.js:3048)
        at new extended (vendors-node_modules_babel_runtime_regenerator_index_js-node_modules_hotwired_stimulus_dist_s-68817b.js:1338)
        at new Context (vendors-node_modules_babel_runtime_regenerator_index_js-node_modules_hotwired_stimulus_dist_s-68817b.js:1198)
        at Module.fetchContextForScope (vendors-node_modules_babel_runtime_regenerator_index_js-node_modules_hotwired_stimulus_dist_s-68817b.js:1400)
        at Module.connectContextForScope (vendors-node_modules_babel_runtime_regenerator_index_js-node_modules_hotwired_stimulus_dist_s-68817b.js:1386)
        at Router.scopeConnected (vendors-node_modules_babel_runtime_regenerator_index_js-node_modules_hotwired_stimulus_dist_s-68817b.js:1705)
        at ScopeObserver.elementMatchedValue (vendors-node_modules_babel_runtime_regenerator_index_js-node_modules_hotwired_stimulus_dist_s-68817b.js:1623)
        at ValueListObserver.tokenMatched (vendors-node_modules_babel_runtime_regenerator_index_js-node_modules_hotwired_stimulus_dist_s-68817b.js:814)
        at TokenListObserver.tokenMatched (vendors-node_modules_babel_runtime_regenerator_index_js-node_modules_hotwired_stimulus_dist_s-68817b.js:747)
        at vendors-node_modules_babel_runtime_regenerator_index_js-node_modules_hotwired_stimulus_dist_s-68817b.js:741
    

    Nothing fancy going on with my controllers - just updated the import from stimulus to @hotwired/stimulus as expected.

    import { Controller } from "@hotwired/stimulus";
    
    export default class extends Controller {
      //
    }
    
    opened by dwightwatson 16
  • Add [key]Classes method to better handle multiple CSS classes

    Add [key]Classes method to better handle multiple CSS classes

    Closes #341

    :wave:

    Utility-first CSS is becoming increasingly popular as an alternative to BEM (the convention used in the Handbook). This PR adds the [key]Classes property that maps a space separated list of classes into an array.

    This new property can be combined with the spread operator for more elegant classList manipulation when you need to change multiple classes at once.

    Here is an adapted example from my production codebase:

    // highlight_controller.js
    export default class extends Controller {
      static targets = [ "subject" ]
      static classes = [ "pulse" ]
    
      pulse() {
        this.subjectTarget.classList.add(...this.pulseClasses)
      }
    }
    
    <div data-controller="highlight" 
         data-highlight-pulse-class="animate-pulse ring-8 ring-teal-600 ring-offset-8 rounded">
    
      <button data-action="click->highlight#pulse">
        Show me
      </button>
    
      <span data-highlight-target="subject">
         ...
      </span>
    </div>
    

    Pros:

    • Cleaner usage with utility CSS (e.g. Tailwind)
    • ${key}Classes plural API mirrors target conventions

    Cons:

    • Inconsistency that fooTargets is mapped by multiple data-x-y-targets attributes, while fooClasses is mapped by space separated string (too much magic?)

    I've added some basic test cases and happy to add docs if this is something you'd like added. Open to any and all feedback or alternatives :)

    opened by swanson 16
  • Webpacker 5.1 breaks recommended TypeScript controller pattern

    Webpacker 5.1 breaks recommended TypeScript controller pattern

    See: https://github.com/rails/webpacker/issues/2558

    Webpacker 5.1 changed the loader for TypeScript, which changed code compilation behavior, which breaks class properties.

    So the controller declaration:

    I have a simple Stimulus controller, defined to start as follows:

    import { Controller } from "stimulus"
    
    export default class extends Controller {
      static classes = ["hidden"]
      static targets = ["filterInput", "concert"]
    
      hiddenClass: string
      concertTargets: HTMLElement[]
      filterInputTarget: HTMLInputElement
    
      // and so on
    

    Now breaks with a TypeError: Cannot set property hiddenClass of #<extended> which has only a getter.

    opened by noelrappin 15
  • Values API default values

    Values API default values

    We know that you're working hard on getting v2 ready for release, so it seems like a good time to formally propose an enhancement to the specification proposed in #202 to support providing default values.

    The best syntax I've seen so far was proposed by @dancallaghan in his comment on the PR:

    static values = {
      url: String,
      refreshInterval: [Number, 300],
      loadOnConnect: [Boolean, true]
    }
    

    I love this because it's non-intrusive and falls back to the current syntax.

    opened by leastbad 14
  • @hotwired/stimulus vs stimulus Package

    @hotwired/stimulus vs stimulus Package

    Is there a difference between the @hotwired/stimulus vs the stimulus package? I'm trying to use an older stimulus package (https://github.com/jgorman/rails-datatables/blob/master/src/index.js) in a rails 7 project. When I tried to pin the package with importmap, it pulled stimulus. Tried using it, but it didn't work.

    I was not completely sure if there was another application being generated and the controller was registering to something else or if there are other incompatibilities (or just plain user error).

    Some insight on why many packages have @hotwired prefixed to them in npm would be great. If I missed the documentation, feel free to point me to it and I apologize in advance.

    opened by jonmchan 1
  • Feature request: polymorphic outlets

    Feature request: polymorphic outlets

    Outlets require that the target controller name corresponds to the outlet name, e.g. if I write data-source-target-outlet then my target element must have data-controller="target".

    This makes sense as a technical requirement, e.g. if there are multiple controllers registered on the target then the source must be able to discriminate between them to choose the controller to receive messages.

    The technical constraint appears to us to be preventing object oriented polymorphism, i.e. we must statically know the name of the controller in order to send it messages, and it gets baked into the code for the source controller.

    An example of where polymorphism would be useful is a generic "trigger" controller, ala aria-controls that triggers a change in state for a modal/menu controller.

    Source:

    class TriggerController extends Controller {
      static outlets = ["controlled"]
      toggle() {
        this.controlledOutlet.toggle();
      }
    }
    

    Targets:

    // menu, or modal, or accordion, etc
    class MenuController extends Controller {
      toggle() {
        ...
      }
    }
    

    We might use the source for a sliding menu, a modal+scrim, a menu, etc – anywhere that aria-controls might be appropriate.

    We have previously solved this problem using events, but there's quite a bit of boilerplate to this approach and were hoping that outlets could simplify this approach.

    opened by sfnelson 3
  • References: Support for ID Ref List

    References: Support for ID Ref List

    While it borrows from targets and outlets, the idea of references also draws inspiration from the WAI ARIA concept of an ID Ref and ID Ref List attribute, like:

    Providing built-in support from Stimulus for elements that a controller establishes an [id]-based relationship with through ARIA attributes could cultivate a virtuous cycle between client-side feature development (reliant on low-friction DOM traversal and state change callbacks) and support for assistive technologies (reliant on semantics, document-hierarchy driven relationships, and occasional hints from ARIA attributes).

    From the Documentation for References:


    References

    References provide direct access to elements within (and without!) a Controller's scope based on their [id] attribute's value.

    They are conceptually similar to Stimulus Targets and Stimulus Outlets, but provide access regardless of where they occur in the document.

    <button aria-controls="accordion" aria-expanded="false"
            data-controller="disclosure" data-action="click->disclosure#toggle">
      Show #accordion
    </button>
    
    ...
    
    <div id="accordion" hidden>
      ...
    </div>
    

    While a target is a specifically marked element within the scope of its own controller element, a reference can be located anywhere on the page.

    Definitions

    A Controller class can define a static references array to declare which of its element's attribute names to use to resolve its references.

    By default, a Controller's static references property is defined to include a list of ARIA ID reference and ID reference list attributes to establish [id]-based relationships out-of-the-box, including:

    Define attribute names in your controller class using the static references array:

    // disclosure_controller.js
    
    export default class extends Controller {
      static references = [ "aria-controls" ]
    
      toggle() {
        const expanded = this.element.getAttribute("aria-expanded")
    
        for (const ariaControlsReference of this.ariaControlsReferences) {
          ariaControlsReference.hidden = expanded != "true"
        }
      }
    }
    

    Properties

    For each attribute name defined in the static references array, Stimulus adds three properties to your controller, where [name] corresponds to an attribute's name:

    | Kind | Property name | Return Type | Effect | ---- | ------------- | ----------- | ----------- | Existential | has[Name]Reference | Boolean | Tests for presence of an element with [id="${name}"] | Singular | [name]Reference | Element | Returns the first Element whose [id] value is included in the [name] attribute's token or throws an exception if none are present | Plural | [name]References | Array<Element> | Returns all Elements whose [id] values are included in the [name] attribute's tokens

    Kebab-case attribute names will be transformed to camelCase and TitleCase. For example, aria-controls will transform into ariaControls and AriaControls.

    Reference Callbacks

    Reference callbacks are specially named functions called by Stimulus to let you respond to whenever a referenced element is added or removed from the document.

    To observe reference changes, define a method named [name]ReferenceConnected() or [name]ReferenceDisconnected().

    // combobox_controller.js
    
    export default class extends Controller {
      static references = [ "aria-activedescendant" ]
      static target = [ "selected" ]
    
      ariaActivedescendantReferenceConnected(element) {
        this.selectedTarget.innerHTML = element.textContent
      }
    
      ariaActivedescendantReferenceDisconnected(element) {
        this.selectedTarget.innerHTML = "No selection"
      }
    }
    

    References are Assumed to be Present

    When you access a Reference property in a Controller, you assert that at least one corresponding Reference is present. If the declaration is missing and no matching reference is found Stimulus will throw an exception:

    Missing element referenced by "[aria-controls]" for "disclosure" controller
    

    Optional references

    If a Reference is optional or you want to assert that at least one Reference is present, you must first check the presence of the Reference using the existential property:

    if (this.hasAriaControlsReference) {
      this.safelyCallSomethingOnTheReference(this.ariaControlsReference)
    }
    

    Alternatively, looping over an empty Array of references would have the same result:

    for (const ariaControlsReference of this.ariaControlsReferences) {
      this.safelyCallSomethingOnTheReference(this.ariaControlsReference)
    }
    
    opened by seanpdoyle 1
  • Outlets: Add observers for controller element attributes

    Outlets: Add observers for controller element attributes

    Extract DomTestCase helpers

    In some cases, calls to Element.setAttribute, Element.removeAttribute, Node.appendChild, or Element.remove must be followed up with a call to await this.nextFrame so that Stimulus has an opportunity to synchronize.

    This commit introduces asynchronous helper method versions of those calls that bake-in the subsequent call to this.nextFrame.

    Outlets: Add observers for controller element attributes

    With the current Outlet implementation, they're only ever connected or disconnected when an element matching the outlet selector connects or disconnects. The selector declared on the controller element can only ever be set once: when it's connected. If that attribute ever changes (for example, when it's set to a new value or removed entirely), the outlets are not updated.

    This commit adds test coverage to ensure the list of outlets and their items is synchronized with changes on both sides: when matching elements are connected and disconnected as well as when the selector that dictates whether or not they match is added, updated, or removed.

    To do so, this commit extends the SelectorObserver to also manage the lifecycle of an AttributeObserver instance alongside its internally managed ElementObserver.

    opened by seanpdoyle 1
  • Expose extra exports

    Expose extra exports

    We have a use case where we need some stimulus controllers to be able to interact with content within an iframe from outside the iframe. To achieve this we've created controllers that can deal with multiple scopes, this required us to extended a few classes that were not being exported so this change exposes those.

    opened by DEfusion 2
  • Value change callbacks are trigged by simply defining the value

    Value change callbacks are trigged by simply defining the value

    If I define a value without a default like

    static values = { "name": String }
    

    the corresponding change callback nameValueChanged() will be triggered, even if the value is never set or changed. I cobbled together a minimal working example

    <!doctype html>
    <html>
      <head>
        <meta charset="utf-8">
        <script type="module">
          import { Application, Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js"
          window.Stimulus = Application.start()
    
          Stimulus.register("hello", class extends Controller {
            static values = { "name": String }
    
            nameValueChanged(oldName, newName) {
              console.log("Name was changed from", oldName, " to ", newName);
            }
          })
        </script>
      </head>
      <body>
        <div data-controller="hello"></div>
      </body>
    </html>
    

    which will print Name was changed from <empty string> to undefined. Any chance on getting this fixed? Or is this the expected behavior? I'm not quite sure, but I guess the reason is

    https://github.com/hotwired/stimulus/blob/f690a6a2e856a35ebdb78246285b734da541338e/src/core/value_observer.ts#L76

    where defaultValue should be checked against null too.

    opened by fabian-as 1
Releases(v3.2.1)
  • v3.2.1(Nov 30, 2022)

    What's Changed

    • Fix compatibility with new hotkey syntax and jQuery/Bootstrap events by @NakajimaTakuya in https://github.com/hotwired/stimulus/pull/613

    Full Changelog: https://github.com/hotwired/stimulus/compare/v3.2.0...v3.2.1

    Source code(tar.gz)
    Source code(zip)
  • v3.2.0(Nov 28, 2022)

    What's Changed

    • Add modifier to filter keyboard events by @NakajimaTakuya in https://github.com/hotwired/stimulus/pull/442
    • Outlets API by @marcoroth in https://github.com/hotwired/stimulus/pull/576
    • Add ability to set afterLoad static methods on Controllers by @lb- in https://github.com/hotwired/stimulus/pull/579
    • Ensure that the Application.start static method uses overridden class by @lb- in https://github.com/hotwired/stimulus/pull/603

    Full Changelog: https://github.com/hotwired/stimulus/compare/v3.1.1...v3.2.0

    Source code(tar.gz)
    Source code(zip)
  • v3.1.1(Oct 31, 2022)

    What's Changed

    • Clear dangling EventListeners and Detached Nodes when a controller is removed from the DOM by @intrip in https://github.com/hotwired/stimulus/pull/592
    • Support custom Action Options by @seanpdoyle in https://github.com/hotwired/stimulus/pull/567
    • Fix exports field for stimulus package and Webpack 5 by @glaszig in https://github.com/hotwired/stimulus/pull/569
    • Make Action Parameters attributes case-insensitive by @marcoroth in https://github.com/hotwired/stimulus/pull/571
    • Retain backtrace for TypeErrors in value change callback by @elliotcm in https://github.com/hotwired/stimulus/pull/584

    Full Changelog: https://github.com/hotwired/stimulus/compare/v3.1.0...v3.1.1

    Source code(tar.gz)
    Source code(zip)
  • v3.1.0(Jul 18, 2022)

    What's Changed

    • Adds new Action Options, namely :stop and :prevent by @radiantshaw in https://github.com/hotwired/stimulus/pull/535
    • Adds new Action Option :self by @radiantshaw in https://github.com/hotwired/stimulus/pull/546
    • Allow action params for global events by @rik in https://github.com/hotwired/stimulus/pull/495
    • move shouldLoad check from register to load function by @adrienpoly in https://github.com/hotwired/stimulus/pull/493
    • Boolean decoding is not case-sensitive by @vastray in https://github.com/hotwired/stimulus/pull/472
    • Fire Value Change Callbacks consistently by @seanpdoyle in https://github.com/hotwired/stimulus/pull/499
    • Allow refining the type of Controller elements by @rik in https://github.com/hotwired/stimulus/pull/529
    • Fix stimulus glue package for cdn use by @marcoroth in https://github.com/hotwired/stimulus/pull/468

    Full Changelog: https://github.com/hotwired/stimulus/compare/v3.0.1...v3.1.0

    Source code(tar.gz)
    Source code(zip)
  • v3.0.1(Oct 7, 2021)

    What's Changed

    • Default to toggle event on details HTML element by @rik in https://github.com/hotwired/stimulus/pull/464
    • Prevent infinite looping in target callbacks by @seanpdoyle in https://github.com/hotwired/stimulus/pull/459
    • Include webpack-helpers in stimulus glue/proxy package by @marcoroth in https://github.com/hotwired/stimulus/pull/453

    Full Changelog: https://github.com/hotwired/stimulus/compare/v3.0.0...v3.0.1

    Source code(tar.gz)
    Source code(zip)
  • v3.0.0-rc.1(Sep 23, 2021)

    • ADDED: Static shouldLoad function can be used to prevent a controller from registering based on environmental circumstances [#448]
    • REMOVED: Warnings were not working in a number of common instances, so will need to wait for 3.1.
    • REMOVED: Color highlighting for debug mode had problems with dark mode and accessibility.
    Source code(tar.gz)
    Source code(zip)
  • v3.0.0-beta.2(Sep 11, 2021)

  • v3.0.0-beta.1(Aug 30, 2021)

    • NEW: Pass action method params [#249]
    • NEW: Fire callbacks when targets are added or removed [#367]
    • NEW: Declare custom default values [#350]
    • NEW: Add [key]Classes method to better handle multiple CSS classes [#344]
    • NEW: Introduce a Debug mode [#354]
    • NEW: Emit warnings for undefined controllers, actions and targets [#413]
    • NEW: Add a convenience method for dispatching DOM events inside a controller [#302]

    Stimulus is moving package location on npm from stimulus to @hotwired/stimulus. The new package is an all-in-one, so no more individual packages for core, multi map, mutation-observers, etc.

    Stimulus 3 will no longer support IE11. Our compile target is now ES6+.

    Note: This release was built from the branch single-package, which will be merged to main as soon as a few issues regarding tests and examples are resolved. But that has no impact on the use of this beta release.

    Rails users: You can use this release via stimulus-rails 0.3.10 with the new importmap-rails approach.

    Source code(tar.gz)
    Source code(zip)
  • v2.0.0(Dec 4, 2020)

    • NEW: Values and CSS classes APIs (https://github.com/stimulusjs/stimulus/pull/202)
    • NEW: Support for DOM event listener options (https://github.com/stimulusjs/stimulus/pull/232)
    • CHANGED: Target attributes are now scoped by identifier (https://github.com/stimulusjs/stimulus/pull/202/commits/2235047c926f4b41abe92fadc250ca764b120e46)
    • CHANGED: Default event for text inputs from change to input (https://github.com/stimulusjs/stimulus/commit/14ba2abf75c7ce97e015199996239645d93cd1a9, https://github.com/stimulusjs/stimulus/issues/248)
    • FIXED: Invoking actions for events dispatched during connect (https://github.com/stimulusjs/stimulus/commit/6129975c2ab98b01e28c64ca9836d829cf6528ea, https://github.com/stimulusjs/stimulus/issues/222)
    • FIXED: Error using SVG elements in IE 11 (https://github.com/stimulusjs/stimulus/commit/aa76e25f104ed165a8985f9c93e25c7912ed218f, https://github.com/stimulusjs/stimulus/issues/274)
    • FIXED: Nested global action binding (https://github.com/stimulusjs/stimulus/commit/2b6facc403804c33d844d126315656a71f19a0dd, https://github.com/stimulusjs/stimulus/issues/307)

    If you're upgrading from a previous version of Stimulus, note that the syntax for target attributes has changed to move the controller identifier into the attribute's name. The new format is data-[identifier]-target="[name]" instead of data-target="[identifier].[name]". You can still use the old syntax, but you will see a warning in the console, and support will be removed in a future version.

    The data map API from Stimulus 1.0 will continue to work but is no longer documented and should be considered internal. We suggest migrating to the new values API.

    Source code(tar.gz)
    Source code(zip)
  • v1.1.1(Jan 7, 2019)

    • CHANGED: Various documentation improvements (#190, #193, #206)
    • FIXED: Type declarations for webpack helpers in the stimulus package (#214)
    • FIXED: Pin @stimulus/polyfills dependencies to work around upstream changes (0251c87795f351a7462d00fa0f028b2daf967a01)
    Source code(tar.gz)
    Source code(zip)
  • v1.1.0(Aug 23, 2018)

    • NEW: Stimulus Reference documentation
    • NEW: Ordered actions (#149)
    • NEW: @stimulus/polyfills package for legacy browser support (#134, #147, #170)
    • CHANGED: Applications now start when the DOM is interactive (#131)
    • CHANGED: Unminified UMD module for easier debugging (#151)
    • FIXED: Stimulus now accounts for missing mutation notifications from nodes removed by innerHTML assignment in IE 11 (#133) and, in rare cases, when annotating elements synchronously after removing them from an observed tree (#161)
    • INTERNAL: Upgraded to TypeScript 2.8.1 and Lerna 3.0.0-rc.0
    • INTERNAL: New build system (#155)
    Source code(tar.gz)
    Source code(zip)
  • v1.1.0-beta.1(Aug 1, 2018)

    • NEW: Ordered actions (#149)
    • NEW: @stimulus/polyfills package for legacy browser support (#134, #147, #170)
    • CHANGED: Applications now start when the DOM is interactive (#131)
    • CHANGED: Unminified UMD module for easier debugging (#151)
    • FIXED: Stimulus now accounts for missing mutation notifications from nodes removed by innerHTML assignment in IE 11 (#133) and, in rare cases, when annotating elements synchronously after removing them from an observed tree (#161)
    • INTERNAL: Upgraded to TypeScript 2.8.1 and Lerna 3.0.0-rc.0
    • INTERNAL: New build system (#155)
    Source code(tar.gz)
    Source code(zip)
  • v1.0.1(Feb 2, 2018)

  • v1.0.0(Jan 30, 2018)

    • NEW: Linked target properties (https://github.com/stimulusjs/stimulus/pull/61, https://github.com/stimulusjs/stimulus/pull/68)

      Define a controller's target names and Stimulus automatically creates properties for accessing them:

      export default class extends Controller {
        static targets = [ "source" ]
      
        initialize() {
          this.sourceTarget    // Element
          this.sourceTargets   // Element[]
          this.hasSourceTarget // boolean
        }
      }
      
    • NEW: Configurable error handler (https://github.com/stimulusjs/stimulus/pull/53)

      const application = Application.start()
      
      application.handleError = (error, message, detail) => {
        console.warn(message, detail)
        Raven.captureException(error)
      }
      
    • NEW: Namespaced identifiers (https://github.com/stimulusjs/stimulus/pull/65)

      If your controller file is named… | its identifier will be… --- | --- list_item_controller.js | list-item users/list_item_controller.js | users--list-item

    • CHANGED: Controller autoloading with webpack (https://github.com/stimulusjs/stimulus/pull/46)

      A new definitionsFromContext helper replaces the old autoload helper:

      const application = Application.start()
      -const context = require.context("./controllers", true, /\.js$/)
      -autoload(context, application)
      +const context = require.context("./controllers", true, /\.js$/)
      +application.load(definitionsFromContext(context))
      
    • REMOVED: Action method event target argument (https://github.com/stimulusjs/stimulus/pull/55)

      Previously, action methods were invoked with two arguments: event, eventTarget. Now, only the event is passed:

      -greet(event, eventTarget) {
      -  console.log(event, eventTarget)
      +greet(event) {
      +  console.log(event, event.target)
       }
      
    • REMOVED: Controller#{add,remove}Action (https://github.com/stimulusjs/stimulus/pull/50)

      Noted for posterity since these methods were undocumented.

    Source code(tar.gz)
    Source code(zip)
  • v0.9.0(Jan 30, 2018)

Owner
Hotwire
Build modern web apps by sending HTML over the wire
Hotwire
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

Supporting Vue.js Vue.js is an MIT-licensed open source project with its ongoing development made possible entirely by the support of these awesome ba

vuejs 201.6k Jan 7, 2023
Ember.js - A JavaScript framework for creating ambitious web applications

Ember.js is a JavaScript framework that greatly reduces the time, effort and resources needed to build any web application. It is focused on making yo

Ember.js 22.4k Jan 4, 2023
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

vue-next This is the repository for Vue 3.0. Quickstart Via CDN: <script src="https://unpkg.com/vue@next"></script> In-browser playground on Codepen S

vuejs 34.6k Jan 4, 2023
Relay is a JavaScript framework for building data-driven React applications.

Relay · Relay is a JavaScript framework for building data-driven React applications. Declarative: Never again communicate with your data store using a

Facebook 17.5k Jan 1, 2023
A rugged, minimal framework for composing JavaScript behavior in your markup.

Alpine.js Alpine.js offers you the reactive and declarative nature of big frameworks like Vue or React at a much lower cost. You get to keep your DOM,

Alpine.js 22.5k Jan 2, 2023
A JavaScript Framework for Building Brilliant Applications

mithril.js What is Mithril? Installation Documentation Getting Help Contributing What is Mithril? A modern client-side JavaScript framework for buildi

null 13.5k Dec 28, 2022
A functional and reactive JavaScript framework for predictable code

Cycle.js A functional and reactive JavaScript framework for predictable code Website | Packages | Contribute | Chat | Support Welcome Question Answer

Cycle.js 10.2k Jan 4, 2023
CrossUI is a free Cross-Browser Javascript framework with cutting-edge functionality for rich web application

CrossUI is a free Cross-Browser Javascript framework with cutting-edge functionality for rich web application

Jack Li 1.4k Jan 3, 2023
AngularJS - HTML enhanced for web apps!

AngularJS AngularJS lets you write client-side web applications as if you had a smarter browser. It lets you use good old HTML (or HAML, Jade/Pug and

Angular 59.3k Jan 1, 2023
htmx - high power tools for HTML

high power tools for HTML introduction htmx allows you to access AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attr

Big Sky Software 10.2k Jan 8, 2023
A declarative, HTML-based language that makes building web apps fun

A declarative, HTML-based language that makes building web apps fun ?? Docs ∙ Try Online ∙ Contribute ∙ Get Support Intro Marko is HTML re-imagined as

Marko 12k Jan 3, 2023
One framework. Mobile & desktop.

Angular - One framework. Mobile & desktop. Angular is a development platform for building mobile and desktop web applications using Typescript/JavaScr

Angular 85.6k Dec 31, 2022
The tiny framework for building hypertext applications.

Hyperapp The tiny framework for building hypertext applications. Do more with less—We have minimized the concepts you need to learn to get stuff done.

Jorge Bucaran 18.9k Jan 1, 2023
🌱 React and redux based, lightweight and elm-style framework. (Inspired by elm and choo)

English | 简体中文 dva Lightweight front-end framework based on redux, redux-saga and react-router. (Inspired by elm and choo) Features Easy to learn, eas

null 16.1k Jan 4, 2023
The AMP web component framework.

AMP ⚡ ⚡ ⚡ ⚡ Metrics Tooling AMP is a web component framework for easily creating user-first websites, stories, ads, emails and more. AMP is an open so

AMP 14.9k Jan 4, 2023
Front End Cross-Frameworks Framework - 前端跨框架跨平台框架

English | 简体中文 Omi - Front End Cross-Frameworks Framework Merge Web Components, JSX, Virtual DOM, Functional style, observe or Proxy into one framewor

Tencent 12.5k Dec 31, 2022
The Aurelia 1 framework entry point, bringing together all the required sub-modules of Aurelia.

aurelia-framework Aurelia is a modern, front-end JavaScript framework for building browser, mobile, and desktop applications. It focuses on aligning c

aurelia 11.7k Jan 7, 2023
🐰 Rax is a progressive React framework for building universal application. https://rax.js.org

Rax is a progressive React framework for building universal applications. ?? Write Once, Run Anywhere: write one codebase, run with Web, Weex, Node.js

Alibaba 7.8k Dec 31, 2022
:steam_locomotive::train: - sturdy 4kb frontend framework

Choo ?? ?? ?? ?? ?? ?? Fun functional programming A 4kb framework for creating sturdy frontend applications Website | Handbook | Ecosystem | Contribut

choo 6.7k Jan 4, 2023