turbo_reflex 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CODE_OF_CONDUCT.md +128 -0
- data/Gemfile +2 -5
- data/Gemfile.lock +128 -101
- data/README.md +469 -14
- data/Rakefile +15 -1
- data/app/assets/builds/turbo_reflex.min.js +2 -0
- data/app/assets/builds/turbo_reflex.min.js.map +7 -0
- data/app/assets/images/turbo-reflex-logo-dark.webp +0 -0
- data/app/assets/images/turbo-reflex-logo-light.webp +0 -0
- data/app/assets/images/turbo-reflex-mark.webp +0 -0
- data/app/controllers/concerns/turbo_reflex/controller.rb +132 -0
- data/app/javascript/elements.js +87 -0
- data/app/javascript/event_registry.js +34 -0
- data/app/javascript/frame_sources.js +28 -0
- data/app/javascript/lifecycle_events.js +24 -0
- data/app/javascript/security.js +7 -0
- data/app/javascript/turbo_reflex.js +111 -0
- data/bin/loc +1 -1
- data/lib/turbo_reflex/base.rb +32 -0
- data/lib/turbo_reflex/engine.rb +9 -0
- data/lib/turbo_reflex/sanitizer.rb +20 -0
- data/lib/turbo_reflex/version.rb +3 -1
- data/lib/turbo_reflex.rb +2 -5
- data/package.json +4 -4
- data/turbo_reflex.gemspec +30 -19
- data/yarn.lock +167 -157
- metadata +177 -10
- data/app/assets/builds/turbo_reflex.js +0 -1
- data/app/assets/builds/turbo_reflex.js.map +0 -7
- data/lib/turbo_reflex/railtie.rb +0 -4
data/README.md
CHANGED
@@ -1,28 +1,483 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
<p align="center">
|
2
|
+
<picture>
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://ik.imagekit.io/hopsoft/turbo-reflex-logo-light_2cG9cdQd1.webp?ik-sdk-version=javascript-1.4.3&updatedAt=1663075749336">
|
4
|
+
<img width="320" src="https://ik.imagekit.io/hopsoft/turbo-reflex-logo-dark_kSmo1eDLm.webp?ik-sdk-version=javascript-1.4.3&updatedAt=1663075749241" />
|
5
|
+
</picture>
|
6
|
+
<h1 align="center">
|
7
|
+
Welcome to TurboReflex 👋<br />
|
8
|
+
</h1>
|
9
|
+
<p align="center">
|
10
|
+
<a href="http://blog.codinghorror.com/the-best-code-is-no-code-at-all/">
|
11
|
+
<img alt="Lines of Code" src="https://img.shields.io/badge/loc-400-47d299.svg" />
|
12
|
+
</a>
|
13
|
+
<a href="https://codeclimate.com/github/hopsoft/turbo_reflex/maintainability">
|
14
|
+
<img src="https://api.codeclimate.com/v1/badges/fe1162a742fe83a4fdfd/maintainability" />
|
15
|
+
</a>
|
16
|
+
<a href="https://rubygems.org/gems/turbo_reflex">
|
17
|
+
<img alt="GEM Version" src="https://img.shields.io/gem/v/turbo_reflex?color=168AFE&include_prereleases&logo=ruby&logoColor=FE1616">
|
18
|
+
</a>
|
19
|
+
<a href="https://rubygems.org/gems/turbo_reflex">
|
20
|
+
<img alt="GEM Downloads" src="https://img.shields.io/gem/dt/turbo_reflex?color=168AFE&logo=ruby&logoColor=FE1616">
|
21
|
+
</a>
|
22
|
+
<a href="https://github.com/testdouble/standard">
|
23
|
+
<img alt="Ruby Style" src="https://img.shields.io/badge/style-standard-168AFE?logo=ruby&logoColor=FE1616" />
|
24
|
+
</a>
|
25
|
+
<a href="https://www.npmjs.com/package/turbo_reflex">
|
26
|
+
<img alt="NPM Version" src="https://img.shields.io/npm/v/turbo_reflex?color=168AFE&logo=npm">
|
27
|
+
</a>
|
28
|
+
<a href="https://www.npmjs.com/package/turbo_reflex">
|
29
|
+
<img alt="NPM Downloads" src="https://img.shields.io/npm/dm/turbo_reflex?color=168AFE&logo=npm">
|
30
|
+
</a>
|
31
|
+
<a href="https://bundlephobia.com/package/turbo_reflex@">
|
32
|
+
<img alt="NPM Bundle Size" src="https://img.shields.io/bundlephobia/minzip/turbo_reflex?label=bundle%20size&logo=npm&color=47d299">
|
33
|
+
</a>
|
34
|
+
<a href="https://github.com/sheerun/prettier-standard">
|
35
|
+
<img alt="JavaScript Style" src="https://img.shields.io/badge/style-prettier--standard-168AFE?logo=javascript&logoColor=f4e137" />
|
36
|
+
</a>
|
37
|
+
<a href="https://github.com/hopsoft/turbo_reflex/actions/workflows/tests.yml">
|
38
|
+
<img alt="Tests" src="https://github.com/hopsoft/turbo_reflex/actions/workflows/tests.yml/badge.svg" />
|
39
|
+
</a>
|
40
|
+
<a href="https://twitter.com/hopsoft">
|
41
|
+
<img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/hopsoft?logo=twitter&style=social">
|
42
|
+
</a>
|
43
|
+
</p>
|
44
|
+
</p>
|
45
|
+
|
46
|
+
#### TurboReflex enhances the [reactive programming](https://en.wikipedia.org/wiki/Reactive_programming) model for [Turbo Frames](https://turbo.hotwired.dev/reference/frames).
|
47
|
+
|
48
|
+
<!-- Tocer[start]: Auto-generated, don't remove. -->
|
49
|
+
|
50
|
+
## Table of Contents
|
51
|
+
|
52
|
+
- [Why TurboReflex?](#why-turboreflex)
|
53
|
+
- [Sponsors](#sponsors)
|
54
|
+
- [Dependencies](#dependencies)
|
55
|
+
- [Setup](#setup)
|
56
|
+
- [Usage](#usage)
|
57
|
+
- [Reflex Triggers](#reflex-triggers)
|
58
|
+
- [Lifecycle Events](#lifecycle-events)
|
59
|
+
- [Targeting Frames](#targeting-frames)
|
60
|
+
- [Working with Forms](#working-with-forms)
|
61
|
+
- [Server Side Reflexes](#server-side-reflexes)
|
62
|
+
- [Appending Turbo Streams](#appending-turbo-streams)
|
63
|
+
- [Setting Instance Variables](#setting-instance-variables)
|
64
|
+
- [Hijacking the Response](#hijacking-the-response)
|
65
|
+
- [Broadcasting Turbo Streams](#broadcasting-turbo-streams)
|
66
|
+
- [Putting it All Together](#putting-it-all-together)
|
67
|
+
- [License](#license)
|
68
|
+
- [Todos](#todos)
|
69
|
+
- [Releasing](#releasing)
|
70
|
+
|
71
|
+
<!-- Tocer[finish]: Auto-generated, don't remove. -->
|
72
|
+
|
73
|
+
## Why TurboReflex?
|
74
|
+
|
75
|
+
[Turbo Frames](https://turbo.hotwired.dev/reference/frames) are a terrific technology that can help you build modern reactive web applications.
|
76
|
+
They are similar to [iframes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) in that they focus on features like
|
77
|
+
discrete isolated content, browser history, and scoped navigation... *with the caveat that they share their parent's DOM tree.*
|
78
|
+
|
79
|
+
**TurboReflex** extends Turbo Frames and adds support for client triggered reflexes [*(think RPC)*](https://en.wikipedia.org/wiki/Remote_procedure_call).
|
80
|
+
Reflexes let you *sprinkle* ✨ in functionality and skip the ceremony of typical [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) boilerplate *(routes, controllers, actions, etc...)*.
|
81
|
+
Reflexes are great for features that ride atop RESTful resources. Things like making selections, toggling switches, adding filters, etc...
|
82
|
+
**Basically any feature where you've been tempted to create a non-RESTful action in a controller.**
|
83
|
+
|
84
|
+
Reflexes improve the developer experience (DX) of creating modern reactive applications.
|
85
|
+
They share the same mental model as React and other client side frameworks.
|
86
|
+
|
87
|
+
1. **Trigger an event**
|
88
|
+
2. **Change state**
|
89
|
+
3. **(Re)render to reflect the new state**
|
90
|
+
4. *repeat...*
|
91
|
+
|
92
|
+
*The primary distinction being that __state is wholly managed by the server__.*
|
93
|
+
|
94
|
+
TurboReflex is a lightweight Turbo Frame extension... which means that reactivity runs over HTTP.
|
95
|
+
**Web sockets are NOT used for the reactive critical path!** 🎉
|
96
|
+
|
97
|
+
## Sponsors
|
98
|
+
|
99
|
+
<p align="center">
|
100
|
+
<em>Proudly sponsored by</em>
|
101
|
+
</p>
|
102
|
+
<p align="center">
|
103
|
+
<a href="https://www.clickfunnels.com?utm_source=hopsoft&utm_medium=open-source&utm_campaign=turbo_reflex">
|
104
|
+
<img src="https://images.clickfunnel.com/uploads/digital_asset/file/176632/clickfunnels-dark-logo.svg" width="575" />
|
105
|
+
</a>
|
106
|
+
</p>
|
107
|
+
|
108
|
+
## Dependencies
|
109
|
+
|
110
|
+
- [rails](https://rubygems.org/gems/rails) `>=6.1`
|
111
|
+
- [turbo-rails](https://rubygems.org/gems/turbo-rails) `>=1.1`
|
112
|
+
- [@hotwired/turbo-rails](https://yarnpkg.com/package/@hotwired/turbo-rails) `>=7.1`
|
113
|
+
|
114
|
+
## Setup
|
115
|
+
|
116
|
+
1. Add the TurboReflex dependencies
|
117
|
+
|
118
|
+
```diff
|
119
|
+
# Gemfile
|
120
|
+
+gem "turbo_reflex", "~> 0.0.2"
|
121
|
+
```
|
122
|
+
|
123
|
+
```diff
|
124
|
+
# package.json
|
125
|
+
"dependencies": {
|
126
|
+
"@hotwired/turbo-rails": ">=7.1",
|
127
|
+
+ "turbo_reflex": "^0.0.2"
|
128
|
+
```
|
129
|
+
|
130
|
+
*Be sure to install the __same version__ of the Ruby and JavaScript libraries.*
|
131
|
+
|
132
|
+
2. Import TurboReflex in your JavaScript app
|
133
|
+
|
134
|
+
```diff
|
135
|
+
# app/javascript/application.js
|
136
|
+
+import 'turbo_reflex'
|
137
|
+
```
|
138
|
+
|
139
|
+
2. Add TurboReflex behavior to the Rails app
|
140
|
+
|
141
|
+
```diff
|
142
|
+
# app/views/layouts/application.html.erb
|
143
|
+
<html>
|
144
|
+
<head>
|
145
|
+
...
|
146
|
+
+ <%= turbo_reflex_meta_tag %>
|
147
|
+
...
|
148
|
+
```
|
149
|
+
|
150
|
+
```diff
|
151
|
+
# /app/controllers/application_controller.rb
|
152
|
+
class ApplicationController < ActionController::Base
|
153
|
+
+ include TurboReflex::Controller
|
154
|
+
end
|
155
|
+
```
|
3
156
|
|
4
157
|
## Usage
|
5
|
-
How to use my plugin.
|
6
158
|
|
7
|
-
|
8
|
-
|
159
|
+
This example illustrates how to use TurboReflex to manage upvotes on a Post.
|
160
|
+
|
161
|
+
1. **Trigger an event** - *register an element to listen for events that trigger reflexes*
|
162
|
+
|
163
|
+
```erb
|
164
|
+
<!-- app/views/posts/show.html.erb -->
|
165
|
+
<%= turbo_frame_tag dom_id(@post) do %>
|
166
|
+
<a href="#" data-turbo-reflex="VotesReflex#upvote">Upvote</a>
|
167
|
+
Upvote Count: <%= @post.votes >
|
168
|
+
<% end %>
|
169
|
+
```
|
170
|
+
|
171
|
+
2. **Change state** - *create a server side reflex that modifies state*
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
# app/reflexes/posts_reflex.rb
|
175
|
+
class PostsReflex < TurboReflex::Base
|
176
|
+
def upvote
|
177
|
+
Post.find(controller.params[:id]).increment! :votes
|
178
|
+
end
|
179
|
+
end
|
180
|
+
```
|
181
|
+
|
182
|
+
3. **(Re)render to reflect the new state** - *normal Rails / Turbo Frame behavior runs and (re)renders the frame*
|
183
|
+
|
184
|
+
### Reflex Triggers
|
185
|
+
|
186
|
+
TurboReady uses [event delegation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_delegation) to capture events that can trigger reflexes.
|
187
|
+
|
188
|
+
Here is the list of default events and respective elements that TurboReflex monitors.
|
189
|
+
|
190
|
+
- **`change`** - `<input>`, `<select>`, `<textarea>`
|
191
|
+
- **`submit`** - `<form>`
|
192
|
+
- **`click`** - `*` *all other elements*
|
193
|
+
|
194
|
+
It's possible to override these defaults like so.
|
195
|
+
|
196
|
+
```js
|
197
|
+
import TurboReflex from 'turbo_reflex'
|
198
|
+
|
199
|
+
// restrict `click` monitoring to <a> and <button> elements
|
200
|
+
TurboReflex.registerEvent('click', ['a', 'button'])
|
201
|
+
```
|
202
|
+
|
203
|
+
You can also register custom events and elements.
|
204
|
+
Here's an example that sets up monitoring for the `sl-change` event on the `sl-switch` element from the [Shoelace web component library](https://shoelace.style/).
|
205
|
+
|
206
|
+
```js
|
207
|
+
TurboReflex.registerEvent('sl-change', ['sl-switch'])
|
208
|
+
```
|
209
|
+
|
210
|
+
### Lifecycle Events
|
211
|
+
|
212
|
+
TurboReflex supports the following lifecycle events.
|
213
|
+
|
214
|
+
- `turbo-reflex:before-start` - fires before reflex processing starts
|
215
|
+
- `turbo-reflex:start` - fires before the reflex is sent to the server
|
216
|
+
- `turbo-reflex:finish` - fires after the server has processed the reflex and responded
|
217
|
+
- `turbo-reflex:missing-frame-id` - fires if the reflex cannot determine the target frame id
|
218
|
+
- `turbo-reflex:missing-frame` - fires if the the reflex cannot locate the frame element
|
219
|
+
- `turbo-reflex:missing-frame-src` - fires if the reflex cannot determine the frame's `src`
|
220
|
+
- `turbo-reflex:error` - fires if an unexpected error occurs
|
221
|
+
|
222
|
+
### Targeting Frames
|
223
|
+
|
224
|
+
By default TurboReflex targets the [`closest`](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest) `<turbo-frame>` element,
|
225
|
+
but you can also explicitly target other frames.
|
226
|
+
|
227
|
+
1. Look for `data-turbo-reflex-frame` on the reflex elemnt
|
228
|
+
|
229
|
+
```erb
|
230
|
+
<input type="checkbox"
|
231
|
+
data-turbo-reflex="ExampleReflex#work"
|
232
|
+
data-turbo-reflex-frame="some-frame-id">
|
233
|
+
```
|
234
|
+
|
235
|
+
2. Look for `data-turbo-frame` on the reflex element
|
236
|
+
|
237
|
+
```erb
|
238
|
+
<input type="checkbox"
|
239
|
+
data-turbo-reflex="ExampleReflex#work"
|
240
|
+
data-turbo-frame="some-frame-id">
|
241
|
+
```
|
242
|
+
|
243
|
+
3. Find the closest `<turbo-frame>` to the reflex element
|
244
|
+
|
245
|
+
```erb
|
246
|
+
<turbo-frame id="example-frame">
|
247
|
+
<input type="checkbox" data-turbo-reflex="ExampleReflex#work">
|
248
|
+
</turbo-frame>
|
249
|
+
```
|
250
|
+
|
251
|
+
### Working with Forms
|
252
|
+
|
253
|
+
TurboReflex works great with Rails forms.
|
254
|
+
Just specify the `data-turbo-reflex` attribute on the form.
|
255
|
+
|
256
|
+
```erb
|
257
|
+
# app/views/posts/post.html.erb
|
258
|
+
<%= turbo_frame_tag dom_id(@post) do %>
|
259
|
+
<%= form_with model: @post, html: { turbo_reflex: "ExampleReflex#work" } do |form| %>
|
260
|
+
...
|
261
|
+
<% end %>
|
262
|
+
<% end %>
|
263
|
+
|
264
|
+
<%= turbo_frame_tag dom_id(@post) do %>
|
265
|
+
<%= form_for @post, remote: true, html: { turbo_reflex: "ExampleReflex#work" } do |form| %>
|
266
|
+
...
|
267
|
+
<% end %>
|
268
|
+
<% end %>
|
269
|
+
|
270
|
+
<%= form_with model: @post,
|
271
|
+
html: { turbo_frame: dom_id(@post), turbo_reflex: "ExampleReflex#work" } do |form| %>
|
272
|
+
...
|
273
|
+
<% end %>
|
274
|
+
```
|
275
|
+
|
276
|
+
### Server Side Reflexes
|
277
|
+
|
278
|
+
The client side DOM attribute `data-turbo-reflex` is indicates what reflex *(Ruby class and method)* to invoke.
|
279
|
+
The attribute value is specified with RDoc notation. i.e. `ClassName#method_name`
|
280
|
+
|
281
|
+
Here's an example.
|
282
|
+
|
283
|
+
```erb
|
284
|
+
<a data-turbo-reflex="DemoReflex#example">
|
285
|
+
```
|
286
|
+
|
287
|
+
Server side reflexes can live anywhere in your app; however, we recommend you keep them in the `app` directory.
|
288
|
+
|
289
|
+
```diff
|
290
|
+
|- app
|
291
|
+
| |...
|
292
|
+
| |- models
|
293
|
+
+| |- reflexes
|
294
|
+
| |- views
|
295
|
+
```
|
296
|
+
|
297
|
+
Reflexes are simple Ruby classes that inherit from `TurboReflex::Base`.
|
298
|
+
They expose the following instance methods and properties.
|
299
|
+
|
300
|
+
- `element` - a struct that represents the DOM element that triggered the reflex
|
301
|
+
- `controller` - the Rails controller processing the HTTP request
|
302
|
+
- `turbo_stream` - a Turbo Stream [`TagBuilder`](https://github.com/hotwired/turbo-rails/blob/main/app/models/turbo/streams/tag_builder.rb)
|
303
|
+
- `turbo_streams` - a list of Turbo Streams to append to the response
|
9
304
|
|
10
305
|
```ruby
|
11
|
-
|
306
|
+
# app/reflexes/demo_reflex.rb
|
307
|
+
class DemoReflex < TurboReflex::Base
|
308
|
+
# The reflex method is invoked by an ActionController before filter.
|
309
|
+
# Standard Rails behavior takes over after the reflex method completes.
|
310
|
+
def example
|
311
|
+
# - execute business logic
|
312
|
+
# - update state
|
313
|
+
# - append additional Turbo Streams
|
314
|
+
end
|
315
|
+
end
|
12
316
|
```
|
13
317
|
|
14
|
-
|
15
|
-
|
16
|
-
|
318
|
+
### Appending Turbo Streams
|
319
|
+
|
320
|
+
It's possible to append additional Turbo Streams to the response in a reflex.
|
321
|
+
Appended streams are added to the response body **after** the Rails controller action has completed and rendered the view template.
|
322
|
+
|
323
|
+
```ruby
|
324
|
+
# app/reflexes/demo_reflex.rb
|
325
|
+
class DemoReflex < TurboReflex::Base
|
326
|
+
def example
|
327
|
+
# logic...
|
328
|
+
turbo_streams << turbo_stream.append("dom_id", "CONTENT")
|
329
|
+
turbo_streams << turbo_stream.prepend("dom_id", "CONTENT")
|
330
|
+
turbo_streams << turbo_stream.replace("dom_id", "CONTENT")
|
331
|
+
turbo_streams << turbo_stream.update("dom_id", "CONTENT")
|
332
|
+
turbo_streams << turbo_stream.remove("dom_id")
|
333
|
+
turbo_streams << turbo_stream.before("dom_id", "CONTENT")
|
334
|
+
turbo_streams << turbo_stream.after("dom_id", "CONTENT")
|
335
|
+
turbo_streams << turbo_stream.invoke("console.log", args: ["Whoa! 🤯"])
|
336
|
+
end
|
337
|
+
end
|
17
338
|
```
|
18
339
|
|
19
|
-
|
20
|
-
|
21
|
-
|
340
|
+
*This proves especially powerful when paired with [TurboReady](https://github.com/hopsoft/turbo_ready).*
|
341
|
+
|
342
|
+
> 📘 **NOTE:** `turbo_stream.invoke` is a [TurboReady](https://github.com/hopsoft/turbo_ready#usage) feature.
|
343
|
+
|
344
|
+
### Setting Instance Variables
|
345
|
+
|
346
|
+
It can be useful to set instance variables on the Rails controller from the reflex.
|
347
|
+
|
348
|
+
Here's an example that shows how to do this.
|
349
|
+
|
350
|
+
```erb
|
351
|
+
<!-- app/views/posts/index.html.erb -->
|
352
|
+
<%= turbo_frame_tag dom_id(@posts) do %>
|
353
|
+
<%= check_box_tag :all, :all, @all, data: { turbo_reflex: "PostsReflex#toggle_all" } %>
|
354
|
+
View All
|
355
|
+
|
356
|
+
<% @posts.each do |post| %>
|
357
|
+
...
|
358
|
+
<% end %>
|
359
|
+
<% end %>
|
360
|
+
```
|
361
|
+
|
362
|
+
```ruby
|
363
|
+
# app/reflexes/posts_reflex.rb
|
364
|
+
class PostsReflex < TurboReflex::Reflex
|
365
|
+
def toggle_all
|
366
|
+
posts = element.checked ? Post.all : Post.unread
|
367
|
+
controller.instance_variable_set(:@all, element.checked)
|
368
|
+
controller.instance_variable_set(:@posts, posts)
|
369
|
+
end
|
370
|
+
end
|
22
371
|
```
|
23
372
|
|
24
|
-
|
25
|
-
|
373
|
+
```ruby
|
374
|
+
# app/controllers/posts_controller.rb
|
375
|
+
class PostsController < ApplicationController
|
376
|
+
def index
|
377
|
+
@posts ||= Post.unread
|
378
|
+
end
|
379
|
+
end
|
380
|
+
```
|
381
|
+
|
382
|
+
### Hijacking the Response
|
383
|
+
|
384
|
+
Sometimes you may want to hijack the normal Rails response from within a reflex.
|
385
|
+
|
386
|
+
For example, consider the need for a related but separate form that updates a subset of user attributes.
|
387
|
+
We'd like to avoid creating a non RESTful route,
|
388
|
+
but aren't thrilled at the prospect of adding REST boilerplate for a new route, controller, action, etc...
|
389
|
+
|
390
|
+
In that scenario we can reuse an existing route and hijack the response handling with a reflex.
|
391
|
+
|
392
|
+
Here's how to do it.
|
393
|
+
|
394
|
+
```erb
|
395
|
+
<!-- app/views/users/show.html.erb -->
|
396
|
+
<%= turbo_frame_tag "user-alt" do %>
|
397
|
+
<%= form_with model: @user, data: { turbo_reflex: "UserReflex#example" } do |form| %>
|
398
|
+
...
|
399
|
+
<% end %>
|
400
|
+
<% end %>
|
401
|
+
```
|
402
|
+
|
403
|
+
The form above will send a `PATCH` request to `users#update`,
|
404
|
+
but we'll hijack the handling in the reflex so we never hit `users#update`.
|
405
|
+
|
406
|
+
```ruby
|
407
|
+
# app/reflexes/user_reflex.html.erb
|
408
|
+
class UserReflex < TurboReflex::Base
|
409
|
+
def example
|
410
|
+
# business logic, save record, etc...
|
411
|
+
controller.render html: "<turbo-frame id='user-alt'>We Hijacked the response!</turbo-frame>".html_safe
|
412
|
+
end
|
413
|
+
end
|
414
|
+
```
|
415
|
+
|
416
|
+
Remember that reflexes are invoked by a controller [before filter](https://guides.rubyonrails.org/action_controller_overview.html#filters).
|
417
|
+
That means rendering from inside a reflex halts the standard request cycle.
|
418
|
+
|
419
|
+
### Broadcasting Turbo Streams
|
420
|
+
|
421
|
+
You can also broadcast Turbo Streams to subscribed users from a reflex.
|
422
|
+
|
423
|
+
```ruby
|
424
|
+
# app/reflexes/demo_reflex.rb
|
425
|
+
class DemoReflex < TurboReflex::Base
|
426
|
+
def example
|
427
|
+
# logic...
|
428
|
+
Turbo::StreamsChannel
|
429
|
+
.broadcast_invoke_later_to "some-subscription", "console.log", args: ["Whoa! 🤯"]
|
430
|
+
end
|
431
|
+
end
|
432
|
+
```
|
433
|
+
|
434
|
+
*Learn more about Turbo Stream broadcasting by reading through the
|
435
|
+
[hotwired/turbo-rails](https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb) source code.*
|
436
|
+
|
437
|
+
> 📘 **NOTE:** `broadcast_invoke_later_to` is a [TurboReady](https://github.com/hopsoft/turbo_ready#broadcasting) feature.
|
438
|
+
|
439
|
+
### Putting it All Together
|
440
|
+
|
441
|
+
The best way to learn this stuff is from working examples.
|
442
|
+
Be sure to clone the library and run the test application.
|
443
|
+
Then dig into the internals.
|
444
|
+
|
445
|
+
```sh
|
446
|
+
git clone https://github.com/hopsoft/turbo_reflex.git
|
447
|
+
cd turbo_reflex
|
448
|
+
bundle
|
449
|
+
cd test/dummy
|
450
|
+
bin/rails s
|
451
|
+
# View the app in a browser at http://localhost:3000
|
452
|
+
```
|
453
|
+
|
454
|
+
You can review the implementation in [`test/dummy/app`](https://github.com/hopsoft/turbo_reflex/tree/main/test/dummy).
|
455
|
+
*Feel free to add some demos and submit a pull request while you're in there.*
|
456
|
+
|
457
|
+

|
26
458
|
|
27
459
|
## License
|
460
|
+
|
28
461
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
462
|
+
|
463
|
+
## Todos
|
464
|
+
|
465
|
+
- [ ] Add tests for lifecycle events
|
466
|
+
- [ ] Add tests for select elements
|
467
|
+
- [ ] Add tests for checkbox elements
|
468
|
+
- [ ] Add controller tests
|
469
|
+
- [ ] Add tests for all variants of frame targeting
|
470
|
+
|
471
|
+
|
472
|
+
## Releasing
|
473
|
+
|
474
|
+
1. Run `yarn upgrade` and `bundle update` to pick up the latest
|
475
|
+
1. Bump version number at `lib/turbo_reflex/version.rb`. Pre-release versions use `.preN`
|
476
|
+
1. Run `bin/standardize`
|
477
|
+
1. Run `rake build` and `yarn build`
|
478
|
+
1. Commit and push changes to GitHub
|
479
|
+
1. Run `rake release`
|
480
|
+
1. Run `yarn publish --no-git-tag-version`
|
481
|
+
1. Yarn will prompt you for the new version. Pre-release versions use `-preN`
|
482
|
+
1. Commit and push any changes to GitHub
|
483
|
+
1. Create a new release on GitHub ([here](https://github.com/hopsoft/turbo_reflex/releases)) and generate the changelog for the stable release for it
|
data/Rakefile
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "bundler/setup"
|
3
4
|
require "bundler/gem_tasks"
|
5
|
+
require "rake/testtask"
|
6
|
+
|
7
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
8
|
+
load "rails/tasks/engine.rake"
|
9
|
+
load "rails/tasks/statistics.rake"
|
10
|
+
|
11
|
+
Rake::TestTask.new do |test|
|
12
|
+
test.libs << "test"
|
13
|
+
test.test_files = FileList["test/**/*_test.rb"]
|
14
|
+
test.warning = false
|
15
|
+
end
|
16
|
+
|
17
|
+
task default: :test
|
@@ -0,0 +1,2 @@
|
|
1
|
+
var l={beforeStart:"turbo-reflex:before-start",start:"turbo-reflex:start",finish:"turbo-reflex:finish",error:"turbo-reflex:error",missingFrameId:"turbo-reflex:missing-frame-id",missingFrame:"turbo-reflex:missing-frame",missingFrameSrc:"turbo-reflex:missing-frame-src"};function y(t,e=document,r={}){let n=new CustomEvent(t,{detail:r,cancelable:!0,bubbles:!0});e.dispatchEvent(n)}function F(){Object.values(l).forEach(t=>console.log(t))}var o={...l,dispatch:y,logEventNames:F};var u={};addEventListener("turbo:before-fetch-response",t=>{let e=t.target;u[e.id]=e.src;let{turboReflexActive:r,turboReflexElementId:n}=e.dataset;if(!r)return;let s=document.getElementById(n);delete e.dataset.turboReflexActive,delete e.dataset.turboReflexElementId,o.dispatch(o.finish,s||document,{frame:e,element:s||"Unknown! Missing id attribute."})});addEventListener("turbo:frame-load",t=>{let e=t.target;e.dataset.turboReflexSrc=u[e.id]||e.src||e.dataset.turboReflexSrc,delete u[e.id]});var L={get token(){return document.getElementById("turbo-reflex-token").getAttribute("content")}},c=L;function m(t){return t.closest("[data-turbo-reflex]")}function S(t){return t.closest("turbo-frame")}function b(t){let e=t.dataset.turboReflexFrame||t.dataset.turboFrame;if(!e){let r=S(t);r&&(e=r.id)}return e||(console.error("The reflex element does not specify a frame!","Please move the reflex element inside a <turbo-frame> or set the 'data-turbo-reflex-frame' or 'data-turbo-frame' attribute.",t),o.dispatch(o.missingFrameId,t,{element:t})),e}function g(t){let e=document.getElementById(t);return e||(console.error(`The frame '${t}' does not exist!`),o.dispatch(o.missingFrame,document,{id:t})),e}function x(t){let e=t.dataset.turboReflexSrc||t.src;return e||(console.error(`The the 'src' for <turbo-frame id='${t.id}'> is unknown!`,"TurboReflex uses 'src' to (re)render frame content after the reflex is invoked.","Please set the 'src' or 'data-turbo-reflex-src' attribute on the <turbo-frame> element.",t),o.dispatch(o.missingFrameSrc,t,{frame:t})),e}function k(t,e={}){if(t.tagName.toLowerCase()!=="select")return e.value=t.value;if(!t.multiple)return e.value=t.options[t.selectedIndex].value;e.values=Array.from(t.options).reduce((r,n)=>(n.selected&&r.push(n.value),r),[])}function v(t){let e=Array.from(t.attributes).reduce((r,n)=>(r[n.name]=n.value,r),{});return e.tag=t.tagName,e.checked=t.checked,e.disabled=t.disabled,k(t,e),e}var a={},h;function p(t){h=t}function f(t,e){a[t]=e,document.addEventListener(t,h,!0)}function E(t,e){return e=e.toLowerCase(),a[t].includes(e)||!Object.values(a).flat().includes(e)&&a[t].includes("*")}function R(){console.log(a)}addEventListener("turbo:before-fetch-request",t=>{let e=t.target,{turboReflexActive:r}=e.dataset;if(!r)return;let{fetchOptions:n}=t.detail;n.headers["Turbo-Reflex"]=c.token});function I(t){let e=document.createElement("a");return e.href=t,new URL(e)}function A(t,e={}){e.token=c.token;let r=document.createElement("input");r.type="hidden",r.name="turbo_reflex",r.value=JSON.stringify(e),t.appendChild(r)}function C(t){let e,r,n,s;try{if(e=m(t.target),!e||!E(t.type,e.tagName)||(o.dispatch(o.beforeStart,e,{element:e}),r=b(e),!r)||(n=g(r),!n)||(s=x(n),!s))return;let i={frameId:r,element:v(e)};if(o.dispatch(o.start,e,{element:e,frameId:r,frame:n,frameSrc:s,payload:i}),n.dataset.turboReflexActive=!0,n.dataset.turboReflexElementId=e.id,e.tagName.toLowerCase()==="form")return A(e,i);t.preventDefault();let d=I(s);d.searchParams.set("turbo_reflex",JSON.stringify(i)),n.src=d.toString()}catch(i){console.error("TurboReflex encountered an unexpected error!",{element:e,frameId:r,frame:n,frameSrc:s,target:t.target},i),o.dispatch(o.error,e||document,{element:e,frameId:r,frame:n,frameSrc:s,error:i})}}p(C);f("change",["input","select","textarea"]);f("submit",["form"]);f("click",["*"]);var M={registerEvent:f,logRegisteredEvents:R,logLifecycleEventNames:o.logEventNames};export{M as default};
|
2
|
+
//# sourceMappingURL=turbo_reflex.min.js.map
|
@@ -0,0 +1,7 @@
|
|
1
|
+
{
|
2
|
+
"version": 3,
|
3
|
+
"sources": ["../../javascript/lifecycle_events.js", "../../javascript/frame_sources.js", "../../javascript/security.js", "../../javascript/elements.js", "../../javascript/event_registry.js", "../../javascript/turbo_reflex.js"],
|
4
|
+
"sourcesContent": ["const events = {\n beforeStart: 'turbo-reflex:before-start',\n start: 'turbo-reflex:start',\n finish: 'turbo-reflex:finish',\n error: 'turbo-reflex:error',\n missingFrameId: 'turbo-reflex:missing-frame-id',\n missingFrame: 'turbo-reflex:missing-frame',\n missingFrameSrc: 'turbo-reflex:missing-frame-src'\n}\n\nfunction dispatch (name, target = document, detail = {}) {\n const event = new CustomEvent(name, {\n detail,\n cancelable: true,\n bubbles: true\n })\n target.dispatchEvent(event)\n}\n\nfunction logEventNames () {\n Object.values(events).forEach(name => console.log(name))\n}\n\nexport default { ...events, dispatch, logEventNames }\n", "import LifecycleEvents from './lifecycle_events'\nconst frameSources = {}\n\n// fires after receiving a turbo HTTP response\naddEventListener('turbo:before-fetch-response', event => {\n const frame = event.target\n frameSources[frame.id] = frame.src\n\n const { turboReflexActive, turboReflexElementId } = frame.dataset\n if (!turboReflexActive) return\n\n const element = document.getElementById(turboReflexElementId)\n delete frame.dataset.turboReflexActive\n delete frame.dataset.turboReflexElementId\n\n LifecycleEvents.dispatch(LifecycleEvents.finish, element || document, {\n frame,\n element: element || 'Unknown! Missing id attribute.'\n })\n})\n\n// fires when a frame element is navigated and finishes loading\naddEventListener('turbo:frame-load', event => {\n const frame = event.target\n frame.dataset.turboReflexSrc =\n frameSources[frame.id] || frame.src || frame.dataset.turboReflexSrc\n delete frameSources[frame.id]\n})\n", "const Security = {\n get token () {\n return document.getElementById('turbo-reflex-token').getAttribute('content')\n }\n}\n\nexport default Security\n", "import LifecycleEvents from './lifecycle_events'\n\nfunction findClosestReflex (element) {\n return element.closest('[data-turbo-reflex]')\n}\n\nfunction findClosestFrame (element) {\n return element.closest('turbo-frame')\n}\n\nfunction findFrameId (element) {\n let id = element.dataset.turboReflexFrame || element.dataset.turboFrame\n if (!id) {\n const frame = findClosestFrame(element)\n if (frame) id = frame.id\n }\n if (!id) {\n console.error(\n `The reflex element does not specify a frame!`,\n `Please move the reflex element inside a <turbo-frame> or set the 'data-turbo-reflex-frame' or 'data-turbo-frame' attribute.`,\n element\n )\n LifecycleEvents.dispatch(LifecycleEvents.missingFrameId, element, {\n element\n })\n }\n return id\n}\n\nfunction findFrame (id) {\n const frame = document.getElementById(id)\n if (!frame) {\n console.error(`The frame '${id}' does not exist!`)\n LifecycleEvents.dispatch(LifecycleEvents.missingFrame, document, { id })\n }\n return frame\n}\n\nfunction findFrameSrc (frame) {\n const frameSrc = frame.dataset.turboReflexSrc || frame.src\n if (!frameSrc) {\n console.error(\n `The the 'src' for <turbo-frame id='${frame.id}'> is unknown!`,\n `TurboReflex uses 'src' to (re)render frame content after the reflex is invoked.`,\n `Please set the 'src' or 'data-turbo-reflex-src' attribute on the <turbo-frame> element.`,\n frame\n )\n LifecycleEvents.dispatch(LifecycleEvents.missingFrameSrc, frame, { frame })\n }\n return frameSrc\n}\n\nfunction assignElementValueToPayload (element, payload = {}) {\n if (element.tagName.toLowerCase() !== 'select')\n return (payload.value = element.value)\n\n if (!element.multiple)\n return (payload.value = element.options[element.selectedIndex].value)\n\n payload.values = Array.from(element.options).reduce((memo, option) => {\n if (option.selected) memo.push(option.value)\n return memo\n }, [])\n}\n\nfunction buildAttributePayload (element) {\n const payload = Array.from(element.attributes).reduce((memo, attr) => {\n memo[attr.name] = attr.value\n return memo\n }, {})\n\n payload.tag = element.tagName\n payload.checked = element.checked\n payload.disabled = element.disabled\n assignElementValueToPayload(element, payload)\n\n return payload\n}\n\nexport {\n findClosestReflex,\n findClosestFrame,\n findFrameId,\n findFrame,\n findFrameSrc,\n buildAttributePayload\n}\n", "const registeredEvents = {}\nlet eventListener\n\nfunction registerEventListener (fn) {\n eventListener = fn\n}\n\nfunction registerEvent (eventName, tagNames) {\n registeredEvents[eventName] = tagNames\n document.addEventListener(eventName, eventListener, true)\n}\n\nfunction isRegisteredEvent (eventName, tagName) {\n tagName = tagName.toLowerCase()\n return (\n registeredEvents[eventName].includes(tagName) ||\n (!Object.values(registeredEvents)\n .flat()\n .includes(tagName) &&\n registeredEvents[eventName].includes('*'))\n )\n}\n\nfunction logRegisteredEvents () {\n console.log(registeredEvents)\n}\n\nexport {\n registerEventListener,\n registerEvent,\n registeredEvents,\n isRegisteredEvent,\n logRegisteredEvents\n}\n", "import './frame_sources'\nimport Security from './security'\nimport LifecycleEvents from './lifecycle_events'\nimport {\n findClosestReflex,\n findClosestFrame,\n findFrameId,\n findFrame,\n findFrameSrc,\n buildAttributePayload\n} from './elements'\nimport {\n registerEventListener,\n registerEvent,\n registeredEvents,\n isRegisteredEvent,\n logRegisteredEvents\n} from './event_registry'\n\n// fires before making a turbo HTTP request\naddEventListener('turbo:before-fetch-request', event => {\n const frame = event.target\n const { turboReflexActive } = frame.dataset\n if (!turboReflexActive) return\n const { fetchOptions } = event.detail\n fetchOptions.headers['Turbo-Reflex'] = Security.token\n})\n\nfunction buildURL (urlString) {\n const a = document.createElement('a')\n a.href = urlString\n return new URL(a)\n}\n\nfunction invokeFormReflex (form, payload = {}) {\n payload.token = Security.token\n const input = document.createElement('input')\n input.type = 'hidden'\n input.name = 'turbo_reflex'\n input.value = JSON.stringify(payload)\n form.appendChild(input)\n}\n\nfunction invokeReflex (event) {\n let element, frameId, frame, frameSrc\n try {\n element = findClosestReflex(event.target)\n if (!element) return\n\n if (!isRegisteredEvent(event.type, element.tagName)) return\n\n LifecycleEvents.dispatch(LifecycleEvents.beforeStart, element, { element })\n\n frameId = findFrameId(element)\n if (!frameId) return\n\n frame = findFrame(frameId)\n if (!frame) return\n\n frameSrc = findFrameSrc(frame)\n if (!frameSrc) return\n\n const payload = {\n frameId: frameId,\n element: buildAttributePayload(element)\n }\n\n LifecycleEvents.dispatch(LifecycleEvents.start, element, {\n element,\n frameId,\n frame,\n frameSrc,\n payload\n })\n frame.dataset.turboReflexActive = true\n frame.dataset.turboReflexElementId = element.id\n\n if (element.tagName.toLowerCase() === 'form')\n return invokeFormReflex(element, payload)\n\n event.preventDefault()\n const frameURL = buildURL(frameSrc)\n frameURL.searchParams.set('turbo_reflex', JSON.stringify(payload))\n frame.src = frameURL.toString()\n } catch (error) {\n console.error(\n `TurboReflex encountered an unexpected error!`,\n { element, frameId, frame, frameSrc, target: event.target },\n error\n )\n LifecycleEvents.dispatch(LifecycleEvents.error, element || document, {\n element,\n frameId,\n frame,\n frameSrc,\n error\n })\n }\n}\n\n// wire things up and setup default events\nregisterEventListener(invokeReflex)\nregisterEvent('change', ['input', 'select', 'textarea'])\nregisterEvent('submit', ['form'])\nregisterEvent('click', ['*'])\n\nexport default {\n registerEvent,\n logRegisteredEvents,\n logLifecycleEventNames: LifecycleEvents.logEventNames\n}\n"],
|
5
|
+
"mappings": "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",
|
6
|
+
"names": ["events", "dispatch", "name", "target", "detail", "event", "logEventNames", "lifecycle_events_default", "frameSources", "event", "frame", "turboReflexActive", "turboReflexElementId", "element", "lifecycle_events_default", "Security", "security_default", "findClosestReflex", "element", "findClosestFrame", "findFrameId", "id", "frame", "lifecycle_events_default", "findFrame", "findFrameSrc", "frameSrc", "assignElementValueToPayload", "payload", "memo", "option", "buildAttributePayload", "attr", "registeredEvents", "eventListener", "registerEventListener", "fn", "registerEvent", "eventName", "tagNames", "isRegisteredEvent", "tagName", "logRegisteredEvents", "event", "frame", "turboReflexActive", "fetchOptions", "security_default", "buildURL", "urlString", "a", "invokeFormReflex", "form", "payload", "input", "invokeReflex", "element", "frameId", "frameSrc", "findClosestReflex", "isRegisteredEvent", "lifecycle_events_default", "findFrameId", "findFrame", "findFrameSrc", "buildAttributePayload", "frameURL", "error", "registerEventListener", "registerEvent", "turbo_reflex_default", "logRegisteredEvents"]
|
7
|
+
}
|
Binary file
|
Binary file
|
Binary file
|