turbo_ready 0.0.4 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  <p align="center">
2
- <img height="200" src="https://ik.imagekit.io/hopsoft/turbo-ready-logo_jYFJI1jgT.png?ik-sdk-version=javascript-1.4.3&updatedAt=1661471047153" />
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://ik.imagekit.io/hopsoft/turbo-ready-logo-light_df4jcvbDL.webp?ik-sdk-version=javascript-1.4.3&updatedAt=1661615678275">
4
+ <img height="200" src="https://ik.imagekit.io/hopsoft/turbo-ready-logo-dark_VN4hA2ctc.webp?ik-sdk-version=javascript-1.4.3&updatedAt=1661615678278" />
5
+ </picture>
3
6
  <h3 align="center">
4
7
  Turbo Stream's Swiss Army Knife
5
8
  </h3>
@@ -7,47 +10,76 @@
7
10
  Welcome to TurboReady 👋
8
11
  </h1>
9
12
  <p align="center">
10
- <a href="http://blog.codinghorror.com/the-best-code-is-no-code-at-all/" target="_blank">
11
- <img alt="Lines of Code" src="https://img.shields.io/badge/lines_of_code-278-brightgreen.svg?style=flat" />
13
+ <a href="http://blog.codinghorror.com/the-best-code-is-no-code-at-all/">
14
+ <img alt="Lines of Code" src="https://img.shields.io/badge/loc-139-47d299.svg" />
12
15
  </a>
13
- <a href="https://github.com/testdouble/standard" target="_blank">
14
- <img alt="Ruby Code Style" src="https://img.shields.io/badge/Ruby_Code_Style-standard-brightgreen.svg" />
16
+ <a href="https://codeclimate.com/github/hopsoft/turbo_ready/maintainability">
17
+ <img src="https://api.codeclimate.com/v1/badges/a69b6f73abc3ccd49261/maintainability" />
15
18
  </a>
16
- <a href="https://github.com/sheerun/prettier-standard" target="_blank">
17
- <img alt="JavaScript Code Style" src="https://img.shields.io/badge/JavaScript_Code_Style-prettier_standard-ff69b4.svg" />
19
+ <a href="https://rubygems.org/gems/turbo_ready">
20
+ <img alt="GEM" src="https://img.shields.io/gem/v/turbo_ready?color=168AFE&include_prereleases&logo=ruby&logoColor=FE1616">
18
21
  </a>
19
- <a href="https://bundlephobia.com/package/turbo_ready" target="_blank">
20
- <img alt="npm bundle size" src="https://img.shields.io/bundlephobia/minzip/turbo_ready?label=minified%20size">
22
+ <a href="https://rubygems.org/gems/turbo_ready">
23
+ <img alt="Gem" src="https://img.shields.io/gem/dt/turbo_ready?color=168AFE&logo=ruby&logoColor=FE1616">
24
+ </a>
25
+ <a href="https://github.com/testdouble/standard">
26
+ <img alt="Ruby Style" src="https://img.shields.io/badge/style-standard-168AFE?logo=ruby&logoColor=FE1616" />
27
+ </a>
28
+ <a href="https://www.npmjs.com/package/turbo_ready">
29
+ <img alt="NPM" src="https://img.shields.io/npm/v/turbo_ready?color=168AFE&logo=npm">
30
+ </a>
31
+ <a href="https://www.npmjs.com/package/turbo_ready">
32
+ <img alt="npm" src="https://img.shields.io/npm/dm/turbo_ready?color=168AFE&logo=npm">
33
+ </a>
34
+ <a href="https://bundlephobia.com/package/turbo_ready@">
35
+ <img alt="npm bundle size" src="https://img.shields.io/bundlephobia/minzip/turbo_ready?label=bundle%20size&logo=npm&color=47d299">
36
+ </a>
37
+ <a href="https://github.com/sheerun/prettier-standard">
38
+ <img alt="JavaScript Style" src="https://img.shields.io/badge/style-prettier--standard-168AFE?logo=javascript&logoColor=f4e137" />
39
+ </a>
40
+ <a href="https://github.com/hopsoft/turbo_ready/actions/workflows/tests.yml">
41
+ <img alt="Tests" src="https://github.com/hopsoft/turbo_ready/actions/workflows/tests.yml/badge.svg" />
42
+ </a>
43
+ <a href="https://twitter.com/hopsoft">
44
+ <img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/hopsoft?logo=twitter&style=social">
21
45
  </a>
22
46
  </p>
23
47
  </p>
