OSDN Git Service

Regular updates
[twpd/master.git] / stimulus-reflex.md
1 ---
2 title: StimulusReflex
3 category: Ruby
4 layout: 2017/sheet
5 updated: 2021-01-07
6 ---
7
8 ### via Data Attributes
9
10 Trigger reflexes without writing any javascript with the `data-reflex` attribute.
11
12 ```erb
13 <!-- index.html.erb -->
14 <a
15   href="#"
16   data-reflex="click->CounterReflex#increment"
17   data-step="1"
18   data-count="<%= @count.to_i %>"
19   >Increment <%= @count.to_i %></a
20 >
21 ```
22
23 ```ruby
24 # counter_reflex.rb
25 class CounterReflex < StimulusReflex::Reflex
26   def increment
27     @count = element.dataset[:count].to_i + element.dataset[:step].to_i
28   end
29 end
30 ```
31
32 ### from Stimulus.js Controller
33
34 Stimulus.js controllers registered with StimulusReflex can use the `stimulate` method to trigger reflexes
35
36 ```erb
37 <!-- index.html.erb -->
38 <a href="#"
39   data-controller="counter"
40   data-action="click->counter#increment"
41 >Increment <%= @count %></a>
42 ```
43
44 ```javascript
45 // counter_controller.js
46 import { Controller } from 'stimulus'
47 import StimulusReflex from 'stimulus_reflex'
48
49 export default class extends Controller {
50   connect() {
51     StimulusReflex.register(this)
52   }
53
54   increment(event) {
55     event.preventDefault()
56     this.stimulate('Counter#increment', 1)
57   }
58 }
59 ```
60
61 ```ruby
62 # counter_reflex.rb
63 class CounterReflex < StimulusReflex::Reflex
64   def increment(step = 1)
65     session[:count] = session[:count].to_i + step
66    end
67 end
68 ```
69
70 ## Morphs
71
72 ### Selector morphs
73
74 Instead of refreshing the entire page, you can specify a portion of the page to update with `morph(selector, content)`
75
76 ```erb
77 <!-- show.html.erb -->
78 <header data-reflex="click->Example#change">
79   <%= render partial: "path/to/foo", locals: {message: "Am I the medium or the massage?"} %>
80 </header>
81 ```
82
83 ```erb
84 <!-- _foo.html.erb -->
85 <div id="foo">
86   <span class="spa"><%= message %></span>
87 </div>
88 ```
89
90 ```ruby
91 # example_reflex.rb
92 class ExampleReflex < ApplicationReflex
93   def change
94     morph "#foo", "Your muscles... they are so tight."
95   end
96 end
97 ```
98
99 ### Nothing morph
100
101 Use `morph :nothing` in reflexes that do something on the server without updating the client.
102
103 ```ruby
104 # example_reflex.rb
105 class ExampleReflex < ApplicationReflex
106   def change
107     LongRunningJob.perform_later
108     morph :nothing
109   end
110 end
111 ```
112
113 ## Lifecycle
114
115 ### Server-side callbacks
116
117 Reflex classes can use the following callbacks. [Full Docs](http://docs.stimulusreflex.com/lifecycle#server-side-reflex-callbacks)
118
119 - `before_reflex`
120 - `around_reflex`
121 - `after_reflex`
122
123 ### Client-side callbacks (generic)
124
125 StimulusReflex controllers automatically support five generic lifecycle callback methods.
126
127 - `beforeReflex(element, reflex, noop, reflexId)` prior to sending a request over the web socket
128 - `reflexSuccess(element, reflex, noop, reflexId)` after the server side Reflex succeeds and the DOM has been updated
129 - `reflexError(element, reflex, error, reflexId)` whenever the server side Reflex raises an error
130 - `reflexHalted(element, reflex, noop, reflexId)` reflex canceled with throw :abort in the before_reflex callback
131 - `afterReflex(element, reflex, noop, reflexId)` after both success and error
132 - `finalizeReflex(element, reflex, noop, reflexId)` after both success and error
133
134 ### Client-side callbacks (custom)
135
136 StimulusReflex controllers can define up to five custom lifecycle callback methods for each Reflex action. These methods use a naming convention based on the name of the Reflex. e.g. for the `add_one` reflex:
137
138 - `beforeAddOne(element, reflex, noop, reflexId)`
139 - `addOneSuccess(element, reflex, noop, reflexId)`
140 - `addOneError(element, reflex, error, reflexId)`
141 - `addOneHalted(element, reflex, noop, reflexId)`
142 - `afterAddOne(element, reflex, noop, reflexId)`
143 - `finalizeAddOne(element, reflex, noop, reflexId)`
144
145 ### Client-side events
146
147 If you need to know when a Reflex method is called, but you're working outside of the Stimulus controller that initiated it, you can subscribe to receive DOM events
148
149 - `stimulus-reflex:before`
150 - `stimulus-reflex:success`
151 - `stimulus-reflex:error`
152 - `stimulus-reflex:halted`
153 - `stimulus-reflex:after`
154
155 There are also events related to the StimulusReflex library setting up and connecting to ActionCable
156
157 - `stimulus-reflex:connected`
158 - `stimulus-reflex:disconnected`
159 - `stimulus-reflex:rejected`
160 - `stimulus-reflex:ready`
161
162 ## Helpful tips
163
164 ### Forms
165
166 If a Reflex is called on a form element - or a child of that form element - then the data for the whole form will be properly serialized and made available to the Reflex action method as the `params` accessor. [Read more](http://docs.stimulusreflex.com/working-with-forms)
167
168 ### Promises
169
170 `stimulate()` method returns a promise
171
172 ```javascript
173 this.stimulate('Comments#create')
174   .then(() => this.doSomething())
175   .catch(() => this.handleError())
176 ```
177
178 ### Inheriting data-attributes from parent elements
179
180 You can use the `data-reflex-dataset="combined"` directive to scoop all data attributes up the DOM hierarchy and pass them as part of the Reflex payload.
181
182 ```erb
183 <!-- new.html.erb -->
184 <div data-post-id="<%= @post.id %>">
185   <div data-category-id="<%= @category.id %>">
186     <button data-reflex="click->Comment#create" data-reflex-dataset="combined">Create</button>
187   </div>
188 </div>
189 ```
190
191 ```ruby
192 # comment_reflex.rb
193 class CommentReflex < ApplicationReflex
194   def create
195     puts element.dataset["post-id"]
196     puts element.dataset["category-id"]
197   end
198 end
199 ```
200
201 ### Reflex root
202
203 Instead of updating your entire page, you can specify exactly which parts of the DOM will be updated using the `data-reflex-root` attribute. [Full docs](http://docs.stimulusreflex.com/morph-modes#scoping-page-morphs)
204
205 ```text
206 <!-- index.html.erb -->
207 <div data-reflex-root="[forward],[backward]">
208   <input type="text" value="<%= @words %>" data-reflex="keyup->Example#words">
209   <div forward><%= @words %></div>
210   <div backward><%= @words&.reverse %></div>
211 </div>
212 ```
213
214 ```ruby
215 # example_reflex.rb
216   def words
217     @words = element[:value]
218   end
219 ```
220
221 ### Permanent elements
222
223 Add data-reflex-permanent to any element in your DOM, and it will be left unchanged by full-page Reflex updates and morph calls that re-render partials.
224
225 ```erb
226 <!-- index.html.erb -->
227 <div data-reflex-permanent>
228   <iframe src="https://ghbtns.com/github-btn.html?user=hopsoft&repo=stimulus_reflex&type=star&count=true" frameborder="0" scrolling="0" class="ghbtn"></iframe>
229   <iframe src="https://ghbtns.com/github-btn.html?user=hopsoft&repo=stimulus_reflex&type=fork&count=true" frameborder="0" scrolling="0" class="ghbtn"></iframe>
230 </div>
231 ```
232
233 ### Aborting a reflex
234
235 call `raise :abort` within a reflex method to cancel it.
236
237 ```ruby
238 # comment_reflex.rb
239 class CommentReflex < ApplicationReflex
240   def create
241     raise :abort
242   end
243 end
244 ```