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