24
48
 
25
- TurboReady extends [Turbo Streams](https://turbo.hotwired.dev/reference/streams) to give you full control of the
26
- browser's [Document Object Model (DOM).](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)
27
-
28
- **Thats right!**
29
- You can `invoke` any DOM method on any DOM object *(including 3rd party libs)* using Turbo Streams.
49
+ **TurboReady extends [Turbo Streams](https://turbo.hotwired.dev/reference/streams) to give you full control of the
50
+ browser's [Document Object Model (DOM).](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)**
30
51
 
31
52
  ```ruby
32
- turbo_stream.invoke "console.log", "Hello World!"
53
+ turbo_stream.invoke "console.log", args: ["Hello World!"]
33
54
  ```
34
55
 
56
+ **Thats right!**
57
+ You can `invoke` any DOM method on the client with Turbo Streams.
58
+
35
59
  <!-- Tocer[start]: Auto-generated, don't remove. -->
36
60
 
37
61
  ## Table of Contents
38
62
 
39
63
  - [Why TurboReady?](#why-turboready)
40
- - [Discord Community](#discord-community)
41
64
  - [Sponsors](#sponsors)
42
65
  - [Dependencies](#dependencies)
43
66
  - [Installation](#installation)
44
67
  - [Setup](#setup)
45
68
  - [Usage](#usage)
46
- - [Endless Possibilities](#endless-possibilities)
47
- - [Advanced Usage](#advanced-usage)
69
+ - [Method Chaining](#method-chaining)
70
+ - [Dispatching Events](#dispatching-events)
71
+ - [Syntax Styles](#syntax-styles)
48
72
  - [Extending Behavior](#extending-behavior)
49
- - [Public API](#public-api)
73
+ - [Implementation Details](#implementation-details)
74
+ - [Broadcasting](#broadcasting)
75
+ - [Background Job Queues](#background-job-queues)
76
+ - [FAQ](#faq)
50
77
  - [A Word of Caution](#a-word-of-caution)
78
+ - [Community](#community)
79
+ - [Discord](#discord)
80
+ - [Discussions](#discussions)
81
+ - [Twitter](#twitter)
82
+ - [TODOs](#todos)
51
83
  - [Releasing](#releasing)
52
84
  - [License](#license)
53
85
 
@@ -55,21 +87,17 @@ turbo_stream.invoke "console.log", "Hello World!"
55
87
 
56
88
  ## Why TurboReady?
57
89
 
58
- Turbo Streams [intentionally restricts](https://turbo.hotwired.dev/handbook/streams#but-what-about-running-javascript)
90
+ Turbo Streams [intentionally restricts](https://turbo.hotwired.dev/handbook/streams#but-what-about-running-javascript%3F)
59
91
  official actions to CRUD related activity.
60
- The [official actions](https://turbo.hotwired.dev/reference/streams#the-seven-actions) work well for a
61
- considerable number of use cases and you should push Streams as far as possible before reaching for TurboReady.
92
+ These [official actions](https://turbo.hotwired.dev/reference/streams#the-seven-actions) work well for a considerable number of use cases.
93
+ *Try pushing Turbo Streams as far as possible before reaching for TurboReady.*
62
94
 
63
- If you discover that CRUD isn't enough, TurboReady covers pretty much everything else.
95
+ If you find that CRUD isn't enough, TurboReady is there to handle pretty much everything else.
64
96
 
65
- ## Community
66
-
67
- Please join nearly 2000 of us on [Discord](https://discord.gg/stimulus-reflex) for support getting started,
68
- as well as active discussions around Rails, Hotwire, Stimulus, Turbo (Drive, Frames, Streams), TurboReady, CableReady, StimulusReflex, ViewComponent, Phlex, and more.
69
-
70
- ![](https://img.shields.io/discord/629472241427415060)
97
+ > ⚠️ TurboReady is intended for Rails apps that use Hotwire but not [CableReady](https://github.com/stimulusreflex/cable_ready).
98
+ This is because CableReady already provides a rich set of powerful [DOM operations](https://cableready.stimulusreflex.com/reference/operations).
71
99
 
72
- Stop by #newcomers and introduce yourselves!
100
+ > 📘 **NOTE:** Efforts are underway to bring [CableReady's DOM operations to Turbo Streams](https://github.com/marcoroth/turbo_power).
73
101
 
74
102
  ## Sponsors
75
103
 
@@ -90,79 +118,82 @@ Stop by #newcomers and introduce yourselves!
90
118
 
91
119
  ## Installation
92
120
 
121
+ Be sure to install the same version for each libary.
122
+
93
123
  ```sh
94
124
  bundle add "turbo_ready --version VERSION"
95
125
  yarn add "turbo_ready@VERSION --exact"
96
126
  ```
97
127
 
98
- **IMPORTANT:** Be sure to use the same version for each libary.
99
-
100
128
  ## Setup
101
129
 
102
- 1. Create a Rails intializer and patch Turbo.
130
+ Import and intialize TurboReady in your application.
103
131
 
104
- ```ruby
105
- # config/initializers/turbo_ready.rb
106
- TurboReady.patch! # Adds TurboReady stream actions to Turbo
107
- ```
108
- 2. Import and intialize TurboReady in your JavaScript application.
132
+ ```diff
133
+ # Gemfile
134
+ +gem "turbo_ready", "~> 0.0.6"
135
+ ```
109
136
 
110
- ```js
111
- // app/javascript/application.js
112
- import '@hotwired/turbo-rails'
113
- import TurboReady from 'turbo_ready'
137
+ ```diff
138
+ # package.json
139
+ "dependencies": {
140
+ + "@hotwired/turbo-rails": ">=7.2.0-beta.2",
141
+ + "turbo_ready": "^0.0.6"
142
+ ```
114
143
 
115
- TurboReady.initialize(Turbo.StreamActions) // Adds TurboReady stream actions to Turbo
116
- ```
144
+ ```diff
145
+ # app/javascript/application.js
146
+ import '@hotwired/turbo-rails'
147
+ +import TurboReady from 'turbo_ready'
117
148
 
118
- ## Usage
149
+ +TurboReady.initialize(Turbo.StreamActions) // Adds TurboReady stream actions to Turbo
150
+ ```
119
151
 
120
- Manipulate the DOM from anywhere you use [official Turbo Streams](https://turbo.hotwired.dev/handbook/streams#integration-with-server-side-frameworks).
121
- Namely, [**M**odels](https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb),
122
- [**V**iews](https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb),
123
- and [**C**ontrollers](https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb).
152
+ ## Usage
124
153
 
125
- You can **chain invocations.** ❤️
154
+ Manipulate the DOM from anywhere you use official [Turbo Streams](https://turbo.hotwired.dev/handbook/streams#integration-with-server-side-frameworks).
155
+ The possibilities are endless.
156
+ [Learn more about the DOM at MDN.](https://developer.mozilla.org/en-US/docs/Web/API.)
126
157
 
127
158
  ```ruby
128
- turbo_stream
129
- .invoke("document.body.insertAdjacentHTML", "afterbegin", "<h1>Hello World!</h1>") # dot notation
130
- .invoke("setAttribute", "data-turbo-ready", true, selector: ".button") # selector
131
- .invoke("classList.add", "turbo-ready", selector: "a") # dot notation + selector
132
- .flush # flush must be called when chaining invocations
159
+ turbo_stream.invoke "console.log", args: ["Hello World!"]
133
160
  ```
134
161
 
135
- You can use [dot notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#dot_notation)
136
- or [selectors](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)... and can even combine them!** 🤯
162
+ ### Method Chaining
137
163
 
138
- Can I dispatch events? **You bet!** ⚡️
164
+ You can use [dot notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#dot_notation)
165
+ or [selectors](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) and even combine them!
139
166
 
140
167
  ```ruby
141
168
  turbo_stream
142
- .invoke("dispatchEvent", "turbo-ready:demo") // fires on window
143
- .invoke("dispatchEvent", "turbo-ready:demo", selector: "#my-element") // fires on matching element(s)
144
- .invoke("dispatchEvent", {bubbles: true, detail: {...}}) // set event options
145
- .flush
169
+ .invoke("document.body.insertAdjacentHTML", args: ["afterbegin", "<h1>Hello World!</h1>"]) # dot notation
170
+ .invoke("setAttribute", args: ["data-turbo-ready", true], selector: ".button") # selector
171
+ .invoke("classList.add", args: ["turbo-ready"], selector: "a") # dot notation + selector
146
172
  ```
147
173
 
148
- ## Endless Possibilities
174
+ ### Dispatching Events
149
175
 
150
- **What else can I do?**
151
- MDN has your back... [learn about the DOM and web APIs here.](https://developer.mozilla.org/en-US/docs/Web/API.)
176
+ It's possible to fire events on `window`, `document`, and element(s).
152
177
 
153
- ## Advanced Usage
178
+ ```ruby
179
+ turbo_stream
180
+ .invoke("dispatchEvent", args: ["turbo-ready:demo"]) # fires on window
181
+ .invoke("document.dispatchEvent", args: ["turbo-ready:demo"]) # fires on document
182
+ .invoke("dispatchEvent", args: ["turbo-ready:demo"], selector: "#my-element") # fires on matching element(s)
183
+ .invoke("dispatchEvent", args: ["turbo-ready:demo", {bubbles: true, detail: {...}}]) # set event options
184
+ ```
154
185
 
155
- You can use symbols and [snake case](https://en.wikipedia.org/wiki/Snake_case) when invoking DOM functionality.
156
- It'll implicitly convert to [camel case](https://en.wikipedia.org/wiki/Camel_case). 💎
186
+ ### Syntax Styles
187
+
188
+ You can use [`snake_case`](https://en.wikipedia.org/wiki/Snake_case) when invoking DOM functionality.
189
+ It will implicitly convert to [`camelCase`](https://en.wikipedia.org/wiki/Camel_case).
157
190
 
158
191
  ```ruby
159
- turbo_stream
160
- .invoke(:animate, [{opacity: 0}, {opacity: 1}], 2000)
161
- .invoke(:dispatch_event, {detail: {converts_to_camel_case: true}})
162
- .flush
192
+ turbo_stream.invoke :dispatch_event,
193
+ args: ["turbo-ready:demo", {detail: {converts_to_camel_case: true}}]
163
194
  ```
164
195
 
165
- Need to opt out of camelize? No problem... just disable it.
196
+ Need to opt-out? No problem... just disable it.
166
197
 
167
198
  ```ruby
168
199
  turbo_stream.invoke :contrived_demo, camelize: false
@@ -170,10 +201,10 @@ turbo_stream.invoke :contrived_demo, camelize: false
170
201
 
171
202
  ### Extending Behavior
172
203
 
173
- Want to extend things with custom functionality? **Let's do it.** 🔌
204
+ If you add new capabilities to the browser, you can control them from the server.
174
205
 
175
206
  ```js
176
- // JavaScript
207
+ // JavaScript on the client
177
208
  import morphdom from 'morphdom'
178
209
 
179
210
  window.MyNamespace = {
@@ -184,51 +215,180 @@ window.MyNamespace = {
184
215
  ```
185
216
 
186
217
  ```ruby
187
- # Ruby
188
- turbo_stream
189
- .invoke "MyNamespace.morph", "#demo", "<div id='demo'><p>You've changed...</p></div>", {childrenOnly: true}
218
+ # Ruby on the server
219
+ turbo_stream.invoke "MyNamespace.morph",
220
+ args: [
221
+ "#demo",
222
+ "<div id='demo'><p>You've changed...</p></div>",
223
+ {children_only: true}
224
+ ]
190
225
  ```
191
226
 
192
- ## Public API
227
+ ### Implementation Details
193
228
 
194
- There's only one method to consider, `invoke` defined in the
195
- [tag builder](https://github.com/hopsoft/turbo_ready/blob/main/lib/turbo_ready/tag_builder.rb).
229
+ There's basically one method to learn... `invoke`
196
230
 
197
231
  ```ruby
198
232
  # Ruby
199
233
  turbo_stream
200
- .invoke(method, *args, selector: nil, camelize: true, id: nil)
201
- # | | | | |
202
- # | | | | |- Identifies this invocation (optional)
203
- # | | | |
204
- # | | | |- Should we camelize the JavaScript stuff? (optional)
205
- # | | | (allows us to write snake_case Ruby)
206
- # | | |
207
- # | | |- An CSS selector for the element(s) to target (optional)
208
- # | |
209
- # | |- The arguments to pass to the JavaScript method being invoked (optional)
234
+ .invoke(method, args: [], selector: nil, camelize: true, id: nil)
235
+ # | | | | |
236
+ # | | | | |- Identifies this invocation (optional)
237
+ # | | | |
238
+ # | | | |- Should we camelize the JavaScript stuff? (optional)
239
+ # | | | (allows us to write snake_case in Ruby)
240
+ # | | |
241
+ # | | |- A CSS selector for the element(s) to target (optional)
242
+ # | |
243
+ # | |- The arguments to pass to the JavaScript method (optional)
210
244
  # |
211
245
  # |- The JavaScript method to invoke (can use dot notation)
212
246
  ```
213
247
 
214
- **NOTE:** The JavaScript method will be invoked on all matching elements when a `selector` is passed.
248
+ > 📘 **NOTE:** The method will be invoked on all matching elements if a `selector` is present.
249
+
250
+ The following Ruby code,
251
+
252
+ ```ruby
253
+ turbo_stream.invoke "console.log", args: ["Hello World!"], id: "123ABC"
254
+ ```
255
+
256
+ emits this HTML markup.
257
+
258
+ ```html
259
+ <turbo-stream action="invoke" target="DOM">
260
+ <template>{"id":"123ABC","receiver":"console","method":"log","args":["Hello World!"]}</template>
261
+ </turbo-stream>
262
+ ```
263
+
264
+ When this element enters the DOM,
265
+ Turbo Streams automatically executes `invoke` on the client with the template's JSON payload and then removes the element from the DOM.
266
+
267
+ ### Broadcasting
268
+
269
+ You can also broadcast DOM invocations to subscribed users.
270
+
271
+ 1. First, setup the stream subscription.
272
+
273
+ ```erb
274
+ <!-- app/views/posts/show.html.erb -->
275
+ <%= turbo_stream_from @post %>
276
+ <!-- |
277
+ |- *streamables - model(s), string(s), etc...
278
+ -->
279
+ ```
280
+
281
+ 2. Then, broadcast to the subscription.
282
+
283
+ ```ruby
284
+ # app/models/post.rb
285
+ class Post < ApplicationRecord
286
+ after_save do
287
+ # emit a message in the browser conosle for anyone subscribed to this post
288
+ broadcast_invoke "console.log", args: ["Post was saved! #{to_gid.to_s}"]
289
+
290
+ # broadcast with a background job
291
+ broadcast_invoke_later "console.log", args: ["Post was saved! #{to_gid.to_s}"]
292
+ end
293
+ end
294
+ ```
295
+
296
+ ```ruby
297
+ # app/controllers/posts_controller.rb
298
+ class PostsController < ApplicationController
299
+ def create
300
+ @post = Post.find params[:id]
301
+
302
+ if @post.update post_params
303
+ # emit a message in the browser conosle for anyone subscribed to this post
304
+ @post.broadcast_invoke "console.log", args: ["Post was saved! #{to_gid.to_s}"]
305
+
306
+ # broadcast with a background job
307
+ @post.broadcast_invoke_later "console.log", args: ["Post was saved! #{to_gid.to_s}"]
308
+
309
+ # you can also broadcast directly from the channel
310
+ Turbo::StreamsChannel.broadcast_invoke_to @post, "console.log",
311
+ args: ["Post was saved! #{@post.to_gid.to_s}"]
312
+
313
+ # broadcast with a background job
314
+ Turbo::StreamsChannel.broadcast_invoke_later_to @post, "console.log",
315
+ args: ["Post was saved! #{@post.to_gid.to_s}"]
316
+ end
317
+ end
318
+ end
319
+ ```
320
+
321
+ > 📘 **NOTE:** [Method Chaining](#method-chaining) is not currently supported when broadcasting.
322
+
323
+ #### Background Job Queues
324
+
325
+ You may want to change the queue name for Turbo Stream background jobs in order to isolate, prioritize, and scale the workers independently.
326
+
327
+ ```ruby
328
+ # config/initializers/turbo_streams.rb
329
+ Turbo::Streams::BroadcastJob.queue_name = :turbo_streams
330
+ TurboReady::BroadcastInvokeJob.queue_name = :turbo_streams
331
+ ```
332
+
333
+ ## FAQ
334
+
335
+ - Isn't this just RJS?
336
+
337
+ > No. But, perhaps it could be considered RJS's "modern" spirtual successor. 🤷‍♂️
338
+ > Though it embraces JavaScript instead of trying to avoid it.
339
+
340
+ - Does it use `eval`?
341
+
342
+ > **No.** TurboReady can only invoke existing functions on the client.
343
+ > It's not a carte blanche invitation to emit free-form JavaScript to be evaluated on the client.
215
344
 
216
345
  ## A Word of Caution
217
346
 
218
- Manually orchestrating DOM activity gets tedious fast.
219
- **⚠️ Don't abuse this superpower!**
347
+ **Don't abuse this superpower!**
220
348
 
221
349
  > With great power comes great responsibility. *-Uncle Ben*
222
350
 
351
+ Manually orchestrating DOM activity is tedious.
352
+ *Don't overdo it... or you may find that you've created spaghetti reminiscent of the jQuery days.*
353
+
223
354
  This library is an extremely sharp tool. 🔪
224
- Consider it a low-level building block that can be used to craft additional libraries with
225
- great [DX](https://en.wikipedia.org/wiki/User_experience#Developer_experience)
355
+ Consider it a low-level building block that can be used to craft additional libraries
226
356
  like [CableReady](https://github.com/stimulusreflex/cable_ready)
227
357
  and [StimulusReflex](https://github.com/stimulusreflex/stimulus_reflex).
228
358
 
229
- Restrict your direct application usage to DOM manipulation that falls outside the purview of
230
- [Turbo's official actions](https://turbo.hotwired.dev/reference/streams#the-seven-actions)...
231
- *and for Pete's sake, don't overdo it and find yourself maintaining spaghetti code reminiscent of the jQuery days.*
359
+ ## Community
360
+
361
+ ### Discord
362
+
363
+ Please join nearly 2000 of us on [Discord](https://discord.gg/stimulus-reflex) for support getting started,
364
+ as well as active discussions around Rails, Hotwire, Stimulus, Turbo (Drive, Frames, Streams), TurboReady, CableReady, StimulusReflex, ViewComponent, Phlex, and more.
365
+
366
+ <a href="https://discord.gg/stimulus-reflex" target="_blank">
367
+ <img alt="Discord" src="https://img.shields.io/discord/629472241427415060?color=168AFE&logo=discord&logoColor=FFF">
368
+ </a>
369
+
370
+ Be sure to introduce yourselves in the #newcomers channel!
371
+
372
+ ### Discussions
373
+
374
+ Feel free to add to the conversation here on [GitHub Discussions](https://github.com/hopsoft/turbo_ready/discussions).
375
+
376
+ <a href="https://github.com/hopsoft/turbo_ready/discussions" target="_blank">
377
+ <img alt="GitHub Discussions" src="https://img.shields.io/github/discussions/hopsoft/turbo_ready?color=168AFE&logo=github">
378
+ </a>
379
+
380
+ ### Twitter
381
+
382
+ Connect with the core team on Twitter.
383
+
384
+ <a href="https://twitter.com/hopsoft" target="_blank">
385
+ <img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/hopsoft?logo=twitter&style=social">
386
+ </a>
387
+
388
+ ## TODOs
389
+
390
+ - [ ] Add system tests [(review turbo-rails for guidance)](https://github.com/hotwired/turbo-rails/blob/main/test/system/broadcasts_test.rb)
391
+ - [ ] Look into adding method chaining for broadcasts
232
392
 
233
393
  ## Releasing
234
394
 
data/Rakefile CHANGED
@@ -1,5 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/setup"
4
-
5
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
@@ -1,2 +1,2 @@
1
- function l(){let n=JSON.parse(this.templateContent.textContent),{id:h,method:c,args:i,receiver:a,selector:r}=n,t=[self];switch(r&&(t=Array.from(document.querySelectorAll(r))),a&&(t=t.map(o=>{let e=o,s=a.split(".");for(;s.length>0;)e=e[s.shift()];return e})),c){case"dispatchEvent":let o=new CustomEvent(i[0],i[1]||{});t.forEach(e=>e.dispatchEvent(o));break;default:t.forEach(e=>e[c].apply(e,i))}}function f(n){n.invoke=l}var p={initialize:f};export{p as default};
1
+ function l(){let n=JSON.parse(this.templateContent.textContent),{id:h,selector:c,receiver:a,method:r,args:i}=n,t=[self];switch(c&&(t=Array.from(document.querySelectorAll(c))),a&&(t=t.map(o=>{let e=o,s=a.split(".");for(;s.length>0;)e=e[s.shift()];return e})),r){case"dispatchEvent":let o=new CustomEvent(i[0],i[1]||{});t.forEach(e=>e.dispatchEvent(o));break;default:t.forEach(e=>e[r].apply(e,i))}}function f(n){n.invoke=l}var p={initialize:f};export{p as default};
2
2
  //# sourceMappingURL=turbo_ready.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../javascript/turbo_ready.js"],
4
- "sourcesContent": ["function invoke () {\n const payload = JSON.parse(this.templateContent.textContent)\n const { id, method, args, receiver, selector } = payload\n let receivers = [self]\n if (selector) receivers = Array.from(document.querySelectorAll(selector))\n\n if (receiver) {\n receivers = receivers.map(r => {\n let context = r\n const chain = receiver.split('.')\n while (chain.length > 0) context = context[chain.shift()]\n return context\n })\n }\n\n switch (method) {\n case 'dispatchEvent':\n const evt = new CustomEvent(args[0], args[1] || {})\n receivers.forEach(r => r.dispatchEvent(evt))\n break\n default:\n receivers.forEach(r => r[method].apply(r, args))\n }\n}\n\nfunction initialize (streamActions) {\n streamActions.invoke = invoke\n}\n\nexport default { initialize }\n"],
5
- "mappings": "AAAA,SAASA,GAAU,CACjB,IAAMC,EAAU,KAAK,MAAM,KAAK,gBAAgB,WAAW,EACrD,CAAE,GAAAC,EAAI,OAAAC,EAAQ,KAAAC,EAAM,SAAAC,EAAU,SAAAC,CAAS,EAAIL,EAC7CM,EAAY,CAAC,IAAI,EAYrB,OAXID,IAAUC,EAAY,MAAM,KAAK,SAAS,iBAAiBD,CAAQ,CAAC,GAEpED,IACFE,EAAYA,EAAU,IAAIC,GAAK,CAC7B,IAAIC,EAAUD,EACRE,EAAQL,EAAS,MAAM,GAAG,EAChC,KAAOK,EAAM,OAAS,GAAGD,EAAUA,EAAQC,EAAM,MAAM,GACvD,OAAOD,CACT,CAAC,GAGKN,OACD,gBACH,IAAMQ,EAAM,IAAI,YAAYP,EAAK,GAAIA,EAAK,IAAM,CAAC,CAAC,EAClDG,EAAU,QAAQC,GAAKA,EAAE,cAAcG,CAAG,CAAC,EAC3C,cAEAJ,EAAU,QAAQC,GAAKA,EAAEL,GAAQ,MAAMK,EAAGJ,CAAI,CAAC,EAErD,CAEA,SAASQ,EAAYC,EAAe,CAClCA,EAAc,OAASb,CACzB,CAEA,IAAOc,EAAQ,CAAE,WAAAF,CAAW",
6
- "names": ["invoke", "payload", "id", "method", "args", "receiver", "selector", "receivers", "r", "context", "chain", "evt", "initialize", "streamActions", "turbo_ready_default"]
4
+ "sourcesContent": ["function invoke () {\n const payload = JSON.parse(this.templateContent.textContent)\n const { id, selector, receiver, method, args } = payload\n let receivers = [self]\n if (selector) receivers = Array.from(document.querySelectorAll(selector))\n\n if (receiver) {\n receivers = receivers.map(r => {\n let context = r\n const chain = receiver.split('.')\n while (chain.length > 0) context = context[chain.shift()]\n return context\n })\n }\n\n switch (method) {\n case 'dispatchEvent':\n const evt = new CustomEvent(args[0], args[1] || {})\n receivers.forEach(r => r.dispatchEvent(evt))\n break\n default:\n receivers.forEach(r => r[method].apply(r, args))\n }\n}\n\nfunction initialize (streamActions) {\n streamActions.invoke = invoke\n}\n\nexport default { initialize }\n"],
5
+ "mappings": "AAAA,SAASA,GAAU,CACjB,IAAMC,EAAU,KAAK,MAAM,KAAK,gBAAgB,WAAW,EACrD,CAAE,GAAAC,EAAI,SAAAC,EAAU,SAAAC,EAAU,OAAAC,EAAQ,KAAAC,CAAK,EAAIL,EAC7CM,EAAY,CAAC,IAAI,EAYrB,OAXIJ,IAAUI,EAAY,MAAM,KAAK,SAAS,iBAAiBJ,CAAQ,CAAC,GAEpEC,IACFG,EAAYA,EAAU,IAAIC,GAAK,CAC7B,IAAIC,EAAUD,EACRE,EAAQN,EAAS,MAAM,GAAG,EAChC,KAAOM,EAAM,OAAS,GAAGD,EAAUA,EAAQC,EAAM,MAAM,GACvD,OAAOD,CACT,CAAC,GAGKJ,OACD,gBACH,IAAMM,EAAM,IAAI,YAAYL,EAAK,GAAIA,EAAK,IAAM,CAAC,CAAC,EAClDC,EAAU,QAAQC,GAAKA,EAAE,cAAcG,CAAG,CAAC,EAC3C,cAEAJ,EAAU,QAAQC,GAAKA,EAAEH,GAAQ,MAAMG,EAAGF,CAAI,CAAC,EAErD,CAEA,SAASM,EAAYC,EAAe,CAClCA,EAAc,OAASb,CACzB,CAEA,IAAOc,EAAQ,CAAE,WAAAF,CAAW",
6
+ "names": ["invoke", "payload", "id", "selector", "receiver", "method", "args", "receivers", "r", "context", "chain", "evt", "initialize", "streamActions", "turbo_ready_default"]
7
7
  }
@@ -1,6 +1,6 @@
1
1
  function invoke () {
2
2
  const payload = JSON.parse(this.templateContent.textContent)
3
- const { id, method, args, receiver, selector } = payload
3
+ const { id, selector, receiver, method, args } = payload
4
4
  let receivers = [self]
5
5
  if (selector) receivers = Array.from(document.querySelectorAll(selector))
6
6
 
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TurboReady::BroadcastInvokeJob < ActiveJob::Base
4
+ include TurboReady::TagHelper
5
+
6
+ def perform(*streamables, method, **kwargs)
7
+ Turbo::StreamsChannel.broadcast_stream_to(*streamables, content: turbo_stream_invoke_tag(method, **kwargs))
8
+ end
9
+ end
data/bin/loc CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/bin/bash
2
2
 
3
- cloc --exclude-dir=Gemfile,Dockerfile,bin,builds,db,docs,log,node_modules,Procfile,public,storage,tmp --exclude-ext=example,json,lock,md,ru,toml,sql,svg,txt,yml "${1:-.}"
3
+ cloc --exclude-dir=assets app lib
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "turbo-rails"
4
+ require_relative "version"
5
+ require_relative "patches"
6
+
7
+ class TurboReady::Engine < ::Rails::Engine
8
+ config.after_initialize do
9
+ ::Turbo::Streams::TagBuilder.send :include, TurboReady::Patches::TagBuilder
10
+ ::Turbo::Streams::Broadcasts.send :include, TurboReady::Patches::Broadcasts
11
+ ::Turbo::Broadcastable.send :include, TurboReady::Patches::Broadcastable
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Patch for Turbo::Broadcastable which is mixed into ActiveRecord
4
+ # SEE: https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb
5
+ module TurboReady::Patches::Broadcastable
6
+ def broadcast_invoke_to(*streamables, method, **kwargs)
7
+ Turbo::StreamsChannel.broadcast_invoke_to(*streamables, method, **kwargs)
8
+ end
9
+
10
+ def broadcast_invoke_later_to(*streamables, method, **kwargs)
11
+ Turbo::StreamsChannel.broadcast_invoke_later_to(*streamables, method, **kwargs)
12
+ end
13
+
14
+ def broadcast_invoke(method, **kwargs)
15
+ broadcast_invoke_to(self, method, **kwargs)
16
+ end
17
+
18
+ def broadcast_invoke_later(method, **kwargs)
19
+ broadcast_invoke_later_to(self, method, **kwargs)
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../tag_helper"
4
+ require_relative "../../../app/jobs/turbo_ready/broadcast_invoke_job"
5
+
6
+ # Patch for Turbo::Streams::Broadcasts which is mixed into Turbo::StreamsChannel
7
+ # SEE: https://github.com/hotwired/turbo-rails/blob/main/app/channels/turbo/streams/broadcasts.rb
8
+ module TurboReady::Patches::Broadcasts
9
+ include TurboReady::TagHelper
10
+
11
+ def broadcast_invoke_to(*streamables, method, **kwargs)
12
+ broadcast_stream_to(*streamables, content: turbo_stream_invoke_tag(method, **kwargs))
13
+ end
14
+
15
+ def broadcast_invoke_later_to(*streamables, method, **kwargs)
16
+ TurboReady::BroadcastInvokeJob.perform_later(*streamables, method, **kwargs)
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../string_wrapper"
4
+ require_relative "../tag_helper"
5
+
6
+ # Patch for Turbo::Streams::TagBuilder typically exposed as `turbo_stream`
7
+ # SEE: https://github.com/hotwired/turbo-rails/blob/main/app/models/turbo/streams/tag_builder.rb
8
+ module TurboReady::Patches::TagBuilder
9
+ include TurboReady::TagHelper
10
+
11
+ def invoke(method, args: [], selector: nil, camelize: true, id: nil)
12
+ tag = turbo_stream_invoke_tag(method, args: args, selector: selector, camelize: camelize, id: id)
13
+ TurboReady::StringWrapper.new tag
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TurboReady::Patches
4
+ end
5
+
6
+ require_relative "patches/broadcastable"
7
+ require_relative "patches/broadcasts"
8
+ require_relative "patches/tag_builder"