turbo_reflex 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![TurboReflex Demos](https://ik.imagekit.io/hopsoft/turbo-reflex-demos_EP54JuWt5.webp?ik-sdk-version=javascript-1.4.3&updatedAt=1663083040904)
|
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
|