Rails Does Destroyfully Call After Destroy Callbacks Again
Reflex classes are full of Reflex actions. Reflex actions are full of love. 🏩
What is a Reflex, really? Is it a transactional UI update that takes place over a persistent open up connection to the server? Is information technology a new tool on your chugalug that operates adjacent to and in tandem with concepts like REST and Ajax? Is it the smug feelings associated with successfully achieving a massive productivity arbitrage? Is information technology the boundless potential for unironic good in every kid?
There are three kinds of Reflex...
StimulusReflex features three distinct modes of operation, and y'all can use all three of them together in your application:
-
Folio Morph, which is the default, performs a full-page update
-
Selector Morph is for replacing the content of an element
-
Zip Morph, for executing functions that don't update your folio
Every Reflex starts off life as a Page Morph. You can change it to a unlike kind of Morph within of your Reflex action; there'southward no way to set a Morph type on the customer.
You can learn more about the control catamenia of each Morph by consulting this flowchart .
The rest of this folio generally assumes that you're working with a Page Morph. Selector and Zippo Morphs are described in particular on their own page:
Declaring a Reflex in HTML with data attributes
The fastest way to enable Reflex actions past using the data-reflex attribute. The syntax follows Stimulus format: [DOM-issue]->[ReflexClass]#[activeness]
< button data-reflex = " click->Comment#create " > Create </ button >
You can use additional information attributes to pass variables equally part of your Reflex payload.
data-reflex = " click->Comment#create "
data-post-id = " <%= @mail service.id %> "
It's a recommended best practice to put an id attribute on any chemical element that has a data-reflex attribute on it. id is unique in a valid DOM, and this is how StimulusReflex locates the controller which called the Reflex afterwards a morph operation.
If yous have multiple identical elements calling Reflex actions, no life-cycle mechanisms (afterReflex callbacks, success events etc) volition exist run.
Thank you to the magic of MutationObserver , a browser feature that allows StimulusReflex to know when the DOM has changed, StimulusReflex can pick upwardly data-reflex attributes on all HTML elements - even if they are dynamically created and inserted into your DOM.
This means that if yous parse a client-side markup format that has declarative Reflexes contained within, they will exist connected to StimulusReflex in less than a millisecond.
Declaring multiple Reflex events on an chemical element
Exercise you desire to trigger different Reflex actions for different events? Nosotros have you lot covered!
You tin specify multiple Reflex operations by separating them with a space:
< img src = " cat.jpg " data-reflex = " mouseenter->True cat#approach mouseleave->Cat#escape " >
There are 2 intentional limitations to this technique:
All Reflex actions must target the same controller. In the to a higher place case, it won't work properly if the mouseleave points to Dog#escape because, obviously, cats and dogs don't mix.
Also, you lot tin only specify ane action per event; this means data-reflex="click->Cat#eat click->Cat#slumber" will not piece of work. In this example, the second action would be discarded.
Inheriting data-attributes from parent elements
Yous might design your interface such that you accept a securely nested structure of data attributes on parent elements. Instead of writing code to travel your DOM and admission those values, you lot can utilise the data-reflex-dataset="combined" directive to scoop all data attributes upward the hierarchy and pass them as part of the Reflex payload.
< div data-postal service-id = " <%= @post.id %> " >
< div data-category-id = " <%= @category.id %> " >
< button data-reflex = " click->Comment#create " data-reflex-dataset = " combined " > Create </ button >
This Reflex action volition have postal service-id and category-id accessible:
class CommentReflex < ApplicationReflex
puts element . dataset [ "post-id" ]
puts element . dataset [ "category-id" ]
If a data attribute appears several times, the deepest ane in the DOM tree is taken. In the following example, information-id would exist 2 .
< push button information-id = " ii " data-reflex = " Example#any " data-reflex-dataset = " combined " > Click me </ button >
Calling a Reflex in a Stimulus controller
In add-on to declared Reflexes, y'all can also initiate a Reflex with JavaScript:
< push button data-controller = " foo "
data-action = " foo#bar " > </ button >
Note that in that location is no relation between the name or activity on the Stimulus controller, and the Reflex being called in the consequence handler:
app/javascript/controllers/foo_controller.js
import ApplicationController from './application_controller.js'
export default class extends ApplicationController {
this . stimulate ( 'Example#test' )
This is possible because ApplicationController imports the StimulusReflex Controller and calls StimulusReflex.register(this) . Every bit a effect, ApplicationController and all Stimulus Controllers that extend it gain a method called stimulate .
When you use declarative Reflex calls via information-reflex attributes in your HTML, the stimulate method is chosen for you. 🤯 You volition larn all well-nigh this procedure in Understanding Reflex Controllers .
stimulate is extremely flexible
this.stimulate(cord target, [DOMElement element], [Object options], ...[JSONObject statement])
target [required] (exception: see "Requesting a Refresh" below): a string containing the server Reflex course and method, in the form Example#increase .
element [optional]: a reference to a DOM chemical element which will provide both attributes and scoping selectors. Frequently pointed to event.target in JavaScript. Defaults to the DOM element of the controller in scope .
options [optional]: an optional object containing at least i of reflexId , selectors, resolveLate, serializeForm or attrs . Tin be used to override the ID of a given Reflex or override the selector(s) to be used for Page or Selector morphs. Avant-garde users might wish to alter the attributes sent to the server for the current Reflex.
statement [optional]: a splat of JSON-compliant JavaScript datatypes - array, object, string, numeric or boolean - volition be received by the Reflex action as ordered arguments.
Receiving arguments
When calling stimulate() with JavaScript, you have the option to ship arguments to the Reflex action method on the server. Options have to be JSON-serializable data types and are received in a predictable order. Objects that are passed as parameters are accessible using both symbol and string keys.
class CatReflex < ApplicationReflex
def adopt ( opinions , legs = 4 )
Note: the method signature has to lucifer. If the Reflex activeness is expecting ii arguments and doesn't receive two arguments, information technology will heighten an exception.
Note that you tin can simply provide parameters to Reflex actions by calling the stimulate method with arguments; at that place is no equivalent for Reflexes declared with data attributes.
Combined data attributes with stimulate()
data-reflex-dataset="combined" also works with the stimulate() office:
< div information-folder-id = " <%= binder.id %> " data-controller = " folders " >
< button data-activity = " click->folders#edit " data-reflex-dataset = " combined " > Edit </ button >
By default, stimulate treats the DOM chemical element that the controller is placed on as the chemical element parameter. Instead, we utilise event.target to make the clicked button element be the source of the Reflex action. All combined data attributes will be picked up, and all callbacks and events will emit from the button.
import ApplicationController from './application_controller.js'
export default class extends ApplicationController {
this . stimulate ( "Folder#edit" , event . target )
Aborting a Reflex
Information technology is possible that you might desire to arrest a Reflex and prevent it from executing. For case, the user might non have appropriate permissions to complete an activeness, or peradventure some other side issue like missing information would cause an exception if the Reflex was immune to continue.
We'll become into much deeper detail on life-bicycle callbacks on the Life-cycle page, merely for now it is important to know that if there is a before_reflex method in your Reflex class, it will be executed before the Reflex action. If y'all call raise :abort in the before_reflex method, the Reflex activity volition non execute. Instead, the client will receive a halted event and execute the reflexHalted callback if it's defined.
Halted Reflexes exercise not execute afterReflex callbacks on the server or customer.
Requesting a "refresh"
If yous are building advanced workflows, there are edge cases where y'all may want to initiate a Reflex action that does cypher but re-render the view template and morph any new changes into the DOM. While this shouldn't be your main tool, information technology's possible for your data to be mutated by destructive external side effects. 🧟
Calling stimulate with no parameters invokes a special global Reflex that allows you to force a re-render of the current state of your application UI. This is the same matter that the user would see if they hit their browser's Refresh button, except without the painfully irksome round-trip bicycle.
It'due south as well possible to trigger this global Reflex by passing nothing but a browser effect to the data-reflex aspect. For instance, the post-obit push button element will refresh the folio content every time the user presses it:
< button information-reflex = " click " > Refresh </ button >
Understanding StimulusReflex Controllers
You've already read that StimulusReflex is based on the Stimulus JavaScript library. When you lot're using Stimulus, you lot put Controllers on your DOM elements by setting data-controller="foo" . In the following example, nosotros adhere an instance of the Controller class defined in foo_controller.js to a div :
< div information-controller = " foo " > </ div >
The StimulusReflex client is literally just a circuitous Stimulus Controller. When you lot first load your page, the first thing information technology does is scan your DOM for data-reflex attributes:
< button data-reflex = " click->Foo#remove " > Remove this push </ button >
When it finds a data-reflex attribute, it adds a Stimulus Controller chosen stimulus-reflex and a __perform action to the element:
< button data-reflex = " click->Foo#remove "
data-controller = " stimulus-reflex "
information-action = " click->stimulus-reflex#__perform " > Remove this push </ button >
These iii attributes contain everything required to call the remove Reflex activeness on the Foo Reflex class when the user clicks the push. The button is at present a Reflex Controller Element , because the StimulusReflex Controller responsible for the Reflex is attached to it. As a consequence, all life-cycle events will exist emitted from it.
StimulusReflex scans all content inserted into the DOM for information-reflex attributes, regardless of whether that content is there when the page loads or if information technology comes from a Reflex, an Ajax fetch, or your ain local JavaScript logic. You lot don't take to do annihilation special to ensure that your UI is Reflex-enabled.
What'due south really interesting near this is that y'all'll notice we don't have to add the foo Stimulus controller to the button element in order to be able to call Foo Reflexes. We are non doing it magically in the background, either; at that place simply doesn't need to be a foo StimulusReflex Controller on the element in guild for a declared Reflex to call FooReflex#remove on the server.
There might non even be a foo_controller.js , and that'due south okay.
If we click the button, we'll see that the StimulusReflex Controller used to handle the Reflex was stimulus-reflex as expected:
You should translate this as "my Reflex was handled by the ApplicationController" - that is, application_controller.js - which was installed during setup. Any generic Reflex callbacks divers in the ApplicationController itself will be run, simply this is usually express to spinners and other "meta" UI effects.
If you want to provide handlers for life-cycle events, you will demand to create a StimulusReflex Controller course with the aforementioned name as your Reflex:
< push button data-reflex = " click->Foo#remove "
data-controller = " foo " > Remove this button </ button >
app/javascript/controllers/foo_controller.js
import ApplicationController from './application_controller'
consign default class extends ApplicationController {
// Remember, Cipher Morphs end on the "later" stage
afterRemove ( element , reflex , noop , reflexId ) {
console . log ( 'Push has been deleted!' )
When yous click the button, it calls the Foo Reflex - and removes the button from the DOM:
app/reflexes/foo_reflex.rb
grade FooReflex < ApplicationReflex
cable_ready . remove ( selector : 'button' ). circulate
And you tin can encounter that the StimulusReflex Controller responsible for the Reflex was foo :
And then, that'due south pretty cool, correct? 🕶️ It knows to use foo instead of stimulus-reflex .
The thing is... where's our console message? It never happened, because we destroyed the Reflex Controller Element (the button ) that was belongings the instance of foo that was responsible for the Reflex. That includes the life-cycle events that make callbacks possible.
Now, it'southward very mutual to utilise information-reflex and information-controller on the same chemical element. There's nothing inherently wrong with doing and then - in fact, it's a solid go-to strategy for handling callbacks - unless your Reflex does something that results in the Reflex Controller Element beingness destroyed (think: innerHTML ) or otherwise disconnected from your DOM.
The primary reason StimulusReflex, Phoenix LiveView and Laravel LiveWire all use the morphdom library for updates is to avert destroying large chunks of your DOM when at that place are very good reasons non to do and so - such as non _Keyser Söze-_ing your Stimulus controllers.
Information technology'southward notable that Hotwire chooses to use innerHTML over morphdom, which could ultimately result in a great many frustrated Stimulus developers.
It'south just a reality of UI design that sometimes when you present a table of rows that represent model records, clicking on the "Delete" push makes the row it lives on go abroad . Nosotros demand the ability to delegate the responsibleness for the Reflex and its life-wheel events to one of the Reflex Controller Element'south ancestors; specifically, an antecedent that will survive whatever DOM mutations are acquired by the Reflex. This brings u.s. back full-circle to the original foo case:
< div information-controller = " foo " >
< button information-reflex = " click->Foo#remove "
data-controller = " stimulus-reflex "
information-action = " click->stimulus-reflex#__perform " > Remove this button </ button >
With the ancestor div safely out of harm'south fashion, we at present see the desired console message:
Every bit you can encounter, it's at present possible to remove the Reflex Controller Element (the push button ) without losing your ability to accept your StimulusReflex Controller still generating life-cycle events.
If your StimulusReflex Controller is getting Sözed , you need to move further up your DOM.
Why no automatic foo Controller?
At present that you are a StimulusReflex Controller good 🧙♂️, you sympathize the reasons that the StimulusReflex library does not and can non automatically add together a foo controller case to an element with data-reflex="click->Foo#remove attribute:
-
Every Reflex Controller Element would exist forced to also hold its own StimulusReflex Controller instance, maybe
-
That Controller might be
fooor it might bestimulus-reflexdepending on whether afoo_controller.jsexists, which is an ugly ambivalence -
In that location could be scenarios where y'all don't want a Reflex to run life-cycle callbacks
-
Delegating the StimulusReflex Controller element to an antecedent would be impossible 😱
It's not just that we prioritize flexibility over magic. To forcefulness automatic StimulusReflex Controllers would brand nearly of the techniques described in this chapter impossible.
Source: https://docs.stimulusreflex.com/reflexes
0 Response to "Rails Does Destroyfully Call After Destroy Callbacks Again"
Post a Comment